From 2ebb183e0867e17d9e26b0a55d8c388d63824ade Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 6 Apr 2023 20:28:40 -0400 Subject: [PATCH 001/462] prep RToken price/lotPrice ahead of time to execute basketRange() once instead of three times --- contracts/facade/FacadeRead.sol | 8 +- contracts/interfaces/IAsset.sol | 6 ++ contracts/interfaces/IBasketHandler.sol | 14 +-- contracts/interfaces/IRTokenOracle.sol | 5 - contracts/p0/BasketHandler.sol | 46 ++++------ contracts/p0/mixins/TradingLib.sol | 8 +- contracts/p1/BasketHandler.sol | 41 +++------ .../p1/mixins/RecollateralizationLib.sol | 91 +++++++++++++++---- contracts/plugins/assets/RTokenAsset.sol | 85 ++++++++--------- test/Revenues.test.ts | 3 + .../Recollateralization.test.ts.snap | 6 +- test/__snapshots__/Revenues.test.ts.snap | 2 +- 12 files changed, 173 insertions(+), 142 deletions(-) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 8eaec295d5..adc6d28e50 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -276,10 +276,10 @@ contract FacadeRead is IFacadeRead { uint192 rsrUoA = rsrBal.mul(lowPrice); // {UoA/BU} - (uint192 buPriceLow, ) = rToken.main().basketHandler().price(); + (Price memory buPrice, ) = rToken.main().basketHandler().prices(); // {UoA} = {BU} * {UoA/BU} - uint192 uoaNeeded = basketsNeeded.mul(buPriceLow); + uint192 uoaNeeded = basketsNeeded.mul(buPrice.low); // {1} = {UoA} / {UoA} overCollateralization = rsrUoA.div(uoaNeeded); @@ -439,8 +439,10 @@ contract FacadeRead is IFacadeRead { Registry memory reg = ctx.reg.getRegistry(); + (Price memory buPrice, ) = bh.prices(); + // will exclude UoA value from RToken balances at BackingManager - range = RecollateralizationLibP1.basketRange(ctx, reg); + range = RecollateralizationLibP1.basketRange(ctx, reg, buPrice); } } } diff --git a/contracts/interfaces/IAsset.sol b/contracts/interfaces/IAsset.sol index cf90ded3fc..faf202c8ca 100644 --- a/contracts/interfaces/IAsset.sol +++ b/contracts/interfaces/IAsset.sol @@ -7,6 +7,12 @@ import "../libraries/Fixed.sol"; import "./IMain.sol"; import "./IRewardable.sol"; +// Not used directly in the IAsset interface, but used by many consumers to save stack space +struct Price { + uint192 low; // {UoA/tok} + uint192 high; // {UoA/tok} +} + /** * @title IAsset * @notice Supertype. Any token that interacts with our system must be wrapped in an asset, diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 3360d0eb83..5c047265cc 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -103,16 +103,10 @@ interface IBasketHandler is IComponent { /// bottom {BU} The number of whole basket units held by the account function basketsHeldBy(address account) external view returns (BasketRange memory); - /// Should not revert - /// @return low {UoA/BU} The lower end of the price estimate - /// @return high {UoA/BU} The upper end of the price estimate - function price() external view returns (uint192 low, uint192 high); - - /// Should not revert - /// lotLow should be nonzero if a BU could be worth selling - /// @return lotLow {UoA/tok} The lower end of the lot price estimate - /// @return lotHigh {UoA/tok} The upper end of the lot price estimate - function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh); + /// Returns both the price + lotPrice at once, for gas optimization + /// @return price {UoA/tok} The low and high price estimate of an RToken + /// @return lotPrice {UoA/tok} The low and high lotprice of an RToken + function prices() external view returns (Price memory price, Price memory lotPrice); /// @return timestamp The timestamp at which the basket was last set function timestamp() external view returns (uint48); diff --git a/contracts/interfaces/IRTokenOracle.sol b/contracts/interfaces/IRTokenOracle.sol index a34b811e2a..728e3a2a4e 100644 --- a/contracts/interfaces/IRTokenOracle.sol +++ b/contracts/interfaces/IRTokenOracle.sol @@ -4,11 +4,6 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./IRToken.sol"; -struct Price { - uint192 low; // {UoA/tok} - uint192 high; // {UoA/tok} -} - interface IRTokenOracle { /// Lookup price by rToken with refresh if necessary /// @param forceRefresh If true, forces a refresh of the price regardless of cache status diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 5c87abea64..823cc7b70f 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -314,47 +314,35 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { return basket.refAmts[erc20].div(refPerTok, CEIL); } - /// Should not revert - /// @return low {UoA/BU} The lower end of the price estimate - /// @return high {UoA/BU} The upper end of the price estimate - // returns sum(quantity(erc20) * price(erc20) for erc20 in basket.erc20s) - function price() external view returns (uint192 low, uint192 high) { - return _price(false); - } - - /// Should not revert - /// lowLow should be nonzero when the asset might be worth selling - /// @return lotLow {UoA/BU} The lower end of the lot price estimate - /// @return lotHigh {UoA/BU} The upper end of the lot price estimate - // returns sum(quantity(erc20) * lotPrice(erc20) for erc20 in basket.erc20s) - function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh) { - return _price(true); - } - - /// Returns the price of a BU, using the lot prices if `useLotPrice` is true - /// @return low {UoA/BU} The lower end of the lot price estimate - /// @return high {UoA/BU} The upper end of the lot price estimate - function _price(bool useLotPrice) internal view returns (uint192 low, uint192 high) { - IAssetRegistry reg = main.assetRegistry(); - + /// Returns both the price + lotPrice at once, for gas optimization + /// @return price {UoA/tok} The low and high price estimate of an RToken + /// @return lotPrice {UoA/tok} The low and high lotprice of an RToken + function prices() external view returns (Price memory price, Price memory lotPrice) { uint256 low256; uint256 high256; + uint256 lotLow256; + uint256 lotHigh256; - for (uint256 i = 0; i < basket.erc20s.length; i++) { + uint256 len = basket.erc20s.length; + for (uint256 i = 0; i < len; ++i) { uint192 qty = quantity(basket.erc20s[i]); if (qty == 0) continue; - (uint192 lowP, uint192 highP) = useLotPrice - ? reg.toAsset(basket.erc20s[i]).lotPrice() - : reg.toAsset(basket.erc20s[i]).price(); + IAsset asset = main.assetRegistry().toAsset(basket.erc20s[i]); + (uint192 lowP, uint192 highP) = asset.price(); + (uint192 lotLowP, uint192 lotHighP) = asset.price(); low256 += safeMul(qty, lowP, RoundingMode.FLOOR); high256 += safeMul(qty, highP, RoundingMode.CEIL); + lotLow256 += safeMul(qty, lotLowP, RoundingMode.FLOOR); + lotHigh256 += safeMul(qty, lotHighP, RoundingMode.CEIL); } // safe downcast: FIX_MAX is type(uint192).max - low = low256 >= FIX_MAX ? FIX_MAX : uint192(low256); - high = high256 >= FIX_MAX ? FIX_MAX : uint192(high256); + price.low = low256 >= FIX_MAX ? FIX_MAX : uint192(low256); + price.high = high256 >= FIX_MAX ? FIX_MAX : uint192(high256); + lotPrice.low = lotLow256 >= FIX_MAX ? FIX_MAX : uint192(lotLow256); + lotPrice.high = lotHigh256 >= FIX_MAX ? FIX_MAX : uint192(lotHigh256); } /// Multiply two fixes, rounding up to FIX_MAX and down to 0 diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index 1e5f96af2c..08883d8c16 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -261,7 +261,7 @@ library TradingLibP0 { view returns (BasketRange memory range) { - (uint192 basketPriceLow, uint192 basketPriceHigh) = ctx.bh.price(); // {UoA/BU} + (Price memory buPrice, ) = ctx.bh.prices(); // {UoA/BU} // Cap ctx.basketsHeld.top if (ctx.basketsHeld.top > ctx.rToken.basketsNeeded()) { @@ -315,14 +315,14 @@ library TradingLibP0 { // deficit: deduct optimistic estimate of baskets missing // {BU} = {UoA/tok} * {tok} / {UoA/BU} - deltaTop -= int256(uint256(low.mulDiv(anchor - bal, basketPriceHigh, FLOOR))); + deltaTop -= int256(uint256(low.mulDiv(anchor - bal, buPrice.high, FLOOR))); // does not need underflow protection: using low price of asset } else { // surplus: add-in optimistic estimate of baskets purchaseable // {BU} = {UoA/tok} * {tok} / {UoA/BU} deltaTop += int256( - uint256(ctx.bm.safeMulDivCeil(high, bal - anchor, basketPriceLow)) + uint256(ctx.bm.safeMulDivCeil(high, bal - anchor, buPrice.low)) ); // needs overflow protection: using high price of asset which can be FIX_MAX } @@ -352,7 +352,7 @@ library TradingLibP0 { // {BU} = {UoA} * {1} / {UoA/BU} range.bottom += val.mulDiv( FIX_ONE.minus(ctx.maxTradeSlippage), - basketPriceHigh, + buPrice.high, FLOOR ); } diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index e5135fa6ad..2d189313ce 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -332,46 +332,35 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { return basket.refAmts[erc20].div(refPerTok, CEIL); } - /// Should not revert - /// @return low {UoA/BU} The lower end of the price estimate - /// @return high {UoA/BU} The upper end of the price estimate - // returns sum(quantity(erc20) * price(erc20) for erc20 in basket.erc20s) - function price() external view returns (uint192 low, uint192 high) { - return _price(false); - } - - /// Should not revert - /// lowLow should be nonzero when the asset might be worth selling - /// @return lotLow {UoA/BU} The lower end of the lot price estimate - /// @return lotHigh {UoA/BU} The upper end of the lot price estimate - // returns sum(quantity(erc20) * lotPrice(erc20) for erc20 in basket.erc20s) - function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh) { - return _price(true); - } - - /// Returns the price of a BU, using the lot prices if `useLotPrice` is true - /// @return low {UoA/BU} The lower end of the price estimate - /// @return high {UoA/BU} The upper end of the price estimate - function _price(bool useLotPrice) internal view returns (uint192 low, uint192 high) { + /// Returns both the price() + lotPrice() at once, for gas optimization + /// @return price {UoA/tok} The low and high price estimate of an RToken + /// @return lotPrice {UoA/tok} The low and high lotprice of an RToken + function prices() external view returns (Price memory price, Price memory lotPrice) { uint256 low256; uint256 high256; + uint256 lotLow256; + uint256 lotHigh256; uint256 len = basket.erc20s.length; for (uint256 i = 0; i < len; ++i) { uint192 qty = quantity(basket.erc20s[i]); if (qty == 0) continue; - (uint192 lowP, uint192 highP) = useLotPrice - ? assetRegistry.toAsset(basket.erc20s[i]).lotPrice() - : assetRegistry.toAsset(basket.erc20s[i]).price(); + IAsset asset = assetRegistry.toAsset(basket.erc20s[i]); + (uint192 lowP, uint192 highP) = asset.price(); + (uint192 lotLowP, uint192 lotHighP) = asset.price(); low256 += safeMul(qty, lowP, RoundingMode.FLOOR); high256 += safeMul(qty, highP, RoundingMode.CEIL); + lotLow256 += safeMul(qty, lotLowP, RoundingMode.FLOOR); + lotHigh256 += safeMul(qty, lotHighP, RoundingMode.CEIL); } // safe downcast: FIX_MAX is type(uint192).max - low = low256 >= FIX_MAX ? FIX_MAX : uint192(low256); - high = high256 >= FIX_MAX ? FIX_MAX : uint192(high256); + price.low = low256 >= FIX_MAX ? FIX_MAX : uint192(low256); + price.high = high256 >= FIX_MAX ? FIX_MAX : uint192(high256); + lotPrice.low = lotLow256 >= FIX_MAX ? FIX_MAX : uint192(lotLow256); + lotPrice.high = lotHigh256 >= FIX_MAX ? FIX_MAX : uint192(lotHigh256); } /// Multiply two fixes, rounding up to FIX_MAX and down to 0 diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index 08a644c330..bc527e36a0 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -5,6 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../../interfaces/IAsset.sol"; import "../../interfaces/IAssetRegistry.sol"; import "../../interfaces/IBackingManager.sol"; +import "../../plugins/assets/RTokenAsset.sol"; import "../../libraries/Fixed.sol"; import "./TradeLib.sol"; @@ -29,6 +30,12 @@ struct TradingContext { uint192 maxTradeSlippage; // {1} } +struct TradingPriceContext { + BasketRange range; + Price rTokenPrice; + Price rTokenLotPrice; +} + /** * @title RecollateralizationLibP1 * @notice An informal extension of the Trading mixin that provides trade preparation views @@ -76,11 +83,28 @@ library RecollateralizationLibP1 { // ============================ + // Compute BU price + (Price memory buPrice, Price memory buLotPrice) = ctx.bh.prices(); + // Compute a target basket range for trading - {BU} - BasketRange memory range = basketRange(ctx, reg); + BasketRange memory range = basketRange(ctx, reg, buPrice); + + // Compute RToken price + RTokenAsset rTokenAsset = RTokenAsset( + address(ctx.reg.toAsset(IERC20(address(ctx.rToken)))) + ); + (Price memory rTokenPrice, Price memory rTokenLotPrice) = rTokenAsset.prices( + range, + buPrice, + buLotPrice + ); // Select a pair to trade next, if one exists - TradeInfo memory trade = nextTradePair(ctx, reg, range); + TradeInfo memory trade = nextTradePair( + ctx, + reg, + TradingPriceContext(range, rTokenPrice, rTokenLotPrice) + ); // Don't trade if no pair is selected if (address(trade.sell) == address(0) || address(trade.buy) == address(0)) { @@ -140,16 +164,15 @@ library RecollateralizationLibP1 { // - range.bottom = min(rToken.basketsNeeded, basketsHeld.bottom + least baskets purchaseable) // where "least baskets purchaseable" involves trading at unfavorable prices, // incurring maxTradeSlippage, and taking up to a minTradeVolume loss due to dust. - function basketRange(TradingContext memory ctx, Registry memory reg) - internal - view - returns (BasketRange memory range) - { - (uint192 basketPriceLow, uint192 basketPriceHigh) = ctx.bh.price(); // {UoA/BU} - + function basketRange( + TradingContext memory ctx, + Registry memory reg, + Price memory buPrice + ) internal view returns (BasketRange memory range) { + uint192 basketsNeeded = ctx.rToken.basketsNeeded(); // Cap ctx.basketsHeld.top - if (ctx.basketsHeld.top > ctx.rToken.basketsNeeded()) { - ctx.basketsHeld.top = ctx.rToken.basketsNeeded(); + if (ctx.basketsHeld.top > basketsNeeded) { + ctx.basketsHeld.top = basketsNeeded; } // === (1/3) Calculate contributions from surplus/deficits === @@ -198,14 +221,14 @@ library RecollateralizationLibP1 { // deficit: deduct optimistic estimate of baskets missing // {BU} = {UoA/tok} * {tok} / {UoA/BU} - deltaTop -= int256(uint256(low.mulDiv(anchor - bal, basketPriceHigh, FLOOR))); + deltaTop -= int256(uint256(low.mulDiv(anchor - bal, buPrice.high, FLOOR))); // does not need underflow protection: using low price of asset } else { // surplus: add-in optimistic estimate of baskets purchaseable // {BU} = {UoA/tok} * {tok} / {UoA/BU} deltaTop += int256( - uint256(ctx.bm.safeMulDivCeil(high, bal - anchor, basketPriceLow)) + uint256(ctx.bm.safeMulDivCeil(high, bal - anchor, buPrice.low)) ); // needs overflow protection: using high price of asset which can be FIX_MAX } @@ -235,7 +258,7 @@ library RecollateralizationLibP1 { // {BU} = {UoA} * {1} / {UoA/BU} range.bottom += val.mulDiv( FIX_ONE.minus(ctx.maxTradeSlippage), - basketPriceHigh, + buPrice.high, FLOOR ); } @@ -259,7 +282,7 @@ library RecollateralizationLibP1 { // ==== (3/3) Enforce (range.bottom <= range.top <= basketsNeeded) ==== - if (range.top > ctx.rToken.basketsNeeded()) range.top = ctx.rToken.basketsNeeded(); + if (range.top > basketsNeeded) range.top = basketsNeeded; if (range.bottom > range.top) range.bottom = range.top; } @@ -306,20 +329,21 @@ library RecollateralizationLibP1 { function nextTradePair( TradingContext memory ctx, Registry memory reg, - BasketRange memory range + TradingPriceContext memory priceCtx ) private view returns (TradeInfo memory trade) { MaxSurplusDeficit memory maxes; maxes.surplusStatus = CollateralStatus.IFFY; // least-desirable sell status - // No space on the stack to cache erc20s.length + // Iterate over non-RSR/non-RToken assets + // (no space on the stack to cache erc20s.length) for (uint256 i = 0; i < reg.erc20s.length; ++i) { - if (reg.erc20s[i] == ctx.rsr) continue; + if (reg.erc20s[i] == ctx.rsr || address(reg.erc20s[i]) == address(ctx.rToken)) continue; uint192 bal = reg.assets[i].bal(address(ctx.bm)); // {tok} // {tok} = {BU} * {tok/BU} // needed(Top): token balance needed for range.top baskets: quantity(e) * range.top - uint192 needed = range.top.mul( + uint192 needed = priceCtx.range.top.mul( ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]), CEIL ); // {tok} @@ -367,7 +391,7 @@ library RecollateralizationLibP1 { } } else { // needed(Bottom): token balance needed at bottom of the basket range - needed = range.bottom.mul( + needed = priceCtx.range.bottom.mul( ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]), CEIL ); // {buyTok}; @@ -391,6 +415,33 @@ library RecollateralizationLibP1 { } } + // Special-case RToken + { + RTokenAsset rTokenAsset = RTokenAsset( + address(ctx.reg.toAsset(IERC20(address(ctx.rToken)))) + ); + uint192 bal = rTokenAsset.bal(address(ctx.bm)); + + if ( + priceCtx.rTokenPrice.high > 0 && + isBetterSurplus( + maxes, + CollateralStatus.SOUND, + bal.mul(priceCtx.rTokenLotPrice.low, FLOOR) + ) && + TradeLib.isEnoughToSell( + rTokenAsset, + bal, + priceCtx.rTokenLotPrice.low, + ctx.minTradeVolume + ) + ) { + trade.sell = rTokenAsset; + trade.sellAmount = bal; + trade.sellPrice = priceCtx.rTokenPrice.low; + } + } + // Use RSR if needed if (address(trade.sell) == address(0) && address(trade.buy) != address(0)) { IAsset rsrAsset = ctx.reg.toAsset(ctx.rsr); diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index 9785885d41..028e9dfeae 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.17; import "../../p1/mixins/RecollateralizationLib.sol"; import "../../interfaces/IMain.sol"; import "../../interfaces/IRToken.sol"; +import "../../interfaces/IRTokenOracle.sol"; import "./Asset.sol"; /// Once an RToken gets large enough to get a price feed, replacing this asset with @@ -38,27 +39,30 @@ contract RTokenAsset is IAsset { maxTradeVolume = maxTradeVolume_; } - /// 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 - function tryPrice() external view virtual returns (uint192 low, uint192 high) { - (uint192 lowBUPrice, uint192 highBUPrice) = basketHandler.price(); // {UoA/BU} - assert(lowBUPrice <= highBUPrice); // not obviously true just by inspection - + /// Calculates price() + lotPrice() in a gas-optimized way using a cached BasketRange + /// @param buRange {BU} The top and bottom of the bu band; how many BUs we expect to hold + /// @param buPrice {UoA/BU} The low and high price estimate of a basket unit + /// @param buLotPrice {UoA/BU} The low and high lotprice of a basket unit + /// @return price_ {UoA/tok} The low and high price estimate of an RToken + /// @return lotPrice_ {UoA/tok} The low and high lotprice of an RToken + function prices( + BasketRange memory buRange, + Price memory buPrice, + Price memory buLotPrice + ) public view returns (Price memory price_, Price memory lotPrice_) { // Here we take advantage of the fact that we know RToken has 18 decimals // to convert between uint256 an uint192. Fits due to assumed max totalSupply. uint192 supply = _safeWrap(IRToken(address(erc20)).totalSupply()); - if (supply == 0) return (lowBUPrice, highBUPrice); - - // The RToken's price is not symmetric like other assets! - // range.bottom is lower because of the slippage from the shortfall - BasketRange memory range = basketRange(); // {BU} + if (supply == 0) return (buPrice, buLotPrice); // {UoA/tok} = {BU} * {UoA/BU} / {tok} - low = range.bottom.mulDiv(lowBUPrice, supply, FLOOR); - high = range.top.mulDiv(highBUPrice, supply, CEIL); - // assert(low <= high); // obviously true at this point just by inspection + price_.low = buRange.bottom.mulDiv(buPrice.low, supply, FLOOR); + price_.high = buRange.top.mulDiv(buPrice.high, supply, CEIL); + lotPrice_.low = buRange.bottom.mulDiv(buLotPrice.low, supply, FLOOR); + lotPrice_.high = buRange.top.mulDiv(buLotPrice.high, supply, CEIL); + assert(price_.low <= price_.high); + assert(lotPrice_.low <= lotPrice_.high); } // solhint-disable no-empty-blocks @@ -72,34 +76,23 @@ contract RTokenAsset is IAsset { /// @return {UoA/tok} The lower end of the price estimate /// @return {UoA/tok} The upper end of the price estimate function price() public view virtual returns (uint192, uint192) { - try this.tryPrice() returns (uint192 low, uint192 high) { - assert(low <= high); - return (low, high); - } catch (bytes memory errData) { - // see: docs/solidity-style.md#Catching-Empty-Data - if (errData.length == 0) revert(); // solhint-disable-line reason-string - return (0, FIX_MAX); - } + (BasketRange memory buRange, Price memory buPrice, Price memory buLotPrice) = basketRange(); + + // prices() should not revert because all underlying.price() calls should not revert + (Price memory p, ) = prices(buRange, buPrice, buLotPrice); + return (p.low, p.high); } /// Should not revert /// lotLow should be nonzero when the asset might be worth selling - /// @return lotLow {UoA/tok} The lower end of the lot price estimate - /// @return lotHigh {UoA/tok} The upper end of the lot price estimate - function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh) { - (uint192 buLow, uint192 buHigh) = basketHandler.lotPrice(); // {UoA/BU} - - // Here we take advantage of the fact that we know RToken has 18 decimals - // to convert between uint256 an uint192. Fits due to assumed max totalSupply. - uint192 supply = _safeWrap(IRToken(address(erc20)).totalSupply()); - - if (supply == 0) return (buLow, buHigh); - - BasketRange memory range = basketRange(); // {BU} - - // {UoA/tok} = {BU} * {UoA/BU} / {tok} - lotLow = range.bottom.mulDiv(buLow, supply); - lotHigh = range.top.mulDiv(buHigh, supply); + /// @return {UoA/tok} The lower end of the lot price estimate + /// @return {UoA/tok} The upper end of the lot price estimate + function lotPrice() external view returns (uint192, uint192) { + (BasketRange memory buRange, Price memory buPrice, Price memory buLotPrice) = basketRange(); + + // prices() should not revert because all underlying.price() calls should not revert + (, Price memory lotP) = prices(buRange, buPrice, buLotPrice); + return (lotP.low, lotP.high); } /// @return {tok} The balance of the ERC20 in whole tokens @@ -124,7 +117,17 @@ contract RTokenAsset is IAsset { // ==== Private ==== - function basketRange() private view returns (BasketRange memory range) { + function basketRange() + private + view + returns ( + BasketRange memory range, + Price memory buPrice, + Price memory buLotPrice + ) + { + (buPrice, buLotPrice) = basketHandler.prices(); + BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(backingManager)); uint192 basketsNeeded = IRToken(address(erc20)).basketsNeeded(); // {BU} @@ -154,7 +157,7 @@ contract RTokenAsset is IAsset { Registry memory reg = assetRegistry.getRegistry(); // will exclude UoA value from RToken balances at BackingManager - range = RecollateralizationLibP1.basketRange(ctx, reg); + range = RecollateralizationLibP1.basketRange(ctx, reg, buPrice); } } } diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 530f5f21f1..e4b402a0b6 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -3181,6 +3181,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Issue rTokens await rToken.connect(addr1).issue(issueAmount) + // Send BackingManager with nonzero RToken balance to incur maximum gas costs + await rToken.connect(addr1).transfer(backingManager.address, 1000) + // Mint some RSR await rsr.connect(owner).mint(addr1.address, initialBal) }) diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 24444908ca..8758346acc 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -2,16 +2,16 @@ exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `63713`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `2637909`; +exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1711332`; exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `386392`; exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `184737`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `2594542`; +exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `1692602`; exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `184745`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 7`] = `2684960`; +exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 7`] = `1785363`; exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 8`] = `212855`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index f09eccdfb8..d110b4bc0b 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -14,7 +14,7 @@ exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `217024`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `721689`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `933090`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `971956`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `243131`; From f9d39809a43b9b561423ce162230c521ca0bb1db Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 6 Apr 2023 20:45:13 -0400 Subject: [PATCH 002/462] cache quantities for another gain --- contracts/interfaces/IAssetRegistry.sol | 1 + contracts/p0/AssetRegistry.sol | 3 +++ contracts/p1/AssetRegistry.sol | 2 ++ .../p1/mixins/RecollateralizationLib.sol | 24 +++++++------------ .../Recollateralization.test.ts.snap | 8 +++---- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/contracts/interfaces/IAssetRegistry.sol b/contracts/interfaces/IAssetRegistry.sol index 58534cf1da..2853279d8a 100644 --- a/contracts/interfaces/IAssetRegistry.sol +++ b/contracts/interfaces/IAssetRegistry.sol @@ -9,6 +9,7 @@ import "./IComponent.sol"; struct Registry { IERC20[] erc20s; IAsset[] assets; + uint192[] quantities; // {tok/BU} Quantity of whole token in the basket } /** diff --git a/contracts/p0/AssetRegistry.sol b/contracts/p0/AssetRegistry.sol index 1fb027464a..0ddb7c2930 100644 --- a/contracts/p0/AssetRegistry.sol +++ b/contracts/p0/AssetRegistry.sol @@ -109,13 +109,16 @@ contract AssetRegistryP0 is ComponentP0, IAssetRegistry { uint256 length = _erc20s.length(); reg.erc20s = new IERC20[](length); reg.assets = new IAsset[](length); + reg.quantities = new uint192[](length); for (uint256 i = 0; i < length; ++i) { reg.erc20s[i] = IERC20(_erc20s.at(i)); reg.assets[i] = assets[IERC20(_erc20s.at(i))]; + reg.quantities[i] = main.basketHandler().quantityUnsafe(reg.erc20s[i], reg.assets[i]); assert(address(reg.erc20s[i]) != address(0)); assert(address(reg.assets[i]) != address(0)); } assert(reg.erc20s.length == reg.assets.length); + assert(reg.quantities.length == reg.assets.length); } // diff --git a/contracts/p1/AssetRegistry.sol b/contracts/p1/AssetRegistry.sol index fe2460aaf2..23e2ec6b89 100644 --- a/contracts/p1/AssetRegistry.sol +++ b/contracts/p1/AssetRegistry.sol @@ -144,9 +144,11 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { uint256 length = _erc20s.length(); reg.erc20s = new IERC20[](length); reg.assets = new IAsset[](length); + reg.quantities = new uint192[](length); for (uint256 i = 0; i < length; ++i) { reg.erc20s[i] = IERC20(_erc20s.at(i)); reg.assets[i] = assets[IERC20(_erc20s.at(i))]; + reg.quantities[i] = basketHandler.quantityUnsafe(reg.erc20s[i], reg.assets[i]); } } diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index bc527e36a0..052c7dd912 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -30,7 +30,7 @@ struct TradingContext { uint192 maxTradeSlippage; // {1} } -struct TradingPriceContext { +struct PricingContext { BasketRange range; Price rTokenPrice; Price rTokenLotPrice; @@ -103,7 +103,7 @@ library RecollateralizationLibP1 { TradeInfo memory trade = nextTradePair( ctx, reg, - TradingPriceContext(range, rTokenPrice, rTokenLotPrice) + PricingContext(range, rTokenPrice, rTokenLotPrice) ); // Don't trade if no pair is selected @@ -170,6 +170,7 @@ library RecollateralizationLibP1 { Price memory buPrice ) internal view returns (BasketRange memory range) { uint192 basketsNeeded = ctx.rToken.basketsNeeded(); + // Cap ctx.basketsHeld.top if (ctx.basketsHeld.top > basketsNeeded) { ctx.basketsHeld.top = basketsNeeded; @@ -195,14 +196,13 @@ library RecollateralizationLibP1 { bal = bal.plus(reg.assets[i].bal(address(ctx.stRSR))); } - uint192 q = ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); // {tok/BU} { // Skip over dust-balance assets not in the basket (uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/tok} // Intentionally include value of IFFY/DISABLED collateral if ( - q == 0 && + reg.quantities[i] == 0 && !TradeLib.isEnoughToSell(reg.assets[i], bal, lotLow, ctx.minTradeVolume) ) continue; } @@ -215,7 +215,7 @@ library RecollateralizationLibP1 { // if in surplus relative to ctx.basketsHeld.top: add-in surplus baskets { // {tok} = {tok/BU} * {BU} - uint192 anchor = q.mul(ctx.basketsHeld.top, CEIL); + uint192 anchor = reg.quantities[i].mul(ctx.basketsHeld.top, CEIL); if (anchor > bal) { // deficit: deduct optimistic estimate of baskets missing @@ -238,7 +238,7 @@ library RecollateralizationLibP1 { // add-in surplus baskets relative to ctx.basketsHeld.bottom { // {tok} = {tok/BU} * {BU} - uint192 anchor = q.mul(ctx.basketsHeld.bottom, FLOOR); + uint192 anchor = reg.quantities[i].mul(ctx.basketsHeld.bottom, FLOOR); // (1) Sell tokens at low price // {UoA} = {UoA/tok} * {tok} @@ -329,7 +329,7 @@ library RecollateralizationLibP1 { function nextTradePair( TradingContext memory ctx, Registry memory reg, - TradingPriceContext memory priceCtx + PricingContext memory priceCtx ) private view returns (TradeInfo memory trade) { MaxSurplusDeficit memory maxes; maxes.surplusStatus = CollateralStatus.IFFY; // least-desirable sell status @@ -343,10 +343,7 @@ library RecollateralizationLibP1 { // {tok} = {BU} * {tok/BU} // needed(Top): token balance needed for range.top baskets: quantity(e) * range.top - uint192 needed = priceCtx.range.top.mul( - ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]), - CEIL - ); // {tok} + uint192 needed = priceCtx.range.top.mul(reg.quantities[i], CEIL); // {tok} if (bal.gt(needed)) { uint192 low; // {UoA/sellTok} @@ -391,10 +388,7 @@ library RecollateralizationLibP1 { } } else { // needed(Bottom): token balance needed at bottom of the basket range - needed = priceCtx.range.bottom.mul( - ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]), - CEIL - ); // {buyTok}; + needed = priceCtx.range.bottom.mul(reg.quantities[i], CEIL); // {buyTok}; if (bal.lt(needed)) { uint192 amtShort = needed.minus(bal); // {buyTok} diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 8758346acc..16c07bf629 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -2,16 +2,16 @@ exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `63713`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1711332`; +exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1671658`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `386392`; +exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `386416`; exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `184737`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `1692602`; +exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `1652643`; exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `184745`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 7`] = `1785363`; +exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 7`] = `1741673`; exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 8`] = `212855`; From 4d6544dd4b547b9aa207651cb585e85ee747582f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 6 Apr 2023 21:35:55 -0400 Subject: [PATCH 003/462] nits --- contracts/interfaces/IAssetRegistry.sol | 2 +- contracts/p0/AssetRegistry.sol | 1 + contracts/p1/AssetRegistry.sol | 1 + .../p1/mixins/RecollateralizationLib.sol | 27 ++++++++++--------- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/contracts/interfaces/IAssetRegistry.sol b/contracts/interfaces/IAssetRegistry.sol index 2853279d8a..9353411d49 100644 --- a/contracts/interfaces/IAssetRegistry.sol +++ b/contracts/interfaces/IAssetRegistry.sol @@ -50,7 +50,7 @@ interface IAssetRegistry is IComponent { /// @return A list of all registered ERC20s function erc20s() external view returns (IERC20[] memory); - /// @return reg The list of registered ERC20s and Assets, in the same order + /// @return reg The list of registered ERC20s, Assets, and basket quantities function getRegistry() external view returns (Registry memory reg); function register(IAsset asset) external returns (bool); diff --git a/contracts/p0/AssetRegistry.sol b/contracts/p0/AssetRegistry.sol index 0ddb7c2930..29af7f587a 100644 --- a/contracts/p0/AssetRegistry.sol +++ b/contracts/p0/AssetRegistry.sol @@ -105,6 +105,7 @@ contract AssetRegistryP0 is ComponentP0, IAssetRegistry { } } + /// @return reg The list of registered ERC20s, Assets, and basket quantities function getRegistry() external view returns (Registry memory reg) { uint256 length = _erc20s.length(); reg.erc20s = new IERC20[](length); diff --git a/contracts/p1/AssetRegistry.sol b/contracts/p1/AssetRegistry.sol index 23e2ec6b89..6f2841f9d9 100644 --- a/contracts/p1/AssetRegistry.sol +++ b/contracts/p1/AssetRegistry.sol @@ -140,6 +140,7 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { /// Returns keys(assets), values(assets) as (duplicate-free) lists. // returns: [keys(assets)], [values(assets)] without duplicates. + /// @return reg The list of registered ERC20s, Assets, and basket quantities function getRegistry() external view returns (Registry memory reg) { uint256 length = _erc20s.length(); reg.erc20s = new IERC20[](length); diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index 052c7dd912..92dad339f4 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -30,7 +30,7 @@ struct TradingContext { uint192 maxTradeSlippage; // {1} } -struct PricingContext { +struct NextTradePairContext { BasketRange range; Price rTokenPrice; Price rTokenLotPrice; @@ -56,7 +56,7 @@ library RecollateralizationLibP1 { /// basket range to avoid overeager/duplicate trading. // This is the "main loop" for recollateralization trading: // actions: - // let range = basketRange(all erc20s) + // let range = basketRange(...) // let trade = nextTradePair(...) // if trade.sell is not a defaulted collateral, prepareTradeToCoverDeficit(...) // otherwise, prepareTradeSell(...) with a 0 minBuyAmount @@ -103,7 +103,7 @@ library RecollateralizationLibP1 { TradeInfo memory trade = nextTradePair( ctx, reg, - PricingContext(range, rTokenPrice, rTokenLotPrice) + NextTradePairContext(range, rTokenPrice, rTokenLotPrice) ); // Don't trade if no pair is selected @@ -146,8 +146,8 @@ library RecollateralizationLibP1 { // run-to-run, but will never increase it // // Preconditions: - // - ctx is correctly populated with current basketsHeld.bottom + basketsHeld.top - // - reg contains erc20 + asset arrays in same order and without duplicates + // - ctx is correctly populated, with current basketsHeld.bottom + basketsHeld.top + // - reg contains erc20 + asset + quantities arrays in same order and without duplicates // Trading Strategy: // - We will not aim to hold more than rToken.basketsNeeded() BUs // - No double trades: if we buy B in one trade, we won't sell B in another trade @@ -185,7 +185,8 @@ library RecollateralizationLibP1 { int256 deltaTop; // D18{BU} even though this is int256, it is D18 // not required for range.bottom - for (uint256 i = 0; i < reg.erc20s.length; ++i) { + uint256 len = reg.erc20s.length; + for (uint256 i = 0; i < len; ++i) { // Exclude RToken balances to avoid double counting value if (reg.erc20s[i] == IERC20(address(ctx.rToken))) continue; @@ -329,7 +330,7 @@ library RecollateralizationLibP1 { function nextTradePair( TradingContext memory ctx, Registry memory reg, - PricingContext memory priceCtx + NextTradePairContext memory ntpCtx ) private view returns (TradeInfo memory trade) { MaxSurplusDeficit memory maxes; maxes.surplusStatus = CollateralStatus.IFFY; // least-desirable sell status @@ -343,7 +344,7 @@ library RecollateralizationLibP1 { // {tok} = {BU} * {tok/BU} // needed(Top): token balance needed for range.top baskets: quantity(e) * range.top - uint192 needed = priceCtx.range.top.mul(reg.quantities[i], CEIL); // {tok} + uint192 needed = ntpCtx.range.top.mul(reg.quantities[i], CEIL); // {tok} if (bal.gt(needed)) { uint192 low; // {UoA/sellTok} @@ -388,7 +389,7 @@ library RecollateralizationLibP1 { } } else { // needed(Bottom): token balance needed at bottom of the basket range - needed = priceCtx.range.bottom.mul(reg.quantities[i], CEIL); // {buyTok}; + needed = ntpCtx.range.bottom.mul(reg.quantities[i], CEIL); // {buyTok}; if (bal.lt(needed)) { uint192 amtShort = needed.minus(bal); // {buyTok} @@ -417,22 +418,22 @@ library RecollateralizationLibP1 { uint192 bal = rTokenAsset.bal(address(ctx.bm)); if ( - priceCtx.rTokenPrice.high > 0 && + ntpCtx.rTokenPrice.high > 0 && isBetterSurplus( maxes, CollateralStatus.SOUND, - bal.mul(priceCtx.rTokenLotPrice.low, FLOOR) + bal.mul(ntpCtx.rTokenLotPrice.low, FLOOR) ) && TradeLib.isEnoughToSell( rTokenAsset, bal, - priceCtx.rTokenLotPrice.low, + ntpCtx.rTokenLotPrice.low, ctx.minTradeVolume ) ) { trade.sell = rTokenAsset; trade.sellAmount = bal; - trade.sellPrice = priceCtx.rTokenPrice.low; + trade.sellPrice = ntpCtx.rTokenPrice.low; } } From da62a2c2d5f3cb9cc293d1232d66a9c980304981 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 6 Apr 2023 21:59:54 -0400 Subject: [PATCH 004/462] use try/catch in RTokenAsset.price --- contracts/plugins/assets/RTokenAsset.sol | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index 028e9dfeae..c315c6c4e1 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -78,9 +78,13 @@ contract RTokenAsset is IAsset { function price() public view virtual returns (uint192, uint192) { (BasketRange memory buRange, Price memory buPrice, Price memory buLotPrice) = basketRange(); - // prices() should not revert because all underlying.price() calls should not revert - (Price memory p, ) = prices(buRange, buPrice, buLotPrice); - return (p.low, p.high); + try this.prices(buRange, buPrice, buLotPrice) returns (Price memory p, Price memory) { + return (p.low, p.high); + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + return (0, FIX_MAX); + } } /// Should not revert @@ -91,8 +95,13 @@ contract RTokenAsset is IAsset { (BasketRange memory buRange, Price memory buPrice, Price memory buLotPrice) = basketRange(); // prices() should not revert because all underlying.price() calls should not revert - (, Price memory lotP) = prices(buRange, buPrice, buLotPrice); - return (lotP.low, lotP.high); + try this.prices(buRange, buPrice, buLotPrice) returns (Price memory, Price memory lotP) { + return (lotP.low, lotP.high); + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + return (0, FIX_MAX); + } } /// @return {tok} The balance of the ERC20 in whole tokens From 572666923152f6710b486b0918d26ff13fa22758 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 6 Apr 2023 22:05:09 -0400 Subject: [PATCH 005/462] keep basketHandler.price()/lotPrice() around --- contracts/interfaces/IBasketHandler.sol | 17 ++++++++++--- contracts/p0/BasketHandler.sol | 33 +++++++++++++++++++------ contracts/p0/mixins/TradingLib.sol | 12 +++------ contracts/p1/BasketHandler.sol | 33 +++++++++++++++++++------ 4 files changed, 70 insertions(+), 25 deletions(-) diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 5c047265cc..c0c16186cf 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -103,10 +103,21 @@ interface IBasketHandler is IComponent { /// bottom {BU} The number of whole basket units held by the account function basketsHeldBy(address account) external view returns (BasketRange memory); + /// Should not revert + /// @return low {UoA/BU} The lower end of the price estimate + /// @return high {UoA/BU} The upper end of the price estimate + function price() external view returns (uint192 low, uint192 high); + + /// Should not revert + /// lotLow should be nonzero if a BU could be worth selling + /// @return lotLow {UoA/tok} The lower end of the lot price estimate + /// @return lotHigh {UoA/tok} The upper end of the lot price estimate + function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh); + /// Returns both the price + lotPrice at once, for gas optimization - /// @return price {UoA/tok} The low and high price estimate of an RToken - /// @return lotPrice {UoA/tok} The low and high lotprice of an RToken - function prices() external view returns (Price memory price, Price memory lotPrice); + /// @return price_ {UoA/tok} The low and high price estimate of an RToken + /// @return lotPrice_ {UoA/tok} The low and high lotprice of an RToken + function prices() external view returns (Price memory price_, Price memory lotPrice_); /// @return timestamp The timestamp at which the basket was last set function timestamp() external view returns (uint48); diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 823cc7b70f..cce8b741cd 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -314,10 +314,29 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { return basket.refAmts[erc20].div(refPerTok, CEIL); } + /// Should not revert + /// @return {UoA/BU} The lower end of the price estimate + /// @return {UoA/BU} The upper end of the price estimate + // returns sum(quantity(erc20) * price(erc20) for erc20 in basket.erc20s) + function price() external view returns (uint192, uint192) { + (Price memory p, ) = prices(); + return (p.low, p.high); + } + + /// Should not revert + /// lowLow should be nonzero when the asset might be worth selling + /// @return {UoA/BU} The lower end of the lot price estimate + /// @return {UoA/BU} The upper end of the lot price estimate + // returns sum(quantity(erc20) * lotPrice(erc20) for erc20 in basket.erc20s) + function lotPrice() external view returns (uint192, uint192) { + (, Price memory lotP) = prices(); + return (lotP.low, lotP.high); + } + /// Returns both the price + lotPrice at once, for gas optimization - /// @return price {UoA/tok} The low and high price estimate of an RToken - /// @return lotPrice {UoA/tok} The low and high lotprice of an RToken - function prices() external view returns (Price memory price, Price memory lotPrice) { + /// @return price_ {UoA/tok} The low and high price estimate of an RToken + /// @return lotPrice_ {UoA/tok} The low and high lotprice of an RToken + function prices() public view returns (Price memory price_, Price memory lotPrice_) { uint256 low256; uint256 high256; uint256 lotLow256; @@ -339,10 +358,10 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } // safe downcast: FIX_MAX is type(uint192).max - price.low = low256 >= FIX_MAX ? FIX_MAX : uint192(low256); - price.high = high256 >= FIX_MAX ? FIX_MAX : uint192(high256); - lotPrice.low = lotLow256 >= FIX_MAX ? FIX_MAX : uint192(lotLow256); - lotPrice.high = lotHigh256 >= FIX_MAX ? FIX_MAX : uint192(lotHigh256); + price_.low = low256 >= FIX_MAX ? FIX_MAX : uint192(low256); + price_.high = high256 >= FIX_MAX ? FIX_MAX : uint192(high256); + lotPrice_.low = lotLow256 >= FIX_MAX ? FIX_MAX : uint192(lotLow256); + lotPrice_.high = lotHigh256 >= FIX_MAX ? FIX_MAX : uint192(lotHigh256); } /// Multiply two fixes, rounding up to FIX_MAX and down to 0 diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index 08883d8c16..dc37cff06b 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -261,7 +261,7 @@ library TradingLibP0 { view returns (BasketRange memory range) { - (Price memory buPrice, ) = ctx.bh.prices(); // {UoA/BU} + (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.price(); // {UoA/BU} // Cap ctx.basketsHeld.top if (ctx.basketsHeld.top > ctx.rToken.basketsNeeded()) { @@ -315,14 +315,14 @@ library TradingLibP0 { // deficit: deduct optimistic estimate of baskets missing // {BU} = {UoA/tok} * {tok} / {UoA/BU} - deltaTop -= int256(uint256(low.mulDiv(anchor - bal, buPrice.high, FLOOR))); + deltaTop -= int256(uint256(low.mulDiv(anchor - bal, buPriceHigh, FLOOR))); // does not need underflow protection: using low price of asset } else { // surplus: add-in optimistic estimate of baskets purchaseable // {BU} = {UoA/tok} * {tok} / {UoA/BU} deltaTop += int256( - uint256(ctx.bm.safeMulDivCeil(high, bal - anchor, buPrice.low)) + uint256(ctx.bm.safeMulDivCeil(high, bal - anchor, buPriceLow)) ); // needs overflow protection: using high price of asset which can be FIX_MAX } @@ -350,11 +350,7 @@ library TradingLibP0 { // (3) Buy BUs at their high price with the remaining value // (4) Assume maximum slippage in trade // {BU} = {UoA} * {1} / {UoA/BU} - range.bottom += val.mulDiv( - FIX_ONE.minus(ctx.maxTradeSlippage), - buPrice.high, - FLOOR - ); + range.bottom += val.mulDiv(FIX_ONE.minus(ctx.maxTradeSlippage), buPriceHigh, FLOOR); } } diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 2d189313ce..dc59f488ff 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -332,10 +332,29 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { return basket.refAmts[erc20].div(refPerTok, CEIL); } + /// Should not revert + /// @return {UoA/BU} The lower end of the price estimate + /// @return {UoA/BU} The upper end of the price estimate + // returns sum(quantity(erc20) * price(erc20) for erc20 in basket.erc20s) + function price() external view returns (uint192, uint192) { + (Price memory p, ) = prices(); + return (p.low, p.high); + } + + /// Should not revert + /// lowLow should be nonzero when the asset might be worth selling + /// @return {UoA/BU} The lower end of the lot price estimate + /// @return {UoA/BU} The upper end of the lot price estimate + // returns sum(quantity(erc20) * lotPrice(erc20) for erc20 in basket.erc20s) + function lotPrice() external view returns (uint192, uint192) { + (, Price memory lotP) = prices(); + return (lotP.low, lotP.high); + } + /// Returns both the price() + lotPrice() at once, for gas optimization - /// @return price {UoA/tok} The low and high price estimate of an RToken - /// @return lotPrice {UoA/tok} The low and high lotprice of an RToken - function prices() external view returns (Price memory price, Price memory lotPrice) { + /// @return price_ {UoA/tok} The low and high price estimate of an RToken + /// @return lotPrice_ {UoA/tok} The low and high lotprice of an RToken + function prices() public view returns (Price memory price_, Price memory lotPrice_) { uint256 low256; uint256 high256; uint256 lotLow256; @@ -357,10 +376,10 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } // safe downcast: FIX_MAX is type(uint192).max - price.low = low256 >= FIX_MAX ? FIX_MAX : uint192(low256); - price.high = high256 >= FIX_MAX ? FIX_MAX : uint192(high256); - lotPrice.low = lotLow256 >= FIX_MAX ? FIX_MAX : uint192(lotLow256); - lotPrice.high = lotHigh256 >= FIX_MAX ? FIX_MAX : uint192(lotHigh256); + price_.low = low256 >= FIX_MAX ? FIX_MAX : uint192(low256); + price_.high = high256 >= FIX_MAX ? FIX_MAX : uint192(high256); + lotPrice_.low = lotLow256 >= FIX_MAX ? FIX_MAX : uint192(lotLow256); + lotPrice_.high = lotHigh256 >= FIX_MAX ? FIX_MAX : uint192(lotHigh256); } /// Multiply two fixes, rounding up to FIX_MAX and down to 0 From 33a512741bd99fb26f094c255a3a681a087b219c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 6 Apr 2023 22:37:04 -0400 Subject: [PATCH 006/462] move quantities calc outside assetRegistry --- contracts/facade/FacadeRead.sol | 7 +++- contracts/interfaces/IAssetRegistry.sol | 3 +- contracts/p0/AssetRegistry.sol | 5 +-- contracts/p1/AssetRegistry.sol | 4 +- .../p1/mixins/RecollateralizationLib.sol | 37 +++++++++++-------- contracts/plugins/assets/RTokenAsset.sol | 7 +++- 6 files changed, 37 insertions(+), 26 deletions(-) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index adc6d28e50..2ac57028db 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -439,10 +439,15 @@ contract FacadeRead is IFacadeRead { Registry memory reg = ctx.reg.getRegistry(); + uint192[] memory quantities = new uint192[](reg.erc20s.length); + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + quantities[i] = ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + } + (Price memory buPrice, ) = bh.prices(); // will exclude UoA value from RToken balances at BackingManager - range = RecollateralizationLibP1.basketRange(ctx, reg, buPrice); + range = RecollateralizationLibP1.basketRange(ctx, reg, quantities, buPrice); } } } diff --git a/contracts/interfaces/IAssetRegistry.sol b/contracts/interfaces/IAssetRegistry.sol index 9353411d49..58534cf1da 100644 --- a/contracts/interfaces/IAssetRegistry.sol +++ b/contracts/interfaces/IAssetRegistry.sol @@ -9,7 +9,6 @@ import "./IComponent.sol"; struct Registry { IERC20[] erc20s; IAsset[] assets; - uint192[] quantities; // {tok/BU} Quantity of whole token in the basket } /** @@ -50,7 +49,7 @@ interface IAssetRegistry is IComponent { /// @return A list of all registered ERC20s function erc20s() external view returns (IERC20[] memory); - /// @return reg The list of registered ERC20s, Assets, and basket quantities + /// @return reg The list of registered ERC20s and Assets, in the same order function getRegistry() external view returns (Registry memory reg); function register(IAsset asset) external returns (bool); diff --git a/contracts/p0/AssetRegistry.sol b/contracts/p0/AssetRegistry.sol index 29af7f587a..eca9e3a1af 100644 --- a/contracts/p0/AssetRegistry.sol +++ b/contracts/p0/AssetRegistry.sol @@ -105,21 +105,18 @@ contract AssetRegistryP0 is ComponentP0, IAssetRegistry { } } - /// @return reg The list of registered ERC20s, Assets, and basket quantities + /// @return reg The list of registered ERC20s and Assets, in the same order function getRegistry() external view returns (Registry memory reg) { uint256 length = _erc20s.length(); reg.erc20s = new IERC20[](length); reg.assets = new IAsset[](length); - reg.quantities = new uint192[](length); for (uint256 i = 0; i < length; ++i) { reg.erc20s[i] = IERC20(_erc20s.at(i)); reg.assets[i] = assets[IERC20(_erc20s.at(i))]; - reg.quantities[i] = main.basketHandler().quantityUnsafe(reg.erc20s[i], reg.assets[i]); assert(address(reg.erc20s[i]) != address(0)); assert(address(reg.assets[i]) != address(0)); } assert(reg.erc20s.length == reg.assets.length); - assert(reg.quantities.length == reg.assets.length); } // diff --git a/contracts/p1/AssetRegistry.sol b/contracts/p1/AssetRegistry.sol index 6f2841f9d9..b42cffd400 100644 --- a/contracts/p1/AssetRegistry.sol +++ b/contracts/p1/AssetRegistry.sol @@ -140,16 +140,14 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { /// Returns keys(assets), values(assets) as (duplicate-free) lists. // returns: [keys(assets)], [values(assets)] without duplicates. - /// @return reg The list of registered ERC20s, Assets, and basket quantities + /// @return reg The list of registered ERC20s and Assets, in the same order function getRegistry() external view returns (Registry memory reg) { uint256 length = _erc20s.length(); reg.erc20s = new IERC20[](length); reg.assets = new IAsset[](length); - reg.quantities = new uint192[](length); for (uint256 i = 0; i < length; ++i) { reg.erc20s[i] = IERC20(_erc20s.at(i)); reg.assets[i] = assets[IERC20(_erc20s.at(i))]; - reg.quantities[i] = basketHandler.quantityUnsafe(reg.erc20s[i], reg.assets[i]); } } diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index 92dad339f4..3be7b67f1a 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -30,7 +30,9 @@ struct TradingContext { uint192 maxTradeSlippage; // {1} } +// All the extra information nextTradePair() needs; necessary to stay under stack limit struct NextTradePairContext { + uint192[] quantities; // {tok/BU} basket quantities BasketRange range; Price rTokenPrice; Price rTokenLotPrice; @@ -65,6 +67,8 @@ library RecollateralizationLibP1 { view returns (bool doTrade, TradeRequest memory req) { + NextTradePairContext memory ntpCtx; + // === Prepare cached values === IMain main = bm.main(); @@ -81,30 +85,32 @@ library RecollateralizationLibP1 { }); Registry memory reg = ctx.reg.getRegistry(); + // Calculate quantities up-front for re-use + ntpCtx.quantities = new uint192[](reg.erc20s.length); + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + ntpCtx.quantities[i] = ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + } + // ============================ // Compute BU price (Price memory buPrice, Price memory buLotPrice) = ctx.bh.prices(); // Compute a target basket range for trading - {BU} - BasketRange memory range = basketRange(ctx, reg, buPrice); + ntpCtx.range = basketRange(ctx, reg, ntpCtx.quantities, buPrice); // Compute RToken price RTokenAsset rTokenAsset = RTokenAsset( address(ctx.reg.toAsset(IERC20(address(ctx.rToken)))) ); - (Price memory rTokenPrice, Price memory rTokenLotPrice) = rTokenAsset.prices( - range, + (ntpCtx.rTokenPrice, ntpCtx.rTokenLotPrice) = rTokenAsset.prices( + ntpCtx.range, buPrice, buLotPrice ); // Select a pair to trade next, if one exists - TradeInfo memory trade = nextTradePair( - ctx, - reg, - NextTradePairContext(range, rTokenPrice, rTokenLotPrice) - ); + TradeInfo memory trade = nextTradePair(ctx, reg, ntpCtx); // Don't trade if no pair is selected if (address(trade.sell) == address(0) || address(trade.buy) == address(0)) { @@ -167,6 +173,7 @@ library RecollateralizationLibP1 { function basketRange( TradingContext memory ctx, Registry memory reg, + uint192[] memory quantities, Price memory buPrice ) internal view returns (BasketRange memory range) { uint192 basketsNeeded = ctx.rToken.basketsNeeded(); @@ -185,8 +192,8 @@ library RecollateralizationLibP1 { int256 deltaTop; // D18{BU} even though this is int256, it is D18 // not required for range.bottom - uint256 len = reg.erc20s.length; - for (uint256 i = 0; i < len; ++i) { + // (no space on the stack to cache erc20s.length) + for (uint256 i = 0; i < reg.erc20s.length; ++i) { // Exclude RToken balances to avoid double counting value if (reg.erc20s[i] == IERC20(address(ctx.rToken))) continue; @@ -203,7 +210,7 @@ library RecollateralizationLibP1 { // Intentionally include value of IFFY/DISABLED collateral if ( - reg.quantities[i] == 0 && + quantities[i] == 0 && !TradeLib.isEnoughToSell(reg.assets[i], bal, lotLow, ctx.minTradeVolume) ) continue; } @@ -216,7 +223,7 @@ library RecollateralizationLibP1 { // if in surplus relative to ctx.basketsHeld.top: add-in surplus baskets { // {tok} = {tok/BU} * {BU} - uint192 anchor = reg.quantities[i].mul(ctx.basketsHeld.top, CEIL); + uint192 anchor = quantities[i].mul(ctx.basketsHeld.top, CEIL); if (anchor > bal) { // deficit: deduct optimistic estimate of baskets missing @@ -239,7 +246,7 @@ library RecollateralizationLibP1 { // add-in surplus baskets relative to ctx.basketsHeld.bottom { // {tok} = {tok/BU} * {BU} - uint192 anchor = reg.quantities[i].mul(ctx.basketsHeld.bottom, FLOOR); + uint192 anchor = quantities[i].mul(ctx.basketsHeld.bottom, FLOOR); // (1) Sell tokens at low price // {UoA} = {UoA/tok} * {tok} @@ -344,7 +351,7 @@ library RecollateralizationLibP1 { // {tok} = {BU} * {tok/BU} // needed(Top): token balance needed for range.top baskets: quantity(e) * range.top - uint192 needed = ntpCtx.range.top.mul(reg.quantities[i], CEIL); // {tok} + uint192 needed = ntpCtx.range.top.mul(ntpCtx.quantities[i], CEIL); // {tok} if (bal.gt(needed)) { uint192 low; // {UoA/sellTok} @@ -389,7 +396,7 @@ library RecollateralizationLibP1 { } } else { // needed(Bottom): token balance needed at bottom of the basket range - needed = ntpCtx.range.bottom.mul(reg.quantities[i], CEIL); // {buyTok}; + needed = ntpCtx.range.bottom.mul(ntpCtx.quantities[i], CEIL); // {buyTok}; if (bal.lt(needed)) { uint192 amtShort = needed.minus(bal); // {buyTok} diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index c315c6c4e1..b31efcd636 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -165,8 +165,13 @@ contract RTokenAsset is IAsset { Registry memory reg = assetRegistry.getRegistry(); + uint192[] memory quantities = new uint192[](reg.erc20s.length); + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + quantities[i] = ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + } + // will exclude UoA value from RToken balances at BackingManager - range = RecollateralizationLibP1.basketRange(ctx, reg, buPrice); + range = RecollateralizationLibP1.basketRange(ctx, reg, quantities, buPrice); } } } From fda219ce2277ede1c8ec3e35bda63c672ad3818f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 6 Apr 2023 22:37:12 -0400 Subject: [PATCH 007/462] gas snapshots --- test/__snapshots__/FacadeAct.test.ts.snap | 2 +- test/__snapshots__/FacadeWrite.test.ts.snap | 2 +- test/__snapshots__/Main.test.ts.snap | 6 +++--- test/__snapshots__/RToken.test.ts.snap | 6 +++--- .../Recollateralization.test.ts.snap | 8 ++++---- test/__snapshots__/Revenues.test.ts.snap | 2 +- .../__snapshots__/MaxBasketSize.test.ts.snap | 16 ++++++++-------- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/test/__snapshots__/FacadeAct.test.ts.snap b/test/__snapshots__/FacadeAct.test.ts.snap index ec5d51db80..411bd39b19 100644 --- a/test/__snapshots__/FacadeAct.test.ts.snap +++ b/test/__snapshots__/FacadeAct.test.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeAct contract Gas Reporting getActCalldata - gas reporting for 128 registered assets 1`] = `18857104`; +exports[`FacadeAct contract Gas Reporting getActCalldata - gas reporting for 128 registered assets 1`] = `19107831`; diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index 8b871b9564..be57ba092e 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8813373`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8844592`; exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5666876`; diff --git a/test/__snapshots__/Main.test.ts.snap b/test/__snapshots__/Main.test.ts.snap index 0a4a22715e..906c5f3264 100644 --- a/test/__snapshots__/Main.test.ts.snap +++ b/test/__snapshots__/Main.test.ts.snap @@ -6,8 +6,8 @@ exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `19 exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `192869`; -exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `169192`; +exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `169302`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `83401`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `83511`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `72913`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `73023`; diff --git a/test/__snapshots__/RToken.test.ts.snap b/test/__snapshots__/RToken.test.ts.snap index 67d3e48a9a..858ed29909 100644 --- a/test/__snapshots__/RToken.test.ts.snap +++ b/test/__snapshots__/RToken.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `750778`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `750756`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `576804`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `576782`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `515715`; +exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `515671`; exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56592`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 16c07bf629..29934e746c 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -2,16 +2,16 @@ exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `63713`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1671658`; +exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1667469`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `386416`; +exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `386392`; exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `184737`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `1652643`; +exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `1648472`; exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `184745`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 7`] = `1741673`; +exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 7`] = `1737517`; exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 8`] = `212855`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index d110b4bc0b..a23724c096 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -14,7 +14,7 @@ exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `217024`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `721689`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `971956`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `974886`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `243131`; diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index c86e9929c9..9e300308df 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `11324699`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `11325033`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `8310510`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `8297573`; exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2360983`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `8453434`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `8453412`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `19425585`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `19817394`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10461193`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10461171`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `7440417`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `7440373`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4082402`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4082384`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `13872733`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `13529818`; From 7184da16c5c97995003bed7aa5d922bc65e95b86 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 6 Apr 2023 23:34:51 -0400 Subject: [PATCH 008/462] safer RTokenAsset.price() --- contracts/plugins/assets/RTokenAsset.sol | 140 +++++++++++++---------- 1 file changed, 80 insertions(+), 60 deletions(-) diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index b31efcd636..cbcfe7c3be 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -39,7 +39,60 @@ contract RTokenAsset is IAsset { maxTradeVolume = maxTradeVolume_; } + /// @return range {BU} An estimate of the range of baskets held by the BackingManager + /// @return buPrice {UoA/BU} Lower and upper bu price + /// @return buLotPrice {UoA/BU} Lower and upper bu lotPrice + function basketRange() + external + view + returns ( + BasketRange memory range, + Price memory buPrice, + Price memory buLotPrice + ) + { + (buPrice, buLotPrice) = basketHandler.prices(); + + BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(backingManager)); + uint192 basketsNeeded = IRToken(address(erc20)).basketsNeeded(); // {BU} + + // if (basketHandler.fullyCollateralized()) + if (basketsHeld.bottom >= basketsNeeded) { + range.bottom = basketsNeeded; + range.top = basketsNeeded; + } else { + // Note: Extremely this is extremely wasteful in terms of gas. This only exists so + // there is _some_ asset to represent the RToken itself when it is deployed, in + // the absence of an external price feed. Any RToken that gets reasonably big + // should switch over to an asset with a price feed. + + IMain main = backingManager.main(); + TradingContext memory ctx = TradingContext({ + basketsHeld: basketsHeld, + bm: backingManager, + bh: main.basketHandler(), + reg: main.assetRegistry(), + stRSR: main.stRSR(), + rsr: main.rsr(), + rToken: main.rToken(), + minTradeVolume: backingManager.minTradeVolume(), + maxTradeSlippage: backingManager.maxTradeSlippage() + }); + + Registry memory reg = assetRegistry.getRegistry(); + + uint192[] memory quantities = new uint192[](reg.erc20s.length); + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + quantities[i] = ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + } + + // will exclude UoA value from RToken balances at BackingManager + range = RecollateralizationLibP1.basketRange(ctx, reg, quantities, buPrice); + } + } + /// Calculates price() + lotPrice() in a gas-optimized way using a cached BasketRange + /// Used by RecollateralizationLib for efficient price calculation /// @param buRange {BU} The top and bottom of the bu band; how many BUs we expect to hold /// @param buPrice {UoA/BU} The low and high price estimate of a basket unit /// @param buLotPrice {UoA/BU} The low and high lotprice of a basket unit @@ -76,10 +129,18 @@ contract RTokenAsset is IAsset { /// @return {UoA/tok} The lower end of the price estimate /// @return {UoA/tok} The upper end of the price estimate function price() public view virtual returns (uint192, uint192) { - (BasketRange memory buRange, Price memory buPrice, Price memory buLotPrice) = basketRange(); - - try this.prices(buRange, buPrice, buLotPrice) returns (Price memory p, Price memory) { - return (p.low, p.high); + try this.basketRange() returns ( + BasketRange memory buRange, + Price memory buPrice, + Price memory buLotPrice + ) { + try this.prices(buRange, buPrice, buLotPrice) returns (Price memory p, Price memory) { + return (p.low, p.high); + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + return (0, FIX_MAX); + } } catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string @@ -92,11 +153,21 @@ contract RTokenAsset is IAsset { /// @return {UoA/tok} The lower end of the lot price estimate /// @return {UoA/tok} The upper end of the lot price estimate function lotPrice() external view returns (uint192, uint192) { - (BasketRange memory buRange, Price memory buPrice, Price memory buLotPrice) = basketRange(); - - // prices() should not revert because all underlying.price() calls should not revert - try this.prices(buRange, buPrice, buLotPrice) returns (Price memory, Price memory lotP) { - return (lotP.low, lotP.high); + try this.basketRange() returns ( + BasketRange memory buRange, + Price memory buPrice, + Price memory buLotPrice + ) { + try this.prices(buRange, buPrice, buLotPrice) returns ( + Price memory, + Price memory lotP + ) { + return (lotP.low, lotP.high); + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + return (0, FIX_MAX); + } } catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string @@ -123,55 +194,4 @@ contract RTokenAsset is IAsset { function claimRewards() external virtual {} // solhint-enable no-empty-blocks - - // ==== Private ==== - - function basketRange() - private - view - returns ( - BasketRange memory range, - Price memory buPrice, - Price memory buLotPrice - ) - { - (buPrice, buLotPrice) = basketHandler.prices(); - - BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(backingManager)); - uint192 basketsNeeded = IRToken(address(erc20)).basketsNeeded(); // {BU} - - // if (basketHandler.fullyCollateralized()) - if (basketsHeld.bottom >= basketsNeeded) { - range.bottom = basketsNeeded; - range.top = basketsNeeded; - } else { - // Note: Extremely this is extremely wasteful in terms of gas. This only exists so - // there is _some_ asset to represent the RToken itself when it is deployed, in - // the absence of an external price feed. Any RToken that gets reasonably big - // should switch over to an asset with a price feed. - - IMain main = backingManager.main(); - TradingContext memory ctx = TradingContext({ - basketsHeld: basketsHeld, - bm: backingManager, - bh: main.basketHandler(), - reg: main.assetRegistry(), - stRSR: main.stRSR(), - rsr: main.rsr(), - rToken: main.rToken(), - minTradeVolume: backingManager.minTradeVolume(), - maxTradeSlippage: backingManager.maxTradeSlippage() - }); - - Registry memory reg = assetRegistry.getRegistry(); - - uint192[] memory quantities = new uint192[](reg.erc20s.length); - for (uint256 i = 0; i < reg.erc20s.length; ++i) { - quantities[i] = ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); - } - - // will exclude UoA value from RToken balances at BackingManager - range = RecollateralizationLibP1.basketRange(ctx, reg, quantities, buPrice); - } - } } From e99f7f26ddfb580bfb23986db86e39ee4d31b41e Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 7 Apr 2023 19:12:39 -0400 Subject: [PATCH 009/462] clean up RTokenAsset --- contracts/facade/FacadeRead.sol | 7 +- .../p1/mixins/RecollateralizationLib.sol | 21 +++--- contracts/plugins/assets/RTokenAsset.sol | 75 +++++++------------ test/__snapshots__/FacadeAct.test.ts.snap | 2 +- test/__snapshots__/FacadeWrite.test.ts.snap | 2 +- .../Recollateralization.test.ts.snap | 6 +- test/__snapshots__/Revenues.test.ts.snap | 14 ++-- .../__snapshots__/MaxBasketSize.test.ts.snap | 6 +- 8 files changed, 56 insertions(+), 77 deletions(-) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 2ac57028db..2f3011f04b 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -428,8 +428,7 @@ contract FacadeRead is IFacadeRead { TradingContext memory ctx = TradingContext({ basketsHeld: basketsHeld, bm: bm, - bh: bh, - reg: main.assetRegistry(), + ar: main.assetRegistry(), stRSR: main.stRSR(), rsr: main.rsr(), rToken: main.rToken(), @@ -437,11 +436,11 @@ contract FacadeRead is IFacadeRead { maxTradeSlippage: bm.maxTradeSlippage() }); - Registry memory reg = ctx.reg.getRegistry(); + Registry memory reg = ctx.ar.getRegistry(); uint192[] memory quantities = new uint192[](reg.erc20s.length); for (uint256 i = 0; i < reg.erc20s.length; ++i) { - quantities[i] = ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + quantities[i] = bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); } (Price memory buPrice, ) = bh.prices(); diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index 3be7b67f1a..4ca6eab1c3 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -20,8 +20,7 @@ struct TradingContext { // Components IBackingManager bm; - IBasketHandler bh; - IAssetRegistry reg; + IAssetRegistry ar; IStRSR stRSR; IERC20 rsr; IRToken rToken; @@ -75,34 +74,32 @@ library RecollateralizationLibP1 { TradingContext memory ctx = TradingContext({ basketsHeld: basketsHeld, bm: bm, - bh: main.basketHandler(), - reg: main.assetRegistry(), + ar: main.assetRegistry(), stRSR: main.stRSR(), rsr: main.rsr(), rToken: main.rToken(), minTradeVolume: bm.minTradeVolume(), maxTradeSlippage: bm.maxTradeSlippage() }); - Registry memory reg = ctx.reg.getRegistry(); + Registry memory reg = ctx.ar.getRegistry(); // Calculate quantities up-front for re-use + IBasketHandler bh = main.basketHandler(); ntpCtx.quantities = new uint192[](reg.erc20s.length); for (uint256 i = 0; i < reg.erc20s.length; ++i) { - ntpCtx.quantities[i] = ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + ntpCtx.quantities[i] = bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); } // ============================ // Compute BU price - (Price memory buPrice, Price memory buLotPrice) = ctx.bh.prices(); + (Price memory buPrice, Price memory buLotPrice) = bh.prices(); // Compute a target basket range for trading - {BU} ntpCtx.range = basketRange(ctx, reg, ntpCtx.quantities, buPrice); // Compute RToken price - RTokenAsset rTokenAsset = RTokenAsset( - address(ctx.reg.toAsset(IERC20(address(ctx.rToken)))) - ); + RTokenAsset rTokenAsset = RTokenAsset(address(ctx.ar.toAsset(IERC20(address(ctx.rToken))))); (ntpCtx.rTokenPrice, ntpCtx.rTokenLotPrice) = rTokenAsset.prices( ntpCtx.range, buPrice, @@ -420,7 +417,7 @@ library RecollateralizationLibP1 { // Special-case RToken { RTokenAsset rTokenAsset = RTokenAsset( - address(ctx.reg.toAsset(IERC20(address(ctx.rToken)))) + address(ctx.ar.toAsset(IERC20(address(ctx.rToken)))) ); uint192 bal = rTokenAsset.bal(address(ctx.bm)); @@ -446,7 +443,7 @@ library RecollateralizationLibP1 { // Use RSR if needed if (address(trade.sell) == address(0) && address(trade.buy) != address(0)) { - IAsset rsrAsset = ctx.reg.toAsset(ctx.rsr); + IAsset rsrAsset = ctx.ar.toAsset(ctx.rsr); uint192 rsrAvailable = rsrAsset.bal(address(ctx.bm)).plus( rsrAsset.bal(address(ctx.stRSR)) diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index cbcfe7c3be..0f26493418 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -39,27 +39,20 @@ contract RTokenAsset is IAsset { maxTradeVolume = maxTradeVolume_; } - /// @return range {BU} An estimate of the range of baskets held by the BackingManager - /// @return buPrice {UoA/BU} Lower and upper bu price - /// @return buLotPrice {UoA/BU} Lower and upper bu lotPrice - function basketRange() - external - view - returns ( - BasketRange memory range, - Price memory buPrice, - Price memory buLotPrice - ) - { - (buPrice, buLotPrice) = basketHandler.prices(); + /// Calculates price() + lotPrice() in a non-gas-optimized way + /// @return price_ {UoA/tok} The low and high price estimate of an RToken + /// @return lotPrice_ {UoA/tok} The low and high lotprice of an RToken + function prices() public view returns (Price memory price_, Price memory lotPrice_) { + BasketRange memory buRange; // {BU} + (Price memory buPrice, Price memory buLotPrice) = basketHandler.prices(); // {UoA/BU} BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(backingManager)); uint192 basketsNeeded = IRToken(address(erc20)).basketsNeeded(); // {BU} // if (basketHandler.fullyCollateralized()) if (basketsHeld.bottom >= basketsNeeded) { - range.bottom = basketsNeeded; - range.top = basketsNeeded; + buRange.bottom = basketsNeeded; + buRange.top = basketsNeeded; } else { // Note: Extremely this is extremely wasteful in terms of gas. This only exists so // there is _some_ asset to represent the RToken itself when it is deployed, in @@ -70,8 +63,7 @@ contract RTokenAsset is IAsset { TradingContext memory ctx = TradingContext({ basketsHeld: basketsHeld, bm: backingManager, - bh: main.basketHandler(), - reg: main.assetRegistry(), + ar: assetRegistry, stRSR: main.stRSR(), rsr: main.rsr(), rToken: main.rToken(), @@ -83,12 +75,26 @@ contract RTokenAsset is IAsset { uint192[] memory quantities = new uint192[](reg.erc20s.length); for (uint256 i = 0; i < reg.erc20s.length; ++i) { - quantities[i] = ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + quantities[i] = basketHandler.quantityUnsafe(reg.erc20s[i], reg.assets[i]); } // will exclude UoA value from RToken balances at BackingManager - range = RecollateralizationLibP1.basketRange(ctx, reg, quantities, buPrice); + buRange = RecollateralizationLibP1.basketRange(ctx, reg, quantities, buPrice); } + + // Here we take advantage of the fact that we know RToken has 18 decimals + // to convert between uint256 an uint192. Fits due to assumed max totalSupply. + uint192 supply = _safeWrap(IRToken(address(erc20)).totalSupply()); + + if (supply == 0) return (buPrice, buLotPrice); + + // {UoA/tok} = {BU} * {UoA/BU} / {tok} + price_.low = buRange.bottom.mulDiv(buPrice.low, supply, FLOOR); + price_.high = buRange.top.mulDiv(buPrice.high, supply, CEIL); + lotPrice_.low = buRange.bottom.mulDiv(buLotPrice.low, supply, FLOOR); + lotPrice_.high = buRange.top.mulDiv(buLotPrice.high, supply, CEIL); + assert(price_.low <= price_.high); + assert(lotPrice_.low <= lotPrice_.high); } /// Calculates price() + lotPrice() in a gas-optimized way using a cached BasketRange @@ -129,18 +135,8 @@ contract RTokenAsset is IAsset { /// @return {UoA/tok} The lower end of the price estimate /// @return {UoA/tok} The upper end of the price estimate function price() public view virtual returns (uint192, uint192) { - try this.basketRange() returns ( - BasketRange memory buRange, - Price memory buPrice, - Price memory buLotPrice - ) { - try this.prices(buRange, buPrice, buLotPrice) returns (Price memory p, Price memory) { - return (p.low, p.high); - } catch (bytes memory errData) { - // see: docs/solidity-style.md#Catching-Empty-Data - if (errData.length == 0) revert(); // solhint-disable-line reason-string - return (0, FIX_MAX); - } + try this.prices() returns (Price memory p, Price memory) { + return (p.low, p.high); } catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string @@ -153,21 +149,8 @@ contract RTokenAsset is IAsset { /// @return {UoA/tok} The lower end of the lot price estimate /// @return {UoA/tok} The upper end of the lot price estimate function lotPrice() external view returns (uint192, uint192) { - try this.basketRange() returns ( - BasketRange memory buRange, - Price memory buPrice, - Price memory buLotPrice - ) { - try this.prices(buRange, buPrice, buLotPrice) returns ( - Price memory, - Price memory lotP - ) { - return (lotP.low, lotP.high); - } catch (bytes memory errData) { - // see: docs/solidity-style.md#Catching-Empty-Data - if (errData.length == 0) revert(); // solhint-disable-line reason-string - return (0, FIX_MAX); - } + try this.prices() returns (Price memory, Price memory lotP) { + return (lotP.low, lotP.high); } catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string diff --git a/test/__snapshots__/FacadeAct.test.ts.snap b/test/__snapshots__/FacadeAct.test.ts.snap index 411bd39b19..d0739385f5 100644 --- a/test/__snapshots__/FacadeAct.test.ts.snap +++ b/test/__snapshots__/FacadeAct.test.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeAct contract Gas Reporting getActCalldata - gas reporting for 128 registered assets 1`] = `19107831`; +exports[`FacadeAct contract Gas Reporting getActCalldata - gas reporting for 128 registered assets 1`] = `19102755`; diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index be57ba092e..54b3999448 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8844592`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8876425`; exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5666876`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 29934e746c..bf151f3b37 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -2,16 +2,16 @@ exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `63713`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1667469`; +exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1667295`; exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `386392`; exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `184737`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `1648472`; +exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `1648298`; exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `184745`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 7`] = `1737517`; +exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 7`] = `1737343`; exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 8`] = `212855`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index a23724c096..51294f5db2 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -1,20 +1,20 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 1`] = `169823`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 1`] = `169846`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `169765`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `169788`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `169765`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `169788`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `251282`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `251305`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `217024`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `217047`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `217024`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `217047`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `721689`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `974886`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `973038`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `243131`; diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index 9e300308df..8589a955d1 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -4,11 +4,11 @@ exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max baske exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `8297573`; -exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2360983`; +exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2361006`; exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `8453412`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `19817394`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `19816331`; exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10461171`; @@ -16,4 +16,4 @@ exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket corr exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4082384`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `13529818`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `13528754`; From 137d941a78d947857f48d2eda0ffc56b59faee2d Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 10 Apr 2023 09:29:41 -0300 Subject: [PATCH 010/462] backing manager interface comments --- contracts/interfaces/IBackingManager.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contracts/interfaces/IBackingManager.sol b/contracts/interfaces/IBackingManager.sol index f01f75b1a2..169607fff3 100644 --- a/contracts/interfaces/IBackingManager.sol +++ b/contracts/interfaces/IBackingManager.sol @@ -17,7 +17,14 @@ import "./ITrading.sol"; * vast majority of cases we expect the the O(n^2) function to be acceptable. */ interface IBackingManager is IComponent, ITrading { + /// Emitted when the trading delay is changed + /// @param oldVal The old trading delay + /// @param newVal The new trading delay event TradingDelaySet(uint48 indexed oldVal, uint48 indexed newVal); + + /// Emitted when the backing buffer is changed + /// @param oldVal The old backing buffer + /// @param newVal The new backing buffer event BackingBufferSet(uint192 indexed oldVal, uint192 indexed newVal); // Initialization From be54a716284bfb76d70f14012793667eb2f17b1f Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 10 Apr 2023 09:41:18 -0300 Subject: [PATCH 011/462] max state count in solhint config --- .solhint.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.solhint.json b/.solhint.json index fec3edb3cd..208382e81f 100644 --- a/.solhint.json +++ b/.solhint.json @@ -3,6 +3,7 @@ "plugins": ["prettier"], "rules": { "prettier/prettier": "error", + "max-states-count": ["warn", 20], "max-line-length": ["error", 100], "func-param-name-mixedcase": "error", "modifier-name-mixedcase": "error", From f691cfcc8a44f9d0c1a6120c9c4edd9c7483b1a9 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 10 Apr 2023 09:42:17 -0300 Subject: [PATCH 012/462] add warmup period functionality --- contracts/interfaces/IBasketHandler.sol | 28 +++++++++++- contracts/interfaces/IDeployer.sol | 3 ++ contracts/p0/AssetRegistry.sol | 5 ++- contracts/p0/BackingManager.sol | 4 +- contracts/p0/BasketHandler.sol | 58 ++++++++++++++++++++++--- contracts/p0/Deployer.sol | 2 +- contracts/p0/RToken.sol | 3 +- contracts/p1/AssetRegistry.sol | 6 ++- contracts/p1/BackingManager.sol | 5 ++- contracts/p1/BasketHandler.sol | 58 ++++++++++++++++++++++--- contracts/p1/Deployer.sol | 2 +- contracts/p1/RToken.sol | 4 +- 12 files changed, 152 insertions(+), 26 deletions(-) diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 3360d0eb83..1575ac9f21 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -38,8 +38,21 @@ interface IBasketHandler is IComponent { /// @param erc20s The set of backup collateral tokens event BackupConfigSet(bytes32 indexed targetName, uint256 indexed max, IERC20[] erc20s); + /// Emitted when the warmup period is changed + /// @param oldVal The old warmup period + /// @param newVal The new warmup period + event WarmupPeriodSet(uint48 indexed oldVal, uint48 indexed newVal); + + /// Emitted when the status of a basket has changed + /// @param oldStatus The previous basket status + /// @param newStatus The new basket status + event BasketStatusChanged( + CollateralStatus indexed oldStatus, + CollateralStatus indexed newStatus + ); + // Initialization - function init(IMain main_) external; + function init(IMain main_, uint48 warmupPeriod_) external; /// Set the prime basket /// @param erc20s The collateral tokens for the new prime basket @@ -75,6 +88,13 @@ interface IBasketHandler is IComponent { /// @return status The worst CollateralStatus of all collateral in the basket function status() external view returns (CollateralStatus status); + /// Track the basket status changes + /// @custom:protected + function trackStatus() external; + + /// @return If the basket is ready to issue and trade + function isReady() external view returns (bool); + /// @param erc20 The ERC20 token contract for the asset /// @return {tok/BU} The whole token quantity of token in the reference basket /// Returns 0 if erc20 is not registered or not in the basket @@ -120,3 +140,9 @@ interface IBasketHandler is IComponent { /// @return The current basket nonce, regardless of status function nonce() external view returns (uint48); } + +interface TestIBasketHandler is IBasketHandler { + function warmupPeriod() external view returns (uint48); + + function setWarmupPeriod(uint48 val) external; +} diff --git a/contracts/interfaces/IDeployer.sol b/contracts/interfaces/IDeployer.sol index 49d7b0656e..4e4477ef18 100644 --- a/contracts/interfaces/IDeployer.sol +++ b/contracts/interfaces/IDeployer.sol @@ -35,6 +35,9 @@ struct DeploymentParams { // === StRSR === uint48 unstakingDelay; // {s} the "thawing time" of staked RSR before withdrawal // + // === BasketHandler === + uint48 warmupPeriod; // {s} how long to wait until issuance/trading after regaining SOUND + // // === BackingManager === uint48 tradingDelay; // {s} how long to wait until starting auctions after switching basket uint48 auctionLength; // {s} the length of an auction diff --git a/contracts/p0/AssetRegistry.sol b/contracts/p0/AssetRegistry.sol index 1fb027464a..ca9b4d6f0f 100644 --- a/contracts/p0/AssetRegistry.sol +++ b/contracts/p0/AssetRegistry.sol @@ -27,13 +27,16 @@ contract AssetRegistryP0 is ComponentP0, IAssetRegistry { } } - /// Force updates in all collateral assets + /// Force updates in all collateral assets. Tracks basket status. /// @custom:refresher function refresh() public { // It's a waste of gas to require notPausedOrFrozen because assets can be updated directly for (uint256 i = 0; i < _erc20s.length(); i++) { assets[IERC20(_erc20s.at(i))].refresh(); } + + IBasketHandler basketHandler = main.basketHandler(); + basketHandler.trackStatus(); } /// Forbids registering a different asset for an ERC20 that is already registered diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index dc09d81f4e..b63f577c9a 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -72,8 +72,8 @@ contract BackingManagerP0 is TradingP0, IBackingManager { if (tradesOpen > 0) return; - // Do not trade when not SOUND - require(main.basketHandler().status() == CollateralStatus.SOUND, "basket not sound"); + // Ensure basket is ready, SOUND and not in warmup period + require(main.basketHandler().isReady(), "basket not ready"); uint48 basketTimestamp = main.basketHandler().timestamp(); if (block.timestamp < basketTimestamp + tradingDelay) return; diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 5c87abea64..02e4cff47a 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -109,6 +109,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { using EnumerableSet for EnumerableSet.Bytes32Set; using FixLib for uint192; + uint48 public constant MAX_WARMUP_PERIOD = 31536000; // {s} 1 year uint192 public constant MAX_TARGET_AMT = 1e3 * FIX_ONE; // {target/BU} max basket weight // config is the basket configuration, from which basket will be computed in a basket-switch @@ -126,6 +127,18 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // be paused. bool private disabled; + // These are effectively local variables of _switchBasket. + // Nothing should use their values from previous transactions. + EnumerableSet.Bytes32Set private targetNames; + Basket private newBasket; // Always empty + + uint48 public warmupPeriod; // {s} how long to wait until issuance/trading after regaining SOUND + + // basket status changes, mainly set when `trackStatus()` is called + // used to enforce warmup period, after regaining SOUND + CollateralStatus private lastStatus; + uint48 private lastStatusTimestamp; + // ==== Invariants ==== // basket is a valid Basket: // basket.erc20s is a valid collateral array and basket.erc20s == keys(basket.refAmts) @@ -136,8 +149,15 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // if basket.erc20s is empty then disabled == true // BasketHandler.init() just leaves the BasketHandler state zeroed - function init(IMain main_) external initializer { + function init(IMain main_, uint48 warmupPeriod_) external initializer { __Component_init(main_); + + setWarmupPeriod(warmupPeriod_); + + // Set last status to DISABLED (default) + lastStatus = CollateralStatus.DISABLED; + lastStatusTimestamp = uint48(block.timestamp); + disabled = true; } @@ -174,6 +194,8 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { "basket unrefreshable" ); _switchBasket(); + + trackStatus(); } /// Set the prime basket in the basket configuration, in terms of erc20s and target amounts @@ -275,6 +297,24 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } } + /// Track basket status changes if they ocurred + // effects: lastStatus' = status(), and lastStatusTimestamp' = current timestamp + function trackStatus() public { + CollateralStatus currentStatus = status(); + if (currentStatus != lastStatus) { + emit BasketStatusChanged(lastStatus, currentStatus); + lastStatus = currentStatus; + lastStatusTimestamp = uint48(block.timestamp); + } + } + + /// @return Whether the basket is ready to issue and trade + function isReady() external view returns (bool) { + return + status() == CollateralStatus.SOUND && + (block.timestamp >= lastStatusTimestamp + warmupPeriod); + } + /// @param erc20 The token contract to check for quantity for /// @return {tok/BU} The token-quantity of an ERC20 token in the basket. // Returns 0 if erc20 is not registered or not in the basket @@ -447,6 +487,15 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } } + // === Governance Setters === + + /// @custom:governance + function setWarmupPeriod(uint48 val) public governance { + require(val <= MAX_WARMUP_PERIOD, "invalid warmupPeriod"); + emit WarmupPeriodSet(warmupPeriod, val); + warmupPeriod = val; + } + /* _switchBasket computes basket' from three inputs: - the basket configuration (config: BasketConfig) - the function (isGood: erc20 -> bool), implemented here by goodCollateral() @@ -502,11 +551,6 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { timestamp' = now */ - // These are effectively local variables of _switchBasket. - // Nothing should use their values from previous transactions. - EnumerableSet.Bytes32Set private targetNames; - Basket private newBasket; // Always empty - /// Select and save the next basket, based on the BasketConfig and Collateral statuses /// (The mutator that actually does all the work in this contract.) function _switchBasket() private { @@ -671,5 +715,5 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[48] private __gap; + uint256[47] private __gap; } diff --git a/contracts/p0/Deployer.sol b/contracts/p0/Deployer.sol index f756319910..e1de9653e2 100644 --- a/contracts/p0/Deployer.sol +++ b/contracts/p0/Deployer.sol @@ -93,7 +93,7 @@ contract DeployerP0 is IDeployer, Versioned { ); // Init Basket Handler - main.basketHandler().init(main); + main.basketHandler().init(main, params.warmupPeriod); // Init Revenue Traders main.rsrTrader().init(main, rsr, params.maxTradeSlippage, params.minTradeVolume); diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 24675ff363..7aef9d53ed 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -101,8 +101,9 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { // Call collective state keepers. main.poke(); + // Ensure basket is ready, SOUND and not in warmup period IBasketHandler basketHandler = main.basketHandler(); - require(basketHandler.status() == CollateralStatus.SOUND, "basket unsound"); + require(basketHandler.isReady(), "basket not ready"); // Revert if issuance exceeds either supply throttle issuanceThrottle.useAvailable(totalSupply(), int256(amount)); // reverts on over-issuance diff --git a/contracts/p1/AssetRegistry.sol b/contracts/p1/AssetRegistry.sol index fe2460aaf2..924dfded52 100644 --- a/contracts/p1/AssetRegistry.sol +++ b/contracts/p1/AssetRegistry.sol @@ -46,13 +46,17 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { /// Update the state of all assets /// @custom:refresher - // actions: calls refresh(c) for c in keys(assets) when c.isCollateral() + // actions: + // calls refresh(c) for c in keys(assets) when c.isCollateral() + // tracks basket status on basketHandler function refresh() public { // It's a waste of gas to require notPausedOrFrozen because assets can be updated directly uint256 length = _erc20s.length(); for (uint256 i = 0; i < length; ++i) { assets[IERC20(_erc20s.at(i))].refresh(); } + + basketHandler.trackStatus(); } /// Register `asset` diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 3e3e12a34e..24d6caff2a 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -107,8 +107,9 @@ contract BackingManagerP1 is TradingP1, IBackingManager { assetRegistry.refresh(); if (tradesOpen > 0) return; - // Only trade when all the collateral assets in the basket are SOUND - require(basketHandler.status() == CollateralStatus.SOUND, "basket not sound"); + + // Ensure basket is ready, SOUND and not in warmup period + require(basketHandler.isReady(), "basket not ready"); uint48 basketTimestamp = basketHandler.timestamp(); if (block.timestamp < basketTimestamp + tradingDelay) return; diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index e5135fa6ad..e3f277c5d8 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -115,6 +115,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { using FixLib for uint192; uint192 public constant MAX_TARGET_AMT = 1e3 * FIX_ONE; // {target/BU} max basket weight + uint48 public constant MAX_WARMUP_PERIOD = 31536000; // {s} 1 year // Peer components IAssetRegistry private assetRegistry; @@ -138,6 +139,19 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // and everything except redemption should be paused. bool private disabled; + // These are effectively local variables of _switchBasket. + // Nothing should use their values from previous transactions. + EnumerableSet.Bytes32Set private _targetNames; + Basket private _newBasket; // Always empty + + // Warmup Period + uint48 public warmupPeriod; // {s} how long to wait until issuance/trading after regaining SOUND + + // basket status changes, mainly set when `trackStatus()` is called + // used to enforce warmup period, after regaining SOUND + CollateralStatus private lastStatus; + uint48 private lastStatusTimestamp; + // ==== Invariants ==== // basket is a valid Basket: // basket.erc20s is a valid collateral array and basket.erc20s == keys(basket.refAmts) @@ -148,7 +162,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // if basket.erc20s is empty then disabled == true // BasketHandler.init() just leaves the BasketHandler state zeroed - function init(IMain main_) external initializer { + function init(IMain main_, uint48 warmupPeriod_) external initializer { __Component_init(main_); assetRegistry = main_.assetRegistry(); @@ -157,6 +171,12 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { rToken = main_.rToken(); stRSR = main_.stRSR(); + setWarmupPeriod(warmupPeriod_); + + // Set last status to DISABLED (default) + lastStatus = CollateralStatus.DISABLED; + lastStatusTimestamp = uint48(block.timestamp); + disabled = true; } @@ -192,6 +212,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { "basket unrefreshable" ); _switchBasket(); + + trackStatus(); } /// Set the prime basket in the basket configuration, in terms of erc20s and target amounts @@ -293,6 +315,24 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } } + /// Track basket status changes if they ocurred + // effects: lastStatus' = status(), and lastStatusTimestamp' = current timestamp + function trackStatus() public { + CollateralStatus currentStatus = status(); + if (currentStatus != lastStatus) { + emit BasketStatusChanged(lastStatus, currentStatus); + lastStatus = currentStatus; + lastStatusTimestamp = uint48(block.timestamp); + } + } + + /// @return Whether the basket is ready to issue and trade + function isReady() external view returns (bool) { + return + status() == CollateralStatus.SOUND && + (block.timestamp >= lastStatusTimestamp + warmupPeriod); + } + /// @param erc20 The token contract to check for quantity for /// @return {tok/BU} The token-quantity of an ERC20 token in the basket. // Returns 0 if erc20 is not registered or not in the basket @@ -481,6 +521,15 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } } + // === Governance Setters === + + /// @custom:governance + function setWarmupPeriod(uint48 val) public governance { + require(val <= MAX_WARMUP_PERIOD, "invalid warmupPeriod"); + emit WarmupPeriodSet(warmupPeriod, val); + warmupPeriod = val; + } + // === Private === /* _switchBasket computes basket' from three inputs: @@ -538,11 +587,6 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { timestamp' = now */ - // These are effectively local variables of _switchBasket. - // Nothing should use their values from previous transactions. - EnumerableSet.Bytes32Set private _targetNames; - Basket private _newBasket; // Always empty - /// Select and save the next basket, based on the BasketConfig and Collateral statuses /// (The mutator that actually does all the work in this contract.) function _switchBasket() private { @@ -764,5 +808,5 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[42] private __gap; + uint256[41] private __gap; } diff --git a/contracts/p1/Deployer.sol b/contracts/p1/Deployer.sol index 439baa0d29..7f16093bfc 100644 --- a/contracts/p1/Deployer.sol +++ b/contracts/p1/Deployer.sol @@ -182,7 +182,7 @@ contract DeployerP1 is IDeployer, Versioned { ); // Init Basket Handler - components.basketHandler.init(main); + components.basketHandler.init(main, params.warmupPeriod); // Init Revenue Traders components.rsrTrader.init(main, rsr, params.maxTradeSlippage, params.minTradeVolume); diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index b71216860f..a6fdd6206f 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -129,8 +129,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // == Checks-effects block == address issuer = _msgSender(); // OK to save: it can't be changed in reentrant runs - // Ensure SOUND basket - require(basketHandler.status() == CollateralStatus.SOUND, "basket unsound"); + // Ensure basket is ready, SOUND and not in warmup period + require(basketHandler.isReady(), "basket not ready"); furnace.melt(); uint256 supply = totalSupply(); From 593038f6d7e5ad58979795f763e7392e93dd4b9b Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 10 Apr 2023 09:42:58 -0300 Subject: [PATCH 013/462] adapt test fixture --- common/configuration.ts | 2 ++ test/fixtures.ts | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/common/configuration.ts b/common/configuration.ts index 8ae3f6aa49..db524bc993 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -315,6 +315,7 @@ export interface IConfig { longFreeze: BigNumber rewardRatio: BigNumber unstakingDelay: BigNumber + warmupPeriod: BigNumber tradingDelay: BigNumber auctionLength: BigNumber backingBuffer: BigNumber @@ -400,6 +401,7 @@ export const MAX_THROTTLE_PCT_RATE = BigNumber.from(10).pow(18) // Timestamps export const MAX_ORACLE_TIMEOUT = BigNumber.from(2).pow(48).sub(1) export const MAX_TRADING_DELAY = 31536000 // 1 year +export const MAX_WARMUP_PERIOD = 31536000 // 1 year export const MAX_AUCTION_LENGTH = 604800 // 1 week export const MAX_UNSTAKING_DELAY = 31536000 // 1 year export const MAX_DELAY_UNTIL_DEFAULT = 1209600 // 2 weeks diff --git a/test/fixtures.ts b/test/fixtures.ts index 10ca8fb980..c548f7af06 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -30,7 +30,6 @@ import { GnosisMock, GnosisTrade, IAssetRegistry, - IBasketHandler, MainP1, MockV3Aggregator, RevenueTraderP1, @@ -40,6 +39,7 @@ import { StaticATokenMock, StRSRP1Votes, TestIBackingManager, + TestIBasketHandler, TestIBroker, TestIDeployer, TestIDistributor, @@ -369,7 +369,7 @@ export interface DefaultFixture extends RSRAndCompAaveAndCollateralAndModuleFixt main: TestIMain assetRegistry: IAssetRegistry backingManager: TestIBackingManager - basketHandler: IBasketHandler + basketHandler: TestIBasketHandler distributor: TestIDistributor rsrAsset: Asset compAsset: Asset @@ -410,6 +410,7 @@ export const defaultFixture: Fixture = async function (): Promis longFreeze: bn('2592000'), // 30 days rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks + warmupPeriod: bn('0'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) auctionLength: bn('900'), // 15 minutes backingBuffer: fp('0.0001'), // 0.01% @@ -565,8 +566,8 @@ export const defaultFixture: Fixture = async function (): Promis const backingManager: TestIBackingManager = ( await ethers.getContractAt('TestIBackingManager', await main.backingManager()) ) - const basketHandler: IBasketHandler = ( - await ethers.getContractAt('IBasketHandler', await main.basketHandler()) + const basketHandler: TestIBasketHandler = ( + await ethers.getContractAt('TestIBasketHandler', await main.basketHandler()) ) const distributor: TestIDistributor = ( await ethers.getContractAt('TestIDistributor', await main.distributor()) From 4560f35781815f4844a557dac486da06da694818 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 10 Apr 2023 09:44:13 -0300 Subject: [PATCH 014/462] deployer and facade tests --- test/Deployer.test.ts | 4 ++-- test/FacadeAct.test.ts | 4 ++-- test/FacadeMonitor.ts | 4 ++-- test/FacadeRead.test.ts | 4 ++-- test/FacadeWrite.test.ts | 8 ++++---- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/Deployer.test.ts b/test/Deployer.test.ts index 434f01f45d..c2764a5bc7 100644 --- a/test/Deployer.test.ts +++ b/test/Deployer.test.ts @@ -12,9 +12,9 @@ import { FacadeRead, GnosisMock, IAssetRegistry, - IBasketHandler, RTokenAsset, TestIBackingManager, + TestIBasketHandler, TestIBroker, TestIDeployer, TestIDistributor, @@ -58,7 +58,7 @@ describe(`DeployerP${IMPLEMENTATION} contract #fast`, () => { let main: TestIMain let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let distributor: TestIDistributor let rsrTrader: TestIRevenueTrader let rTokenTrader: TestIRevenueTrader diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts index 350d54dca3..e1ce54fa8c 100644 --- a/test/FacadeAct.test.ts +++ b/test/FacadeAct.test.ts @@ -26,11 +26,11 @@ import { FiatCollateral, GnosisMock, IAssetRegistry, - IBasketHandler, InvalidATokenFiatCollateralMock, MockV3Aggregator, StaticATokenMock, TestIBackingManager, + TestIBasketHandler, TestIDistributor, TestIRevenueTrader, TestIRToken, @@ -93,7 +93,7 @@ describe('FacadeAct contract', () => { // Main let rToken: TestIRToken let stRSR: TestIStRSR - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager let distributor: TestIDistributor diff --git a/test/FacadeMonitor.ts b/test/FacadeMonitor.ts index 53dfab9394..66f8525088 100644 --- a/test/FacadeMonitor.ts +++ b/test/FacadeMonitor.ts @@ -23,9 +23,9 @@ import { FacadeAct, FiatCollateral, GnosisMock, - IBasketHandler, StaticATokenMock, TestIBackingManager, + TestIBasketHandler, TestIDistributor, TestIRevenueTrader, TestIRToken, @@ -71,7 +71,7 @@ describe('FacadeMonitor Contract', () => { // Main let rToken: TestIRToken - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let backingManager: TestIBackingManager let distributor: TestIDistributor let gnosis: GnosisMock diff --git a/test/FacadeRead.test.ts b/test/FacadeRead.test.ts index 15a8bdf5e6..8879935cda 100644 --- a/test/FacadeRead.test.ts +++ b/test/FacadeRead.test.ts @@ -14,7 +14,7 @@ import { MockV3Aggregator, StaticATokenMock, StRSRP1, - IBasketHandler, + TestIBasketHandler, TestIMain, TestIStRSR, TestIRToken, @@ -59,7 +59,7 @@ describe('FacadeRead contract', () => { let rToken: TestIRToken let main: TestIMain let stRSR: TestIStRSR - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler // RSR let rsrAsset: Asset diff --git a/test/FacadeWrite.test.ts b/test/FacadeWrite.test.ts index b62d05357b..2c5629ca56 100644 --- a/test/FacadeWrite.test.ts +++ b/test/FacadeWrite.test.ts @@ -30,7 +30,6 @@ import { CTokenFiatCollateral, CTokenMock, ERC20Mock, - IBasketHandler, FacadeRead, FacadeTest, FacadeWrite, @@ -39,6 +38,7 @@ import { IAssetRegistry, RTokenAsset, TestIBackingManager, + TestIBasketHandler, TestIBroker, TestIDeployer, TestIDistributor, @@ -109,7 +109,7 @@ describe('FacadeWrite contract', () => { let main: TestIMain let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let broker: TestIBroker let distributor: TestIDistributor let furnace: TestIFurnace @@ -301,8 +301,8 @@ describe('FacadeWrite contract', () => { backingManager = ( await ethers.getContractAt('TestIBackingManager', await main.backingManager()) ) - basketHandler = ( - await ethers.getContractAt('IBasketHandler', await main.basketHandler()) + basketHandler = ( + await ethers.getContractAt('TestIBasketHandler', await main.basketHandler()) ) broker = await ethers.getContractAt('TestIBroker', await main.broker()) From 625f378e008656b67aef83289ffd3d22819a1539 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 10 Apr 2023 09:45:01 -0300 Subject: [PATCH 015/462] adapt tests --- test/Main.test.ts | 105 +++++++++++++++++++++++++-- test/RToken.test.ts | 81 ++++++++++++++++++++- test/Recollateralization.test.ts | 119 +++++++++++++++++++++++++++++-- test/Upgradeability.test.ts | 6 +- test/ZTradingExtremes.test.ts | 4 +- test/ZZStRSR.test.ts | 4 +- 6 files changed, 297 insertions(+), 22 deletions(-) diff --git a/test/Main.test.ts b/test/Main.test.ts index 75aa32202f..2ef0e30a4f 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -10,6 +10,7 @@ import { MAX_BACKING_BUFFER, MAX_TARGET_AMT, MAX_MIN_TRADE_VOLUME, + MAX_WARMUP_PERIOD, IComponents, } from '../common/configuration' import { @@ -38,13 +39,13 @@ import { GnosisMock, GnosisTrade, IAssetRegistry, - IBasketHandler, InvalidRefPerTokCollateralMock, MockV3Aggregator, MockableCollateral, RTokenAsset, StaticATokenMock, TestIBackingManager, + TestIBasketHandler, TestIBroker, TestIDeployer, TestIDistributor, @@ -126,7 +127,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { let facadeTest: FacadeTest let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let distributor: TestIDistributor let basket: Collateral[] @@ -231,6 +232,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(await backingManager.tradingDelay()).to.equal(config.tradingDelay) expect(await backingManager.maxTradeSlippage()).to.equal(config.maxTradeSlippage) expect(await backingManager.backingBuffer()).to.equal(config.backingBuffer) + expect(await basketHandler.warmupPeriod()).to.equal(config.warmupPeriod) // Should have semver version from deployer expect(await main.version()).to.equal(await deployer.version()) @@ -350,7 +352,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { ).to.be.revertedWith('Initializable: contract is already initialized') // Attempt to reinitialize - Basket Handler - await expect(basketHandler.init(main.address)).to.be.revertedWith( + await expect(basketHandler.init(main.address, config.warmupPeriod)).to.be.revertedWith( 'Initializable: contract is already initialized' ) @@ -879,6 +881,32 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) describe('Configuration/State #fast', () => { + it('Should allow to update warmupPeriod if OWNER and perform validations', async () => { + const newValue: BigNumber = bn('360') + + // Check existing value + expect(await basketHandler.warmupPeriod()).to.equal(config.warmupPeriod) + + // If not owner cannot update + await expect(basketHandler.connect(other).setWarmupPeriod(newValue)).to.be.reverted + + // Check value did not change + expect(await basketHandler.warmupPeriod()).to.equal(config.warmupPeriod) + + // Update with owner + await expect(basketHandler.connect(owner).setWarmupPeriod(newValue)) + .to.emit(basketHandler, 'WarmupPeriodSet') + .withArgs(config.warmupPeriod, newValue) + + // Check value was updated + expect(await basketHandler.warmupPeriod()).to.equal(newValue) + + // Cannot update with value > max + await expect( + basketHandler.connect(owner).setWarmupPeriod(MAX_WARMUP_PERIOD + 1) + ).to.be.revertedWith('invalid warmupPeriod') + }) + it('Should allow to update tradingDelay if OWNER and perform validations', async () => { const newValue: BigNumber = bn('360') @@ -1174,6 +1202,69 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) }) + it('Should track basket status in BasketHandler when changed', async () => { + // Check initial basket status + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + + // Refreshing from SOUND -> SOUND should not track status change + await expect(assetRegistry.refresh()).to.not.emit(basketHandler, 'BasketStatusChanged') + + // Check Status remains SOUND + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + + // Set Token1 to default - 50% price reduction + await setOraclePrice(collateral1.address, bn('0.5e8')) + + // Mark default as probable + await expect(assetRegistry.refresh()) + .to.emit(basketHandler, 'BasketStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) + + // Advance time post delayUntilDefault + await advanceTime((await collateral1.delayUntilDefault()).toString()) + + // Mark default as confirmed + await expect(assetRegistry.refresh()) + .to.emit(basketHandler, 'BasketStatusChanged') + .withArgs(CollateralStatus.IFFY, CollateralStatus.DISABLED) + + // Check status + expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) + }) + + it('Should be able to track basket status', async () => { + // Check initial basket status + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + + // Refreshing from SOUND -> SOUND should not track status change + await expect(assetRegistry.refresh()).to.not.emit(basketHandler, 'BasketStatusChanged') + + // Check Status remains SOUND + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + + // Set Token1 to default - 50% price reduction + await setOraclePrice(collateral1.address, bn('0.5e8')) + + // Mark default as probable + await expect(assetRegistry.refresh()) + .to.emit(basketHandler, 'BasketStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) + + // Advance time post delayUntilDefault + await advanceTime((await collateral1.delayUntilDefault()).toString()) + + // Mark default as confirmed + await collateral1.refresh() + + // Anyone can update status on BasketHandler + await expect(basketHandler.trackStatus()) + .to.emit(basketHandler, 'BasketStatusChanged') + .withArgs(CollateralStatus.IFFY, CollateralStatus.DISABLED) + + // Check status + expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) + }) + it('Should allow to register Asset if OWNER', async () => { // Setup new Asset const AssetFactory: ContractFactory = await ethers.getContractFactory('Asset') @@ -1621,8 +1712,8 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { ).wait() const mainAddr = expectInReceipt(receipt, 'RTokenCreated').args.main const newMain: TestIMain = await ethers.getContractAt('TestIMain', mainAddr) - const emptyBasketHandler: IBasketHandler = ( - await ethers.getContractAt('IBasketHandler', await newMain.basketHandler()) + const emptyBasketHandler: TestIBasketHandler = ( + await ethers.getContractAt('TestIBasketHandler', await newMain.basketHandler()) ) const busHeld = await emptyBasketHandler.basketsHeldBy(addr1.address) expect(busHeld[0]).to.equal(0) @@ -1739,7 +1830,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) }) - it('Should handle full collateral deregistration and disable the basket', async () => { + it.skip('Should handle full collateral deregistration and disable the basket', async () => { // Check status expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.quantity(token1.address)).to.equal(basketsNeededAmts[1]) @@ -1763,6 +1854,8 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { 'AssetUnregistered' ) + expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) + await expect(basketHandler.refreshBasket()).to.emit(basketHandler, 'BasketSet') // Basket should be 100% collateral0 diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 5990cdfcac..8be1a3ad66 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -24,11 +24,11 @@ import { FacadeTest, FiatCollateral, IAssetRegistry, - IBasketHandler, MockV3Aggregator, RTokenAsset, StaticATokenMock, TestIBackingManager, + TestIBasketHandler, TestIFurnace, TestIMain, TestIRToken, @@ -94,7 +94,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { let facadeTest: FacadeTest let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let furnace: TestIFurnace beforeEach(async () => { @@ -332,7 +332,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, issueAmount))) // Try to issue - await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith('basket unsound') + await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith('basket not ready') // Check values expect(await rToken.totalSupply()).to.equal(bn(0)) @@ -608,6 +608,81 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await hre.network.provider.send('evm_setAutomine', [true]) }) + it('Should apply warmup period on issuance', async function () { + const issueAmount: BigNumber = bn('10e18') + const warmupPeriod = bn('259200') // 3 days + + // Set warmup period + await basketHandler.connect(owner).setWarmupPeriod(warmupPeriod) + + // Start issuance + await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, issueAmount))) + + // Try to issue + await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith('basket not ready') + + // Check values + expect(await rToken.totalSupply()).to.equal(bn(0)) + + // Move past warmup period + await advanceTime(warmupPeriod.toString()) + + // Try to issue + await expect(rToken.connect(addr1).issue(issueAmount)).to.not.be.reverted + + // Check values - rTokens issued + expect(await rToken.totalSupply()).to.equal(issueAmount) + }) + + it('Should use warmup period on issuance after regaining SOUND', async function () { + const issueAmount: BigNumber = bn('10e18') + const warmupPeriod = bn('259200') // 3 days + + // Set warmup period and move time forward + await basketHandler.connect(owner).setWarmupPeriod(warmupPeriod) + await advanceTime(warmupPeriod.toString()) + + // Set collateral and basket status to IFFY to prevent issuance + await setOraclePrice(collateral1.address, bn('0.5e8')) + await assetRegistry.refresh() + + // Collateral and basket in IFFY + expect(await collateral1.status()).to.equal(CollateralStatus.IFFY) + expect(await basketHandler.status()).to.equal(CollateralStatus.IFFY) + + // Start issuance + await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, issueAmount))) + + // Try to issue, reverts because basket is not SOUND + await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith('basket not ready') + + // Check values + expect(await rToken.totalSupply()).to.equal(bn(0)) + + // Set collateral and basket status to SOUND again + await setOraclePrice(collateral1.address, bn('1e8')) + await collateral1.refresh() + + // Collateral and basket in SOUND + expect(await collateral1.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + + // Need to track status in BH to record timestamp on when SOUND was regained + await basketHandler.trackStatus() + + // Try to issuem still cannot issue due to warmup period + await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith('basket not ready') + + // Move past warmup period + await advanceTime(warmupPeriod.toString()) + + // Should be able to issue now + await expect(rToken.connect(addr1).issue(issueAmount)).to.not.be.reverted + + // Check values - rTokens issued + expect(await rToken.totalSupply()).to.equal(issueAmount) + }) + it('Should handle issuance throttle correctly', async function () { const rechargePerBlock = config.issuanceThrottle.amtRate.div(BLOCKS_PER_HOUR) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 058484e2da..8005039f8c 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -18,11 +18,11 @@ import { FiatCollateral, GnosisMock, IAssetRegistry, - IBasketHandler, MockV3Aggregator, RTokenAsset, StaticATokenMock, TestIBackingManager, + TestIBasketHandler, TestIMain, TestIRToken, TestIStRSR, @@ -91,7 +91,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { let facadeTest: FacadeTest let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let main: TestIMain interface IBackingInfo { @@ -518,7 +518,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await basketHandler.fullyCollateralized()).to.equal(false) // Cannot issue because collateral is not sound - await expect(rToken.connect(addr1).issue(bn('1e18'))).to.be.revertedWith('basket unsound') + await expect(rToken.connect(addr1).issue(bn('1e18'))).to.be.revertedWith('basket not ready') }) it('Should handle having invalid tokens in the backup configuration', async () => { @@ -765,7 +765,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await basketHandler.fullyCollateralized()).to.equal(false) // Cannot issue because collateral is not sound - await expect(rToken.connect(addr1).issue(bn('1e18'))).to.be.revertedWith('basket unsound') + await expect(rToken.connect(addr1).issue(bn('1e18'))).to.be.revertedWith('basket not ready') }) }) }) @@ -814,12 +814,119 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { it('Should not trade if UNPRICED', async () => { await advanceTime(ORACLE_TIMEOUT.toString()) - await expect(backingManager.manageTokens([])).to.be.revertedWith('basket not sound') + await expect(backingManager.manageTokens([])).to.be.revertedWith('basket not ready') await expect(backingManager.manageTokensSortedOrder([])).to.be.revertedWith( - 'basket not sound' + 'basket not ready' ) }) + it('Should not trade during warmup period', async () => { + const warmupPeriod = bn('259200') // 3 days + + // Set warmup period + await basketHandler.connect(owner).setWarmupPeriod(warmupPeriod) + + await expect(backingManager.manageTokens([])).to.be.revertedWith('basket not ready') + await expect(backingManager.manageTokensSortedOrder([])).to.be.revertedWith( + 'basket not ready' + ) + }) + + it('Should not apply warmup period when moving from SOUND -> SOUND', async () => { + const warmupPeriod = bn('259200') // 3 days + + // Set warmup period and advance time + await basketHandler.connect(owner).setWarmupPeriod(warmupPeriod) + await advanceTime(warmupPeriod.add(1).toString()) + + // Setup new prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(3, [token1.address], [fp('1')], false) + + // Trigger recollateralization + const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + // Auction can be run even during warmupPeriod + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs( + anyValue, + token0.address, + token1.address, + sellAmt, + toBNDecimals(minBuyAmt, 6).add(1) + ) + + const auctionTimestamp: number = await getLatestBlockTimestamp() + + // Check auction registered + // Token0 -> Token1 Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.auctionLength), + externalId: bn('0'), + }) + }) + + it('Should skip start recollateralization after warmupPeriod, when regaining SOUND', async () => { + const warmupPeriod = bn('259200') // 3 days + + // Set warmup period + await basketHandler.connect(owner).setWarmupPeriod(warmupPeriod) + + // Set basket to IFFY + await setOraclePrice(collateral0.address, bn('0.5e8')) + await assetRegistry.refresh() + + // Setup new prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(3, [token1.address], [fp('1')], false) + + // Trigger recollateralization + const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('0.5'), fp('1')) + + // Attempt to trigger before warmup period - will revert + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.be.revertedWith( + 'basket not ready' + ) + + // Advance time post warmup period + await advanceTime(warmupPeriod.add(1).toString()) + + // Auction can be run now + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs( + anyValue, + token0.address, + token1.address, + sellAmt, + toBNDecimals(minBuyAmt, 6).add(1) + ) + + const auctionTimestamp: number = await getLatestBlockTimestamp() + + // Check auction registered + // Token0 -> Token1 Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.auctionLength), + externalId: bn('0'), + }) + }) + it('Should skip start recollateralization after tradingDelay', async () => { // Set trading delay const newDelay = 3600 diff --git a/test/Upgradeability.test.ts b/test/Upgradeability.test.ts index 2de476e424..eceb60f7ad 100644 --- a/test/Upgradeability.test.ts +++ b/test/Upgradeability.test.ts @@ -24,7 +24,6 @@ import { GnosisMock, GnosisTrade, IAssetRegistry, - IBasketHandler, MainP1, MainP1V2, RevenueTraderP1, @@ -35,6 +34,7 @@ import { StRSRP1Votes, StRSRP1VotesV2, TestIBackingManager, + TestIBasketHandler, TestIBroker, TestIDistributor, TestIFurnace, @@ -70,7 +70,7 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { let main: TestIMain let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let distributor: TestIDistributor let rsrTrader: TestIRevenueTrader let rTokenTrader: TestIRevenueTrader @@ -237,7 +237,7 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { it('Should deploy valid implementation - BasketHandler', async () => { const newBasketHandler: BasketHandlerP1 = await upgrades.deployProxy( BasketHandlerFactory, - [main.address], + [main.address, config.warmupPeriod], { initializer: 'init', kind: 'uups', diff --git a/test/ZTradingExtremes.test.ts b/test/ZTradingExtremes.test.ts index 90d9a04233..0c13704e59 100644 --- a/test/ZTradingExtremes.test.ts +++ b/test/ZTradingExtremes.test.ts @@ -18,9 +18,9 @@ import { GnosisMock, GnosisTrade, IAssetRegistry, - IBasketHandler, MockV3Aggregator, TestIBackingManager, + TestIBasketHandler, TestIDistributor, TestIStRSR, TestIRevenueTrader, @@ -71,7 +71,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, let facadeTest: FacadeTest let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let distributor: TestIDistributor let ERC20Mock: ContractFactory diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 1fea6e6f7b..56502f54e2 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -13,11 +13,11 @@ import { ERC20Mock, ERC1271Mock, FacadeRead, - IBasketHandler, StRSRP0, StRSRP1Votes, StaticATokenMock, TestIBackingManager, + TestIBasketHandler, TestIMain, TestIRToken, TestIStRSR, @@ -66,7 +66,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { // Main let main: TestIMain let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let rToken: TestIRToken let facade: FacadeRead From f9a7984d236b6d9d9c759aa4ce800784e00aaf4d Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 10 Apr 2023 09:45:21 -0300 Subject: [PATCH 016/462] adapt tests in Revenues --- test/Revenues.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 530f5f21f1..0aad33abec 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -24,12 +24,12 @@ import { FacadeTest, GnosisMock, IAssetRegistry, - IBasketHandler, InvalidATokenFiatCollateralMock, MockV3Aggregator, RTokenAsset, StaticATokenMock, TestIBackingManager, + TestIBasketHandler, TestIBroker, TestIDistributor, TestIFurnace, @@ -108,7 +108,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { let facadeTest: FacadeTest let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let distributor: TestIDistributor let main: TestIMain From 685e63e123b5b5a59187aee94bc67d3598b1caf7 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 10 Apr 2023 09:45:35 -0300 Subject: [PATCH 017/462] plugin tests --- test/plugins/Asset.test.ts | 4 ++-- .../compoundv2/CTokenFiatCollateral.test.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index f6d5e03836..42fec10fcb 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -22,12 +22,12 @@ import { ERC20Mock, FiatCollateral, IAssetRegistry, - IBasketHandler, InvalidFiatCollateral, InvalidMockV3Aggregator, RTokenAsset, StaticATokenMock, TestIBackingManager, + TestIBasketHandler, TestIRToken, USDCMock, UnpricedAssetMock, @@ -74,7 +74,7 @@ describe('Assets contracts #fast', () => { let wallet: Wallet let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler // Factory let AssetFactory: ContractFactory diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index b04e2c29b1..400ed7c550 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -36,11 +36,11 @@ import { FacadeTest, FacadeWrite, IAssetRegistry, - IBasketHandler, InvalidMockV3Aggregator, MockV3Aggregator, RTokenAsset, TestIBackingManager, + TestIBasketHandler, TestIDeployer, TestIMain, TestIRToken, @@ -90,7 +90,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi let rTokenAsset: RTokenAsset let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let deployer: TestIDeployer let facade: FacadeRead @@ -242,8 +242,8 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi backingManager = ( await ethers.getContractAt('TestIBackingManager', await main.backingManager()) ) - basketHandler = ( - await ethers.getContractAt('IBasketHandler', await main.basketHandler()) + basketHandler = ( + await ethers.getContractAt('TestIBasketHandler', await main.basketHandler()) ) rToken = await ethers.getContractAt('TestIRToken', await main.rToken()) rTokenAsset = ( From 6781984f6bd91861160ce84e5a19e1c8589e8460 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 10 Apr 2023 09:45:51 -0300 Subject: [PATCH 018/462] integration tests --- test/integration/AssetPlugins.test.ts | 4 ++-- test/integration/EasyAuction.test.ts | 4 ++-- test/integration/StaticATokenLM.test.ts | 4 +++- test/integration/fixtures.ts | 9 +++++---- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/test/integration/AssetPlugins.test.ts b/test/integration/AssetPlugins.test.ts index 894bc9c1c7..ee93df3448 100644 --- a/test/integration/AssetPlugins.test.ts +++ b/test/integration/AssetPlugins.test.ts @@ -37,12 +37,12 @@ import { IAToken, IERC20, IAssetRegistry, - IBasketHandler, MockV3Aggregator, NonFiatCollateral, RTokenAsset, StaticATokenLM, TestIBackingManager, + TestIBasketHandler, TestIMain, TestIRToken, USDCMock, @@ -153,7 +153,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, let facadeTest: FacadeTest let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let config: IConfig // Factories diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index b21ed3bad2..76a4986a5c 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -38,9 +38,9 @@ import { FacadeTest, FiatCollateral, IAssetRegistry, - IBasketHandler, RTokenAsset, TestIBackingManager, + TestIBasketHandler, TestIBroker, TestIRToken, TestIStRSR, @@ -65,7 +65,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function let rToken: TestIRToken let broker: TestIBroker let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let facadeTest: FacadeTest let assetRegistry: IAssetRegistry diff --git a/test/integration/StaticATokenLM.test.ts b/test/integration/StaticATokenLM.test.ts index c4871eaf2f..39ffef66fa 100644 --- a/test/integration/StaticATokenLM.test.ts +++ b/test/integration/StaticATokenLM.test.ts @@ -168,7 +168,9 @@ const getContext = async ({ staticATokenSupply: await staticAToken.totalSupply(), }) -describe('StaticATokenLM: aToken wrapper with static balances and liquidity mining', () => { +const describeFork = useEnv('FORK') ? describe : describe.skip + +describeFork('StaticATokenLM: aToken wrapper with static balances and liquidity mining', () => { let user1: SignerWithAddress let user2: SignerWithAddress let userSigner: providers.JsonRpcSigner diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index dc8504d8b7..92baf89940 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -30,7 +30,6 @@ import { FurnaceP1, GnosisTrade, IAssetRegistry, - IBasketHandler, IERC20Metadata, MainP1, NonFiatCollateral, @@ -41,6 +40,7 @@ import { StaticATokenLM, StRSRP1Votes, TestIBackingManager, + TestIBasketHandler, TestIBroker, TestIDeployer, TestIDistributor, @@ -570,7 +570,7 @@ interface DefaultFixture extends RSRAndCompAaveAndCollateralAndModuleFixture { main: TestIMain assetRegistry: IAssetRegistry backingManager: TestIBackingManager - basketHandler: IBasketHandler + basketHandler: TestIBasketHandler distributor: TestIDistributor rsrAsset: Asset compAsset: Asset @@ -614,6 +614,7 @@ export const defaultFixture: Fixture = async function (): Promis longFreeze: bn('2592000'), // 30 days rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks + warmupPeriod: bn('0'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) auctionLength: bn('900'), // 15 minutes backingBuffer: fp('0.0001'), // 0.01% @@ -758,8 +759,8 @@ export const defaultFixture: Fixture = async function (): Promis const backingManager: TestIBackingManager = ( await ethers.getContractAt('TestIBackingManager', await main.backingManager()) ) - const basketHandler: IBasketHandler = ( - await ethers.getContractAt('IBasketHandler', await main.basketHandler()) + const basketHandler: TestIBasketHandler = ( + await ethers.getContractAt('TestIBasketHandler', await main.basketHandler()) ) const distributor: TestIDistributor = ( await ethers.getContractAt('TestIDistributor', await main.distributor()) From 90d9c35bed0e34afb6b6b9ea896f05c502c1c942 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 10 Apr 2023 09:46:06 -0300 Subject: [PATCH 019/462] scenario tests --- test/scenario/BadCollateralPlugin.test.ts | 4 ++-- test/scenario/BadERC20.test.ts | 4 ++-- test/scenario/ComplexBasket.test.ts | 4 ++-- test/scenario/EURT.test.ts | 4 ++-- test/scenario/MaxBasketSize.test.ts | 4 ++-- test/scenario/NontrivialPeg.test.ts | 4 ++-- test/scenario/RevenueHiding.test.ts | 4 ++-- test/scenario/SetProtocol.test.ts | 4 ++-- test/scenario/WBTC.test.ts | 4 ++-- test/scenario/WETH.test.ts | 4 ++-- test/scenario/cETH.test.ts | 4 ++-- test/scenario/cWBTC.test.ts | 4 ++-- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/test/scenario/BadCollateralPlugin.test.ts b/test/scenario/BadCollateralPlugin.test.ts index 439d5933f5..2202378e99 100644 --- a/test/scenario/BadCollateralPlugin.test.ts +++ b/test/scenario/BadCollateralPlugin.test.ts @@ -11,11 +11,11 @@ import { BadCollateralPlugin, ERC20Mock, IAssetRegistry, - IBasketHandler, MockV3Aggregator, RTokenAsset, StaticATokenMock, TestIBackingManager, + TestIBasketHandler, TestIStRSR, TestIRToken, } from '../../typechain' @@ -60,7 +60,7 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { let rToken: TestIRToken let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler beforeEach(async () => { ;[owner, addr1, addr2] = await ethers.getSigners() diff --git a/test/scenario/BadERC20.test.ts b/test/scenario/BadERC20.test.ts index bab562df9f..53b7391ac6 100644 --- a/test/scenario/BadERC20.test.ts +++ b/test/scenario/BadERC20.test.ts @@ -10,10 +10,10 @@ import { BadERC20, ERC20Mock, IAssetRegistry, - IBasketHandler, MockV3Aggregator, RTokenAsset, TestIBackingManager, + TestIBasketHandler, TestIFurnace, TestIStRSR, TestIRevenueTrader, @@ -62,7 +62,7 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { let backingManager: TestIBackingManager let rTokenTrader: TestIRevenueTrader let rsrTrader: TestIRevenueTrader - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler // Computes the minBuyAmt for a sellAmt at two prices // sellPrice + buyPrice should not be the low and high estimates, but rather the oracle prices diff --git a/test/scenario/ComplexBasket.test.ts b/test/scenario/ComplexBasket.test.ts index 633263b7d4..d711067fe9 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -15,11 +15,11 @@ import { FacadeTest, GnosisMock, IAssetRegistry, - IBasketHandler, MockV3Aggregator, RTokenAsset, StaticATokenMock, TestIBackingManager, + TestIBasketHandler, TestIRevenueTrader, TestIRToken, WETH9, @@ -100,7 +100,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { let rToken: TestIRToken let stRSR: TestIStRSR let assetRegistry: IAssetRegistry - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let facade: FacadeRead let facadeTest: FacadeTest let backingManager: TestIBackingManager diff --git a/test/scenario/EURT.test.ts b/test/scenario/EURT.test.ts index 8482905d28..46059f966e 100644 --- a/test/scenario/EURT.test.ts +++ b/test/scenario/EURT.test.ts @@ -11,11 +11,11 @@ import { ERC20Mock, EURFiatCollateral, IAssetRegistry, - IBasketHandler, IFacadeTest, MockV3Aggregator, StaticATokenMock, TestIBackingManager, + TestIBasketHandler, TestIStRSR, TestIRevenueTrader, TestIRToken, @@ -61,7 +61,7 @@ describe(`EUR fiatcoins (eg EURT) - P${IMPLEMENTATION}`, () => { let rToken: TestIRToken let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let rsrTrader: TestIRevenueTrader let rTokenTrader: TestIRevenueTrader let facadeTest: IFacadeTest diff --git a/test/scenario/MaxBasketSize.test.ts b/test/scenario/MaxBasketSize.test.ts index 7c524eed85..c8a5449a9f 100644 --- a/test/scenario/MaxBasketSize.test.ts +++ b/test/scenario/MaxBasketSize.test.ts @@ -16,11 +16,11 @@ import { FacadeTest, FiatCollateral, IAssetRegistry, - IBasketHandler, TestIStRSR, MockV3Aggregator, StaticATokenMock, TestIBackingManager, + TestIBasketHandler, TestIRToken, } from '../../typechain' import { advanceTime, getLatestBlockTimestamp } from '../utils/time' @@ -65,7 +65,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { let rsr: ERC20Mock let stRSR: TestIStRSR let assetRegistry: IAssetRegistry - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let facade: FacadeRead let facadeTest: FacadeTest let backingManager: TestIBackingManager diff --git a/test/scenario/NontrivialPeg.test.ts b/test/scenario/NontrivialPeg.test.ts index 2d3389bf65..4c4d229560 100644 --- a/test/scenario/NontrivialPeg.test.ts +++ b/test/scenario/NontrivialPeg.test.ts @@ -10,11 +10,11 @@ import { expectEvents } from '../../common/events' import { ERC20Mock, IAssetRegistry, - IBasketHandler, FiatCollateral, MockV3Aggregator, RTokenAsset, TestIBackingManager, + TestIBasketHandler, TestIStRSR, TestIRToken, } from '../../typechain' @@ -56,7 +56,7 @@ describe(`The peg (target/ref) should be arbitrary - P${IMPLEMENTATION}`, () => let rToken: TestIRToken let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler beforeEach(async () => { ;[owner, addr1, addr2] = await ethers.getSigners() diff --git a/test/scenario/RevenueHiding.test.ts b/test/scenario/RevenueHiding.test.ts index 54730bd649..8dc5b7f6c3 100644 --- a/test/scenario/RevenueHiding.test.ts +++ b/test/scenario/RevenueHiding.test.ts @@ -12,9 +12,9 @@ import { ComptrollerMock, ERC20Mock, IAssetRegistry, - IBasketHandler, SelfReferentialCollateral, TestIBackingManager, + TestIBasketHandler, TestIStRSR, TestIRevenueTrader, TestIRToken, @@ -59,7 +59,7 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME let rToken: TestIRToken let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let rsrTrader: TestIRevenueTrader let rTokenTrader: TestIRevenueTrader diff --git a/test/scenario/SetProtocol.test.ts b/test/scenario/SetProtocol.test.ts index e382050a89..614e7fc29f 100644 --- a/test/scenario/SetProtocol.test.ts +++ b/test/scenario/SetProtocol.test.ts @@ -11,10 +11,10 @@ import { expectEvents } from '../../common/events' import { ERC20Mock, IAssetRegistry, - IBasketHandler, MockV3Aggregator, SelfReferentialCollateral, TestIBackingManager, + TestIBasketHandler, TestIStRSR, TestIRevenueTrader, TestIRToken, @@ -56,7 +56,7 @@ describe(`Linear combination of self-referential collateral - P${IMPLEMENTATION} let rToken: TestIRToken let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let rsrTrader: TestIRevenueTrader let rTokenTrader: TestIRevenueTrader diff --git a/test/scenario/WBTC.test.ts b/test/scenario/WBTC.test.ts index c55ce95e69..513a7ac7fd 100644 --- a/test/scenario/WBTC.test.ts +++ b/test/scenario/WBTC.test.ts @@ -10,12 +10,12 @@ import { CollateralStatus } from '../../common/constants' import { ERC20Mock, IAssetRegistry, - IBasketHandler, IFacadeTest, MockV3Aggregator, NonFiatCollateral, StaticATokenMock, TestIBackingManager, + TestIBasketHandler, TestIStRSR, TestIRevenueTrader, TestIRToken, @@ -63,7 +63,7 @@ describe(`Non-fiat collateral (eg WBTC) - P${IMPLEMENTATION}`, () => { let rToken: TestIRToken let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let rsrTrader: TestIRevenueTrader let rTokenTrader: TestIRevenueTrader let facadeTest: IFacadeTest diff --git a/test/scenario/WETH.test.ts b/test/scenario/WETH.test.ts index e7e3ca7770..ab915c5137 100644 --- a/test/scenario/WETH.test.ts +++ b/test/scenario/WETH.test.ts @@ -11,10 +11,10 @@ import { SelfReferentialCollateral, ERC20Mock, IAssetRegistry, - IBasketHandler, IFacadeTest, MockV3Aggregator, TestIBackingManager, + TestIBasketHandler, TestIStRSR, TestIRevenueTrader, TestIRToken, @@ -58,7 +58,7 @@ describe(`Self-referential collateral (eg ETH via WETH) - P${IMPLEMENTATION}`, ( let rToken: TestIRToken let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let rsrTrader: TestIRevenueTrader let rTokenTrader: TestIRevenueTrader let facadeTest: IFacadeTest diff --git a/test/scenario/cETH.test.ts b/test/scenario/cETH.test.ts index 6e51f58946..7aaaf3453d 100644 --- a/test/scenario/cETH.test.ts +++ b/test/scenario/cETH.test.ts @@ -12,11 +12,11 @@ import { ComptrollerMock, ERC20Mock, IAssetRegistry, - IBasketHandler, IFacadeTest, MockV3Aggregator, SelfReferentialCollateral, TestIBackingManager, + TestIBasketHandler, TestIStRSR, TestIRevenueTrader, TestIRToken, @@ -66,7 +66,7 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, let rToken: TestIRToken let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let rsrTrader: TestIRevenueTrader let rTokenTrader: TestIRevenueTrader let facadeTest: IFacadeTest diff --git a/test/scenario/cWBTC.test.ts b/test/scenario/cWBTC.test.ts index 266583b5fc..95a4e76f37 100644 --- a/test/scenario/cWBTC.test.ts +++ b/test/scenario/cWBTC.test.ts @@ -13,11 +13,11 @@ import { ComptrollerMock, ERC20Mock, IAssetRegistry, - IBasketHandler, IFacadeTest, MockV3Aggregator, SelfReferentialCollateral, TestIBackingManager, + TestIBasketHandler, TestIStRSR, TestIRevenueTrader, TestIRToken, @@ -70,7 +70,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => let rToken: TestIRToken let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: IBasketHandler + let basketHandler: TestIBasketHandler let rsrTrader: TestIRevenueTrader let rTokenTrader: TestIRevenueTrader let facadeTest: IFacadeTest From c42d69f6b13cde1465d550f724ac1a2aee79aa87 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 10 Apr 2023 09:48:16 -0300 Subject: [PATCH 020/462] fix comment --- contracts/p0/AssetRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/p0/AssetRegistry.sol b/contracts/p0/AssetRegistry.sol index ca9b4d6f0f..a2872ca5bd 100644 --- a/contracts/p0/AssetRegistry.sol +++ b/contracts/p0/AssetRegistry.sol @@ -27,7 +27,7 @@ contract AssetRegistryP0 is ComponentP0, IAssetRegistry { } } - /// Force updates in all collateral assets. Tracks basket status. + /// Force updates in all collateral assets. Track basket status. /// @custom:refresher function refresh() public { // It's a waste of gas to require notPausedOrFrozen because assets can be updated directly From 6d38a4e2ccb4af750d72de54db833de80f905999 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 10 Apr 2023 10:14:09 -0300 Subject: [PATCH 021/462] fix integration test for compoundv2 --- .../compoundv2/CTokenFiatCollateral.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 400ed7c550..8dad576bf4 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -111,6 +111,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi longFreeze: bn('2592000'), // 30 days rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks + warmupPeriod: bn('0'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) auctionLength: bn('900'), // 15 minutes backingBuffer: fp('0.0001'), // 0.01% From 0a964e222590f5cdc2e49df9aa2b57dba01bb082 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 10 Apr 2023 19:44:19 -0400 Subject: [PATCH 022/462] fix lotPrice call --- contracts/p0/BasketHandler.sol | 2 +- contracts/p1/BasketHandler.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index cce8b741cd..7e750a40ca 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -349,7 +349,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { IAsset asset = main.assetRegistry().toAsset(basket.erc20s[i]); (uint192 lowP, uint192 highP) = asset.price(); - (uint192 lotLowP, uint192 lotHighP) = asset.price(); + (uint192 lotLowP, uint192 lotHighP) = asset.lotPrice(); low256 += safeMul(qty, lowP, RoundingMode.FLOOR); high256 += safeMul(qty, highP, RoundingMode.CEIL); diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index dc59f488ff..238d46d778 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -367,7 +367,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { IAsset asset = assetRegistry.toAsset(basket.erc20s[i]); (uint192 lowP, uint192 highP) = asset.price(); - (uint192 lotLowP, uint192 lotHighP) = asset.price(); + (uint192 lotLowP, uint192 lotHighP) = asset.lotPrice(); low256 += safeMul(qty, lowP, RoundingMode.FLOOR); high256 += safeMul(qty, highP, RoundingMode.CEIL); From ec0e287784eaea7fcd2aea09773cac114423b518 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 10 Apr 2023 19:51:48 -0400 Subject: [PATCH 023/462] add regression test for BasketHandler.prices() --- test/Main.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/Main.test.ts b/test/Main.test.ts index 75aa32202f..1d7f68ec19 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1963,6 +1963,24 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(highPrice).to.equal(MAX_UINT192) }) + it('Should distinguish between price/lotPrice - prices()', async () => { + // Set basket with single collateral + await basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1')]) + await basketHandler.refreshBasket() + + await collateral0.refresh() + const [low, high] = await collateral0.price() + await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) // oracle error + + const [price, lotPrice] = await basketHandler.prices() + expect(price.low).to.equal(0) + expect(price.high).to.equal(MAX_UINT192) + expect(lotPrice.low).to.be.closeTo(low, low.div(bn('1e5'))) // small decay + expect(lotPrice.low).to.be.lt(low) + expect(lotPrice.high).to.be.closeTo(high, high.div(bn('1e5'))) // small decay + expect(lotPrice.high).to.be.lt(high) + }) + it('Should disable basket on asset deregistration + return quantities correctly', async () => { // Check values expect(await facadeTest.wholeBasketsHeldBy(rToken.address, addr1.address)).to.equal( From 884bb6d6c885d595bfad1441abd9328557da4427 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 10 Apr 2023 19:53:08 -0400 Subject: [PATCH 024/462] use & instead of +, for clarity --- contracts/p0/BasketHandler.sol | 2 +- contracts/p1/BasketHandler.sol | 2 +- contracts/plugins/assets/RTokenAsset.sol | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 7e750a40ca..2e68be4d5f 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -333,7 +333,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { return (lotP.low, lotP.high); } - /// Returns both the price + lotPrice at once, for gas optimization + /// Returns both the price() & lotPrice() at once, for gas optimization /// @return price_ {UoA/tok} The low and high price estimate of an RToken /// @return lotPrice_ {UoA/tok} The low and high lotprice of an RToken function prices() public view returns (Price memory price_, Price memory lotPrice_) { diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 238d46d778..ffaedff7aa 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -351,7 +351,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { return (lotP.low, lotP.high); } - /// Returns both the price() + lotPrice() at once, for gas optimization + /// Returns both the price() & lotPrice() at once, for gas optimization /// @return price_ {UoA/tok} The low and high price estimate of an RToken /// @return lotPrice_ {UoA/tok} The low and high lotprice of an RToken function prices() public view returns (Price memory price_, Price memory lotPrice_) { diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index 0f26493418..d5c771e052 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -39,7 +39,7 @@ contract RTokenAsset is IAsset { maxTradeVolume = maxTradeVolume_; } - /// Calculates price() + lotPrice() in a non-gas-optimized way + /// Calculates price() & lotPrice() in a non-gas-optimized way /// @return price_ {UoA/tok} The low and high price estimate of an RToken /// @return lotPrice_ {UoA/tok} The low and high lotprice of an RToken function prices() public view returns (Price memory price_, Price memory lotPrice_) { @@ -97,7 +97,7 @@ contract RTokenAsset is IAsset { assert(lotPrice_.low <= lotPrice_.high); } - /// Calculates price() + lotPrice() in a gas-optimized way using a cached BasketRange + /// Calculates price() & lotPrice() in a gas-optimized way using a cached BasketRange /// Used by RecollateralizationLib for efficient price calculation /// @param buRange {BU} The top and bottom of the bu band; how many BUs we expect to hold /// @param buPrice {UoA/BU} The low and high price estimate of a basket unit From 81df5d249cc5b6850c1d404b83e6e0b7be654ebc Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 10 Apr 2023 20:14:27 -0400 Subject: [PATCH 025/462] isolate RTokenAsset.price()/lotPrice(), as it once was --- contracts/plugins/assets/RTokenAsset.sol | 156 +++++++++++++---------- 1 file changed, 86 insertions(+), 70 deletions(-) diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index d5c771e052..e8bdebb817 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -39,64 +39,6 @@ contract RTokenAsset is IAsset { maxTradeVolume = maxTradeVolume_; } - /// Calculates price() & lotPrice() in a non-gas-optimized way - /// @return price_ {UoA/tok} The low and high price estimate of an RToken - /// @return lotPrice_ {UoA/tok} The low and high lotprice of an RToken - function prices() public view returns (Price memory price_, Price memory lotPrice_) { - BasketRange memory buRange; // {BU} - (Price memory buPrice, Price memory buLotPrice) = basketHandler.prices(); // {UoA/BU} - - BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(backingManager)); - uint192 basketsNeeded = IRToken(address(erc20)).basketsNeeded(); // {BU} - - // if (basketHandler.fullyCollateralized()) - if (basketsHeld.bottom >= basketsNeeded) { - buRange.bottom = basketsNeeded; - buRange.top = basketsNeeded; - } else { - // Note: Extremely this is extremely wasteful in terms of gas. This only exists so - // there is _some_ asset to represent the RToken itself when it is deployed, in - // the absence of an external price feed. Any RToken that gets reasonably big - // should switch over to an asset with a price feed. - - IMain main = backingManager.main(); - TradingContext memory ctx = TradingContext({ - basketsHeld: basketsHeld, - bm: backingManager, - ar: assetRegistry, - stRSR: main.stRSR(), - rsr: main.rsr(), - rToken: main.rToken(), - minTradeVolume: backingManager.minTradeVolume(), - maxTradeSlippage: backingManager.maxTradeSlippage() - }); - - Registry memory reg = assetRegistry.getRegistry(); - - uint192[] memory quantities = new uint192[](reg.erc20s.length); - for (uint256 i = 0; i < reg.erc20s.length; ++i) { - quantities[i] = basketHandler.quantityUnsafe(reg.erc20s[i], reg.assets[i]); - } - - // will exclude UoA value from RToken balances at BackingManager - buRange = RecollateralizationLibP1.basketRange(ctx, reg, quantities, buPrice); - } - - // Here we take advantage of the fact that we know RToken has 18 decimals - // to convert between uint256 an uint192. Fits due to assumed max totalSupply. - uint192 supply = _safeWrap(IRToken(address(erc20)).totalSupply()); - - if (supply == 0) return (buPrice, buLotPrice); - - // {UoA/tok} = {BU} * {UoA/BU} / {tok} - price_.low = buRange.bottom.mulDiv(buPrice.low, supply, FLOOR); - price_.high = buRange.top.mulDiv(buPrice.high, supply, CEIL); - lotPrice_.low = buRange.bottom.mulDiv(buLotPrice.low, supply, FLOOR); - lotPrice_.high = buRange.top.mulDiv(buLotPrice.high, supply, CEIL); - assert(price_.low <= price_.high); - assert(lotPrice_.low <= lotPrice_.high); - } - /// Calculates price() & lotPrice() in a gas-optimized way using a cached BasketRange /// Used by RecollateralizationLib for efficient price calculation /// @param buRange {BU} The top and bottom of the bu band; how many BUs we expect to hold @@ -124,6 +66,29 @@ contract RTokenAsset is IAsset { assert(lotPrice_.low <= lotPrice_.high); } + /// 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 + function tryPrice() external view virtual returns (uint192 low, uint192 high) { + (uint192 lowBUPrice, uint192 highBUPrice) = basketHandler.price(); // {UoA/BU} + assert(lowBUPrice <= highBUPrice); // not obviously true just by inspection + + // Here we take advantage of the fact that we know RToken has 18 decimals + // to convert between uint256 an uint192. Fits due to assumed max totalSupply. + uint192 supply = _safeWrap(IRToken(address(erc20)).totalSupply()); + + if (supply == 0) return (lowBUPrice, highBUPrice); + + // The RToken's price is not symmetric like other assets! + // range.bottom is lower because of the slippage from the shortfall + BasketRange memory range = basketRange(); // {BU} + + // {UoA/tok} = {BU} * {UoA/BU} / {tok} + low = range.bottom.mulDiv(lowBUPrice, supply, FLOOR); + high = range.top.mulDiv(highBUPrice, supply, CEIL); + assert(low <= high); // not obviously true + } + // solhint-disable no-empty-blocks function refresh() public virtual override { // No need to save lastPrice; can piggyback off the backing collateral's lotPrice() @@ -135,8 +100,8 @@ contract RTokenAsset is IAsset { /// @return {UoA/tok} The lower end of the price estimate /// @return {UoA/tok} The upper end of the price estimate function price() public view virtual returns (uint192, uint192) { - try this.prices() returns (Price memory p, Price memory) { - return (p.low, p.high); + try this.tryPrice() returns (uint192 low, uint192 high) { + return (low, high); } catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string @@ -146,16 +111,23 @@ contract RTokenAsset is IAsset { /// Should not revert /// lotLow should be nonzero when the asset might be worth selling - /// @return {UoA/tok} The lower end of the lot price estimate - /// @return {UoA/tok} The upper end of the lot price estimate - function lotPrice() external view returns (uint192, uint192) { - try this.prices() returns (Price memory, Price memory lotP) { - return (lotP.low, lotP.high); - } catch (bytes memory errData) { - // see: docs/solidity-style.md#Catching-Empty-Data - if (errData.length == 0) revert(); // solhint-disable-line reason-string - return (0, FIX_MAX); - } + /// @return lotLow {UoA/tok} The lower end of the lot price estimate + /// @return lotHigh {UoA/tok} The upper end of the lot price estimate + function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh) { + (uint192 buLow, uint192 buHigh) = basketHandler.lotPrice(); // {UoA/BU} + + // Here we take advantage of the fact that we know RToken has 18 decimals + // to convert between uint256 an uint192. Fits due to assumed max totalSupply. + uint192 supply = _safeWrap(IRToken(address(erc20)).totalSupply()); + + if (supply == 0) return (buLow, buHigh); + + BasketRange memory range = basketRange(); // {BU} + + // {UoA/tok} = {BU} * {UoA/BU} / {tok} + lotLow = range.bottom.mulDiv(buLow, supply, FLOOR); + lotHigh = range.top.mulDiv(buHigh, supply, CEIL); + assert(lotLow <= lotHigh); // not obviously true } /// @return {tok} The balance of the ERC20 in whole tokens @@ -177,4 +149,48 @@ contract RTokenAsset is IAsset { function claimRewards() external virtual {} // solhint-enable no-empty-blocks + + // ==== Private ==== + + /// Computationally expensive basketRange calculation; used in price() & lotPrice() + function basketRange() private view returns (BasketRange memory range) { + Price memory buPrice; + (buPrice.low, buPrice.high) = basketHandler.price(); // {UoA/BU} + + BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(backingManager)); + uint192 basketsNeeded = IRToken(address(erc20)).basketsNeeded(); // {BU} + + // if (basketHandler.fullyCollateralized()) + if (basketsHeld.bottom >= basketsNeeded) { + range.bottom = basketsNeeded; + range.top = basketsNeeded; + } else { + // Note: Extremely this is extremely wasteful in terms of gas. This only exists so + // there is _some_ asset to represent the RToken itself when it is deployed, in + // the absence of an external price feed. Any RToken that gets reasonably big + // should switch over to an asset with a price feed. + + IMain main = backingManager.main(); + TradingContext memory ctx = TradingContext({ + basketsHeld: basketsHeld, + bm: backingManager, + ar: main.assetRegistry(), + stRSR: main.stRSR(), + rsr: main.rsr(), + rToken: main.rToken(), + minTradeVolume: backingManager.minTradeVolume(), + maxTradeSlippage: backingManager.maxTradeSlippage() + }); + + Registry memory reg = assetRegistry.getRegistry(); + + uint192[] memory quantities = new uint192[](reg.erc20s.length); + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + quantities[i] = basketHandler.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + } + + // will exclude UoA value from RToken balances at BackingManager + range = RecollateralizationLibP1.basketRange(ctx, reg, quantities, buPrice); + } + } } From a2f8dfc1a76d7991967e67c7688957760b7b43cb Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 10 Apr 2023 20:25:58 -0400 Subject: [PATCH 026/462] last remaining + --- contracts/interfaces/IBasketHandler.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index c0c16186cf..98f2bc3474 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -114,7 +114,7 @@ interface IBasketHandler is IComponent { /// @return lotHigh {UoA/tok} The upper end of the lot price estimate function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh); - /// Returns both the price + lotPrice at once, for gas optimization + /// Returns both the price & lotPrice at once, for gas optimization /// @return price_ {UoA/tok} The low and high price estimate of an RToken /// @return lotPrice_ {UoA/tok} The low and high lotprice of an RToken function prices() external view returns (Price memory price_, Price memory lotPrice_); From d147bb446912f1750872d5962432a3e9f9dcf26d Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Tue, 11 Apr 2023 09:28:09 -0300 Subject: [PATCH 027/462] update docs --- docs/system-design.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/system-design.md b/docs/system-design.md index 8f90194096..a9d54b73ef 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -207,6 +207,15 @@ The trading delay is how many seconds should pass after the basket has been chan Default value: `7200` = 2 hours Mainnet reasonable range: 0 to 604800 +### `warmupPeriod` + +Dimension: `{seconds}` + +The warmup period is how many seconds should pass after the basket regained the SOUND status before an RToken can be issued and/or a trade can be opened. + +Default value: `900` = 15 minutes +Mainnet reasonable range: 0 to 604800 + ### `auctionLength` Dimension: `{seconds}` From 006d7add632685659fe51237fd27225170853162 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Tue, 11 Apr 2023 23:22:38 +0530 Subject: [PATCH 028/462] Cancel Unstaking --- contracts/interfaces/IStRSR.sol | 21 +++++ contracts/p0/StRSR.sol | 44 +++++++++++ contracts/p1/StRSR.sol | 48 ++++++++++++ test/ZZStRSR.test.ts | 131 +++++++++++++++++++++++++++++--- 4 files changed, 235 insertions(+), 9 deletions(-) diff --git a/contracts/interfaces/IStRSR.sol b/contracts/interfaces/IStRSR.sol index a8d76ec3aa..cef4d9d5cb 100644 --- a/contracts/interfaces/IStRSR.sol +++ b/contracts/interfaces/IStRSR.sol @@ -65,6 +65,23 @@ interface IStRSR is IERC20MetadataUpgradeable, IERC20PermitUpgradeable, ICompone uint256 rsrAmount ); + /// Emitted when RSR unstaking is cancelled + /// @param firstId The beginning of the range of draft IDs withdrawn in this transaction + /// @param endId The end of range of draft IDs withdrawn in this transaction + /// (ID i was withdrawn if firstId <= i < endId) + /// @param draftEra The era of the draft. + /// The triple (staker, draftEra, id) is a unique ID among drafts + /// @param staker The address of the unstaker + + /// @param rsrAmount {qRSR} How much RSR this unstaking was worth + event UnstakingCancelled( + uint256 indexed firstId, + uint256 indexed endId, + uint256 draftEra, + address indexed staker, + uint256 rsrAmount + ); + /// Emitted whenever the exchange rate changes event ExchangeRateSet(uint192 indexed oldVal, uint192 indexed newVal); @@ -107,6 +124,10 @@ interface IStRSR is IERC20MetadataUpgradeable, IERC20PermitUpgradeable, ICompone /// @custom:interaction function withdraw(address account, uint256 endId) external; + /// Cancel unstaking for the account, up to (but not including!) `endId` + /// @custom:interaction + function cancelUnstake(uint256 endId) external; + /// Seize RSR, only callable by main.backingManager() /// @custom:protected function seizeRSR(uint256 amount) external; diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index fc62d2fd12..7041acef0b 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -218,6 +218,50 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { main.rsr().safeTransfer(account, total); } + function cancelUnstake(uint256 endId) external notPausedOrFrozen { + address account = _msgSender(); + + // Call state keepers + main.poke(); + + IBasketHandler bh = main.basketHandler(); + require(bh.fullyCollateralized(), "RToken uncollateralized"); + require(bh.status() == CollateralStatus.SOUND, "basket defaulted"); + + Withdrawal[] storage queue = withdrawals[account]; + if (endId == 0) return; + require(endId <= queue.length, "index out-of-bounds"); + // require(queue[endId - 1].availableAt <= block.timestamp, "withdrawal unavailable"); + + // Skip executed withdrawals - Both amounts should be 0 + uint256 start = 0; + while (start < endId && queue[start].rsrAmount == 0 && queue[start].stakeAmount == 0) + start++; + + // Accumulate and zero executable withdrawals + uint256 total = 0; + uint256 i = start; + for (; i < endId; i++) { + total += queue[i].rsrAmount; + queue[i].rsrAmount = 0; + queue[i].stakeAmount = 0; + } + + // Execute accumulated withdrawals + emit UnstakingCancelled(start, i, era, account, total); + + uint256 stakeAmount = total; + if (totalStaked > 0) stakeAmount = (total * totalStaked) / rsrBacking; + + // Create stRSR balance + if (balances[account] == 0) accounts.add(account); + balances[account] += stakeAmount; + totalStaked += stakeAmount; + + // Move deposited RSR to backing + rsrBacking += total; + } + /// Return the maximum valid value of endId such that withdraw(endId) should immediately work function endIdForWithdraw(address account) external view returns (uint256) { Withdrawal[] storage queue = withdrawals[account]; diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 5702c20aea..e2200fc2f6 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -335,6 +335,54 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab IERC20Upgradeable(address(rsr)).safeTransfer(account, rsrAmount); } + function cancelUnstake(uint256 endId) external notPausedOrFrozen { + address account = _msgSender(); + + // == Refresh == + assetRegistry.refresh(); + + // == Checks + Effects == + require(basketHandler.fullyCollateralized(), "RToken uncollateralized"); + require(basketHandler.status() == CollateralStatus.SOUND, "basket defaulted"); + + uint256 firstId = firstRemainingDraft[draftEra][account]; + CumulativeDraft[] storage queue = draftQueues[draftEra][account]; + if (endId == 0 || firstId >= endId) return; + + require(endId <= queue.length, "index out-of-bounds"); + + // Cancelling unstake does not require checking if the unstaking was available + // require(queue[endId - 1].availableAt <= block.timestamp, "withdrawal unavailable"); + + uint192 oldDrafts = firstId > 0 ? queue[firstId - 1].drafts : 0; + uint192 draftAmount = queue[endId - 1].drafts - oldDrafts; + + // advance queue past withdrawal + firstRemainingDraft[draftEra][account] = endId; + + // ==== Compute RSR amount + uint256 newTotalDrafts = totalDrafts - draftAmount; + // newDraftRSR: {qRSR} = {qDrafts} * D18 / D18{qDrafts/qRSR} + uint256 newDraftRSR = (newTotalDrafts * FIX_ONE_256 + (draftRate - 1)) / draftRate; + uint256 rsrAmount = draftRSR - newDraftRSR; + + if (rsrAmount == 0) return; + + // ==== Transfer RSR from the draft pool + totalDrafts = newTotalDrafts; + draftRSR = newDraftRSR; + + emit UnstakingCancelled(firstId, endId, draftEra, account, rsrAmount); + + uint256 newStakeRSR = stakeRSR + rsrAmount; + // newTotalStakes: {qStRSR} = D18{qStRSR/qRSR} * {qRSR} / D18 + uint256 newTotalStakes = (stakeRate * newStakeRSR) / FIX_ONE; + uint256 stakeAmount = newTotalStakes - totalStakes; + + stakeRSR += rsrAmount; + _mint(account, stakeAmount); + } + /// @param rsrAmount {qRSR} /// Must seize at least `rsrAmount`, or revert /// @custom:protected diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 1fea6e6f7b..c20db858e8 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -597,6 +597,46 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { expect(await stRSR.exchangeRate()).to.equal(fp('1')) }) + it('Should allow cancelling unstaking', async () => { + const amount: BigNumber = bn('1000e18') + + // Stake + await rsr.connect(addr1).approve(stRSR.address, amount) + await stRSR.connect(addr1).stake(amount) + const availableAt = (await getLatestBlockTimestamp()) + config.unstakingDelay.toNumber() + 1 + + // Set next block timestamp - for deterministic result + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 1) + + // Unstake + await expect(stRSR.connect(addr1).unstake(amount)) + .emit(stRSR, 'UnstakingStarted') + .withArgs(0, 1, addr1.address, amount, amount, availableAt) + + // Check withdrawal properly registered + await expectWithdrawal(addr1.address, 0, { rsrAmount: amount }) + + // Check balances and stakes + expect(await rsr.balanceOf(stRSR.address)).to.equal(amount) + expect(await rsr.balanceOf(addr1.address)).to.equal(initialBal.sub(amount)) + + // All staked funds withdrawn upfront + expect(await stRSR.balanceOf(addr1.address)).to.equal(0) + expect(await stRSR.totalSupply()).to.equal(0) + + // Exchange rate remains steady + expect(await stRSR.exchangeRate()).to.equal(fp('1')) + + // Let's cancel the unstake + await expect(stRSR.connect(addr1).cancelUnstake(1)).to.emit(stRSR, 'UnstakingCancelled') + + // Check balances and stakes + expect(await stRSR.balanceOf(addr1.address)).to.equal(amount) + expect(await stRSR.totalSupply()).to.equal(amount) + expect(await rsr.balanceOf(stRSR.address)).to.equal(amount) // RSR is still in the contract + expect(await rsr.balanceOf(addr1.address)).to.equal(initialBal.sub(amount)) // RSR wasn't returned + }) + context('With deposits and withdrawals', () => { let amount1: BigNumber let amount2: BigNumber @@ -908,7 +948,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { expect(await stRSR.exchangeRate()).to.equal(fp('1')) }) - it('Should calculate available withrawals correctly', async function () { + it('Should calculate available withdrawals correctly', async function () { // Create an additional third stake for user 2 await rsr.connect(addr2).approve(stRSR.address, amount3) await stRSR.connect(addr2).stake(amount3) @@ -925,34 +965,34 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay / 2 ) - // Create additional withrawal for user 2 + // Create additional withdrawal for user 2 await stRSR.connect(addr2).unstake(amount3) - // Check withrawals - Nothing available yet + // Check withdrawals - Nothing available yet expect(await stRSR.endIdForWithdraw(addr1.address)).to.equal(0) expect(await stRSR.endIdForWithdraw(addr2.address)).to.equal(0) // Move time forward to first period complete await advanceToTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay / 2) - // Check withrawals - We can withdraw the first stakes for each + // Check withdrawals - We can withdraw the first stakes for each expect(await stRSR.endIdForWithdraw(addr1.address)).to.equal(1) expect(await stRSR.endIdForWithdraw(addr2.address)).to.equal(1) - // Create additional withrawal for user 2 + // Create additional withdrawal for user 2 await stRSR.connect(addr2).unstake(amount3) // Move time forward to end of second period await advanceToTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay / 2) - // Check withrawals - We can withdraw the second stake for user 2 + // Check withdrawals - We can withdraw the second stake for user 2 expect(await stRSR.endIdForWithdraw(addr1.address)).to.equal(1) expect(await stRSR.endIdForWithdraw(addr2.address)).to.equal(2) // Move time forward to end of all periods await advanceToTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay * 2) - // Check withrawals - We can withdraw the third stake for user 2 + // Check withdrawals - We can withdraw the third stake for user 2 expect(await stRSR.endIdForWithdraw(addr1.address)).to.equal(1) expect(await stRSR.endIdForWithdraw(addr2.address)).to.equal(3) @@ -978,6 +1018,79 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { expect(await stRSR.exchangeRate()).to.equal(fp('1')) }) + it('Allow cancelling unstake with multiple withdraws', async function () { + // Create an additional third stake for user 2 + await rsr.connect(addr2).approve(stRSR.address, amount3) + await stRSR.connect(addr2).stake(amount3) + + // Get current balances for users + const prevAddr1Balance = await rsr.balanceOf(addr1.address) + const prevAddr2Balance = await rsr.balanceOf(addr2.address) + + // Create withdrawal for user 2 + await stRSR.connect(addr2).unstake(amount2) + + // Move time forward to half of first period + await setNextBlockTimestamp( + Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay / 2 + ) + + // Create additional withdrawal for user 2 + await stRSR.connect(addr2).unstake(amount3) + + // Check withdrawals - Nothing available yet + expect(await stRSR.endIdForWithdraw(addr1.address)).to.equal(0) + expect(await stRSR.endIdForWithdraw(addr2.address)).to.equal(0) + + // Cancel withdrawal + await stRSR.connect(addr2).cancelUnstake(1) + + // Move time forward to first period complete + await advanceToTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay / 2) + + // Check withdrawals - We can withdraw the first stakes for each + expect(await stRSR.endIdForWithdraw(addr1.address)).to.equal(1) + expect(await stRSR.endIdForWithdraw(addr2.address)).to.equal(1) + + // Create additional withdrawal for user 2 + await stRSR.connect(addr2).unstake(amount3) + + // Move time forward to end of second period + await advanceToTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay / 2) + + // Check withdrawals - We can withdraw the second stake for user 2 + expect(await stRSR.endIdForWithdraw(addr1.address)).to.equal(1) + expect(await stRSR.endIdForWithdraw(addr2.address)).to.equal(2) + + // Move time forward to end of all periods + await advanceToTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay * 2) + + // Check withdrawals - We can withdraw the third stake for user 2 + expect(await stRSR.endIdForWithdraw(addr1.address)).to.equal(1) + expect(await stRSR.endIdForWithdraw(addr2.address)).to.equal(3) + + // Withdraw + await stRSR + .connect(addr1) + .withdraw(addr1.address, await stRSR.endIdForWithdraw(addr1.address)) + await stRSR + .connect(addr2) + .withdraw(addr2.address, await stRSR.endIdForWithdraw(addr2.address)) + + // Withdrawals completed + expect(await stRSR.totalSupply()).to.equal(amount2) + expect(await rsr.balanceOf(stRSR.address)).to.equal(await stRSR.totalSupply()) + expect(await rsr.balanceOf(addr1.address)).to.equal(prevAddr1Balance.add(amount1)) + expect(await stRSR.balanceOf(addr1.address)).to.equal(0) + expect(await rsr.balanceOf(addr2.address)).to.equal( + prevAddr2Balance.add(amount3).add(amount3) + ) + expect(await stRSR.balanceOf(addr2.address)).to.equal(amount2) + + /// Exchange rate remains steady + expect(await stRSR.exchangeRate()).to.equal(fp('1')) + }) + it('Should handle changes in stakingWithdrawalDelay correctly', async function () { // Get current balance for user 2 const prevAddr2Balance = await rsr.balanceOf(addr2.address) @@ -999,7 +1112,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { // Should not be processed, only after the first pending stake is done await advanceToTimestamp(Number(newUnstakingDelay.add(await getLatestBlockTimestamp()))) - // Check withrawals - Nothing available yet + // Check withdrawals - Nothing available yet expect(await stRSR.endIdForWithdraw(addr2.address)).to.equal(0) // Nothing completed still @@ -1011,7 +1124,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { // Move forward past first stakingWithdrawalDelay await advanceToTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay) - // Check withrawals - We can withdraw both stakes + // Check withdrawals - We can withdraw both stakes expect(await stRSR.endIdForWithdraw(addr2.address)).to.equal(2) // Withdraw with correct index From 04668ea46832d469a7df4fdd00bdd877680d7df9 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 11 Apr 2023 16:50:42 -0400 Subject: [PATCH 029/462] initial design --- contracts/interfaces/IAssetRegistry.sol | 3 + contracts/interfaces/IBasketHandler.sol | 14 +++ contracts/interfaces/IRToken.sol | 13 +++ contracts/p1/AssetRegistry.sol | 5 ++ contracts/p1/BasketHandler.sol | 108 +++++++++++++++++++++++- contracts/p1/RToken.sol | 71 ++++++++++++++-- 6 files changed, 202 insertions(+), 12 deletions(-) diff --git a/contracts/interfaces/IAssetRegistry.sol b/contracts/interfaces/IAssetRegistry.sol index 58534cf1da..450aa7e3fe 100644 --- a/contracts/interfaces/IAssetRegistry.sol +++ b/contracts/interfaces/IAssetRegistry.sol @@ -52,6 +52,9 @@ interface IAssetRegistry is IComponent { /// @return reg The list of registered ERC20s and Assets, in the same order function getRegistry() external view returns (Registry memory reg); + /// @return The number of registered ERC20s + function size() external view returns (uint256); + function register(IAsset asset) external returns (bool); function swapRegistered(IAsset asset) external returns (bool swapped); diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 1575ac9f21..7bd30c19cf 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -119,6 +119,20 @@ interface IBasketHandler is IComponent { view returns (address[] memory erc20s, uint256[] memory quantities); + /// Return the redemption value of `amount` BUs for a linear combination of historical baskets + /// Requires `portions` sums to FIX_ONE + /// @param basketNonces An array of basket nonces to do redemption from + /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE + /// @param amount {BU} + /// @return erc20s The backing collateral erc20s + /// @return quantities {qTok} ERC20 token quantities equal to `amount` BUs + // Returns (erc20s, [quantity(e) * amount {as qTok} for e in erc20s]) + function quoteHistoricalRedemption( + uint48[] memory basketNonces, + uint192[] memory portions, + uint192 amount + ) external view returns (address[] memory erc20s, uint256[] memory quantities); + /// @return top {BU} The number of partial basket units: e.g max(coll.map((c) => c.balAsBUs()) /// bottom {BU} The number of whole basket units held by the account function basketsHeldBy(address account) external view returns (BasketRange memory); diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index ad0e1ea1b1..23c65931b7 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -95,6 +95,19 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea uint48 basketNonce ) external; + /// Redeem RToken for a linear combination of historical baskets, to a particular recipient + /// @param recipient The address to receive the backing collateral tokens + /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem + /// @param basketNonces An array of basket nonces to do redemption from + /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE + /// @custom:interaction + function historicalRedeemTo( + address recipient, + uint256 amount, + uint48[] memory basketNonces, + uint192[] memory portions + ) external; + /// Mints a quantity of RToken to the `recipient`, callable only by the BackingManager /// @param recipient The recipient of the newly minted RToken /// @param amount {qRTok} The amount to be minted diff --git a/contracts/p1/AssetRegistry.sol b/contracts/p1/AssetRegistry.sol index 924dfded52..31f653cb16 100644 --- a/contracts/p1/AssetRegistry.sol +++ b/contracts/p1/AssetRegistry.sol @@ -154,6 +154,11 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { } } + /// @return The number of registered ERC20s + function size() external view returns (uint256) { + return _erc20s.length(); + } + /// Register an asset /// Forbids registering a different asset for an ERC20 that is already registered /// @return registered If the asset was moved from unregistered to registered diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index e3f277c5d8..56c61588fb 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -139,11 +139,16 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // and everything except redemption should be paused. bool private disabled; + // === Function-local transitory vars === + // These are effectively local variables of _switchBasket. // Nothing should use their values from previous transactions. EnumerableSet.Bytes32Set private _targetNames; Basket private _newBasket; // Always empty + // === Warmup Period === + // Added in 3.0.0 + // Warmup Period uint48 public warmupPeriod; // {s} how long to wait until issuance/trading after regaining SOUND @@ -152,6 +157,20 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { CollateralStatus private lastStatus; uint48 private lastStatusTimestamp; + // === Historical basket nonces === + // Added in 3.0.0 + + // Nonce of the first reference basket from the current history + // A new historical record begins whenever the prime basket is changed + // There can be 0 to any number of reference baskets from the current history + uint48 private historicalNonce; // {nonce} + // TODO double-check historicalNonce fits in the Warmup Period slot + + // The historical baskets by basket nonce; includes current basket + mapping(uint48 => Basket) private historicalBaskets; + + // === + // ==== Invariants ==== // basket is a valid Basket: // basket.erc20s is a valid collateral array and basket.erc20s == keys(basket.refAmts) @@ -262,6 +281,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { config.targetNames[erc20s[i]] = names[i]; } + historicalNonce = nonce + 1; // set historicalNonce to the next nonce emit PrimeBasketSet(erc20s, targetAmts, names); } @@ -489,6 +509,86 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } } + /// Return the redemption value of `amount` BUs for a linear combination of historical baskets + /// Requires `portions` sums to FIX_ONE + /// @param basketNonces An array of basket nonces to do redemption from + /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE + /// @param amount {BU} + /// @return erc20s The backing collateral erc20s + /// @return quantities {qTok} ERC20 token quantities equal to `amount` BUs + // Returns (erc20s, [quantity(e) * amount {as qTok} for e in erc20s]) + function quoteHistoricalRedemption( + uint48[] memory basketNonces, + uint192[] memory portions, + uint192 amount + ) external view returns (address[] memory erc20s, uint256[] memory quantities) { + require(basketNonces.length == portions.length, "portions does not mirror basketNonces"); + uint256 lenAll = assetRegistry.size(); + IERC20[] memory erc20sAll = new IERC20[](lenAll); + uint192[] memory refAmtsAll = new uint192[](lenAll); + + uint256 len; // length of return arrays + + // Calculate the linear combination basket + for (uint48 i = 0; i < basketNonces.length; ++i) { + require( + basketNonces[i] >= historicalNonce && basketNonces[i] <= nonce, + "invalid basketNonce" + ); // will always revert directly after setPrimeBasket() + Basket storage b = historicalBaskets[i]; + + // Add-in refAmts contribution from historical basket + for (uint256 j = 0; j < b.erc20s.length; ++j) { + IERC20 erc20 = b.erc20s[j]; + if (address(erc20) == address(0)) continue; + + // Ugly search through erc20sAll + uint256 erc20Index = type(uint256).max; + for (uint256 k = 0; k < len; ++k) { + if (erc20 == erc20sAll[k]) { + erc20Index = k; + continue; + } + } + + // Add new ERC20 entry if not found + if (erc20Index == type(uint256).max) { + erc20sAll[len] = erc20; + + // {ref} = {1} * {ref} + refAmtsAll[len] = portions[j].mul(b.refAmts[erc20], FLOOR); + ++len; + } else { + // {ref} = {1} * {ref} + refAmtsAll[erc20Index] += portions[j].mul(b.refAmts[erc20], FLOOR); + } + } + } + + erc20s = new address[](len); + quantities = new uint256[](len); + + // Calculate quantities + for (uint256 i = 0; i < len; ++i) { + IERC20Metadata erc20 = IERC20Metadata(address(erc20sAll[i])); + erc20s[i] = address(erc20); + IAsset asset = assetRegistry.toAsset(erc20); + if (!asset.isCollateral()) continue; // skip token if no longer registered + + // prevent div-by-zero + uint192 refPerTok = ICollateral(address(asset)).refPerTok(); + if (refPerTok == 0) continue; // quantities[i] = 0; + + // {tok} = {BU} * {ref/BU} / {ref/tok} + quantities[i] = amount.mulDiv(refAmtsAll[i], refPerTok, FLOOR).shiftl_toUint( + int8(erc20.decimals()), + FLOOR + ); + // slightly more penalizing than its sibling calculation that uses through _quantity() + // because does not intermediately CEIL as part of the division + } + } + /// @return baskets {BU} /// .top The number of partial basket units: e.g max(coll.map((c) => c.balAsBUs()) /// .bottom The number of whole basket units held by the account @@ -582,8 +682,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { ==== Usual specs ==== Then, finally, given all that, the effects of _switchBasket() are: - basket' = _newBasket, as defined above nonce' = nonce + 1 + basket' = _newBasket, as defined above timestamp' = now */ @@ -594,6 +694,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // _targetNames := {} while (_targetNames.length() > 0) _targetNames.remove(_targetNames.at(0)); + // _newBasket := {} _newBasket.empty(); @@ -705,8 +806,9 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // Update the basket if it's not disabled if (!disabled) { - basket.setFrom(_newBasket); nonce += 1; + basket.setFrom(_newBasket); + historicalBaskets[nonce].setFrom(_newBasket); timestamp = uint48(block.timestamp); } @@ -808,5 +910,5 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[41] private __gap; + uint256[40] private __gap; } diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index a6fdd6206f..e7428eeae7 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -185,6 +185,40 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { } /// Redeem RToken for basket collateral to a particular recipient + /// @param recipient The address to receive the backing collateral tokens + /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem + /// @param basketNonce The nonce of the basket the redemption should be from; else reverts + /// @custom:interaction + function redeemTo( + address recipient, + uint256 amount, + uint48 basketNonce + ) public { + uint48[] memory basketNonces = new uint48[](1); + basketNonces[0] = basketNonce; + uint192[] memory portions = new uint192[](1); + portions[0] = FIX_ONE; + + _redeemTo(recipient, amount, basketNonces, portions); + } + + /// Redeem RToken for a linear combination of historical baskets, to a particular recipient + /// @param recipient The address to receive the backing collateral tokens + /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem + /// @param basketNonces An array of basket nonces to do redemption from + /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE + /// @custom:interaction + function historicalRedeemTo( + address recipient, + uint256 amount, + uint48[] memory basketNonces, + uint192[] memory portions + ) external { + _redeemTo(recipient, amount, basketNonces, portions); + } + + /// Redeem RToken for basket collateral to a particular recipient + /// Handles both historical and present-basket redemptions // checks: // amount > 0 // amount <= balanceOf(caller) @@ -206,13 +240,15 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem - /// @param basketNonce The nonce of the basket the redemption should be from; else reverts + /// @param basketNonces An array of basket nonces to do redemption from + /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE /// @custom:interaction - function redeemTo( + function _redeemTo( address recipient, uint256 amount, - uint48 basketNonce - ) public notFrozen exchangeRateIsValidAfter { + uint48[] memory basketNonces, + uint192[] memory portions + ) internal notFrozen exchangeRateIsValidAfter { // == Refresh == assetRegistry.refresh(); @@ -238,11 +274,28 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { uint192 basketsRedeemed = basketsNeeded.muluDivu(amount, supply); // FLOOR emit Redemption(redeemer, recipient, amount, basketsRedeemed); - require(basketHandler.nonce() == basketNonce, "non-current basket nonce"); - (address[] memory erc20s, uint256[] memory amounts) = basketHandler.quote( - basketsRedeemed, - FLOOR - ); + // Confirm portions sums to FIX_ONE + uint256 portionsSum; + for (uint256 i = 0; i < portions.length; ++i) portionsSum += portions[i]; + require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); + + address[] memory erc20s; + uint256[] memory amounts; + if (basketNonces.length == 1 && basketHandler.nonce() == basketNonces[0]) { + // Current-basket redemption + + require(portions.length == 1, "portions does not mirror basketNonces"); + (erc20s, amounts) = basketHandler.quote(basketsRedeemed, FLOOR); + } else { + // Historical basket redemption + + // BasketHandler will handle the require that portions sum to FIX_ZERO + (erc20s, amounts) = basketHandler.quoteHistoricalRedemption( + basketNonces, + portions, + basketsRedeemed + ); + } // ==== Prorate redemption ==== // i.e, set amounts = min(amounts, balances * amount / totalSupply) From b08a3f4ddfbc10f274bf52417e6be7803707c1af Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Wed, 12 Apr 2023 03:13:19 +0530 Subject: [PATCH 030/462] Remove unnecessary checks --- contracts/p0/StRSR.sol | 9 +++------ contracts/p1/StRSR.sol | 9 +++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index 7041acef0b..9f7e733e5a 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -221,12 +221,9 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { function cancelUnstake(uint256 endId) external notPausedOrFrozen { address account = _msgSender(); - // Call state keepers - main.poke(); - - IBasketHandler bh = main.basketHandler(); - require(bh.fullyCollateralized(), "RToken uncollateralized"); - require(bh.status() == CollateralStatus.SOUND, "basket defaulted"); + // IBasketHandler bh = main.basketHandler(); + // require(bh.fullyCollateralized(), "RToken uncollateralized"); + // require(bh.status() == CollateralStatus.SOUND, "basket defaulted"); Withdrawal[] storage queue = withdrawals[account]; if (endId == 0) return; diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index e2200fc2f6..e52fd791ca 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -338,12 +338,9 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab function cancelUnstake(uint256 endId) external notPausedOrFrozen { address account = _msgSender(); - // == Refresh == - assetRegistry.refresh(); - - // == Checks + Effects == - require(basketHandler.fullyCollateralized(), "RToken uncollateralized"); - require(basketHandler.status() == CollateralStatus.SOUND, "basket defaulted"); + // We specifically allow unstaking when under collateralized + // require(basketHandler.fullyCollateralized(), "RToken uncollateralized"); + // require(basketHandler.status() == CollateralStatus.SOUND, "basket defaulted"); uint256 firstId = firstRemainingDraft[draftEra][account]; CumulativeDraft[] storage queue = draftQueues[draftEra][account]; From 2d2cdd0d2b33d49c53034aa0c858c8f001a3033f Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 12 Apr 2023 11:27:51 -0300 Subject: [PATCH 031/462] remove __gap in p0 --- contracts/p0/BasketHandler.sol | 7 ------- 1 file changed, 7 deletions(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 02e4cff47a..5dfe250a80 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -709,11 +709,4 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { return false; } } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - uint256[47] private __gap; } From b41ab1a78ff670a3217f8bbc3fe1a0aaf2cf4a46 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 12 Apr 2023 11:33:40 -0300 Subject: [PATCH 032/462] enable test --- test/Main.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Main.test.ts b/test/Main.test.ts index 2ef0e30a4f..1214369935 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1830,7 +1830,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) }) - it.skip('Should handle full collateral deregistration and disable the basket', async () => { + it('Should handle full collateral deregistration and disable the basket', async () => { // Check status expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.quantity(token1.address)).to.equal(basketsNeededAmts[1]) From ea22e5bde86e7e36113ae04c7499915edb8651a3 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 12 Apr 2023 11:33:58 -0300 Subject: [PATCH 033/462] reorder variables --- contracts/p0/BasketHandler.sol | 2 +- contracts/p1/BasketHandler.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 5dfe250a80..1c2d0fd513 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -136,8 +136,8 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // basket status changes, mainly set when `trackStatus()` is called // used to enforce warmup period, after regaining SOUND - CollateralStatus private lastStatus; uint48 private lastStatusTimestamp; + CollateralStatus private lastStatus; // ==== Invariants ==== // basket is a valid Basket: diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index e3f277c5d8..de821b9bb5 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -149,8 +149,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // basket status changes, mainly set when `trackStatus()` is called // used to enforce warmup period, after regaining SOUND - CollateralStatus private lastStatus; uint48 private lastStatusTimestamp; + CollateralStatus private lastStatus; // ==== Invariants ==== // basket is a valid Basket: From cb3c0766e1faa89065dd24f8b8680601bf6be21b Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 12 Apr 2023 18:22:06 -0300 Subject: [PATCH 034/462] add minimum warmup period --- common/configuration.ts | 1 + contracts/p0/BasketHandler.sol | 3 ++- contracts/p1/BasketHandler.sol | 3 ++- test/Main.test.ts | 6 ++++++ test/fixtures.ts | 2 +- test/integration/fixtures.ts | 2 +- 6 files changed, 13 insertions(+), 4 deletions(-) diff --git a/common/configuration.ts b/common/configuration.ts index db524bc993..411fb30432 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -401,6 +401,7 @@ export const MAX_THROTTLE_PCT_RATE = BigNumber.from(10).pow(18) // Timestamps export const MAX_ORACLE_TIMEOUT = BigNumber.from(2).pow(48).sub(1) export const MAX_TRADING_DELAY = 31536000 // 1 year +export const MIN_WARMUP_PERIOD = 60 // 1 minute export const MAX_WARMUP_PERIOD = 31536000 // 1 year export const MAX_AUCTION_LENGTH = 604800 // 1 week export const MAX_UNSTAKING_DELAY = 31536000 // 1 year diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 1c2d0fd513..3f19b77f4e 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -109,6 +109,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { using EnumerableSet for EnumerableSet.Bytes32Set; using FixLib for uint192; + uint48 public constant MIN_WARMUP_PERIOD = 60; // {s} 1 minute uint48 public constant MAX_WARMUP_PERIOD = 31536000; // {s} 1 year uint192 public constant MAX_TARGET_AMT = 1e3 * FIX_ONE; // {target/BU} max basket weight @@ -491,7 +492,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { /// @custom:governance function setWarmupPeriod(uint48 val) public governance { - require(val <= MAX_WARMUP_PERIOD, "invalid warmupPeriod"); + require(val >= MIN_WARMUP_PERIOD && val <= MAX_WARMUP_PERIOD, "invalid warmupPeriod"); emit WarmupPeriodSet(warmupPeriod, val); warmupPeriod = val; } diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index de821b9bb5..4046ed4f68 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -115,6 +115,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { using FixLib for uint192; uint192 public constant MAX_TARGET_AMT = 1e3 * FIX_ONE; // {target/BU} max basket weight + uint48 public constant MIN_WARMUP_PERIOD = 60; // {s} 1 minute uint48 public constant MAX_WARMUP_PERIOD = 31536000; // {s} 1 year // Peer components @@ -525,7 +526,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { /// @custom:governance function setWarmupPeriod(uint48 val) public governance { - require(val <= MAX_WARMUP_PERIOD, "invalid warmupPeriod"); + require(val >= MIN_WARMUP_PERIOD && val <= MAX_WARMUP_PERIOD, "invalid warmupPeriod"); emit WarmupPeriodSet(warmupPeriod, val); warmupPeriod = val; } diff --git a/test/Main.test.ts b/test/Main.test.ts index 1214369935..3cf07e882c 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -10,6 +10,7 @@ import { MAX_BACKING_BUFFER, MAX_TARGET_AMT, MAX_MIN_TRADE_VOLUME, + MIN_WARMUP_PERIOD, MAX_WARMUP_PERIOD, IComponents, } from '../common/configuration' @@ -901,6 +902,11 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { // Check value was updated expect(await basketHandler.warmupPeriod()).to.equal(newValue) + // Cannot update with value < min + await expect( + basketHandler.connect(owner).setWarmupPeriod(MIN_WARMUP_PERIOD - 1) + ).to.be.revertedWith('invalid warmupPeriod') + // Cannot update with value > max await expect( basketHandler.connect(owner).setWarmupPeriod(MAX_WARMUP_PERIOD + 1) diff --git a/test/fixtures.ts b/test/fixtures.ts index c548f7af06..e67cebe703 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -410,7 +410,7 @@ export const defaultFixture: Fixture = async function (): Promis longFreeze: bn('2592000'), // 30 days rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks - warmupPeriod: bn('0'), // (the delay _after_ SOUND was regained) + warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) auctionLength: bn('900'), // 15 minutes backingBuffer: fp('0.0001'), // 0.01% diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index 92baf89940..78b8dbf11b 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -614,7 +614,7 @@ export const defaultFixture: Fixture = async function (): Promis longFreeze: bn('2592000'), // 30 days rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks - warmupPeriod: bn('0'), // (the delay _after_ SOUND was regained) + warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) auctionLength: bn('900'), // 15 minutes backingBuffer: fp('0.0001'), // 0.01% From e239c85b21616de3c5de03628f8a985df87aff77 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 12 Apr 2023 18:26:16 -0300 Subject: [PATCH 035/462] add min warmup value --- .../compoundv2/CTokenFiatCollateral.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 8dad576bf4..1c524e5569 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -111,7 +111,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi longFreeze: bn('2592000'), // 30 days rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks - warmupPeriod: bn('0'), // (the delay _after_ SOUND was regained) + warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) auctionLength: bn('900'), // 15 minutes backingBuffer: fp('0.0001'), // 0.01% From 57de8dce75fa07557dfde6a3db1ef78e153532b7 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 12 Apr 2023 18:49:42 -0300 Subject: [PATCH 036/462] increase warmup in fixture --- test/fixtures.ts | 13 +++++++++++-- test/integration/fixtures.ts | 12 +++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/test/fixtures.ts b/test/fixtures.ts index e67cebe703..6af93a452e 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -2,7 +2,13 @@ import { BigNumber, ContractFactory } from 'ethers' import { expect } from 'chai' import hre, { ethers } from 'hardhat' import { getChainId } from '../common/blockchain-utils' -import { IConfig, IImplementations, IRevenueShare, networkConfig } from '../common/configuration' +import { + IConfig, + IImplementations, + IRevenueShare, + MIN_WARMUP_PERIOD, + networkConfig, +} from '../common/configuration' import { expectInReceipt } from '../common/events' import { bn, fp } from '../common/numbers' import { CollateralStatus } from '../common/constants' @@ -55,7 +61,7 @@ import { FacadeMonitor, ZeroDecimalMock, } from '../typechain' -import { getLatestBlockTimestamp, setNextBlockTimestamp } from './utils/time' +import { advanceTime, getLatestBlockTimestamp, setNextBlockTimestamp } from './utils/time' import { useEnv } from '#/utils/env' export enum Implementation { @@ -651,6 +657,9 @@ export const defaultFixture: Fixture = async function (): Promis await basketHandler.connect(owner).setPrimeBasket(basketERC20s, basketsNeededAmts) await basketHandler.connect(owner).refreshBasket() + // Advance time post warmup period + await advanceTime(MIN_WARMUP_PERIOD + 1) + // Set up allowances for (let i = 0; i < basket.length; i++) { await backingManager.grantRTokenAllowance(await basket[i].erc20()) diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index 78b8dbf11b..7348b5b353 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -1,8 +1,15 @@ import { BigNumber, ContractFactory } from 'ethers' import hre, { ethers } from 'hardhat' import { getChainId } from '../../common/blockchain-utils' -import { IConfig, IImplementations, IRevenueShare, networkConfig } from '../../common/configuration' +import { + IConfig, + IImplementations, + IRevenueShare, + MIN_WARMUP_PERIOD, + networkConfig, +} from '../../common/configuration' import { expectInReceipt } from '../../common/events' +import { advanceTime } from '../utils/time' import { bn, fp } from '../../common/numbers' import { AaveLendingPoolMock, @@ -839,6 +846,9 @@ export const defaultFixture: Fixture = async function (): Promis await basketHandler.connect(owner).setPrimeBasket(basketERC20s, basketsNeededAmts) await basketHandler.connect(owner).refreshBasket() + // Advance time post warmup period + await advanceTime(MIN_WARMUP_PERIOD + 1) + // Set up allowances for (let i = 0; i < basket.length; i++) { await backingManager.grantRTokenAllowance(await basket[i].erc20()) From a9c66b733ea968eafc0bb325bf1e77c0bcd57e6d Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 12 Apr 2023 19:01:32 -0300 Subject: [PATCH 037/462] fix warmup setup in tests --- test/fixtures.ts | 10 ++-------- test/integration/AssetPlugins.test.ts | 2 +- test/integration/EasyAuction.test.ts | 6 ++++++ test/integration/fixtures.ts | 10 ++-------- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/test/fixtures.ts b/test/fixtures.ts index 6af93a452e..7b578a1398 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -2,13 +2,7 @@ import { BigNumber, ContractFactory } from 'ethers' import { expect } from 'chai' import hre, { ethers } from 'hardhat' import { getChainId } from '../common/blockchain-utils' -import { - IConfig, - IImplementations, - IRevenueShare, - MIN_WARMUP_PERIOD, - networkConfig, -} from '../common/configuration' +import { IConfig, IImplementations, IRevenueShare, networkConfig } from '../common/configuration' import { expectInReceipt } from '../common/events' import { bn, fp } from '../common/numbers' import { CollateralStatus } from '../common/constants' @@ -658,7 +652,7 @@ export const defaultFixture: Fixture = async function (): Promis await basketHandler.connect(owner).refreshBasket() // Advance time post warmup period - await advanceTime(MIN_WARMUP_PERIOD + 1) + await advanceTime(Number(config.warmupPeriod) + 1) // Set up allowances for (let i = 0; i < basket.length; i++) { diff --git a/test/integration/AssetPlugins.test.ts b/test/integration/AssetPlugins.test.ts index ee93df3448..7071de5926 100644 --- a/test/integration/AssetPlugins.test.ts +++ b/test/integration/AssetPlugins.test.ts @@ -1661,7 +1661,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await basketHandler.timestamp()).to.be.gt(bn(0)) expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) - await expectPrice(basketHandler.address, fp('1'), ORACLE_ERROR, true, bn('1e8')) + await expectPrice(basketHandler.address, fp('1'), ORACLE_ERROR, true, bn('1e12')) // Check RToken price const issueAmount: BigNumber = bn('10000e18') diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 76a4986a5c..ca6937cc08 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -569,6 +569,9 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function expect(await collateral0.status()).to.equal(CollateralStatus.DISABLED) expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + // Advance warmup period + await advanceTime(Number(config.warmupPeriod) + 1) + // Should launch auction for token1 await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') @@ -664,6 +667,9 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await advanceTime((await collateral0.delayUntilDefault()).toString()) await basketHandler.refreshBasket() + // Advance warmup period + await advanceTime(Number(config.warmupPeriod) + 1) + // Should launch auction for token1 await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index 7348b5b353..f6e3a8491e 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -1,13 +1,7 @@ import { BigNumber, ContractFactory } from 'ethers' import hre, { ethers } from 'hardhat' import { getChainId } from '../../common/blockchain-utils' -import { - IConfig, - IImplementations, - IRevenueShare, - MIN_WARMUP_PERIOD, - networkConfig, -} from '../../common/configuration' +import { IConfig, IImplementations, IRevenueShare, networkConfig } from '../../common/configuration' import { expectInReceipt } from '../../common/events' import { advanceTime } from '../utils/time' import { bn, fp } from '../../common/numbers' @@ -847,7 +841,7 @@ export const defaultFixture: Fixture = async function (): Promis await basketHandler.connect(owner).refreshBasket() // Advance time post warmup period - await advanceTime(MIN_WARMUP_PERIOD + 1) + await advanceTime(Number(config.warmupPeriod) + 1) // Set up allowances for (let i = 0; i < basket.length; i++) { From a2be9f84400906a88879b7e5ac3e8e42d203abae Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 12 Apr 2023 19:07:57 -0300 Subject: [PATCH 038/462] fix test --- test/integration/AssetPlugins.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/AssetPlugins.test.ts b/test/integration/AssetPlugins.test.ts index 7071de5926..9ccbaff904 100644 --- a/test/integration/AssetPlugins.test.ts +++ b/test/integration/AssetPlugins.test.ts @@ -1661,7 +1661,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await basketHandler.timestamp()).to.be.gt(bn(0)) expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) - await expectPrice(basketHandler.address, fp('1'), ORACLE_ERROR, true, bn('1e12')) + await expectPrice(basketHandler.address, fp('1'), ORACLE_ERROR, true, bn('1e5')) // Check RToken price const issueAmount: BigNumber = bn('10000e18') From 0b9992b352f094a719494e513f4c73ca9a1cd650 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 13 Apr 2023 10:07:28 -0300 Subject: [PATCH 039/462] fix p0 p1 tests --- test/Recollateralization.test.ts | 30 ++++++++++++++++++++++++++++++ test/ZTradingExtremes.test.ts | 6 +++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 8005039f8c..7e2fd21582 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -641,6 +641,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await basketHandler.connect(owner).setPrimeBasket(initialTokens, [fp('0.5'), fp('0.5')]) await basketHandler.connect(owner).refreshBasket() + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + // Set initial values initialQuotes = [bn('0.5e18'), bn('0.5e6')] initialQuantities = initialQuotes.map((q) => { @@ -1116,6 +1119,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { .to.emit(basketHandler, 'BasketSet') .withArgs(3, [token1.address], [fp('1')], false) + // Advance time post warmup period - temporary IFFY->SOUND + await advanceTime(Number(config.warmupPeriod) + 1) + // Check state remains SOUND expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(false) @@ -2009,6 +2015,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await basketHandler.refreshBasket() expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + // Check initial state expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(true) @@ -2037,6 +2046,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Confirm default and trigger basket switch await basketHandler.refreshBasket() + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + // Check new state after basket switch expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(false) @@ -2356,6 +2368,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await assetRegistry.refresh() await basketHandler.refreshBasket() + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + // Check new state after basket switch expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(false) @@ -2553,6 +2568,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { .to.emit(basketHandler, 'BasketSet') .withArgs(3, newTokens, newRefAmounts, false) + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + // Check new state after basket switch expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(false) @@ -2729,6 +2747,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await assetRegistry.refresh() await basketHandler.refreshBasket() + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + // Running auctions will trigger recollateralization - All balance can be redeemed const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('0.5'), fp('1')) @@ -3179,6 +3200,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { .to.emit(basketHandler, 'BasketSet') .withArgs(2, newTokens, newRefAmounts, false) + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + // Check state - After basket switch expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(false) @@ -3413,6 +3437,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { .to.emit(basketHandler, 'BasketSet') .withArgs(2, newTokens, newRefAmounts, false) + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + // Check state - After basket switch expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(false) @@ -4111,6 +4138,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { config.minTradeVolume.mul((await assetRegistry.erc20s()).length) ) + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + // Check quotes ;[, quotes] = await facade.connect(addr1).callStatic.issue(rToken.address, bn('1e18')) expect(quotes).to.eql(newQuotes) diff --git a/test/ZTradingExtremes.test.ts b/test/ZTradingExtremes.test.ts index 0c13704e59..2eff82ffa9 100644 --- a/test/ZTradingExtremes.test.ts +++ b/test/ZTradingExtremes.test.ts @@ -641,7 +641,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, ) await basketHandler.connect(owner).refreshBasket() - // Insure with RSR + // Over-collateralize with RSR await rsr.connect(owner).mint(addr1.address, fp('1e29')) await rsr.connect(addr1).approve(stRSR.address, fp('1e29')) await stRSR.connect(addr1).stake(fp('1e29')) @@ -661,6 +661,10 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, } await basketHandler.refreshBasket() + + // Advance time post warmup period + await advanceTime(Number(config.warmupPeriod) + 1) + await runRecollateralizationAuctions(basketSize) } From fffe7463eaba7ac57b8d0005dc35c9fc3bc4e26f Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 13 Apr 2023 10:19:23 -0300 Subject: [PATCH 040/462] fix scenario tests --- test/scenario/BadERC20.test.ts | 14 +++++++++++++- test/scenario/ComplexBasket.test.ts | 3 +++ test/scenario/MaxBasketSize.test.ts | 3 +++ test/scenario/WBTC.test.ts | 4 ++++ test/scenario/WETH.test.ts | 5 +++++ test/scenario/cETH.test.ts | 8 ++++++++ test/scenario/cWBTC.test.ts | 7 +++++++ 7 files changed, 43 insertions(+), 1 deletion(-) diff --git a/test/scenario/BadERC20.test.ts b/test/scenario/BadERC20.test.ts index 53b7391ac6..8893f157cc 100644 --- a/test/scenario/BadERC20.test.ts +++ b/test/scenario/BadERC20.test.ts @@ -244,12 +244,16 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { await furnace.melt() }) - it('should be able to unregister and use RSR to recollateralize', async () => { + it.only('should be able to unregister and use RSR to recollateralize', async () => { await assetRegistry.connect(owner).unregister(collateral0.address) expect(await assetRegistry.isRegistered(collateral0.address)).to.equal(false) await expect(basketHandler.refreshBasket()) .to.emit(basketHandler, 'BasketSet') .withArgs(3, [backupToken.address], [fp('1')], false) + + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') // Should be trading RSR for backup token @@ -310,6 +314,10 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { await expect(basketHandler.refreshBasket()) .to.emit(basketHandler, 'BasketSet') .withArgs(3, [backupToken.address], [fp('1')], false) + + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + await expect(backingManager.manageTokens([])).to.be.revertedWith('censored') // Should work now @@ -352,6 +360,10 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { await expect(basketHandler.refreshBasket()) .to.emit(basketHandler, 'BasketSet') .withArgs(3, [backupToken.address], [fp('1')], false) + + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') // Should be trading RSR for backup token diff --git a/test/scenario/ComplexBasket.test.ts b/test/scenario/ComplexBasket.test.ts index d711067fe9..8a05edc76d 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -393,6 +393,9 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await basketHandler.setPrimeBasket(primeBasketERC20s, targetAmts) await basketHandler.connect(owner).refreshBasket() + // Advance time post warmup period + await advanceTime(Number(config.warmupPeriod) + 1) + // Mint and approve initial balances const backing: string[] = await facade.basketTokens(rToken.address) await prepareBacking(backing) diff --git a/test/scenario/MaxBasketSize.test.ts b/test/scenario/MaxBasketSize.test.ts index c8a5449a9f..7600af417e 100644 --- a/test/scenario/MaxBasketSize.test.ts +++ b/test/scenario/MaxBasketSize.test.ts @@ -386,6 +386,9 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { await basketHandler.refreshBasket() } + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + // Check new basket expect(await basketHandler.fullyCollateralized()).to.equal(false) const newBacking: string[] = await facade.basketTokens(rToken.address) diff --git a/test/scenario/WBTC.test.ts b/test/scenario/WBTC.test.ts index 513a7ac7fd..09781a15a3 100644 --- a/test/scenario/WBTC.test.ts +++ b/test/scenario/WBTC.test.ts @@ -196,6 +196,10 @@ describe(`Non-fiat collateral (eg WBTC) - P${IMPLEMENTATION}`, () => { it('should change basket around wbtc', async () => { await token0.setExchangeRate(fp('0.99')) // default await basketHandler.refreshBasket() + + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + await expect(backingManager.manageTokens([token0.address, wbtc.address])).to.emit( backingManager, 'TradeStarted' diff --git a/test/scenario/WETH.test.ts b/test/scenario/WETH.test.ts index ab915c5137..0d12c375fc 100644 --- a/test/scenario/WETH.test.ts +++ b/test/scenario/WETH.test.ts @@ -21,6 +21,7 @@ import { WETH9, } from '../../typechain' import { setOraclePrice } from '../utils/oracles' +import { advanceTime } from '../utils/time' import { getTrade } from '../utils/trades' import { Collateral, @@ -191,6 +192,10 @@ describe(`Self-referential collateral (eg ETH via WETH) - P${IMPLEMENTATION}`, ( it('should change basket around WETH', async () => { await token0.setExchangeRate(fp('0.99')) // default await basketHandler.refreshBasket() + + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + await expect(backingManager.manageTokens([token0.address, weth.address])).to.emit( backingManager, 'TradeStarted' diff --git a/test/scenario/cETH.test.ts b/test/scenario/cETH.test.ts index 7aaaf3453d..fc85a62c9a 100644 --- a/test/scenario/cETH.test.ts +++ b/test/scenario/cETH.test.ts @@ -22,6 +22,7 @@ import { TestIRToken, WETH9, } from '../../typechain' +import { advanceTime } from '../utils/time' import { getTrade } from '../utils/trades' import { setOraclePrice } from '../utils/oracles' import { @@ -226,6 +227,10 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, it('should change basket around cETH', async () => { await token0.setExchangeRate(fp('0.99')) // default await basketHandler.refreshBasket() + + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + await expect(backingManager.manageTokens([token0.address, cETH.address])).to.emit( backingManager, 'TradeStarted' @@ -302,6 +307,9 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, await cETH.setExchangeRate(fp('0.99')) await basketHandler.refreshBasket() + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + // Should swap WETH in for cETH const [tokens] = await basketHandler.quote(fp('1'), 2) expect(tokens[0]).to.equal(token0.address) diff --git a/test/scenario/cWBTC.test.ts b/test/scenario/cWBTC.test.ts index 95a4e76f37..4427d595df 100644 --- a/test/scenario/cWBTC.test.ts +++ b/test/scenario/cWBTC.test.ts @@ -239,6 +239,10 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => it('should change basket around cWBTC', async () => { await token0.setExchangeRate(fp('0.99')) // default await basketHandler.refreshBasket() + + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + await expect(backingManager.manageTokens([token0.address, cWBTC.address])).to.emit( backingManager, 'TradeStarted' @@ -331,6 +335,9 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => await cWBTC.setExchangeRate(fp('0.99')) await basketHandler.refreshBasket() + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + // Should swap WBTC in for cWBTC const [tokens] = await basketHandler.quote(fp('1'), 2) expect(tokens[0]).to.equal(token0.address) From 5a3137b591993a5b7d7612954193d4bfd7217bfb Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 13 Apr 2023 10:20:41 -0300 Subject: [PATCH 041/462] fix scenario tests --- test/scenario/BadERC20.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scenario/BadERC20.test.ts b/test/scenario/BadERC20.test.ts index 8893f157cc..f9ba9e8762 100644 --- a/test/scenario/BadERC20.test.ts +++ b/test/scenario/BadERC20.test.ts @@ -244,7 +244,7 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { await furnace.melt() }) - it.only('should be able to unregister and use RSR to recollateralize', async () => { + it('should be able to unregister and use RSR to recollateralize', async () => { await assetRegistry.connect(owner).unregister(collateral0.address) expect(await assetRegistry.isRegistered(collateral0.address)).to.equal(false) await expect(basketHandler.refreshBasket()) From b6533ad81229c2427edd2f124ba95038bcab6a88 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 13 Apr 2023 10:33:47 -0300 Subject: [PATCH 042/462] fix scenario tests --- test/scenario/ComplexBasket.test.ts | 6 ++++++ test/scenario/MaxBasketSize.test.ts | 3 +++ test/scenario/RevenueHiding.test.ts | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/test/scenario/ComplexBasket.test.ts b/test/scenario/ComplexBasket.test.ts index 8a05edc76d..6aac8837ed 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -1324,6 +1324,9 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // Ensure valid basket await basketHandler.refreshBasket() + // Advance time post warmup period + await advanceTime(Number(config.warmupPeriod) + 1) + const [, newQuotes] = await facade.connect(addr1).callStatic.issue(rToken.address, issueAmount) const newExpectedTkn4: BigNumber = issueAmount .mul(targetAmts[4].add(targetAmts[5])) @@ -1549,6 +1552,9 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // Ensure valid basket await basketHandler.refreshBasket() + // Advance time post warmup period + await advanceTime(Number(config.warmupPeriod) + 1) + const [, newQuotes] = await facade.connect(addr1).callStatic.issue(rToken.address, issueAmount) const newExpectedTkn6: BigNumber = issueAmount .mul(targetAmts[6].add(targetAmts[7])) diff --git a/test/scenario/MaxBasketSize.test.ts b/test/scenario/MaxBasketSize.test.ts index 7600af417e..1bb50d0ac1 100644 --- a/test/scenario/MaxBasketSize.test.ts +++ b/test/scenario/MaxBasketSize.test.ts @@ -510,6 +510,9 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { await basketHandler.refreshBasket() } + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + // Check new basket expect(await basketHandler.fullyCollateralized()).to.equal(false) const newBacking: string[] = await facade.basketTokens(rToken.address) diff --git a/test/scenario/RevenueHiding.test.ts b/test/scenario/RevenueHiding.test.ts index 8dc5b7f6c3..4eecf8a61a 100644 --- a/test/scenario/RevenueHiding.test.ts +++ b/test/scenario/RevenueHiding.test.ts @@ -19,6 +19,7 @@ import { TestIRevenueTrader, TestIRToken, } from '../../typechain' +import { advanceTime } from '../utils/time' import { getTrade } from '../utils/trades' import { Collateral, @@ -138,6 +139,9 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME .setBackupConfig(await ethers.utils.formatBytes32String('USD'), 1, [dai.address]) await basketHandler.refreshBasket() + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + await backingManager.grantRTokenAllowance(cDAI.address) await backingManager.grantRTokenAllowance(dai.address) From 3e565ca1615ca8e8ca46df83ef08dc1f42fb2967 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 13 Apr 2023 10:59:57 -0300 Subject: [PATCH 043/462] revert on trading delay --- contracts/p0/BackingManager.sol | 2 +- contracts/p1/BackingManager.sol | 2 +- test/Recollateralization.test.ts | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index b63f577c9a..3182c75b32 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -76,7 +76,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { require(main.basketHandler().isReady(), "basket not ready"); uint48 basketTimestamp = main.basketHandler().timestamp(); - if (block.timestamp < basketTimestamp + tradingDelay) return; + require(block.timestamp >= basketTimestamp + tradingDelay, "trading delayed"); BasketRange memory basketsHeld = main.basketHandler().basketsHeldBy(address(this)); diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 24d6caff2a..4b42d535d0 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -112,7 +112,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { require(basketHandler.isReady(), "basket not ready"); uint48 basketTimestamp = basketHandler.timestamp(); - if (block.timestamp < basketTimestamp + tradingDelay) return; + require(block.timestamp >= basketTimestamp + tradingDelay, "trading delayed"); BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(this)); uint192 basketsNeeded = rToken.basketsNeeded(); // {BU} diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 7e2fd21582..34522af036 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -947,10 +947,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) - // Attempt to trigger before trading delay - will not open auction - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.not.emit( - backingManager, - 'TradeStarted' + // Attempt to trigger before trading delay - Should revert + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.be.revertedWith( + 'trading delayed' ) // Advance time post trading delay From 006310e5da8e75cf3d573559a328863c7d4d5561 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 13 Apr 2023 20:26:56 -0400 Subject: [PATCH 044/462] final nits before merge --- contracts/interfaces/IBasketHandler.sol | 1 - contracts/p0/BasketHandler.sol | 23 ++++++++++++----------- contracts/p1/BasketHandler.sol | 23 ++++++++++++----------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 1575ac9f21..811079e3a0 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -89,7 +89,6 @@ interface IBasketHandler is IComponent { function status() external view returns (CollateralStatus status); /// Track the basket status changes - /// @custom:protected function trackStatus() external; /// @return If the basket is ready to issue and trade diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 3f19b77f4e..c6e29bd4cc 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -199,6 +199,18 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { trackStatus(); } + /// Track basket status changes if they ocurred + // effects: lastStatus' = status(), and lastStatusTimestamp' = current timestamp + /// @custom:refresher + function trackStatus() public { + CollateralStatus currentStatus = status(); + if (currentStatus != lastStatus) { + emit BasketStatusChanged(lastStatus, currentStatus); + lastStatus = currentStatus; + lastStatusTimestamp = uint48(block.timestamp); + } + } + /// Set the prime basket in the basket configuration, in terms of erc20s and target amounts /// @param erc20s The collateral for the new prime basket /// @param targetAmts The target amounts (in) {target/BU} for the new prime basket @@ -298,17 +310,6 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } } - /// Track basket status changes if they ocurred - // effects: lastStatus' = status(), and lastStatusTimestamp' = current timestamp - function trackStatus() public { - CollateralStatus currentStatus = status(); - if (currentStatus != lastStatus) { - emit BasketStatusChanged(lastStatus, currentStatus); - lastStatus = currentStatus; - lastStatusTimestamp = uint48(block.timestamp); - } - } - /// @return Whether the basket is ready to issue and trade function isReady() external view returns (bool) { return diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 4046ed4f68..96b3f44213 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -217,6 +217,18 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { trackStatus(); } + /// Track basket status changes if they ocurred + // effects: lastStatus' = status(), and lastStatusTimestamp' = current timestamp + /// @custom:refresher + function trackStatus() public { + CollateralStatus currentStatus = status(); + if (currentStatus != lastStatus) { + emit BasketStatusChanged(lastStatus, currentStatus); + lastStatus = currentStatus; + lastStatusTimestamp = uint48(block.timestamp); + } + } + /// Set the prime basket in the basket configuration, in terms of erc20s and target amounts /// @param erc20s The collateral for the new prime basket /// @param targetAmts The target amounts (in) {target/BU} for the new prime basket @@ -316,17 +328,6 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } } - /// Track basket status changes if they ocurred - // effects: lastStatus' = status(), and lastStatusTimestamp' = current timestamp - function trackStatus() public { - CollateralStatus currentStatus = status(); - if (currentStatus != lastStatus) { - emit BasketStatusChanged(lastStatus, currentStatus); - lastStatus = currentStatus; - lastStatusTimestamp = uint48(block.timestamp); - } - } - /// @return Whether the basket is ready to issue and trade function isReady() external view returns (bool) { return From 737f26496ca6f7d2965799a4069df1aec1ace98e Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 13 Apr 2023 20:29:59 -0400 Subject: [PATCH 045/462] organize IBasketHandler too --- contracts/interfaces/IBasketHandler.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 811079e3a0..4ceb8e9ff1 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -82,15 +82,16 @@ interface IBasketHandler is IComponent { /// @custom:interaction function refreshBasket() external; + /// Track the basket status changes + /// @custom:refresher + function trackStatus() external; + /// @return If the BackingManager has sufficient collateral to redeem the entire RToken supply function fullyCollateralized() external view returns (bool); /// @return status The worst CollateralStatus of all collateral in the basket function status() external view returns (CollateralStatus status); - /// Track the basket status changes - function trackStatus() external; - /// @return If the basket is ready to issue and trade function isReady() external view returns (bool); From c6d89beacc08d165c7f04d6c0c110dc366ba97e4 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 13 Apr 2023 20:35:18 -0400 Subject: [PATCH 046/462] remove last mention of RTokenOracle --- contracts/plugins/assets/RTokenAsset.sol | 1 - tasks/deployment/deploy-rtoken-oracle.ts | 59 ------------------------ 2 files changed, 60 deletions(-) delete mode 100644 tasks/deployment/deploy-rtoken-oracle.ts diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index e8bdebb817..1ad1a28bce 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.17; import "../../p1/mixins/RecollateralizationLib.sol"; import "../../interfaces/IMain.sol"; import "../../interfaces/IRToken.sol"; -import "../../interfaces/IRTokenOracle.sol"; import "./Asset.sol"; /// Once an RToken gets large enough to get a price feed, replacing this asset with diff --git a/tasks/deployment/deploy-rtoken-oracle.ts b/tasks/deployment/deploy-rtoken-oracle.ts deleted file mode 100644 index 7400656bd2..0000000000 --- a/tasks/deployment/deploy-rtoken-oracle.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { getChainId } from '../../common/blockchain-utils' -import { task, types } from 'hardhat/config' -import { RTokenOracle } from '../../typechain' - -export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' - -task('deploy-rtoken-oracle', 'Deploys an RTokenOracle') - .addParam('cacheTimeout', 'The length of the cache timeout in seconds') - .addOptionalParam('noOutput', 'Suppress output', false, types.boolean) - .setAction(async (params, hre) => { - const [wallet] = await hre.ethers.getSigners() - - const chainId = await getChainId(hre) - - if (!params.noOutput) { - console.log( - `Deploying RTokenOracle to ${hre.network.name} (${chainId}) with burner account ${wallet.address}` - ) - } - - const RTokenOracleFactory = await hre.ethers.getContractFactory('RTokenOracle') - const rTokenOracle = ( - await RTokenOracleFactory.connect(wallet).deploy(params.cacheTimeout) - ) - await rTokenOracle.deployed() - - if (!params.noOutput) { - console.log( - `Deployed RTokenOracle to ${hre.network.name} (${chainId}): ${rTokenOracle.address}` - ) - } - - // Uncomment to verify - if (!params.noOutput) { - console.log('sleeping 30s') - } - - // Sleep to ensure API is in sync with chain - await new Promise((r) => setTimeout(r, 30000)) // 30s - - if (!params.noOutput) { - console.log('verifying') - } - - /** ******************** Verify RTokenOracle ****************************************/ - console.time('Verifying RTokenOracle') - await hre.run('verify:verify', { - address: rTokenOracle.address, - constructorArguments: [params.cacheTimeout], - contract: 'contracts/p1/RTokenOracle.sol:RTokenOracle', - }) - console.timeEnd('Verifying RTokenOracle') - - if (!params.noOutput) { - console.log('verified') - } - - return { rTokenOracle: rTokenOracle.address } - }) From f122c9322f5133b0a5adbff52548564382cbd146 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 13 Apr 2023 20:39:24 -0400 Subject: [PATCH 047/462] compile --- tasks/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tasks/index.ts b/tasks/index.ts index d970456c84..2bd595a962 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -15,7 +15,6 @@ import './deployment/mock/deploy-mock-usdc' import './deployment/mock/deploy-mock-aave' import './deployment/mock/deploy-mock-wbtc' import './deployment/create-deployer-registry' -import './deployment/deploy-rtoken-oracle' import './deployment/empty-wallet' import './deployment/cancel-tx' import './deployment/sign-msg' From bfb2096b385b5af429be9a42f2b632b483808f9d Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Fri, 14 Apr 2023 10:24:07 -0300 Subject: [PATCH 048/462] split pausing in contracts --- contracts/facade/FacadeWrite.sol | 8 +-- contracts/interfaces/IMain.sol | 34 +++++++++---- contracts/mixins/Auth.sol | 51 ++++++++++++++----- contracts/p0/BackingManager.sol | 4 +- contracts/p0/BasketHandler.sol | 2 +- contracts/p0/Broker.sol | 4 +- contracts/p0/Distributor.sol | 2 +- contracts/p0/Furnace.sol | 2 +- contracts/p0/Main.sol | 2 +- contracts/p0/RToken.sol | 8 +-- contracts/p0/RevenueTrader.sol | 2 +- contracts/p0/StRSR.sol | 6 +-- contracts/p0/mixins/Component.sol | 10 +++- contracts/p0/mixins/Rewardable.sol | 4 +- contracts/p0/mixins/Trading.sol | 2 +- contracts/p1/BackingManager.sol | 4 +- contracts/p1/BasketHandler.sol | 2 +- contracts/p1/Broker.sol | 8 +-- contracts/p1/Distributor.sol | 2 +- contracts/p1/Furnace.sol | 2 +- contracts/p1/Main.sol | 2 +- contracts/p1/RToken.sol | 12 ++--- contracts/p1/RevenueTrader.sol | 4 +- contracts/p1/StRSR.sol | 12 ++--- contracts/p1/mixins/Component.sol | 9 +++- contracts/p1/mixins/Trading.sol | 8 +-- contracts/plugins/mocks/InvalidBrokerMock.sol | 2 +- contracts/plugins/mocks/upgrades/MainV2.sol | 2 +- 28 files changed, 130 insertions(+), 80 deletions(-) diff --git a/contracts/facade/FacadeWrite.sol b/contracts/facade/FacadeWrite.sol index 57da1183d5..9ccafd7b98 100644 --- a/contracts/facade/FacadeWrite.sol +++ b/contracts/facade/FacadeWrite.sol @@ -111,8 +111,9 @@ contract FacadeWrite is IFacadeWrite { } // Pause until setupGovernance - main.pause(); - + main.tradingPause(); + main.issuancePause(); + // Setup deployer as owner to complete next step - do not renounce roles yet main.grantRole(OWNER, msg.sender); @@ -189,7 +190,8 @@ contract FacadeWrite is IFacadeWrite { // Unpause if required if (unpause) { - main.unpause(); + main.tradingUnpause(); + main.issuanceUnpause(); } // Transfer Ownership and renounce roles diff --git a/contracts/interfaces/IMain.sol b/contracts/interfaces/IMain.sol index 5a7bc25abb..aa0a192f6a 100644 --- a/contracts/interfaces/IMain.sol +++ b/contracts/interfaces/IMain.sol @@ -61,18 +61,26 @@ interface IAuth is IAccessControlUpgradeable { /// @param newDuration The new long freeze duration event LongFreezeDurationSet(uint48 indexed oldDuration, uint48 indexed newDuration); - /// Emitted when the system is paused or unpaused - /// @param oldVal The old value of `paused` - /// @param newVal The new value of `paused` - event PausedSet(bool indexed oldVal, bool indexed newVal); + /// Emitted when the system is paused or unpaused for trading + /// @param oldVal The old value of `tradingPaused` + /// @param newVal The new value of `tradingPaused` + event TradingPausedSet(bool indexed oldVal, bool indexed newVal); + + /// Emitted when the system is paused or unpaused for issuance + /// @param oldVal The old value of `issuancePaused` + /// @param newVal The new value of `issuancePaused` + event IssuancePausedSet(bool indexed oldVal, bool indexed newVal); /** - * Paused: Disable everything except for OWNER actions, RToken.redeem, StRSR.stake, - * and StRSR.payoutRewards + * Trading Paused: Disable everything except for OWNER actions, RToken.issue, RToken.redeem, + * StRSR.stake, and StRSR.payoutRewards + * Issuance Paused: Disable RToken.issue * Frozen: Disable everything except for OWNER actions + StRSR.stake (for governance) */ - function pausedOrFrozen() external view returns (bool); + function tradingPausedOrFrozen() external view returns (bool); + + function issuancePausedOrFrozen() external view returns (bool); function frozen() external view returns (bool); @@ -94,9 +102,13 @@ interface IAuth is IAccessControlUpgradeable { // onlyRole(OWNER) function unfreeze() external; - function pause() external; + function tradingPause() external; + + function tradingUnpause() external; - function unpause() external; + function issuancePause() external; + + function issuanceUnpause() external; } interface IComponentRegistry { @@ -177,5 +189,7 @@ interface TestIMain is IMain { function longFreezes(address account) external view returns (uint256); - function paused() external view returns (bool); + function tradingPaused() external view returns (bool); + + function issuancePaused() external view returns (bool); } diff --git a/contracts/mixins/Auth.sol b/contracts/mixins/Auth.sol index f936b9a762..7de6862ae6 100644 --- a/contracts/mixins/Auth.sol +++ b/contracts/mixins/Auth.sol @@ -17,7 +17,9 @@ abstract contract Auth is AccessControlUpgradeable, IAuth { /** * System-wide states (does not impact ERC20 functions) * - Frozen: only allow OWNER actions and staking - * - Paused: only allow OWNER actions, redemption, staking, and rewards payout + * - Trading Paused: only allow OWNER actions, issuance, redemption, staking, + * and rewards payout + * - Issuance Paused: disallow issuance * * Typically freezing thaws on its own in a predetemined number of blocks. * However, OWNER can also freeze forever. @@ -39,8 +41,9 @@ abstract contract Auth is AccessControlUpgradeable, IAuth { // === Pausing === - bool public paused; - + bool public tradingPaused; + bool public issuancePaused; + /* ==== Invariants ==== 0 <= longFreeze[a] <= LONG_FREEZE_CHARGES for all addrs a set{a has LONG_FREEZER} == set{a : longFreeze[a] == 0} @@ -103,9 +106,15 @@ abstract contract Auth is AccessControlUpgradeable, IAuth { } /// @dev This -or- condition is a performance optimization for the consuming Component - // returns: bool(main is frozen or paused) == paused || (now < unfreezeAt) - function pausedOrFrozen() public view returns (bool) { - return paused || block.timestamp < unfreezeAt; + // returns: bool(main is frozen or tradingPaused) == tradingPaused || (now < unfreezeAt) + function tradingPausedOrFrozen() public view returns (bool) { + return tradingPaused || block.timestamp < unfreezeAt; + } + + /// @dev This -or- condition is a performance optimization for the consuming Component + // returns: bool(main is frozen or issuancePaused) == issuancePaused || (now < unfreezeAt) + function issuancePausedOrFrozen() public view returns (bool) { + return issuancePaused || block.timestamp < unfreezeAt; } // === Freezing === @@ -161,17 +170,31 @@ abstract contract Auth is AccessControlUpgradeable, IAuth { // === Pausing === // checks: caller has PAUSER - // effects: paused' = true - function pause() external onlyRole(PAUSER) { - emit PausedSet(paused, true); - paused = true; + // effects: tradingPaused' = true + function tradingPause() external onlyRole(PAUSER) { + emit TradingPausedSet(tradingPaused, true); + tradingPaused = true; + } + + // checks: caller has PAUSER + // effects: tradingPaused' = false + function tradingUnpause() external onlyRole(PAUSER) { + emit TradingPausedSet(tradingPaused, false); + tradingPaused = false; + } + + // checks: caller has PAUSER + // effects: issuancePaused' = true + function issuancePause() external onlyRole(PAUSER) { + emit IssuancePausedSet(issuancePaused, true); + issuancePaused = true; } // checks: caller has PAUSER - // effects: paused' = false - function unpause() external onlyRole(PAUSER) { - emit PausedSet(paused, false); - paused = false; + // effects: issuancePaused' = false + function issuanceUnpause() external onlyRole(PAUSER) { + emit IssuancePausedSet(issuancePaused, false); + issuancePaused = false; } // === Gov params === diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index dc09d81f4e..933feb5066 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -50,7 +50,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { /// Maintain the overall backing policy; handout assets otherwise /// @custom:interaction - function manageTokens(IERC20[] calldata erc20s) external notPausedOrFrozen { + function manageTokens(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { // Token list must not contain duplicates require(ArrayLib.allUnique(erc20s), "duplicate tokens"); _manageTokens(erc20s); @@ -60,7 +60,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { /// @dev Tokens must be in sorted order! /// @dev Performs a uniqueness check on the erc20s list in O(n) /// @custom:interaction - function manageTokensSortedOrder(IERC20[] calldata erc20s) external notPausedOrFrozen { + function manageTokensSortedOrder(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { // Token list must not contain duplicates require(ArrayLib.sortedAndAllUnique(erc20s), "duplicate/unsorted tokens"); _manageTokens(erc20s); diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 5c87abea64..fd27a82e9c 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -170,7 +170,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { require( main.hasRole(OWNER, _msgSender()) || - (status() == CollateralStatus.DISABLED && !main.pausedOrFrozen()), + (status() == CollateralStatus.DISABLED && !main.tradingPausedOrFrozen()), "basket unrefreshable" ); _switchBasket(); diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 9f4ec169e8..9eb5e3ee85 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -50,7 +50,7 @@ contract BrokerP0 is ComponentP0, IBroker { /// Handle a trade request by deploying a customized disposable trading contract /// @dev Requires setting an allowance in advance /// @custom:protected - function openTrade(TradeRequest memory req) external notPausedOrFrozen returns (ITrade) { + function openTrade(TradeRequest memory req) external notTradingPausedOrFrozen returns (ITrade) { require(!disabled, "broker disabled"); assert(req.sellAmount > 0); @@ -77,7 +77,7 @@ contract BrokerP0 is ComponentP0, IBroker { /// Disable the broker until re-enabled by governance /// @custom:protected - function reportViolation() external notPausedOrFrozen { + function reportViolation() external notTradingPausedOrFrozen { require(trades[_msgSender()], "unrecognized trade contract"); emit DisabledSet(disabled, true); disabled = true; diff --git a/contracts/p0/Distributor.sol b/contracts/p0/Distributor.sol index cce0b540da..3eaae7dffb 100644 --- a/contracts/p0/Distributor.sol +++ b/contracts/p0/Distributor.sol @@ -41,7 +41,7 @@ contract DistributorP0 is ComponentP0, IDistributor { /// Distribute revenue, in rsr or rtoken, per the distribution table. /// Requires that this contract has an allowance of at least /// `amount` tokens, from `from`, of the token at `erc20`. - function distribute(IERC20 erc20, uint256 amount) external notPausedOrFrozen { + function distribute(IERC20 erc20, uint256 amount) external notTradingPausedOrFrozen { IERC20 rsr = main.rsr(); require(erc20 == rsr || erc20 == IERC20(address(main.rToken())), "RSR or RToken"); diff --git a/contracts/p0/Furnace.sol b/contracts/p0/Furnace.sol index 3e8ea53e56..7c70de1126 100644 --- a/contracts/p0/Furnace.sol +++ b/contracts/p0/Furnace.sol @@ -30,7 +30,7 @@ contract FurnaceP0 is ComponentP0, IFurnace { /// Performs any melting that has vested since last call. /// @custom:refresher - function melt() public notPausedOrFrozen { + function melt() public notTradingPausedOrFrozen { if (uint48(block.timestamp) < uint64(lastPayout) + PERIOD) return; // # of whole periods that have passed since lastPayout diff --git a/contracts/p0/Main.sol b/contracts/p0/Main.sol index 917d019ee9..12d85514cf 100644 --- a/contracts/p0/Main.sol +++ b/contracts/p0/Main.sol @@ -37,7 +37,7 @@ contract MainP0 is Versioned, Initializable, Auth, ComponentRegistry, IMain { /// @custom:refresher function poke() external { - require(!pausedOrFrozen(), "paused or frozen"); + require(!tradingPausedOrFrozen(), "frozen or trading paused"); assetRegistry.refresh(); furnace.melt(); stRSR.payoutRewards(); diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 24675ff363..acb124c432 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -94,7 +94,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// @custom:interaction function issueTo(address recipient, uint256 amount) public - notPausedOrFrozen + notIssuancePausedOrFrozen exchangeRateIsValidAfter { require(amount > 0, "Cannot issue zero"); @@ -207,7 +207,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// @custom:protected function mint(address recipient, uint256 amount) external - notPausedOrFrozen + notTradingPausedOrFrozen exchangeRateIsValidAfter { require(_msgSender() == address(main.backingManager()), "not backing manager"); @@ -226,7 +226,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// @custom:protected function setBasketsNeeded(uint192 basketsNeeded_) external - notPausedOrFrozen + notTradingPausedOrFrozen exchangeRateIsValidAfter { require(_msgSender() == address(main.backingManager()), "not backing manager"); @@ -236,7 +236,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// Sends all token balance of erc20 (if it is registered) to the BackingManager /// @custom:interaction - function monetizeDonations(IERC20 erc20) external notPausedOrFrozen { + function monetizeDonations(IERC20 erc20) external notTradingPausedOrFrozen { require(main.assetRegistry().isRegistered(erc20), "erc20 unregistered"); erc20.safeTransfer(address(main.backingManager()), erc20.balanceOf(address(this))); } diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index bd316ed99c..fb06ece1e4 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -31,7 +31,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { /// Processes a single token; unpermissioned /// @dev Intended to be used with multicall /// @custom:interaction - function manageToken(IERC20 erc20) external notPausedOrFrozen { + function manageToken(IERC20 erc20) external notTradingPausedOrFrozen { if (address(trades[erc20]) != address(0)) return; uint256 bal = erc20.balanceOf(address(this)); diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index fc62d2fd12..da35b5ecfd 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -155,7 +155,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { /// Begins a delayed unstaking for `amount` stRSR /// @param stakeAmount {qStRSR} /// @custom:interaction - function unstake(uint256 stakeAmount) external notPausedOrFrozen { + function unstake(uint256 stakeAmount) external notTradingPausedOrFrozen { address account = _msgSender(); require(stakeAmount > 0, "Cannot withdraw zero"); require(balances[account] >= stakeAmount, "Not enough balance"); @@ -186,7 +186,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { /// Complete delayed staking for an account, up to but not including draft ID `endId` /// @custom:interaction - function withdraw(address account, uint256 endId) external notPausedOrFrozen { + function withdraw(address account, uint256 endId) external notTradingPausedOrFrozen { // Call state keepers main.poke(); @@ -230,7 +230,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { /// seizedRSR might be dust-larger than rsrAmount due to rounding. /// seizedRSR will _not_ be smaller than rsrAmount. /// @custom:protected - function seizeRSR(uint256 rsrAmount) external notPausedOrFrozen { + function seizeRSR(uint256 rsrAmount) external notTradingPausedOrFrozen { require(_msgSender() == address(main.backingManager()), "not backing manager"); require(rsrAmount > 0, "Amount cannot be zero"); main.poke(); diff --git a/contracts/p0/mixins/Component.sol b/contracts/p0/mixins/Component.sol index 91b27172fd..cc484b5090 100644 --- a/contracts/p0/mixins/Component.sol +++ b/contracts/p0/mixins/Component.sol @@ -25,10 +25,16 @@ abstract contract ComponentP0 is Versioned, Initializable, ContextUpgradeable, I // === See docs/security.md === - modifier notPausedOrFrozen() { - require(!main.pausedOrFrozen(), "paused or frozen"); + modifier notTradingPausedOrFrozen() { + require(!main.tradingPausedOrFrozen(), "frozen or trading paused"); _; } + + modifier notIssuancePausedOrFrozen() { + require(!main.issuancePausedOrFrozen(), "frozen or issuance paused"); + _; + } + modifier notFrozen() { require(!main.frozen(), "frozen"); _; diff --git a/contracts/p0/mixins/Rewardable.sol b/contracts/p0/mixins/Rewardable.sol index ae40dc67f8..ade8b07a4b 100644 --- a/contracts/p0/mixins/Rewardable.sol +++ b/contracts/p0/mixins/Rewardable.sol @@ -14,7 +14,7 @@ abstract contract RewardableP0 is ComponentP0, IRewardableComponent { /// Claim all rewards /// Collective Action - function claimRewards() external notPausedOrFrozen { + function claimRewards() external notTradingPausedOrFrozen { IAssetRegistry reg = main.assetRegistry(); IERC20[] memory erc20s = reg.erc20s(); @@ -33,7 +33,7 @@ abstract contract RewardableP0 is ComponentP0, IRewardableComponent { /// Collective Action /// @param erc20 The ERC20 to claimRewards on /// @custom:interaction CEI - function claimRewardsSingle(IERC20 erc20) external notPausedOrFrozen { + function claimRewardsSingle(IERC20 erc20) external notTradingPausedOrFrozen { IAsset asset = main.assetRegistry().toAsset(erc20); // Claim rewards via delegatecall diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index ba0e12e4e5..3f6c27494a 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -41,7 +41,7 @@ abstract contract TradingP0 is RewardableP0, ITrading { /// Settle a single trade, expected to be used with multicall for efficient mass settlement /// @custom:interaction - function settleTrade(IERC20 sell) public notPausedOrFrozen { + function settleTrade(IERC20 sell) public notTradingPausedOrFrozen { ITrade trade = trades[sell]; if (address(trade) == address(0)) return; require(trade.canSettle(), "cannot settle yet"); diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 3e3e12a34e..703bde6f2a 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -80,7 +80,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// @custom:interaction // checks: the addresses in `erc20s` are unique // effect: _manageTokens(erc20s) - function manageTokens(IERC20[] calldata erc20s) external notPausedOrFrozen { + function manageTokens(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { // Token list must not contain duplicates require(ArrayLib.allUnique(erc20s), "duplicate tokens"); _manageTokens(erc20s); @@ -92,7 +92,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// @custom:interaction // checks: the addresses in `erc20s` are unique (and sorted) // effect: _manageTokens(erc20s) - function manageTokensSortedOrder(IERC20[] calldata erc20s) external notPausedOrFrozen { + function manageTokensSortedOrder(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { // Token list must not contain duplicates require(ArrayLib.sortedAndAllUnique(erc20s), "duplicate/unsorted tokens"); _manageTokens(erc20s); diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index e5135fa6ad..a18c5624fb 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -188,7 +188,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { require( main.hasRole(OWNER, _msgSender()) || - (status() == CollateralStatus.DISABLED && !main.pausedOrFrozen()), + (status() == CollateralStatus.DISABLED && !main.tradingPausedOrFrozen()), "basket unrefreshable" ); _switchBasket(); diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 41b5ef19ab..36c57a2695 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -68,7 +68,7 @@ contract BrokerP1 is ComponentP1, IBroker { /// @dev Requires setting an allowance in advance /// @custom:interaction CEI // checks: - // not disabled, paused, or frozen + // not disabled, paused (trading), or frozen // caller is a system Trader // effects: // Deploys a new trade clone, `trade` @@ -76,7 +76,7 @@ contract BrokerP1 is ComponentP1, IBroker { // actions: // Transfers req.sellAmount of req.sell.erc20 from caller to `trade` // Calls trade.init() with appropriate parameters - function openTrade(TradeRequest memory req) external notPausedOrFrozen returns (ITrade) { + function openTrade(TradeRequest memory req) external notTradingPausedOrFrozen returns (ITrade) { require(!disabled, "broker disabled"); address caller = _msgSender(); @@ -113,9 +113,9 @@ contract BrokerP1 is ComponentP1, IBroker { /// Disable the broker until re-enabled by governance /// @custom:protected - // checks: not paused, not frozen, caller is a Trade this contract cloned + // checks: not paused (trading), not frozen, caller is a Trade this contract cloned // effects: disabled' = true - function reportViolation() external notPausedOrFrozen { + function reportViolation() external notTradingPausedOrFrozen { require(trades[_msgSender()], "unrecognized trade contract"); emit DisabledSet(disabled, true); disabled = true; diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index cf8b318457..8dccce968e 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -84,7 +84,7 @@ contract DistributorP1 is ComponentP1, IDistributor { // actions: // for dest where w[dest] != 0: // erc20.transferFrom(from, addrOf(dest), tokensPerShare * w[dest]) - function distribute(IERC20 erc20, uint256 amount) external notPausedOrFrozen { + function distribute(IERC20 erc20, uint256 amount) external notTradingPausedOrFrozen { require(erc20 == rsr || erc20 == rToken, "RSR or RToken"); bool isRSR = erc20 == rsr; // if false: isRToken uint256 tokensPerShare; diff --git a/contracts/p1/Furnace.sol b/contracts/p1/Furnace.sol index e17db782e1..e6e960b84a 100644 --- a/contracts/p1/Furnace.sol +++ b/contracts/p1/Furnace.sol @@ -63,7 +63,7 @@ contract FurnaceP1 is ComponentP1, IFurnace { // actions: // rToken.melt(payoutAmount), paying payoutAmount to RToken holders - function melt() external notPausedOrFrozen { + function melt() external notTradingPausedOrFrozen { if (uint48(block.timestamp) < uint64(lastPayout) + PERIOD) return; // # of whole periods that have passed since lastPayout diff --git a/contracts/p1/Main.sol b/contracts/p1/Main.sol index 68ba842c57..aac47a98a2 100644 --- a/contracts/p1/Main.sol +++ b/contracts/p1/Main.sol @@ -45,7 +45,7 @@ contract MainP1 is Versioned, Initializable, Auth, ComponentRegistry, UUPSUpgrad assetRegistry.refresh(); // == CE block == - require(!pausedOrFrozen(), "paused or frozen"); + require(!tradingPausedOrFrozen(), "frozen or trading paused"); furnace.melt(); stRSR.payoutRewards(); } diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index b71216860f..2541e49e6e 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -118,7 +118,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. function issueTo(address recipient, uint256 amount) public - notPausedOrFrozen + notIssuancePausedOrFrozen exchangeRateIsValidAfter { require(amount > 0, "Cannot issue zero"); @@ -299,7 +299,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // but it is fully covered for `mint` (limitations of coverage plugin) function mint(address recipient, uint256 amtRToken) external - notPausedOrFrozen + notTradingPausedOrFrozen exchangeRateIsValidAfter { require(_msgSender() == address(backingManager), "not backing manager"); @@ -308,7 +308,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// Melt a quantity of RToken from the caller's account, increasing the basket rate /// @param amtRToken {qRTok} The amtRToken to be melted - // checks: not paused or frozen + // checks: not trading paused or frozen // effects: // bal'[caller] = bal[caller] - amtRToken // totalSupply' = totalSupply - amtRToken @@ -324,7 +324,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// An affordance of last resort for Main in order to ensure re-capitalization /// @custom:protected - // checks: unpaused; unfrozen; caller is backingManager + // checks: trading unpaused; unfrozen; caller is backingManager // effects: basketsNeeded' = basketsNeeded_ // // untestable: @@ -332,7 +332,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // but it is fully covered for `setBasketsNeeded` (limitations of coverage plugin) function setBasketsNeeded(uint192 basketsNeeded_) external - notPausedOrFrozen + notTradingPausedOrFrozen exchangeRateIsValidAfter { require(_msgSender() == address(backingManager), "not backing manager"); @@ -342,7 +342,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// Sends all token balance of erc20 (if it is registered) to the BackingManager /// @custom:interaction - function monetizeDonations(IERC20 erc20) external notPausedOrFrozen { + function monetizeDonations(IERC20 erc20) external notTradingPausedOrFrozen { require(assetRegistry.isRegistered(erc20), "erc20 unregistered"); IERC20Upgradeable(address(erc20)).safeTransfer( address(backingManager), diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 9fa8ce696c..28c09281a7 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -38,7 +38,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { /// @dev Intended to be used with multicall /// @custom:interaction CEI // let bal = this contract's balance of erc20 - // checks: !paused, !frozen + // checks: !paused (trading), !frozen // does nothing if erc20 == addr(0) or bal == 0 // // If erc20 is tokenToBuy: @@ -50,7 +50,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { // actions: // tryTrade(prepareTradeSell(toAsset(erc20), toAsset(tokenToBuy), bal)) // (i.e, start a trade, selling as much of our bal of erc20 as we can, to buy tokenToBuy) - function manageToken(IERC20 erc20) external notPausedOrFrozen { + function manageToken(IERC20 erc20) external notTradingPausedOrFrozen { if (address(trades[erc20]) != address(0)) return; uint256 bal = erc20.balanceOf(address(this)); diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 5702c20aea..7dd8249faa 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -238,7 +238,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab /// Begins a delayed unstaking for `amount` StRSR /// @param stakeAmount {qStRSR} // checks: - // not paused or frozen + // not paused (trading) or frozen // 0 < stakeAmount <= bal[caller] // // effects: @@ -253,7 +253,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // // A draft for (totalDrafts' - totalDrafts) drafts // is freshly appended to the caller's draft record. - function unstake(uint256 stakeAmount) external notPausedOrFrozen { + function unstake(uint256 stakeAmount) external notTradingPausedOrFrozen { address account = _msgSender(); require(stakeAmount > 0, "Cannot withdraw zero"); require(stakes[era][account] >= stakeAmount, "Not enough balance"); @@ -283,7 +283,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // // checks: // RToken is fully collateralized and the basket is sound. - // The system is not paused or frozen. + // The system is not paused (trading) or frozen. // endId <= r.right // r.queue[endId - 1].availableAt <= now // @@ -296,7 +296,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // // actions: // rsr.transfer(account, rsrOut) - function withdraw(address account, uint256 endId) external notPausedOrFrozen { + function withdraw(address account, uint256 endId) external notTradingPausedOrFrozen { // == Refresh == assetRegistry.refresh(); @@ -343,7 +343,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // // checks: // 0 < rsrAmount <= rsr.balanceOf(this) - // not paused or frozen + // not paused (trading) or frozen // caller is backingManager // // effects, in two phases. Phase 1: (from x to x') @@ -370,7 +370,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // other properties: // seized >= rsrAmount, which should be a logical consequence of the above effects - function seizeRSR(uint256 rsrAmount) external notPausedOrFrozen { + function seizeRSR(uint256 rsrAmount) external notTradingPausedOrFrozen { require(_msgSender() == address(backingManager), "not backing manager"); require(rsrAmount > 0, "Amount cannot be zero"); diff --git a/contracts/p1/mixins/Component.sol b/contracts/p1/mixins/Component.sol index e6a5763d90..ccdf81700b 100644 --- a/contracts/p1/mixins/Component.sol +++ b/contracts/p1/mixins/Component.sol @@ -38,8 +38,13 @@ abstract contract ComponentP1 is // === See docs/security.md === - modifier notPausedOrFrozen() { - require(!main.pausedOrFrozen(), "paused or frozen"); + modifier notTradingPausedOrFrozen() { + require(!main.tradingPausedOrFrozen(), "frozen or trading paused"); + _; + } + + modifier notIssuancePausedOrFrozen() { + require(!main.issuancePausedOrFrozen(), "frozen or issuance paused"); _; } diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index decfa32317..8a162be298 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -54,7 +54,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl /// Settle a single trade, expected to be used with multicall for efficient mass settlement /// @custom:interaction (only reads or writes trades, and is marked `nonReentrant`) // checks: - // !paused, !frozen + // !paused (trading), !frozen // trade[sell].canSettle() // actions: // trade[sell].settle() @@ -63,7 +63,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // tradesOpen' = tradesOpen - 1 // untested: // OZ nonReentrant line is assumed to be working. cost/benefit of direct testing is high - function settleTrade(IERC20 sell) external notPausedOrFrozen nonReentrant { + function settleTrade(IERC20 sell) external notTradingPausedOrFrozen nonReentrant { ITrade trade = trades[sell]; if (address(trade) == address(0)) return; require(trade.canSettle(), "cannot settle yet"); @@ -79,7 +79,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl /// Claim all rewards /// Collective Action /// @custom:interaction CEI - function claimRewards() external notPausedOrFrozen { + function claimRewards() external notTradingPausedOrFrozen { RewardableLibP1.claimRewards(main.assetRegistry()); } @@ -87,7 +87,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl /// Collective Action /// @param erc20 The ERC20 to claimRewards on /// @custom:interaction CEI - function claimRewardsSingle(IERC20 erc20) external notPausedOrFrozen { + function claimRewardsSingle(IERC20 erc20) external notTradingPausedOrFrozen { RewardableLibP1.claimRewardsSingle(main.assetRegistry().toAsset(erc20)); } diff --git a/contracts/plugins/mocks/InvalidBrokerMock.sol b/contracts/plugins/mocks/InvalidBrokerMock.sol index 3b98352a57..078cd78a16 100644 --- a/contracts/plugins/mocks/InvalidBrokerMock.sol +++ b/contracts/plugins/mocks/InvalidBrokerMock.sol @@ -35,7 +35,7 @@ contract InvalidBrokerMock is ComponentP0, IBroker { } /// Invalid implementation - Reverts - function openTrade(TradeRequest memory req) external view notPausedOrFrozen returns (ITrade) { + function openTrade(TradeRequest memory req) external view notTradingPausedOrFrozen returns (ITrade) { require(!disabled, "broker disabled"); req; diff --git a/contracts/plugins/mocks/upgrades/MainV2.sol b/contracts/plugins/mocks/upgrades/MainV2.sol index f31f2f1b44..3ed96ce4fb 100644 --- a/contracts/plugins/mocks/upgrades/MainV2.sol +++ b/contracts/plugins/mocks/upgrades/MainV2.sol @@ -7,7 +7,7 @@ contract MainP1V2 is MainP1 { uint256 public newValue; function setNewValue(uint256 newValue_) external { - require(!pausedOrFrozen(), "paused or frozen"); + require(!tradingPausedOrFrozen(), "frozen or trading paused"); newValue = newValue_; } From a8dc7967d742a68135b2ecd763c90b1d46694000 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Fri, 14 Apr 2023 10:24:24 -0300 Subject: [PATCH 049/462] update docs for new pausing --- docs/deployment.md | 2 +- docs/system-design.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/deployment.md b/docs/deployment.md index 9b5c9cf2d4..f2c85987b9 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -219,7 +219,7 @@ yarn deploy:run:confirm --network mainnet This checks that: - For each asset, confirm `lotPrice()` and `price()` are close. -- `main.paused()` is true +- `main.tradingPaused()` and `main.issuancePaused()` are true - `timelockController.minDelay()` is > 1e12 End state: All addresses are verified, the contracts are in the correct state, and it's time to verify the contracts on Etherscan. diff --git a/docs/system-design.md b/docs/system-design.md index 8f90194096..bef1ab502f 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -110,7 +110,8 @@ On the other hand, while a redemption is pending in the mempool, the quantities ## System States -- `paused`: all interactions disabled EXCEPT ERC20 functions + RToken.redeem + StRSR.stake + StRSR.payoutRewards +- `tradingPaused`: all interactions disabled EXCEPT ERC20 functions + RToken.issue + RToken.redeem + StRSR.stake + StRSR.payoutRewards +- `issuancePaused`: all interactions enabled EXCEPT RToken.issue - `frozen`: all interactions disabled EXCEPT ERC20 functions + StRSR.stake Freezing can occur over two timescales: short freezing + long freezing. From 2d49f82cfe43c42373a66cf90cd999e1b0a75aa0 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Fri, 14 Apr 2023 10:24:40 -0300 Subject: [PATCH 050/462] adapt scripts --- scripts/confirmation/0_confirm_components.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/confirmation/0_confirm_components.ts b/scripts/confirmation/0_confirm_components.ts index 33dfd0fba9..772f36b8bd 100644 --- a/scripts/confirmation/0_confirm_components.ts +++ b/scripts/confirmation/0_confirm_components.ts @@ -32,7 +32,8 @@ async function main() { if (isMainnet) { // Confirm Main is paused console.log('Checking main is configured correctly') - if (!(await mainComponent.paused())) throw new Error('main is unpaused') + if (!(await mainComponent.tradingPaused())) throw new Error('main is unpaused for trading') + if (!(await mainComponent.issuancePaused())) throw new Error('main is unpaused for issuance') // Confirm governance is configured correctly const timelock = await hre.ethers.getContractAt( From f8e494079d96e6702a16178d244c62e62eb58e70 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Fri, 14 Apr 2023 10:24:52 -0300 Subject: [PATCH 051/462] adapt tasks --- tasks/deprecated/deploy.ts | 3 ++- tasks/deprecated/p0/deploy_all.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tasks/deprecated/deploy.ts b/tasks/deprecated/deploy.ts index 87e3529925..ef67fbefd7 100644 --- a/tasks/deprecated/deploy.ts +++ b/tasks/deprecated/deploy.ts @@ -95,7 +95,8 @@ task('deploy', 'Deploy protocol smart contracts').setAction(async (params, hre) await basketHandler.connect(deployer).refreshBasket() console.log('Unpausing...') - await main.connect(deployer).unpause() + await main.connect(deployer).tradingUnpause() + await main.connect(deployer).issuanceUnpause() // Grant allowances for (let i = 0; i < basketCollaterals.length; i++) { diff --git a/tasks/deprecated/p0/deploy_all.ts b/tasks/deprecated/p0/deploy_all.ts index 9c5654d20b..6f972b9f9e 100644 --- a/tasks/deprecated/p0/deploy_all.ts +++ b/tasks/deprecated/p0/deploy_all.ts @@ -84,7 +84,8 @@ task('P0-deploy', 'Deploys all Protocol components and an RToken').setAction( await basketHandler.connect(deployer).refreshBasket() console.log('Unpausing...') - await main.connect(deployer).unpause() + await main.connect(deployer).tradingUnpause() + await main.connect(deployer).issuanceUnpause() // TODO: Test remove console.log('RSR Funding') From 9019d0ab91a0ffd53bada26d895aca8ce85809ec Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Fri, 14 Apr 2023 10:25:07 -0300 Subject: [PATCH 052/462] adapt tests for pausing --- test/Broker.test.ts | 16 ++++---- test/FacadeRead.test.ts | 4 +- test/FacadeWrite.test.ts | 33 +++++++++++------ test/Furnace.test.ts | 6 +-- test/Main.test.ts | 63 ++++++++++++++++++++------------ test/RToken.test.ts | 27 +++++++------- test/Recollateralization.test.ts | 10 ++--- test/Revenues.test.ts | 32 ++++++++-------- test/Upgradeability.test.ts | 6 ++- test/ZZStRSR.test.ts | 25 +++++++------ 10 files changed, 127 insertions(+), 95 deletions(-) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 7c021bb223..dc7020f251 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -274,7 +274,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) it('Should not allow to open trade if paused', async () => { - await main.connect(owner).pause() + await main.connect(owner).tradingPause() // Attempt to open trade const tradeRequest: ITradeRequest = { @@ -286,13 +286,13 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await whileImpersonating(backingManager.address, async (bmSigner) => { await expect(broker.connect(bmSigner).openTrade(tradeRequest)).to.be.revertedWith( - 'paused or frozen' + 'frozen or trading paused' ) }) }) it('Should not allow to open trade if frozen', async () => { - await main.connect(owner).pause() + await main.connect(owner).freezeShort() // Attempt to open trade const tradeRequest: ITradeRequest = { @@ -304,7 +304,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await whileImpersonating(backingManager.address, async (bmSigner) => { await expect(broker.connect(bmSigner).openTrade(tradeRequest)).to.be.revertedWith( - 'paused or frozen' + 'frozen or trading paused' ) }) }) @@ -373,15 +373,15 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // Check not disabled expect(await broker.disabled()).to.equal(false) - await main.connect(owner).pause() + await main.connect(owner).tradingPause() - await expect(broker.connect(addr1).reportViolation()).to.be.revertedWith('paused or frozen') + await expect(broker.connect(addr1).reportViolation()).to.be.revertedWith('frozen or trading paused') - await main.connect(owner).unpause() + await main.connect(owner).tradingUnpause() await main.connect(owner).freezeShort() - await expect(broker.connect(addr1).reportViolation()).to.be.revertedWith('paused or frozen') + await expect(broker.connect(addr1).reportViolation()).to.be.revertedWith('frozen or trading paused') // Check nothing changed expect(await broker.disabled()).to.equal(false) diff --git a/test/FacadeRead.test.ts b/test/FacadeRead.test.ts index 15a8bdf5e6..f27c331deb 100644 --- a/test/FacadeRead.test.ts +++ b/test/FacadeRead.test.ts @@ -375,7 +375,7 @@ describe('FacadeRead contract', () => { }) it('Should return basketBreakdown correctly for paused token', async () => { - await main.connect(owner).pause() + await main.connect(owner).tradingPause() await expectValidBasketBreakdown(rToken) }) @@ -394,7 +394,7 @@ describe('FacadeRead contract', () => { ) // set price of dai to 0 await chainlinkFeed.updateAnswer(0) - await main.connect(owner).pause() + await main.connect(owner).tradingPause() const [erc20s, breakdown, targets] = await facade.callStatic.basketBreakdown(rToken.address) expect(erc20s.length).to.equal(4) expect(breakdown.length).to.equal(4) diff --git a/test/FacadeWrite.test.ts b/test/FacadeWrite.test.ts index b62d05357b..1f945c34ff 100644 --- a/test/FacadeWrite.test.ts +++ b/test/FacadeWrite.test.ts @@ -371,8 +371,10 @@ describe('FacadeWrite contract', () => { expect(await main.hasRole(LONG_FREEZER, facadeWrite.address)).to.equal(true) expect(await main.hasRole(PAUSER, facadeWrite.address)).to.equal(true) expect(await main.frozen()).to.equal(false) - expect(await main.paused()).to.equal(true) - expect(await main.pausedOrFrozen()).to.equal(true) + expect(await main.tradingPaused()).to.equal(true) + expect(await main.tradingPausedOrFrozen()).to.equal(true) + expect(await main.issuancePaused()).to.equal(true) + expect(await main.issuancePausedOrFrozen()).to.equal(true) // RToken expect(await assetRegistry.toAsset(rToken.address)).to.equal(rTokenAsset.address) @@ -517,7 +519,8 @@ describe('FacadeWrite contract', () => { it('Should register Basket correctly', async () => { // Unpause - await main.connect(owner).unpause() + await main.connect(owner).tradingUnpause() + await main.connect(owner).issuanceUnpause() // Basket expect(await basketHandler.fullyCollateralized()).to.equal(true) @@ -595,8 +598,10 @@ describe('FacadeWrite contract', () => { expect(await main.hasRole(PAUSER, deployerUser.address)).to.equal(false) expect(await main.frozen()).to.equal(false) - expect(await main.paused()).to.equal(true) - expect(await main.pausedOrFrozen()).to.equal(true) + expect(await main.tradingPaused()).to.equal(true) + expect(await main.tradingPausedOrFrozen()).to.equal(true) + expect(await main.issuancePaused()).to.equal(true) + expect(await main.issuancePausedOrFrozen()).to.equal(true) }) it('Should not allow to complete setup again if already complete', async () => { @@ -659,8 +664,10 @@ describe('FacadeWrite contract', () => { expect(await main.hasRole(PAUSER, deployerUser.address)).to.equal(false) expect(await main.frozen()).to.equal(false) - expect(await main.paused()).to.equal(false) - expect(await main.pausedOrFrozen()).to.equal(false) + expect(await main.tradingPaused()).to.equal(false) + expect(await main.tradingPausedOrFrozen()).to.equal(false) + expect(await main.issuancePaused()).to.equal(false) + expect(await main.issuancePausedOrFrozen()).to.equal(false) }) }) @@ -717,8 +724,10 @@ describe('FacadeWrite contract', () => { expect(await main.hasRole(PAUSER, deployerUser.address)).to.equal(false) expect(await main.frozen()).to.equal(false) - expect(await main.paused()).to.equal(true) - expect(await main.pausedOrFrozen()).to.equal(true) + expect(await main.tradingPaused()).to.equal(true) + expect(await main.tradingPausedOrFrozen()).to.equal(true) + expect(await main.issuancePaused()).to.equal(true) + expect(await main.issuancePausedOrFrozen()).to.equal(true) }) it('Should deploy Governor correctly', async () => { @@ -792,8 +801,10 @@ describe('FacadeWrite contract', () => { expect(await main.hasRole(PAUSER, deployerUser.address)).to.equal(false) expect(await main.frozen()).to.equal(false) - expect(await main.paused()).to.equal(false) - expect(await main.pausedOrFrozen()).to.equal(false) + expect(await main.tradingPaused()).to.equal(false) + expect(await main.tradingPausedOrFrozen()).to.equal(false) + expect(await main.issuancePaused()).to.equal(false) + expect(await main.issuancePausedOrFrozen()).to.equal(false) }) }) }) diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index be2331f651..bf16690d0e 100644 --- a/test/Furnace.test.ts +++ b/test/Furnace.test.ts @@ -180,13 +180,13 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { }) it('Should not melt if paused #fast', async () => { - await main.connect(owner).pause() - await expect(furnace.connect(addr1).melt()).to.be.revertedWith('paused or frozen') + await main.connect(owner).tradingPause() + await expect(furnace.connect(addr1).melt()).to.be.revertedWith('frozen or trading paused') }) it('Should not melt if frozen #fast', async () => { await main.connect(owner).freezeShort() - await expect(furnace.connect(addr1).melt()).to.be.revertedWith('paused or frozen') + await expect(furnace.connect(addr1).melt()).to.be.revertedWith('frozen or trading paused') }) it('Should not melt any funds in the initial block #fast', async () => { diff --git a/test/Main.test.ts b/test/Main.test.ts index 75aa32202f..e9cfefe0df 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -204,8 +204,10 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(await main.getRoleAdmin(PAUSER)).to.equal(OWNER) // Should start unfrozen and unpaused - expect(await main.paused()).to.equal(false) - expect(await main.pausedOrFrozen()).to.equal(false) + expect(await main.tradingPaused()).to.equal(false) + expect(await main.tradingPausedOrFrozen()).to.equal(false) + expect(await main.issuancePaused()).to.equal(false) + expect(await main.issuancePausedOrFrozen()).to.equal(false) expect(await main.frozen()).to.equal(false) // Components @@ -529,41 +531,54 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { // Check initial status expect(await main.hasRole(PAUSER, owner.address)).to.equal(true) expect(await main.hasRole(PAUSER, addr1.address)).to.equal(true) - expect(await main.paused()).to.equal(false) - expect(await main.pausedOrFrozen()).to.equal(false) + expect(await main.tradingPaused()).to.equal(false) + expect(await main.tradingPausedOrFrozen()).to.equal(false) + expect(await main.issuancePaused()).to.equal(false) + expect(await main.issuancePausedOrFrozen()).to.equal(false) // Pause with PAUSER - await main.connect(addr1).pause() + await main.connect(addr1).tradingPause() + await main.connect(addr1).issuancePause() // Check if Paused, should not lose PAUSER - expect(await main.paused()).to.equal(true) + expect(await main.tradingPaused()).to.equal(true) + expect(await main.issuancePaused()).to.equal(true) expect(await main.hasRole(PAUSER, owner.address)).to.equal(true) expect(await main.hasRole(PAUSER, addr1.address)).to.equal(true) // Unpause - await main.connect(addr1).unpause() + await main.connect(addr1).tradingUnpause() + await main.connect(addr1).issuanceUnpause() - expect(await main.paused()).to.equal(false) + expect(await main.tradingPaused()).to.equal(false) + expect(await main.issuancePaused()).to.equal(false) // OWNER should still be able to Pause - await main.connect(owner).pause() + await main.connect(owner).tradingPause() + await main.connect(owner).issuancePause() // Check if Paused - expect(await main.pausedOrFrozen()).to.equal(true) - expect(await main.paused()).to.equal(true) + expect(await main.tradingPausedOrFrozen()).to.equal(true) + expect(await main.tradingPaused()).to.equal(true) + expect(await main.issuancePausedOrFrozen()).to.equal(true) + expect(await main.issuancePaused()).to.equal(true) }) it('Should not allow to Pause/Unpause if not PAUSER or OWNER', async () => { - await expect(main.connect(other).pause()).to.be.reverted + await expect(main.connect(other).tradingPause()).to.be.reverted + await expect(main.connect(other).issuancePause()).to.be.reverted // Check no changes - expect(await main.paused()).to.equal(false) + expect(await main.tradingPaused()).to.equal(false) + expect(await main.issuancePaused()).to.equal(false) // Attempt to unpause - await expect(main.connect(other).unpause()).to.be.reverted + await expect(main.connect(other).tradingUnpause()).to.be.reverted + await expect(main.connect(other).issuanceUnpause()).to.be.reverted // Check no changes - expect(await main.paused()).to.equal(false) + expect(await main.tradingPaused()).to.equal(false) + expect(await main.issuancePaused()).to.equal(false) }) it('Should not allow to set PAUSER if not OWNER', async () => { @@ -622,7 +637,8 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(await main.hasRole(LONG_FREEZER, addr1.address)).to.equal(false) expect(await main.hasRole(LONG_FREEZER, addr2.address)).to.equal(true) expect(await main.frozen()).to.equal(false) - expect(await main.pausedOrFrozen()).to.equal(false) + expect(await main.tradingPausedOrFrozen()).to.equal(false) + expect(await main.issuancePausedOrFrozen()).to.equal(false) }) it('Should only permit owner to freeze forever', async () => { @@ -997,7 +1013,8 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) it('Should grant allowances when paused', async () => { - await main.connect(owner).pause() + await main.connect(owner).tradingPause() + await main.connect(owner).issuancePause() await expect(backingManager.grantRTokenAllowance(ZERO_ADDRESS)).to.be.revertedWith( 'erc20 unregistered' ) @@ -1682,7 +1699,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) it('Should not allow to refresh basket if not OWNER when paused', async () => { - await main.connect(owner).pause() + await main.connect(owner).tradingPause() await expect(basketHandler.connect(other).refreshBasket()).to.be.revertedWith( 'basket unrefreshable' ) @@ -1696,13 +1713,13 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) it('Should not allow to poke when paused', async () => { - await main.connect(owner).pause() - await expect(main.connect(other).poke()).to.be.revertedWith('paused or frozen') + await main.connect(owner).tradingPause() + await expect(main.connect(other).poke()).to.be.revertedWith('frozen or trading paused') }) it('Should not allow to poke when frozen', async () => { await main.connect(owner).freezeForever() - await expect(main.connect(other).poke()).to.be.revertedWith('paused or frozen') + await expect(main.connect(other).poke()).to.be.revertedWith('frozen or trading paused') }) it('Should not allow to refresh basket if not OWNER when unfrozen and unpaused', async () => { @@ -1718,7 +1735,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) it('Should allow to call refresh Basket if OWNER and paused - No changes', async () => { - await main.connect(owner).pause() + await main.connect(owner).tradingPause() // Switch basket - No backup nor default await expect(basketHandler.connect(owner).refreshBasket()).to.emit(basketHandler, 'BasketSet') @@ -1735,7 +1752,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { // Not updated so basket last changed is not set expect(await basketHandler.timestamp()).to.be.gt(bn(0)) expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - await main.connect(owner).unpause() + await main.connect(owner).tradingUnpause() expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) }) diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 5990cdfcac..9bc17c2d3a 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -301,10 +301,10 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { const issueAmount: BigNumber = bn('10e18') // Pause Main - await main.connect(owner).pause() + await main.connect(owner).issuancePause() // Try to issue - await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith('paused or frozen') + await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith('frozen or issuance paused') // Check values expect(await rToken.totalSupply()).to.equal(bn(0)) @@ -317,7 +317,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await main.connect(owner).freezeShort() // Try to issue - await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith('paused or frozen') + await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith('frozen or issuance paused') // Check values expect(await rToken.totalSupply()).to.equal(bn(0)) @@ -949,7 +949,8 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) it('Should redeem if paused #fast', async function () { - await main.connect(owner).pause() + await main.connect(owner).tradingPause() + await main.connect(owner).issuancePause() await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) expect(await rToken.totalSupply()).to.equal(0) }) @@ -1411,11 +1412,11 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) it('Should not mint if paused', async () => { - await main.connect(owner).pause() + await main.connect(owner).tradingPause() await whileImpersonating(backingManager.address, async (signer) => { await expect(rToken.connect(signer).mint(addr1.address, bn('10e18'))).to.be.revertedWith( - 'paused or frozen' + 'frozen or trading paused' ) }) }) @@ -1425,7 +1426,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await whileImpersonating(backingManager.address, async (signer) => { await expect(rToken.connect(signer).mint(addr1.address, bn('10e18'))).to.be.revertedWith( - 'paused or frozen' + 'frozen or trading paused' ) }) }) @@ -1449,12 +1450,12 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.basketsNeeded()).to.equal(issueAmount) // Pause Main - await main.connect(owner).pause() + await main.connect(owner).tradingPause() // Try to set baskets needed await whileImpersonating(backingManager.address, async (bhSigner) => { await expect(rToken.connect(bhSigner).setBasketsNeeded(fp('1'))).to.be.revertedWith( - 'paused or frozen' + 'frozen or trading paused' ) }) @@ -1472,7 +1473,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Try to set baskets needed await whileImpersonating(backingManager.address, async (bhSigner) => { await expect(rToken.connect(bhSigner).setBasketsNeeded(fp('1'))).to.be.revertedWith( - 'paused or frozen' + 'frozen or trading paused' ) }) @@ -1794,13 +1795,13 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) it('should not monetize while paused', async () => { - await main.connect(owner).pause() - await expect(rToken.monetizeDonations(token3.address)).to.be.revertedWith('paused or frozen') + await main.connect(owner).tradingPause() + await expect(rToken.monetizeDonations(token3.address)).to.be.revertedWith('frozen or trading paused') }) it('should not monetize while frozen', async () => { await main.connect(owner).freezeShort() - await expect(rToken.monetizeDonations(token3.address)).to.be.revertedWith('paused or frozen') + await expect(rToken.monetizeDonations(token3.address)).to.be.revertedWith('frozen or trading paused') }) it('should monetize registered erc20s', async () => { diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 058484e2da..48643c42a8 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -797,18 +797,18 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) it('Should not trade if paused', async () => { - await main.connect(owner).pause() - await expect(backingManager.manageTokens([])).to.be.revertedWith('paused or frozen') + await main.connect(owner).tradingPause() + await expect(backingManager.manageTokens([])).to.be.revertedWith('frozen or trading paused') await expect(backingManager.manageTokensSortedOrder([])).to.be.revertedWith( - 'paused or frozen' + 'frozen or trading paused' ) }) it('Should not trade if frozen', async () => { await main.connect(owner).freezeShort() - await expect(backingManager.manageTokens([])).to.be.revertedWith('paused or frozen') + await expect(backingManager.manageTokens([])).to.be.revertedWith('frozen or trading paused') await expect(backingManager.manageTokensSortedOrder([])).to.be.revertedWith( - 'paused or frozen' + 'frozen or trading paused' ) }) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 530f5f21f1..5b6bd4d744 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -379,36 +379,36 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) it('Should not trade if paused', async () => { - await main.connect(owner).pause() - await expect(rsrTrader.manageToken(ZERO_ADDRESS)).to.be.revertedWith('paused or frozen') + await main.connect(owner).tradingPause() + await expect(rsrTrader.manageToken(ZERO_ADDRESS)).to.be.revertedWith('frozen or trading paused') }) it('Should not trade if frozen', async () => { await main.connect(owner).freezeShort() - await expect(rTokenTrader.manageToken(ZERO_ADDRESS)).to.be.revertedWith('paused or frozen') + await expect(rTokenTrader.manageToken(ZERO_ADDRESS)).to.be.revertedWith('frozen or trading paused') }) it('Should not claim rewards if paused', async () => { - await main.connect(owner).pause() - await expect(rTokenTrader.claimRewards()).to.be.revertedWith('paused or frozen') + await main.connect(owner).tradingPause() + await expect(rTokenTrader.claimRewards()).to.be.revertedWith('frozen or trading paused') }) it('Should not claim rewards if frozen', async () => { await main.connect(owner).freezeShort() - await expect(rTokenTrader.claimRewards()).to.be.revertedWith('paused or frozen') + await expect(rTokenTrader.claimRewards()).to.be.revertedWith('frozen or trading paused') }) it('Should not claim single rewards if paused', async () => { - await main.connect(owner).pause() + await main.connect(owner).tradingPause() await expect(rTokenTrader.claimRewardsSingle(token2.address)).to.be.revertedWith( - 'paused or frozen' + 'frozen or trading paused' ) }) it('Should not claim single rewards if frozen', async () => { await main.connect(owner).freezeShort() await expect(rTokenTrader.claimRewardsSingle(token2.address)).to.be.revertedWith( - 'paused or frozen' + 'frozen or trading paused' ) }) @@ -421,13 +421,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) it('Should not settle trade if paused', async () => { - await main.connect(owner).pause() - await expect(rTokenTrader.settleTrade(ZERO_ADDRESS)).to.be.revertedWith('paused or frozen') + await main.connect(owner).tradingPause() + await expect(rTokenTrader.settleTrade(ZERO_ADDRESS)).to.be.revertedWith('frozen or trading paused') }) it('Should not settle trade if frozen', async () => { await main.connect(owner).freezeShort() - await expect(rTokenTrader.settleTrade(ZERO_ADDRESS)).to.be.revertedWith('paused or frozen') + await expect(rTokenTrader.settleTrade(ZERO_ADDRESS)).to.be.revertedWith('frozen or trading paused') }) it('Should still launch revenue auction if IFFY', async () => { @@ -1608,18 +1608,18 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should not distribute if paused or frozen', async () => { const distAmount: BigNumber = bn('100e18') - await main.connect(owner).pause() + await main.connect(owner).tradingPause() await expect(distributor.distribute(rsr.address, distAmount)).to.be.revertedWith( - 'paused or frozen' + 'frozen or trading paused' ) - await main.connect(owner).unpause() + await main.connect(owner).tradingUnpause() await main.connect(owner).freezeShort() await expect(distributor.distribute(rsr.address, distAmount)).to.be.revertedWith( - 'paused or frozen' + 'frozen or trading paused' ) }) diff --git a/test/Upgradeability.test.ts b/test/Upgradeability.test.ts index 2de476e424..e2a7bff512 100644 --- a/test/Upgradeability.test.ts +++ b/test/Upgradeability.test.ts @@ -390,9 +390,11 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { expect(mainV2.address).to.equal(main.address) // Check state is preserved - expect(await mainV2.paused()).to.equal(false) + expect(await mainV2.tradingPaused()).to.equal(false) + expect(await mainV2.issuancePaused()).to.equal(false) expect(await mainV2.frozen()).to.equal(false) - expect(await mainV2.pausedOrFrozen()).to.equal(false) + expect(await mainV2.tradingPausedOrFrozen()).to.equal(false) + expect(await mainV2.issuancePausedOrFrozen()).to.equal(false) expect(await mainV2.hasRole(OWNER, owner.address)).to.equal(true) expect(await mainV2.hasRole(OWNER, main.address)).to.equal(false) expect(await mainV2.hasRole(SHORT_FREEZER, owner.address)).to.equal(true) diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 1fea6e6f7b..8c6e1f2cf7 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -387,7 +387,8 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { const amount: BigNumber = bn('1000e18') // Pause Main - await main.connect(owner).pause() + await main.connect(owner).tradingPause() + await main.connect(owner).issuancePause() // Approve transfer and stake await rsr.connect(addr1).approve(stRSR.address, amount) @@ -485,13 +486,13 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { }) it('Should not unstake if paused', async () => { - await main.connect(owner).pause() - await expect(stRSR.connect(addr1).unstake(0)).to.be.revertedWith('paused or frozen') + await main.connect(owner).tradingPause() + await expect(stRSR.connect(addr1).unstake(0)).to.be.revertedWith('frozen or trading paused') }) it('Should not unstake if frozen', async () => { await main.connect(owner).freezeShort() - await expect(stRSR.connect(addr1).unstake(0)).to.be.revertedWith('paused or frozen') + await expect(stRSR.connect(addr1).unstake(0)).to.be.revertedWith('frozen or trading paused') }) it('Should create Pending withdrawal when unstaking', async () => { @@ -631,15 +632,15 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay) // Pause Main - await main.connect(owner).pause() + await main.connect(owner).tradingPause() // Withdraw await expect(stRSR.connect(addr1).withdraw(addr1.address, 1)).to.be.revertedWith( - 'paused or frozen' + 'frozen or trading paused' ) // If unpaused should withdraw OK - await main.connect(owner).unpause() + await main.connect(owner).tradingUnpause() // Withdraw await stRSR.connect(addr1).withdraw(addr1.address, 1) @@ -663,7 +664,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { // Withdraw await expect(stRSR.connect(addr1).withdraw(addr1.address, 1)).to.be.revertedWith( - 'paused or frozen' + 'frozen or trading paused' ) // If unpaused should withdraw OK @@ -1125,7 +1126,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { }) it('Rewards should not be handed out when paused but staking should still work', async () => { - await main.connect(owner).pause() + await main.connect(owner).tradingPause() await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp()))) // Stake @@ -1310,16 +1311,16 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { }) it('Should not allow to remove RSR if paused', async () => { - await main.connect(owner).pause() + await main.connect(owner).tradingPause() await whileImpersonating(backingManager.address, async (signer) => { - await expect(stRSR.connect(signer).seizeRSR(1)).to.be.revertedWith('paused or frozen') + await expect(stRSR.connect(signer).seizeRSR(1)).to.be.revertedWith('frozen or trading paused') }) }) it('Should not allow to remove RSR if frozen', async () => { await main.connect(owner).freezeShort() await whileImpersonating(backingManager.address, async (signer) => { - await expect(stRSR.connect(signer).seizeRSR(1)).to.be.revertedWith('paused or frozen') + await expect(stRSR.connect(signer).seizeRSR(1)).to.be.revertedWith('frozen or trading paused') }) }) From 30b7a15be631f1e3ca97a806d0f48d9b0b7358bc Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Fri, 14 Apr 2023 10:26:20 -0300 Subject: [PATCH 053/462] fix lint --- contracts/facade/FacadeWrite.sol | 4 ++-- contracts/interfaces/IMain.sol | 2 +- contracts/mixins/Auth.sol | 4 ++-- contracts/plugins/mocks/InvalidBrokerMock.sol | 7 ++++++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/contracts/facade/FacadeWrite.sol b/contracts/facade/FacadeWrite.sol index 9ccafd7b98..190aa1b812 100644 --- a/contracts/facade/FacadeWrite.sol +++ b/contracts/facade/FacadeWrite.sol @@ -113,7 +113,7 @@ contract FacadeWrite is IFacadeWrite { // Pause until setupGovernance main.tradingPause(); main.issuancePause(); - + // Setup deployer as owner to complete next step - do not renounce roles yet main.grantRole(OWNER, msg.sender); @@ -191,7 +191,7 @@ contract FacadeWrite is IFacadeWrite { // Unpause if required if (unpause) { main.tradingUnpause(); - main.issuanceUnpause(); + main.issuanceUnpause(); } // Transfer Ownership and renounce roles diff --git a/contracts/interfaces/IMain.sol b/contracts/interfaces/IMain.sol index aa0a192f6a..c59eaf22f9 100644 --- a/contracts/interfaces/IMain.sol +++ b/contracts/interfaces/IMain.sol @@ -66,7 +66,7 @@ interface IAuth is IAccessControlUpgradeable { /// @param newVal The new value of `tradingPaused` event TradingPausedSet(bool indexed oldVal, bool indexed newVal); - /// Emitted when the system is paused or unpaused for issuance + /// Emitted when the system is paused or unpaused for issuance /// @param oldVal The old value of `issuancePaused` /// @param newVal The new value of `issuancePaused` event IssuancePausedSet(bool indexed oldVal, bool indexed newVal); diff --git a/contracts/mixins/Auth.sol b/contracts/mixins/Auth.sol index 7de6862ae6..0f831cb695 100644 --- a/contracts/mixins/Auth.sol +++ b/contracts/mixins/Auth.sol @@ -43,7 +43,7 @@ abstract contract Auth is AccessControlUpgradeable, IAuth { bool public tradingPaused; bool public issuancePaused; - + /* ==== Invariants ==== 0 <= longFreeze[a] <= LONG_FREEZE_CHARGES for all addrs a set{a has LONG_FREEZER} == set{a : longFreeze[a] == 0} @@ -183,7 +183,7 @@ abstract contract Auth is AccessControlUpgradeable, IAuth { tradingPaused = false; } - // checks: caller has PAUSER + // checks: caller has PAUSER // effects: issuancePaused' = true function issuancePause() external onlyRole(PAUSER) { emit IssuancePausedSet(issuancePaused, true); diff --git a/contracts/plugins/mocks/InvalidBrokerMock.sol b/contracts/plugins/mocks/InvalidBrokerMock.sol index 078cd78a16..c0ccb988b0 100644 --- a/contracts/plugins/mocks/InvalidBrokerMock.sol +++ b/contracts/plugins/mocks/InvalidBrokerMock.sol @@ -35,7 +35,12 @@ contract InvalidBrokerMock is ComponentP0, IBroker { } /// Invalid implementation - Reverts - function openTrade(TradeRequest memory req) external view notTradingPausedOrFrozen returns (ITrade) { + function openTrade(TradeRequest memory req) + external + view + notTradingPausedOrFrozen + returns (ITrade) + { require(!disabled, "broker disabled"); req; From 011cf13922ebea2d3e9bbe6cbcff5e246523ab53 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Fri, 14 Apr 2023 10:26:29 -0300 Subject: [PATCH 054/462] fix lint in tests --- test/Broker.test.ts | 8 ++++++-- test/RToken.test.ts | 16 ++++++++++++---- test/Revenues.test.ts | 16 ++++++++++++---- test/ZZStRSR.test.ts | 8 ++++++-- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index dc7020f251..9ffd697ba8 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -375,13 +375,17 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await main.connect(owner).tradingPause() - await expect(broker.connect(addr1).reportViolation()).to.be.revertedWith('frozen or trading paused') + await expect(broker.connect(addr1).reportViolation()).to.be.revertedWith( + 'frozen or trading paused' + ) await main.connect(owner).tradingUnpause() await main.connect(owner).freezeShort() - await expect(broker.connect(addr1).reportViolation()).to.be.revertedWith('frozen or trading paused') + await expect(broker.connect(addr1).reportViolation()).to.be.revertedWith( + 'frozen or trading paused' + ) // Check nothing changed expect(await broker.disabled()).to.equal(false) diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 9bc17c2d3a..6ec391d2c6 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -304,7 +304,9 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await main.connect(owner).issuancePause() // Try to issue - await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith('frozen or issuance paused') + await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith( + 'frozen or issuance paused' + ) // Check values expect(await rToken.totalSupply()).to.equal(bn(0)) @@ -317,7 +319,9 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await main.connect(owner).freezeShort() // Try to issue - await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith('frozen or issuance paused') + await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith( + 'frozen or issuance paused' + ) // Check values expect(await rToken.totalSupply()).to.equal(bn(0)) @@ -1796,12 +1800,16 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { it('should not monetize while paused', async () => { await main.connect(owner).tradingPause() - await expect(rToken.monetizeDonations(token3.address)).to.be.revertedWith('frozen or trading paused') + await expect(rToken.monetizeDonations(token3.address)).to.be.revertedWith( + 'frozen or trading paused' + ) }) it('should not monetize while frozen', async () => { await main.connect(owner).freezeShort() - await expect(rToken.monetizeDonations(token3.address)).to.be.revertedWith('frozen or trading paused') + await expect(rToken.monetizeDonations(token3.address)).to.be.revertedWith( + 'frozen or trading paused' + ) }) it('should monetize registered erc20s', async () => { diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 5b6bd4d744..de38a49f1d 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -380,12 +380,16 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should not trade if paused', async () => { await main.connect(owner).tradingPause() - await expect(rsrTrader.manageToken(ZERO_ADDRESS)).to.be.revertedWith('frozen or trading paused') + await expect(rsrTrader.manageToken(ZERO_ADDRESS)).to.be.revertedWith( + 'frozen or trading paused' + ) }) it('Should not trade if frozen', async () => { await main.connect(owner).freezeShort() - await expect(rTokenTrader.manageToken(ZERO_ADDRESS)).to.be.revertedWith('frozen or trading paused') + await expect(rTokenTrader.manageToken(ZERO_ADDRESS)).to.be.revertedWith( + 'frozen or trading paused' + ) }) it('Should not claim rewards if paused', async () => { @@ -422,12 +426,16 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should not settle trade if paused', async () => { await main.connect(owner).tradingPause() - await expect(rTokenTrader.settleTrade(ZERO_ADDRESS)).to.be.revertedWith('frozen or trading paused') + await expect(rTokenTrader.settleTrade(ZERO_ADDRESS)).to.be.revertedWith( + 'frozen or trading paused' + ) }) it('Should not settle trade if frozen', async () => { await main.connect(owner).freezeShort() - await expect(rTokenTrader.settleTrade(ZERO_ADDRESS)).to.be.revertedWith('frozen or trading paused') + await expect(rTokenTrader.settleTrade(ZERO_ADDRESS)).to.be.revertedWith( + 'frozen or trading paused' + ) }) it('Should still launch revenue auction if IFFY', async () => { diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 8c6e1f2cf7..c48287d9b8 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -1313,14 +1313,18 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { it('Should not allow to remove RSR if paused', async () => { await main.connect(owner).tradingPause() await whileImpersonating(backingManager.address, async (signer) => { - await expect(stRSR.connect(signer).seizeRSR(1)).to.be.revertedWith('frozen or trading paused') + await expect(stRSR.connect(signer).seizeRSR(1)).to.be.revertedWith( + 'frozen or trading paused' + ) }) }) it('Should not allow to remove RSR if frozen', async () => { await main.connect(owner).freezeShort() await whileImpersonating(backingManager.address, async (signer) => { - await expect(stRSR.connect(signer).seizeRSR(1)).to.be.revertedWith('frozen or trading paused') + await expect(stRSR.connect(signer).seizeRSR(1)).to.be.revertedWith( + 'frozen or trading paused' + ) }) }) From a3665709d15bbdd6865bcab799c7cd0098b378a1 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Fri, 14 Apr 2023 10:35:54 -0300 Subject: [PATCH 055/462] fix modifier --- contracts/p0/StRSR.sol | 2 +- contracts/p1/StRSR.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index a1e42afb5f..c347ffcfce 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -218,7 +218,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { main.rsr().safeTransfer(account, total); } - function cancelUnstake(uint256 endId) external notPausedOrFrozen { + function cancelUnstake(uint256 endId) external notTradingPausedOrFrozen { address account = _msgSender(); // IBasketHandler bh = main.basketHandler(); diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index a1ebd1ee02..df7008c512 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -335,7 +335,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab IERC20Upgradeable(address(rsr)).safeTransfer(account, rsrAmount); } - function cancelUnstake(uint256 endId) external notPausedOrFrozen { + function cancelUnstake(uint256 endId) external notTradingPausedOrFrozen { address account = _msgSender(); // We specifically allow unstaking when under collateralized From 42defe1bd92846855bcfb26de83a189887bdf0d1 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Fri, 14 Apr 2023 10:50:33 -0300 Subject: [PATCH 056/462] fix extreme test --- test/integration/EasyAuction.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index ca6937cc08..44f6c4dc59 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -770,7 +770,8 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function 1, 1 ) - await main.connect(owner).unpause() + await main.connect(owner).tradingUnpause() + await main.connect(owner).issuanceUnpause() await broker.init(main.address, easyAuction.address, ONE_ADDRESS, config.auctionLength) const sellTok = await ERC20Factory.deploy('Sell Token', 'SELL', sellTokDecimals) const buyTok = await ERC20Factory.deploy('Buy Token', 'BUY', buyTokDecimals) From 8e48d40ed24477fda5799b61ce8fb392e9b928a0 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 17 Apr 2023 09:20:57 -0300 Subject: [PATCH 057/462] rename functions --- contracts/facade/FacadeWrite.sol | 8 +++---- contracts/interfaces/IMain.sol | 8 +++---- contracts/mixins/Auth.sol | 8 +++---- tasks/deprecated/deploy.ts | 4 ++-- tasks/deprecated/p0/deploy_all.ts | 4 ++-- test/Broker.test.ts | 6 +++--- test/FacadeRead.test.ts | 4 ++-- test/FacadeWrite.test.ts | 4 ++-- test/Furnace.test.ts | 2 +- test/Main.test.ts | 32 ++++++++++++++-------------- test/RToken.test.ts | 12 +++++------ test/Recollateralization.test.ts | 2 +- test/Revenues.test.ts | 12 +++++------ test/ZZStRSR.test.ts | 14 ++++++------ test/integration/EasyAuction.test.ts | 4 ++-- 15 files changed, 62 insertions(+), 62 deletions(-) diff --git a/contracts/facade/FacadeWrite.sol b/contracts/facade/FacadeWrite.sol index 190aa1b812..4ce6b2fb22 100644 --- a/contracts/facade/FacadeWrite.sol +++ b/contracts/facade/FacadeWrite.sol @@ -111,8 +111,8 @@ contract FacadeWrite is IFacadeWrite { } // Pause until setupGovernance - main.tradingPause(); - main.issuancePause(); + main.pauseTrading(); + main.pauseIssuance(); // Setup deployer as owner to complete next step - do not renounce roles yet main.grantRole(OWNER, msg.sender); @@ -190,8 +190,8 @@ contract FacadeWrite is IFacadeWrite { // Unpause if required if (unpause) { - main.tradingUnpause(); - main.issuanceUnpause(); + main.unpauseTrading(); + main.unpauseIssuance(); } // Transfer Ownership and renounce roles diff --git a/contracts/interfaces/IMain.sol b/contracts/interfaces/IMain.sol index c59eaf22f9..06ff3639b3 100644 --- a/contracts/interfaces/IMain.sol +++ b/contracts/interfaces/IMain.sol @@ -102,13 +102,13 @@ interface IAuth is IAccessControlUpgradeable { // onlyRole(OWNER) function unfreeze() external; - function tradingPause() external; + function pauseTrading() external; - function tradingUnpause() external; + function unpauseTrading() external; - function issuancePause() external; + function pauseIssuance() external; - function issuanceUnpause() external; + function unpauseIssuance() external; } interface IComponentRegistry { diff --git a/contracts/mixins/Auth.sol b/contracts/mixins/Auth.sol index 0f831cb695..fb06e82aff 100644 --- a/contracts/mixins/Auth.sol +++ b/contracts/mixins/Auth.sol @@ -171,28 +171,28 @@ abstract contract Auth is AccessControlUpgradeable, IAuth { // === Pausing === // checks: caller has PAUSER // effects: tradingPaused' = true - function tradingPause() external onlyRole(PAUSER) { + function pauseTrading() external onlyRole(PAUSER) { emit TradingPausedSet(tradingPaused, true); tradingPaused = true; } // checks: caller has PAUSER // effects: tradingPaused' = false - function tradingUnpause() external onlyRole(PAUSER) { + function unpauseTrading() external onlyRole(PAUSER) { emit TradingPausedSet(tradingPaused, false); tradingPaused = false; } // checks: caller has PAUSER // effects: issuancePaused' = true - function issuancePause() external onlyRole(PAUSER) { + function pauseIssuance() external onlyRole(PAUSER) { emit IssuancePausedSet(issuancePaused, true); issuancePaused = true; } // checks: caller has PAUSER // effects: issuancePaused' = false - function issuanceUnpause() external onlyRole(PAUSER) { + function unpauseIssuance() external onlyRole(PAUSER) { emit IssuancePausedSet(issuancePaused, false); issuancePaused = false; } diff --git a/tasks/deprecated/deploy.ts b/tasks/deprecated/deploy.ts index ef67fbefd7..39ffb0472a 100644 --- a/tasks/deprecated/deploy.ts +++ b/tasks/deprecated/deploy.ts @@ -95,8 +95,8 @@ task('deploy', 'Deploy protocol smart contracts').setAction(async (params, hre) await basketHandler.connect(deployer).refreshBasket() console.log('Unpausing...') - await main.connect(deployer).tradingUnpause() - await main.connect(deployer).issuanceUnpause() + await main.connect(deployer).unpauseTrading() + await main.connect(deployer).unpauseIssuance() // Grant allowances for (let i = 0; i < basketCollaterals.length; i++) { diff --git a/tasks/deprecated/p0/deploy_all.ts b/tasks/deprecated/p0/deploy_all.ts index 6f972b9f9e..e37f2f3cfd 100644 --- a/tasks/deprecated/p0/deploy_all.ts +++ b/tasks/deprecated/p0/deploy_all.ts @@ -84,8 +84,8 @@ task('P0-deploy', 'Deploys all Protocol components and an RToken').setAction( await basketHandler.connect(deployer).refreshBasket() console.log('Unpausing...') - await main.connect(deployer).tradingUnpause() - await main.connect(deployer).issuanceUnpause() + await main.connect(deployer).unpauseTrading() + await main.connect(deployer).unpauseIssuance() // TODO: Test remove console.log('RSR Funding') diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 9ffd697ba8..34edb5af00 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -274,7 +274,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) it('Should not allow to open trade if paused', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() // Attempt to open trade const tradeRequest: ITradeRequest = { @@ -373,13 +373,13 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // Check not disabled expect(await broker.disabled()).to.equal(false) - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await expect(broker.connect(addr1).reportViolation()).to.be.revertedWith( 'frozen or trading paused' ) - await main.connect(owner).tradingUnpause() + await main.connect(owner).unpauseTrading() await main.connect(owner).freezeShort() diff --git a/test/FacadeRead.test.ts b/test/FacadeRead.test.ts index d10185d76a..0b2ed0c76a 100644 --- a/test/FacadeRead.test.ts +++ b/test/FacadeRead.test.ts @@ -400,7 +400,7 @@ describe('FacadeRead contract', () => { }) it('Should return basketBreakdown correctly for paused token', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await expectValidBasketBreakdown(rToken) }) @@ -419,7 +419,7 @@ describe('FacadeRead contract', () => { ) // set price of dai to 0 await chainlinkFeed.updateAnswer(0) - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() const [erc20s, breakdown, targets] = await facade.callStatic.basketBreakdown(rToken.address) expect(erc20s.length).to.equal(4) expect(breakdown.length).to.equal(4) diff --git a/test/FacadeWrite.test.ts b/test/FacadeWrite.test.ts index 7b7ba8923d..5147331ec8 100644 --- a/test/FacadeWrite.test.ts +++ b/test/FacadeWrite.test.ts @@ -519,8 +519,8 @@ describe('FacadeWrite contract', () => { it('Should register Basket correctly', async () => { // Unpause - await main.connect(owner).tradingUnpause() - await main.connect(owner).issuanceUnpause() + await main.connect(owner).unpauseTrading() + await main.connect(owner).unpauseIssuance() // Basket expect(await basketHandler.fullyCollateralized()).to.equal(true) diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index bf16690d0e..1b91eabc8a 100644 --- a/test/Furnace.test.ts +++ b/test/Furnace.test.ts @@ -180,7 +180,7 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { }) it('Should not melt if paused #fast', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await expect(furnace.connect(addr1).melt()).to.be.revertedWith('frozen or trading paused') }) diff --git a/test/Main.test.ts b/test/Main.test.ts index e7b7559bd9..6c0c15ebbf 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -540,8 +540,8 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(await main.issuancePausedOrFrozen()).to.equal(false) // Pause with PAUSER - await main.connect(addr1).tradingPause() - await main.connect(addr1).issuancePause() + await main.connect(addr1).pauseTrading() + await main.connect(addr1).pauseIssuance() // Check if Paused, should not lose PAUSER expect(await main.tradingPaused()).to.equal(true) @@ -550,15 +550,15 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(await main.hasRole(PAUSER, addr1.address)).to.equal(true) // Unpause - await main.connect(addr1).tradingUnpause() - await main.connect(addr1).issuanceUnpause() + await main.connect(addr1).unpauseTrading() + await main.connect(addr1).unpauseIssuance() expect(await main.tradingPaused()).to.equal(false) expect(await main.issuancePaused()).to.equal(false) // OWNER should still be able to Pause - await main.connect(owner).tradingPause() - await main.connect(owner).issuancePause() + await main.connect(owner).pauseTrading() + await main.connect(owner).pauseIssuance() // Check if Paused expect(await main.tradingPausedOrFrozen()).to.equal(true) @@ -568,16 +568,16 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) it('Should not allow to Pause/Unpause if not PAUSER or OWNER', async () => { - await expect(main.connect(other).tradingPause()).to.be.reverted - await expect(main.connect(other).issuancePause()).to.be.reverted + await expect(main.connect(other).pauseTrading()).to.be.reverted + await expect(main.connect(other).pauseIssuance()).to.be.reverted // Check no changes expect(await main.tradingPaused()).to.equal(false) expect(await main.issuancePaused()).to.equal(false) // Attempt to unpause - await expect(main.connect(other).tradingUnpause()).to.be.reverted - await expect(main.connect(other).issuanceUnpause()).to.be.reverted + await expect(main.connect(other).unpauseTrading()).to.be.reverted + await expect(main.connect(other).unpauseIssuance()).to.be.reverted // Check no changes expect(await main.tradingPaused()).to.equal(false) @@ -1047,8 +1047,8 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) it('Should grant allowances when paused', async () => { - await main.connect(owner).tradingPause() - await main.connect(owner).issuancePause() + await main.connect(owner).pauseTrading() + await main.connect(owner).pauseIssuance() await expect(backingManager.grantRTokenAllowance(ZERO_ADDRESS)).to.be.revertedWith( 'erc20 unregistered' ) @@ -1796,7 +1796,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) it('Should not allow to refresh basket if not OWNER when paused', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await expect(basketHandler.connect(other).refreshBasket()).to.be.revertedWith( 'basket unrefreshable' ) @@ -1810,7 +1810,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) it('Should not allow to poke when paused', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await expect(main.connect(other).poke()).to.be.revertedWith('frozen or trading paused') }) @@ -1832,7 +1832,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) it('Should allow to call refresh Basket if OWNER and paused - No changes', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() // Switch basket - No backup nor default await expect(basketHandler.connect(owner).refreshBasket()).to.emit(basketHandler, 'BasketSet') @@ -1849,7 +1849,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { // Not updated so basket last changed is not set expect(await basketHandler.timestamp()).to.be.gt(bn(0)) expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - await main.connect(owner).tradingUnpause() + await main.connect(owner).unpauseTrading() expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) }) diff --git a/test/RToken.test.ts b/test/RToken.test.ts index b9ffdabf98..5af276aa2b 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -301,7 +301,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { const issueAmount: BigNumber = bn('10e18') // Pause Main - await main.connect(owner).issuancePause() + await main.connect(owner).pauseIssuance() // Try to issue await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith( @@ -1028,8 +1028,8 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) it('Should redeem if paused #fast', async function () { - await main.connect(owner).tradingPause() - await main.connect(owner).issuancePause() + await main.connect(owner).pauseTrading() + await main.connect(owner).pauseIssuance() await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) expect(await rToken.totalSupply()).to.equal(0) }) @@ -1491,7 +1491,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) it('Should not mint if paused', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await whileImpersonating(backingManager.address, async (signer) => { await expect(rToken.connect(signer).mint(addr1.address, bn('10e18'))).to.be.revertedWith( @@ -1529,7 +1529,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.basketsNeeded()).to.equal(issueAmount) // Pause Main - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() // Try to set baskets needed await whileImpersonating(backingManager.address, async (bhSigner) => { @@ -1874,7 +1874,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) it('should not monetize while paused', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await expect(rToken.monetizeDonations(token3.address)).to.be.revertedWith( 'frozen or trading paused' ) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 6323acdab0..081e158845 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -800,7 +800,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) it('Should not trade if paused', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await expect(backingManager.manageTokens([])).to.be.revertedWith('frozen or trading paused') await expect(backingManager.manageTokensSortedOrder([])).to.be.revertedWith( 'frozen or trading paused' diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 52d0582e8f..24156f4b30 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -379,7 +379,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) it('Should not trade if paused', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await expect(rsrTrader.manageToken(ZERO_ADDRESS)).to.be.revertedWith( 'frozen or trading paused' ) @@ -393,7 +393,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) it('Should not claim rewards if paused', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await expect(rTokenTrader.claimRewards()).to.be.revertedWith('frozen or trading paused') }) @@ -403,7 +403,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) it('Should not claim single rewards if paused', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await expect(rTokenTrader.claimRewardsSingle(token2.address)).to.be.revertedWith( 'frozen or trading paused' ) @@ -425,7 +425,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) it('Should not settle trade if paused', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await expect(rTokenTrader.settleTrade(ZERO_ADDRESS)).to.be.revertedWith( 'frozen or trading paused' ) @@ -1616,13 +1616,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should not distribute if paused or frozen', async () => { const distAmount: BigNumber = bn('100e18') - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await expect(distributor.distribute(rsr.address, distAmount)).to.be.revertedWith( 'frozen or trading paused' ) - await main.connect(owner).tradingUnpause() + await main.connect(owner).unpauseTrading() await main.connect(owner).freezeShort() diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index c64ddc0f48..df4427a2aa 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -387,8 +387,8 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { const amount: BigNumber = bn('1000e18') // Pause Main - await main.connect(owner).tradingPause() - await main.connect(owner).issuancePause() + await main.connect(owner).pauseTrading() + await main.connect(owner).pauseIssuance() // Approve transfer and stake await rsr.connect(addr1).approve(stRSR.address, amount) @@ -486,7 +486,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { }) it('Should not unstake if paused', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await expect(stRSR.connect(addr1).unstake(0)).to.be.revertedWith('frozen or trading paused') }) @@ -672,7 +672,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay) // Pause Main - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() // Withdraw await expect(stRSR.connect(addr1).withdraw(addr1.address, 1)).to.be.revertedWith( @@ -680,7 +680,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { ) // If unpaused should withdraw OK - await main.connect(owner).tradingUnpause() + await main.connect(owner).unpauseTrading() // Withdraw await stRSR.connect(addr1).withdraw(addr1.address, 1) @@ -1239,7 +1239,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { }) it('Rewards should not be handed out when paused but staking should still work', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp()))) // Stake @@ -1424,7 +1424,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { }) it('Should not allow to remove RSR if paused', async () => { - await main.connect(owner).tradingPause() + await main.connect(owner).pauseTrading() await whileImpersonating(backingManager.address, async (signer) => { await expect(stRSR.connect(signer).seizeRSR(1)).to.be.revertedWith( 'frozen or trading paused' diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 44f6c4dc59..39fc9d64f5 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -770,8 +770,8 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function 1, 1 ) - await main.connect(owner).tradingUnpause() - await main.connect(owner).issuanceUnpause() + await main.connect(owner).unpauseTrading() + await main.connect(owner).unpauseIssuance() await broker.init(main.address, easyAuction.address, ONE_ADDRESS, config.auctionLength) const sellTok = await ERC20Factory.deploy('Sell Token', 'SELL', sellTokDecimals) const buyTok = await ERC20Factory.deploy('Buy Token', 'BUY', buyTokDecimals) From 087c396619893e2554d364751455c4f5946db762 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Tue, 18 Apr 2023 09:24:46 -0300 Subject: [PATCH 058/462] stake and delegate in StRSR --- contracts/interfaces/IStRSRVotes.sol | 4 ++++ contracts/p1/StRSR.sol | 2 +- contracts/p1/StRSRVotes.sol | 8 ++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/contracts/interfaces/IStRSRVotes.sol b/contracts/interfaces/IStRSRVotes.sol index 0a1218483a..d7062a0df6 100644 --- a/contracts/interfaces/IStRSRVotes.sol +++ b/contracts/interfaces/IStRSRVotes.sol @@ -9,4 +9,8 @@ interface IStRSRVotes is IVotesUpgradeable { /// @return The era at a past block number function getPastEra(uint256 blockNumber) external view returns (uint256); + + /// Stakes an RSR `amount` on the corresponding RToken and delegates + /// votes from the sender to `delegatee` or self + function stakeAndDelegate(uint256 amount, address delegatee) external; } diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index df7008c512..829a2c1ea1 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -208,7 +208,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // // actions: // rsr.transferFrom(account, this, rsrAmount) - function stake(uint256 rsrAmount) external { + function stake(uint256 rsrAmount) public { require(rsrAmount > 0, "Cannot stake zero"); if (!main.frozen()) _payoutRewards(); diff --git a/contracts/p1/StRSRVotes.sol b/contracts/p1/StRSRVotes.sol index d5ca30912e..67b12d60c6 100644 --- a/contracts/p1/StRSRVotes.sol +++ b/contracts/p1/StRSRVotes.sol @@ -134,6 +134,14 @@ contract StRSRP1Votes is StRSRP1, IStRSRVotes { _delegate(signer, delegatee); } + /// Stakes an RSR `amount` on the corresponding RToken and delegates + /// votes from the sender to `delegatee` or self + function stakeAndDelegate(uint256 rsrAmount, address delegatee) public { + stake(rsrAmount); + address actualDelegatee = (delegatee == address(0) ? _msgSender() : delegatee); + _delegate(_msgSender(), actualDelegatee); + } + function _mint(address account, uint256 amount) internal override { super._mint(account, amount); _writeCheckpoint(_totalSupplyCheckpoints[era], _add, amount); From bf9c98bbf35a99ab5fb87604067ec42b66f2171f Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Tue, 18 Apr 2023 09:25:01 -0300 Subject: [PATCH 059/462] tests for stake and delegate --- test/Governance.test.ts | 5 ++- test/ZZStRSR.test.ts | 74 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/test/Governance.test.ts b/test/Governance.test.ts index 8bd8dce013..0247d8040f 100644 --- a/test/Governance.test.ts +++ b/test/Governance.test.ts @@ -198,10 +198,9 @@ describeP1(`Governance - P${IMPLEMENTATION}`, () => { expect(await governor.getVotes(addr2.address, currBlockNumber)).to.equal(0) expect(await governor.getVotes(addr3.address, currBlockNumber)).to.equal(0) - // Stake some RSR with addr2 - And delegate + // Stake some RSR with addr2, delegate in same transaction await rsr.connect(addr2).approve(stRSRVotes.address, stkAmt1) - await stRSRVotes.connect(addr2).stake(stkAmt1) - await stRSRVotes.connect(addr2).delegate(addr2.address) + await stRSRVotes.connect(addr2).stakeAndDelegate(stkAmt1, ZERO_ADDRESS) // Advance a few blocks await advanceBlocks(2) diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index df4427a2aa..8b3ff8b5aa 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -2642,6 +2642,80 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { expect(await stRSRVotes.getVotes(addr3.address)).to.equal(0) }) + it('Should allow to stake and delegate in single transaction', async function () { + // Should perform basic validations on stake + await expect( + stRSRVotes.connect(addr1).stakeAndDelegate(bn(0), ZERO_ADDRESS) + ).to.be.revertedWith('Cannot stake zero') + + // Perform some stakes - delegate to self + const amount1: BigNumber = bn('50e18') + await rsr.connect(addr1).approve(stRSRVotes.address, amount1) + await expect(stRSRVotes.connect(addr1).stakeAndDelegate(amount1, ZERO_ADDRESS)).to.emit( + stRSRVotes, + 'Staked' + ) + + // Check deposit registered + expect(await rsr.balanceOf(stRSRVotes.address)).to.equal(amount1) + expect(await rsr.balanceOf(addr1.address)).to.equal(initialBal.sub(amount1)) + expect(await stRSR.balanceOf(addr1.address)).to.equal(amount1) + + // Check checkpoint stored - no need to delegate + expect(await stRSRVotes.numCheckpoints(addr1.address)).to.equal(1) + expect(await stRSRVotes.checkpoints(addr1.address, 0)).to.eql([ + await getLatestBlockNumber(), + amount1, + ]) + + // Advance block + await advanceBlocks(1) + + // Check new values - Now properly counted + let currentBlockNumber = (await getLatestBlockNumber()) - 1 + expect(await stRSRVotes.getPastTotalSupply(currentBlockNumber)).to.equal(amount1) + expect(await stRSRVotes.getPastVotes(addr1.address, currentBlockNumber)).to.equal(amount1) + expect(await stRSRVotes.getPastVotes(addr2.address, currentBlockNumber)).to.equal(0) + expect(await stRSRVotes.getPastVotes(addr3.address, currentBlockNumber)).to.equal(0) + expect(await stRSRVotes.getPastEra(currentBlockNumber)).to.equal(1) + + // Check current votes + expect(await stRSRVotes.getVotes(addr1.address)).to.equal(amount1) + + // Perform some stakes with another user, delegate to a third address + const amount2: BigNumber = bn('40e18') + await rsr.connect(addr2).approve(stRSRVotes.address, amount2) + await stRSRVotes.connect(addr2).stakeAndDelegate(amount2, addr3.address) + + // Check deposit registered + expect(await rsr.balanceOf(stRSRVotes.address)).to.equal(amount1.add(amount2)) + expect(await rsr.balanceOf(addr2.address)).to.equal(initialBal.sub(amount2)) + expect(await stRSR.balanceOf(addr2.address)).to.equal(amount2) + + // Check checkpoint stored for delegatee correctly + expect(await stRSRVotes.numCheckpoints(addr3.address)).to.equal(1) + expect(await stRSRVotes.checkpoints(addr3.address, 0)).to.eql([ + await getLatestBlockNumber(), + amount2, + ]) + + // Advance block + await advanceBlocks(1) + + // Check new values - Counting votes for addr3 + currentBlockNumber = (await getLatestBlockNumber()) - 1 + expect(await stRSRVotes.getPastTotalSupply(currentBlockNumber)).to.equal(amount1.add(amount2)) + expect(await stRSRVotes.getPastVotes(addr1.address, currentBlockNumber)).to.equal(amount1) + expect(await stRSRVotes.getPastVotes(addr2.address, currentBlockNumber)).to.equal(0) + expect(await stRSRVotes.getPastVotes(addr3.address, currentBlockNumber)).to.equal(amount2) + expect(await stRSRVotes.getPastEra(currentBlockNumber)).to.equal(1) + + // Check current votes + expect(await stRSRVotes.getVotes(addr1.address)).to.equal(amount1) + expect(await stRSRVotes.getVotes(addr2.address)).to.equal(0) + expect(await stRSRVotes.getVotes(addr3.address)).to.equal(amount2) + }) + it('Should register single checkpoint per block per account', async function () { // Set automine to false for multiple transactions in one block await hre.network.provider.send('evm_setAutomine', [false]) From 5c8eed1db254c242694266aa52864b52fa9dbb5a Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:10:51 -0300 Subject: [PATCH 060/462] do not set new delegate with zero address --- contracts/p1/StRSRVotes.sol | 13 +++++++++---- test/ZZStRSR.test.ts | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/contracts/p1/StRSRVotes.sol b/contracts/p1/StRSRVotes.sol index 67b12d60c6..bbff91ade6 100644 --- a/contracts/p1/StRSRVotes.sol +++ b/contracts/p1/StRSRVotes.sol @@ -134,12 +134,17 @@ contract StRSRP1Votes is StRSRP1, IStRSRVotes { _delegate(signer, delegatee); } - /// Stakes an RSR `amount` on the corresponding RToken and delegates + /// Stakes an RSR `amount` on the corresponding RToken and allows to delegate /// votes from the sender to `delegatee` or self - function stakeAndDelegate(uint256 rsrAmount, address delegatee) public { + function stakeAndDelegate(uint256 rsrAmount, address delegatee) external { stake(rsrAmount); - address actualDelegatee = (delegatee == address(0) ? _msgSender() : delegatee); - _delegate(_msgSender(), actualDelegatee); + address msgSender = _msgSender(); + + // Delegate if a specific delegatee is provided or if no delegate yet + if (delegatee != address(0) || delegates(msgSender) == address(0)) { + address newDelegatee = (delegatee == address(0) ? msgSender : delegatee); + _delegate(msgSender, newDelegatee); + } } function _mint(address account, uint256 amount) internal override { diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 8b3ff8b5aa..0273cfd111 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -2648,6 +2648,8 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { stRSRVotes.connect(addr1).stakeAndDelegate(bn(0), ZERO_ADDRESS) ).to.be.revertedWith('Cannot stake zero') + expect(await stRSRVotes.delegates(addr1.address)).to.equal(ZERO_ADDRESS) + // Perform some stakes - delegate to self const amount1: BigNumber = bn('50e18') await rsr.connect(addr1).approve(stRSRVotes.address, amount1) @@ -2661,7 +2663,8 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { expect(await rsr.balanceOf(addr1.address)).to.equal(initialBal.sub(amount1)) expect(await stRSR.balanceOf(addr1.address)).to.equal(amount1) - // Check checkpoint stored - no need to delegate + // Check checkpoint stored - no need to delegate again + expect(await stRSRVotes.delegates(addr1.address)).to.equal(addr1.address) expect(await stRSRVotes.numCheckpoints(addr1.address)).to.equal(1) expect(await stRSRVotes.checkpoints(addr1.address, 0)).to.eql([ await getLatestBlockNumber(), @@ -2683,6 +2686,8 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { expect(await stRSRVotes.getVotes(addr1.address)).to.equal(amount1) // Perform some stakes with another user, delegate to a third address + expect(await stRSRVotes.delegates(addr2.address)).to.equal(ZERO_ADDRESS) + const amount2: BigNumber = bn('40e18') await rsr.connect(addr2).approve(stRSRVotes.address, amount2) await stRSRVotes.connect(addr2).stakeAndDelegate(amount2, addr3.address) @@ -2693,6 +2698,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { expect(await stRSR.balanceOf(addr2.address)).to.equal(amount2) // Check checkpoint stored for delegatee correctly + expect(await stRSRVotes.delegates(addr2.address)).to.equal(addr3.address) expect(await stRSRVotes.numCheckpoints(addr3.address)).to.equal(1) expect(await stRSRVotes.checkpoints(addr3.address, 0)).to.eql([ await getLatestBlockNumber(), @@ -2714,6 +2720,34 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { expect(await stRSRVotes.getVotes(addr1.address)).to.equal(amount1) expect(await stRSRVotes.getVotes(addr2.address)).to.equal(0) expect(await stRSRVotes.getVotes(addr3.address)).to.equal(amount2) + + // By passing the zero address it keeps current delegate (if one defined already) + const amount3: BigNumber = bn('10e18') + await rsr.connect(addr2).approve(stRSRVotes.address, amount3) + await stRSRVotes.connect(addr2).stakeAndDelegate(amount3, ZERO_ADDRESS) + + // Delegate remains the same + expect(await stRSRVotes.delegates(addr2.address)).to.equal(addr3.address) + + // Check deposit registered + expect(await rsr.balanceOf(stRSRVotes.address)).to.equal(amount1.add(amount2).add(amount3)) + expect(await rsr.balanceOf(addr2.address)).to.equal(initialBal.sub(amount2).sub(amount3)) + expect(await stRSR.balanceOf(addr2.address)).to.equal(amount2.add(amount3)) + + // Check checkpoint stored for delegatee correctly + expect(await stRSRVotes.numCheckpoints(addr3.address)).to.equal(2) + expect(await stRSRVotes.checkpoints(addr3.address, 1)).to.eql([ + await getLatestBlockNumber(), + amount2.add(amount3), + ]) + + // Advance block + await advanceBlocks(1) + + // Check current votes + expect(await stRSRVotes.getVotes(addr1.address)).to.equal(amount1) + expect(await stRSRVotes.getVotes(addr2.address)).to.equal(0) + expect(await stRSRVotes.getVotes(addr3.address)).to.equal(amount2.add(amount3)) }) it('Should register single checkpoint per block per account', async function () { From e0c208b31d4343d231ee82a62dae1b6eeb51470f Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:16:53 -0300 Subject: [PATCH 061/462] change comment --- contracts/interfaces/IStRSRVotes.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/interfaces/IStRSRVotes.sol b/contracts/interfaces/IStRSRVotes.sol index d7062a0df6..1f59684708 100644 --- a/contracts/interfaces/IStRSRVotes.sol +++ b/contracts/interfaces/IStRSRVotes.sol @@ -10,7 +10,7 @@ interface IStRSRVotes is IVotesUpgradeable { /// @return The era at a past block number function getPastEra(uint256 blockNumber) external view returns (uint256); - /// Stakes an RSR `amount` on the corresponding RToken and delegates + /// Stakes an RSR `amount` on the corresponding RToken and allows to delegate /// votes from the sender to `delegatee` or self function stakeAndDelegate(uint256 amount, address delegatee) external; } From d63f575005874c3862346658cec2ccbdd895f21b Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 19 Apr 2023 14:18:31 -0300 Subject: [PATCH 062/462] avoid redelegating --- contracts/p1/StRSRVotes.sol | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/contracts/p1/StRSRVotes.sol b/contracts/p1/StRSRVotes.sol index bbff91ade6..4ba528bd72 100644 --- a/contracts/p1/StRSRVotes.sol +++ b/contracts/p1/StRSRVotes.sol @@ -139,11 +139,14 @@ contract StRSRP1Votes is StRSRP1, IStRSRVotes { function stakeAndDelegate(uint256 rsrAmount, address delegatee) external { stake(rsrAmount); address msgSender = _msgSender(); - - // Delegate if a specific delegatee is provided or if no delegate yet - if (delegatee != address(0) || delegates(msgSender) == address(0)) { - address newDelegatee = (delegatee == address(0) ? msgSender : delegatee); - _delegate(msgSender, newDelegatee); + address currentDelegate = delegates(msgSender); + + if (delegatee == address(0) && currentDelegate == address(0)) { + // Delegate to self if no delegate defined and no delegatee provided + _delegate(msgSender, msgSender); + } else if (delegatee != address(0) && currentDelegate != delegatee) { + // Delegate to delegatee if provided and different than current delegate + _delegate(msgSender, delegatee); } } From 47d1e0a9ea7a6ac644a1f23545244d3cfa3b926f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 11 Apr 2023 16:50:42 -0400 Subject: [PATCH 063/462] initial design --- contracts/interfaces/IAssetRegistry.sol | 3 + contracts/interfaces/IBasketHandler.sol | 14 +++ contracts/interfaces/IRToken.sol | 13 +++ contracts/p1/AssetRegistry.sol | 5 ++ contracts/p1/BasketHandler.sol | 108 +++++++++++++++++++++++- contracts/p1/RToken.sol | 71 ++++++++++++++-- 6 files changed, 202 insertions(+), 12 deletions(-) diff --git a/contracts/interfaces/IAssetRegistry.sol b/contracts/interfaces/IAssetRegistry.sol index 58534cf1da..450aa7e3fe 100644 --- a/contracts/interfaces/IAssetRegistry.sol +++ b/contracts/interfaces/IAssetRegistry.sol @@ -52,6 +52,9 @@ interface IAssetRegistry is IComponent { /// @return reg The list of registered ERC20s and Assets, in the same order function getRegistry() external view returns (Registry memory reg); + /// @return The number of registered ERC20s + function size() external view returns (uint256); + function register(IAsset asset) external returns (bool); function swapRegistered(IAsset asset) external returns (bool swapped); diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 9454343517..f3030c7995 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -119,6 +119,20 @@ interface IBasketHandler is IComponent { view returns (address[] memory erc20s, uint256[] memory quantities); + /// Return the redemption value of `amount` BUs for a linear combination of historical baskets + /// Requires `portions` sums to FIX_ONE + /// @param basketNonces An array of basket nonces to do redemption from + /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE + /// @param amount {BU} + /// @return erc20s The backing collateral erc20s + /// @return quantities {qTok} ERC20 token quantities equal to `amount` BUs + // Returns (erc20s, [quantity(e) * amount {as qTok} for e in erc20s]) + function quoteHistoricalRedemption( + uint48[] memory basketNonces, + uint192[] memory portions, + uint192 amount + ) external view returns (address[] memory erc20s, uint256[] memory quantities); + /// @return top {BU} The number of partial basket units: e.g max(coll.map((c) => c.balAsBUs()) /// bottom {BU} The number of whole basket units held by the account function basketsHeldBy(address account) external view returns (BasketRange memory); diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index ad0e1ea1b1..23c65931b7 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -95,6 +95,19 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea uint48 basketNonce ) external; + /// Redeem RToken for a linear combination of historical baskets, to a particular recipient + /// @param recipient The address to receive the backing collateral tokens + /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem + /// @param basketNonces An array of basket nonces to do redemption from + /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE + /// @custom:interaction + function historicalRedeemTo( + address recipient, + uint256 amount, + uint48[] memory basketNonces, + uint192[] memory portions + ) external; + /// Mints a quantity of RToken to the `recipient`, callable only by the BackingManager /// @param recipient The recipient of the newly minted RToken /// @param amount {qRTok} The amount to be minted diff --git a/contracts/p1/AssetRegistry.sol b/contracts/p1/AssetRegistry.sol index dae31f0cd0..71a17e0544 100644 --- a/contracts/p1/AssetRegistry.sol +++ b/contracts/p1/AssetRegistry.sol @@ -155,6 +155,11 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { } } + /// @return The number of registered ERC20s + function size() external view returns (uint256) { + return _erc20s.length(); + } + /// Register an asset /// Forbids registering a different asset for an ERC20 that is already registered /// @return registered If the asset was moved from unregistered to registered diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 4d92643a6e..19df6f2cc5 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -140,11 +140,16 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // and everything except redemption should be paused. bool private disabled; + // === Function-local transitory vars === + // These are effectively local variables of _switchBasket. // Nothing should use their values from previous transactions. EnumerableSet.Bytes32Set private _targetNames; Basket private _newBasket; // Always empty + // === Warmup Period === + // Added in 3.0.0 + // Warmup Period uint48 public warmupPeriod; // {s} how long to wait until issuance/trading after regaining SOUND @@ -153,6 +158,20 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { uint48 private lastStatusTimestamp; CollateralStatus private lastStatus; + // === Historical basket nonces === + // Added in 3.0.0 + + // Nonce of the first reference basket from the current history + // A new historical record begins whenever the prime basket is changed + // There can be 0 to any number of reference baskets from the current history + uint48 private historicalNonce; // {nonce} + // TODO double-check historicalNonce fits in the Warmup Period slot + + // The historical baskets by basket nonce; includes current basket + mapping(uint48 => Basket) private historicalBaskets; + + // === + // ==== Invariants ==== // basket is a valid Basket: // basket.erc20s is a valid collateral array and basket.erc20s == keys(basket.refAmts) @@ -275,6 +294,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { config.targetNames[erc20s[i]] = names[i]; } + historicalNonce = nonce + 1; // set historicalNonce to the next nonce emit PrimeBasketSet(erc20s, targetAmts, names); } @@ -449,6 +469,86 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } } + /// Return the redemption value of `amount` BUs for a linear combination of historical baskets + /// Requires `portions` sums to FIX_ONE + /// @param basketNonces An array of basket nonces to do redemption from + /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE + /// @param amount {BU} + /// @return erc20s The backing collateral erc20s + /// @return quantities {qTok} ERC20 token quantities equal to `amount` BUs + // Returns (erc20s, [quantity(e) * amount {as qTok} for e in erc20s]) + function quoteHistoricalRedemption( + uint48[] memory basketNonces, + uint192[] memory portions, + uint192 amount + ) external view returns (address[] memory erc20s, uint256[] memory quantities) { + require(basketNonces.length == portions.length, "portions does not mirror basketNonces"); + uint256 lenAll = assetRegistry.size(); + IERC20[] memory erc20sAll = new IERC20[](lenAll); + uint192[] memory refAmtsAll = new uint192[](lenAll); + + uint256 len; // length of return arrays + + // Calculate the linear combination basket + for (uint48 i = 0; i < basketNonces.length; ++i) { + require( + basketNonces[i] >= historicalNonce && basketNonces[i] <= nonce, + "invalid basketNonce" + ); // will always revert directly after setPrimeBasket() + Basket storage b = historicalBaskets[i]; + + // Add-in refAmts contribution from historical basket + for (uint256 j = 0; j < b.erc20s.length; ++j) { + IERC20 erc20 = b.erc20s[j]; + if (address(erc20) == address(0)) continue; + + // Ugly search through erc20sAll + uint256 erc20Index = type(uint256).max; + for (uint256 k = 0; k < len; ++k) { + if (erc20 == erc20sAll[k]) { + erc20Index = k; + continue; + } + } + + // Add new ERC20 entry if not found + if (erc20Index == type(uint256).max) { + erc20sAll[len] = erc20; + + // {ref} = {1} * {ref} + refAmtsAll[len] = portions[j].mul(b.refAmts[erc20], FLOOR); + ++len; + } else { + // {ref} = {1} * {ref} + refAmtsAll[erc20Index] += portions[j].mul(b.refAmts[erc20], FLOOR); + } + } + } + + erc20s = new address[](len); + quantities = new uint256[](len); + + // Calculate quantities + for (uint256 i = 0; i < len; ++i) { + IERC20Metadata erc20 = IERC20Metadata(address(erc20sAll[i])); + erc20s[i] = address(erc20); + IAsset asset = assetRegistry.toAsset(erc20); + if (!asset.isCollateral()) continue; // skip token if no longer registered + + // prevent div-by-zero + uint192 refPerTok = ICollateral(address(asset)).refPerTok(); + if (refPerTok == 0) continue; // quantities[i] = 0; + + // {tok} = {BU} * {ref/BU} / {ref/tok} + quantities[i] = amount.mulDiv(refAmtsAll[i], refPerTok, FLOOR).shiftl_toUint( + int8(erc20.decimals()), + FLOOR + ); + // slightly more penalizing than its sibling calculation that uses through _quantity() + // because does not intermediately CEIL as part of the division + } + } + /// @return baskets {BU} /// .top The number of partial basket units: e.g max(coll.map((c) => c.balAsBUs()) /// .bottom The number of whole basket units held by the account @@ -542,8 +642,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { ==== Usual specs ==== Then, finally, given all that, the effects of _switchBasket() are: - basket' = _newBasket, as defined above nonce' = nonce + 1 + basket' = _newBasket, as defined above timestamp' = now */ @@ -554,6 +654,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // _targetNames := {} while (_targetNames.length() > 0) _targetNames.remove(_targetNames.at(0)); + // _newBasket := {} _newBasket.empty(); @@ -665,8 +766,9 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // Update the basket if it's not disabled if (!disabled) { - basket.setFrom(_newBasket); nonce += 1; + basket.setFrom(_newBasket); + historicalBaskets[nonce].setFrom(_newBasket); timestamp = uint48(block.timestamp); } @@ -768,5 +870,5 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[41] private __gap; + uint256[40] private __gap; } diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index ac41d33ba7..bcfe393ae4 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -185,6 +185,40 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { } /// Redeem RToken for basket collateral to a particular recipient + /// @param recipient The address to receive the backing collateral tokens + /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem + /// @param basketNonce The nonce of the basket the redemption should be from; else reverts + /// @custom:interaction + function redeemTo( + address recipient, + uint256 amount, + uint48 basketNonce + ) public { + uint48[] memory basketNonces = new uint48[](1); + basketNonces[0] = basketNonce; + uint192[] memory portions = new uint192[](1); + portions[0] = FIX_ONE; + + _redeemTo(recipient, amount, basketNonces, portions); + } + + /// Redeem RToken for a linear combination of historical baskets, to a particular recipient + /// @param recipient The address to receive the backing collateral tokens + /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem + /// @param basketNonces An array of basket nonces to do redemption from + /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE + /// @custom:interaction + function historicalRedeemTo( + address recipient, + uint256 amount, + uint48[] memory basketNonces, + uint192[] memory portions + ) external { + _redeemTo(recipient, amount, basketNonces, portions); + } + + /// Redeem RToken for basket collateral to a particular recipient + /// Handles both historical and present-basket redemptions // checks: // amount > 0 // amount <= balanceOf(caller) @@ -206,13 +240,15 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem - /// @param basketNonce The nonce of the basket the redemption should be from; else reverts + /// @param basketNonces An array of basket nonces to do redemption from + /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE /// @custom:interaction - function redeemTo( + function _redeemTo( address recipient, uint256 amount, - uint48 basketNonce - ) public notFrozen exchangeRateIsValidAfter { + uint48[] memory basketNonces, + uint192[] memory portions + ) internal notFrozen exchangeRateIsValidAfter { // == Refresh == assetRegistry.refresh(); @@ -238,11 +274,28 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { uint192 basketsRedeemed = basketsNeeded.muluDivu(amount, supply); // FLOOR emit Redemption(redeemer, recipient, amount, basketsRedeemed); - require(basketHandler.nonce() == basketNonce, "non-current basket nonce"); - (address[] memory erc20s, uint256[] memory amounts) = basketHandler.quote( - basketsRedeemed, - FLOOR - ); + // Confirm portions sums to FIX_ONE + uint256 portionsSum; + for (uint256 i = 0; i < portions.length; ++i) portionsSum += portions[i]; + require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); + + address[] memory erc20s; + uint256[] memory amounts; + if (basketNonces.length == 1 && basketHandler.nonce() == basketNonces[0]) { + // Current-basket redemption + + require(portions.length == 1, "portions does not mirror basketNonces"); + (erc20s, amounts) = basketHandler.quote(basketsRedeemed, FLOOR); + } else { + // Historical basket redemption + + // BasketHandler will handle the require that portions sum to FIX_ZERO + (erc20s, amounts) = basketHandler.quoteHistoricalRedemption( + basketNonces, + portions, + basketsRedeemed + ); + } // ==== Prorate redemption ==== // i.e, set amounts = min(amounts, balances * amount / totalSupply) From 9efe00237dc669792b47bb6d0ac2e3d7fc7ac7c5 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 12 Apr 2023 16:52:59 -0400 Subject: [PATCH 064/462] minor nits --- contracts/p1/RToken.sol | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index bcfe393ae4..44cf4fcd18 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -257,6 +257,11 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { require(amount > 0, "Cannot redeem zero"); require(amount <= balanceOf(redeemer), "insufficient balance"); + // Confirm portions sums to FIX_ONE + uint256 portionsSum; + for (uint256 i = 0; i < portions.length; ++i) portionsSum += portions[i]; + require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); + // Failure to melt results in a lower redemption price, so we can allow it when paused // solhint-disable-next-line no-empty-blocks try main.furnace().melt() {} catch {} @@ -270,15 +275,9 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // i.e, set (erc20s, amounts) = basketHandler.quote(amount * basketsNeeded / totalSupply) // D18{BU} = D18{BU} * {qRTok} / {qRTok} - // downcast is safe: amount < totalSupply and basketsNeeded < 1e57 < 2^190 (just barely) uint192 basketsRedeemed = basketsNeeded.muluDivu(amount, supply); // FLOOR emit Redemption(redeemer, recipient, amount, basketsRedeemed); - // Confirm portions sums to FIX_ONE - uint256 portionsSum; - for (uint256 i = 0; i < portions.length; ++i) portionsSum += portions[i]; - require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); - address[] memory erc20s; uint256[] memory amounts; if (basketNonces.length == 1 && basketHandler.nonce() == basketNonces[0]) { From 53488a264f14b1832b4c3c4e138fbab16051d190 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 10:46:00 -0400 Subject: [PATCH 065/462] add storage layout printout to solc outputs --- hardhat.config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index f463f81e1c..e72723e734 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -22,7 +22,9 @@ const MNEMONIC = useEnv('MNEMONIC') ?? 'test test test test test test test test const TIMEOUT = useEnv('SLOW') ? 6_000_000 : 600_000 const src_dir = `./contracts/${useEnv('PROTO')}` -const settings = useEnv('NO_OPT') ? {} : { optimizer: { enabled: true, runs: 200 } } +const settings = useEnv('NO_OPT') + ? { outputSelection: { '*': { '*': ['storage'] } } } + : { outputSelection: { '*': { '*': ['storage'] } }, optimizer: { enabled: true, runs: 200 } } const config: HardhatUserConfig = { defaultNetwork: 'hardhat', From 1dd7ac3bc124a12251b2f7e658180ecd9ab0db17 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 17:16:50 -0400 Subject: [PATCH 066/462] bugfix: make RToken.issue() succeed when tradingPaused --- contracts/p0/Furnace.sol | 2 +- contracts/p1/Furnace.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/p0/Furnace.sol b/contracts/p0/Furnace.sol index 7c70de1126..c4e0cb994a 100644 --- a/contracts/p0/Furnace.sol +++ b/contracts/p0/Furnace.sol @@ -30,7 +30,7 @@ contract FurnaceP0 is ComponentP0, IFurnace { /// Performs any melting that has vested since last call. /// @custom:refresher - function melt() public notTradingPausedOrFrozen { + function melt() public notFrozen { if (uint48(block.timestamp) < uint64(lastPayout) + PERIOD) return; // # of whole periods that have passed since lastPayout diff --git a/contracts/p1/Furnace.sol b/contracts/p1/Furnace.sol index e6e960b84a..14edfacbd1 100644 --- a/contracts/p1/Furnace.sol +++ b/contracts/p1/Furnace.sol @@ -63,7 +63,7 @@ contract FurnaceP1 is ComponentP1, IFurnace { // actions: // rToken.melt(payoutAmount), paying payoutAmount to RToken holders - function melt() external notTradingPausedOrFrozen { + function melt() external notFrozen { if (uint48(block.timestamp) < uint64(lastPayout) + PERIOD) return; // # of whole periods that have passed since lastPayout From 483770785efdf0fbe31f373887e72db9433878db Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 17:16:56 -0400 Subject: [PATCH 067/462] add regression tests --- test/Furnace.test.ts | 11 ++++++++--- test/RToken.test.ts | 14 +++++++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index 1b91eabc8a..a0ef6d5c30 100644 --- a/test/Furnace.test.ts +++ b/test/Furnace.test.ts @@ -179,14 +179,19 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { await rToken.connect(addr2).issue(issueAmount) }) - it('Should not melt if paused #fast', async () => { + it('Should melt if trading paused #fast', async () => { await main.connect(owner).pauseTrading() - await expect(furnace.connect(addr1).melt()).to.be.revertedWith('frozen or trading paused') + await expect(furnace.connect(addr1).melt()).to.be.revertedWith('frozen') + }) + + it('Should melt if issuance paused #fast', async () => { + await main.connect(owner).pauseIssuance() + await furnace.connect(addr1).melt() }) it('Should not melt if frozen #fast', async () => { await main.connect(owner).freezeShort() - await expect(furnace.connect(addr1).melt()).to.be.revertedWith('frozen or trading paused') + await expect(furnace.connect(addr1).melt()).to.be.revertedWith('frozen') }) it('Should not melt any funds in the initial block #fast', async () => { diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 5af276aa2b..bdf43c3d53 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -297,7 +297,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) describe('Issuance', function () { - it('Should not issue RTokens if paused', async function () { + it('Should not issue RTokens if issuance paused', async function () { const issueAmount: BigNumber = bn('10e18') // Pause Main @@ -312,6 +312,18 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.totalSupply()).to.equal(bn(0)) }) + it('Should issue RTokens if trading paused', async function () { + const issueAmount: BigNumber = bn('10e18') + + // Pause Main + await main.connect(owner).pauseTrading() + + // Issue + await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, issueAmount))) + await rToken.connect(addr1).issue(issueAmount) + expect(await rToken.totalSupply()).to.equal(issueAmount) + }) + it('Should not issue RTokens if frozen', async function () { const issueAmount: BigNumber = bn('10e18') From 971e13e9a5f043501f14291988015b712d4afd0f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 18:35:55 -0400 Subject: [PATCH 068/462] fix furnace test --- test/Furnace.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index a0ef6d5c30..5ae81c0b07 100644 --- a/test/Furnace.test.ts +++ b/test/Furnace.test.ts @@ -181,7 +181,7 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { it('Should melt if trading paused #fast', async () => { await main.connect(owner).pauseTrading() - await expect(furnace.connect(addr1).melt()).to.be.revertedWith('frozen') + await furnace.connect(addr1).melt() }) it('Should melt if issuance paused #fast', async () => { From b5d7e2aed348752801d166249e283eb0055ce48d Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 26 Apr 2023 10:51:52 -0300 Subject: [PATCH 069/462] add rename var annotation for upgrade --- contracts/mixins/Auth.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/mixins/Auth.sol b/contracts/mixins/Auth.sol index fb06e82aff..b878777fe7 100644 --- a/contracts/mixins/Auth.sol +++ b/contracts/mixins/Auth.sol @@ -41,6 +41,7 @@ abstract contract Auth is AccessControlUpgradeable, IAuth { // === Pausing === + /// @custom:oz-renamed-from paused bool public tradingPaused; bool public issuancePaused; From cea2108ae5c7d170eda1c9307a7b39d20abace5d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 11:31:06 -0400 Subject: [PATCH 070/462] remove notTradingPaused from main.poke() --- contracts/mixins/Auth.sol | 2 +- contracts/p0/Main.sol | 2 +- contracts/p1/Main.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/mixins/Auth.sol b/contracts/mixins/Auth.sol index fb06e82aff..14574f5550 100644 --- a/contracts/mixins/Auth.sol +++ b/contracts/mixins/Auth.sol @@ -101,7 +101,7 @@ abstract contract Auth is AccessControlUpgradeable, IAuth { // ==== System-wide views ==== // returns: bool(main is frozen) == now < unfreezeAt - function frozen() external view returns (bool) { + function frozen() public view returns (bool) { return block.timestamp < unfreezeAt; } diff --git a/contracts/p0/Main.sol b/contracts/p0/Main.sol index 12d85514cf..3a3374c0cd 100644 --- a/contracts/p0/Main.sol +++ b/contracts/p0/Main.sol @@ -37,7 +37,7 @@ contract MainP0 is Versioned, Initializable, Auth, ComponentRegistry, IMain { /// @custom:refresher function poke() external { - require(!tradingPausedOrFrozen(), "frozen or trading paused"); + require(!frozen(), "frozen"); assetRegistry.refresh(); furnace.melt(); stRSR.payoutRewards(); diff --git a/contracts/p1/Main.sol b/contracts/p1/Main.sol index aac47a98a2..7bd551151f 100644 --- a/contracts/p1/Main.sol +++ b/contracts/p1/Main.sol @@ -45,7 +45,7 @@ contract MainP1 is Versioned, Initializable, Auth, ComponentRegistry, UUPSUpgrad assetRegistry.refresh(); // == CE block == - require(!tradingPausedOrFrozen(), "frozen or trading paused"); + require(!frozen(), "frozen"); furnace.melt(); stRSR.payoutRewards(); } From 35843970a44afc0ad18eb6c583ca15674afea3d7 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 11:31:33 -0400 Subject: [PATCH 071/462] fix CI inconsistency --- .../individual-collateral/compoundv3/CusdcV3Wrapper.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts b/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts index 49f71de097..c0c4ac9f9d 100644 --- a/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts @@ -199,7 +199,7 @@ describeFork('Wrapped CUSDCv3', () => { await wcusdcV3.connect(charles).withdrawFrom(bob.address, don.address, withdrawAmount) expect(await cusdcV3.balanceOf(don.address)).to.closeTo(withdrawAmount, 100) - expect(await cusdcV3.balanceOf(charles.address)).to.closeTo(bn('0'), 10) + expect(await cusdcV3.balanceOf(charles.address)).to.closeTo(bn('0'), 11) expect(await wcusdcV3.balanceOf(bob.address)).to.closeTo(bn(0), 50) }) From 6231af42054d073ffea3fec05f045bb5cfcbdc53 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 13:22:01 -0400 Subject: [PATCH 072/462] Main tests --- test/Main.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/Main.test.ts b/test/Main.test.ts index 6c0c15ebbf..ed775a2bfb 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1809,14 +1809,19 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { ) }) - it('Should not allow to poke when paused', async () => { + it('Should allow to poke when trading paused', async () => { await main.connect(owner).pauseTrading() - await expect(main.connect(other).poke()).to.be.revertedWith('frozen or trading paused') + await main.connect(other).poke() + }) + + it('Should allow to poke when issuance paused', async () => { + await main.connect(owner).pauseIssuance() + await main.connect(other).poke() }) it('Should not allow to poke when frozen', async () => { await main.connect(owner).freezeForever() - await expect(main.connect(other).poke()).to.be.revertedWith('frozen or trading paused') + await expect(main.connect(other).poke()).to.be.revertedWith('frozen') }) it('Should not allow to refresh basket if not OWNER when unfrozen and unpaused', async () => { From f411d7ffe0c03c5b6c7274a462e33357e6a0b1ff Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Thu, 20 Apr 2023 09:59:44 -0400 Subject: [PATCH 073/462] add claimRewards to convex wrapper (#784) * add claimRewards function to convex wrapper. * add test for claimRewards() on wrapper. * add tests for all plugin/wrappers. * fix linting. --- .../convex/vendor/ConvexStakingWrapper.sol | 9 +++++++++ .../convex/CvxStableMetapoolSuite.test.ts | 18 +++++++++++++++++- .../CvxStableRTokenMetapoolTestSuite.test.ts | 18 +++++++++++++++++- .../convex/CvxStableTestSuite.test.ts | 18 +++++++++++++++++- .../convex/CvxVolatileTestSuite.test.ts | 18 +++++++++++++++++- 5 files changed, 77 insertions(+), 4 deletions(-) diff --git a/contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol b/contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol index c2984c5861..71caaf5035 100644 --- a/contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol +++ b/contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol @@ -92,6 +92,7 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { ); event Withdrawn(address indexed _user, uint256 _amount, bool _unwrapped); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + event RewardsClaimed(IERC20 indexed erc20, uint256 indexed amount); constructor() public ERC20("StakedConvexToken", "stkCvx") {} @@ -404,6 +405,14 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { return claimable; } + function claimRewards() external { + uint256 cvxOldBal = IERC20(cvx).balanceOf(msg.sender); + uint256 crvOldBal = IERC20(crv).balanceOf(msg.sender); + _checkpointAndClaim([address(msg.sender), address(msg.sender)]); + emit RewardsClaimed(IERC20(cvx), IERC20(cvx).balanceOf(msg.sender) - cvxOldBal); + emit RewardsClaimed(IERC20(crv), IERC20(crv).balanceOf(msg.sender) - crvOldBal); + } + function getReward(address _account) external { //claim directly in checkpoint logic to save a bit of gas _checkpointAndClaim([_account, _account]); diff --git a/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts b/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts index dc16d43a2b..163fc010fb 100644 --- a/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts +++ b/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts @@ -450,7 +450,7 @@ describeFork(`Collateral: Convex - Stable Metapool (MIM+3Pool)`, () => { await expect(collateral.claimRewards()).to.not.be.reverted }) - it('claims rewards', async () => { + it('claims rewards (plugin)', async () => { const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) await mintCollateralTo(ctx, amount, alice, collateral.address) @@ -465,6 +465,22 @@ describeFork(`Collateral: Convex - Stable Metapool (MIM+3Pool)`, () => { expect(crvAfter).gt(crvBefore) expect(cvxAfter).gt(cvxBefore) }) + + it('claims rewards (wrapper)', async () => { + const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) + await mintCollateralTo(ctx, amount, alice, alice.address) + + await advanceBlocks(1000) + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) + + const crvBefore = await crv.balanceOf(alice.address) + const cvxBefore = await cvx.balanceOf(alice.address) + await expect(ctx.wPool.connect(alice).claimRewards()).to.emit(ctx.wPool, 'RewardsClaimed') + const crvAfter = await crv.balanceOf(alice.address) + const cvxAfter = await cvx.balanceOf(alice.address) + expect(crvAfter).gt(crvBefore) + expect(cvxAfter).gt(cvxBefore) + }) }) describe('prices', () => { diff --git a/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts index 44cac85b53..e240dfe99f 100644 --- a/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts @@ -435,7 +435,7 @@ describeFork(`Collateral: Convex - RToken Metapool (eUSD/fraxBP)`, () => { await expect(collateral.claimRewards()).to.not.be.reverted }) - it('claims rewards', async () => { + it('claims rewards (plugin)', async () => { const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) await mintCollateralTo(ctx, amount, alice, collateral.address) @@ -450,6 +450,22 @@ describeFork(`Collateral: Convex - RToken Metapool (eUSD/fraxBP)`, () => { expect(crvAfter).gt(crvBefore) expect(cvxAfter).gt(cvxBefore) }) + + it('claims rewards (wrapper)', async () => { + const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) + await mintCollateralTo(ctx, amount, alice, alice.address) + + await advanceBlocks(1000) + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) + + const crvBefore = await crv.balanceOf(alice.address) + const cvxBefore = await cvx.balanceOf(alice.address) + await expect(ctx.wPool.connect(alice).claimRewards()).to.emit(ctx.wPool, 'RewardsClaimed') + const crvAfter = await crv.balanceOf(alice.address) + const cvxAfter = await cvx.balanceOf(alice.address) + expect(crvAfter).gt(crvBefore) + expect(cvxAfter).gt(cvxBefore) + }) }) describe('prices', () => { diff --git a/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts b/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts index 35e331c292..84ef545663 100644 --- a/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts +++ b/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts @@ -442,7 +442,7 @@ describeFork(`Collateral: Convex - Stable (3Pool)`, () => { await expect(collateral.claimRewards()).to.not.be.reverted }) - it('claims rewards', async () => { + it('claims rewards (plugin)', async () => { const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) await mintCollateralTo(ctx, amount, alice, collateral.address) @@ -457,6 +457,22 @@ describeFork(`Collateral: Convex - Stable (3Pool)`, () => { expect(crvAfter).gt(crvBefore) expect(cvxAfter).gt(cvxBefore) }) + + it('claims rewards (wrapper)', async () => { + const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) + await mintCollateralTo(ctx, amount, alice, alice.address) + + await advanceBlocks(1000) + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) + + const crvBefore = await crv.balanceOf(alice.address) + const cvxBefore = await cvx.balanceOf(alice.address) + await expect(ctx.w3Pool.connect(alice).claimRewards()).to.emit(ctx.w3Pool, 'RewardsClaimed') + const crvAfter = await crv.balanceOf(alice.address) + const cvxAfter = await cvx.balanceOf(alice.address) + expect(crvAfter).gt(crvBefore) + expect(cvxAfter).gt(cvxBefore) + }) }) describe('prices', () => { diff --git a/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts b/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts index 6fea7aa79f..749f5b29df 100644 --- a/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts +++ b/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts @@ -472,7 +472,7 @@ describeFork(`Collateral: Convex - Volatile`, () => { await expect(collateral.claimRewards()).to.not.be.reverted }) - it('claims rewards', async () => { + it('claims rewards (plugin)', async () => { const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) await mintCollateralTo(ctx, amount, alice, collateral.address) @@ -487,6 +487,22 @@ describeFork(`Collateral: Convex - Volatile`, () => { expect(crvAfter).gt(crvBefore) expect(cvxAfter).gt(cvxBefore) }) + + it('claims rewards (wrapper)', async () => { + const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) + await mintCollateralTo(ctx, amount, alice, alice.address) + + await advanceBlocks(1000) + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) + + const crvBefore = await crv.balanceOf(alice.address) + const cvxBefore = await cvx.balanceOf(alice.address) + await expect(ctx.w3Pool.connect(alice).claimRewards()).to.emit(ctx.w3Pool, 'RewardsClaimed') + const crvAfter = await crv.balanceOf(alice.address) + const cvxAfter = await cvx.balanceOf(alice.address) + expect(crvAfter).gt(crvBefore) + expect(cvxAfter).gt(cvxBefore) + }) }) describe('prices', () => { From ef7c4ae848a5a06bd52731c674050706d9542fea Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Fri, 21 Apr 2023 17:10:53 -0400 Subject: [PATCH 074/462] deploy new convex wrappers and plugins. (#785) --- .../addresses/mainnet-2.1.0/1-tmp-assets-collateral.json | 6 +++--- utils/env.ts | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/addresses/mainnet-2.1.0/1-tmp-assets-collateral.json b/scripts/addresses/mainnet-2.1.0/1-tmp-assets-collateral.json index 8a23ad0ef4..38d5a5a24b 100644 --- a/scripts/addresses/mainnet-2.1.0/1-tmp-assets-collateral.json +++ b/scripts/addresses/mainnet-2.1.0/1-tmp-assets-collateral.json @@ -33,9 +33,9 @@ "fDAI": "0xA4410B71033fFE8fA41c6096332Be58E3641326d", "fFRAX": "0xcd46Ff27c0d6F088FB94896dcE8F17491BD84c75", "cUSDCv3": "0x615D92fAF203Faa9ea7a4D8cdDC49b2Ad0702a1f", - "cvx3Pool": "0x14548a0aEcA46418cD9cFd08c6Bf8E02FbE53B5E", + "cvx3Pool": "0xC34E547D66B5a57B370217aAe4F34b882a9933Dc", "cvxTriCrypto": "0xb2EeD19C381b71d0f54327D61596312144f66fA7", - "cvxeUSDFRAXBP": "0x8DC1750B1fe69e940f570c021d658C14D8041834", - "cvxMIM3Pool": "0xD5BE0AeC2b537481A4fE2EcF52422a24644e1EF3" + "cvxeUSDFRAXBP": "0x0D41E86D019cadaAA32a5a12A35d456711879770", + "cvxMIM3Pool": "0x9866020B7A59022C2F017C6d358868cB11b86E2d" } } diff --git a/utils/env.ts b/utils/env.ts index ea18051802..9d5e776239 100644 --- a/utils/env.ts +++ b/utils/env.ts @@ -19,6 +19,7 @@ type IEnvVars = | 'JOBS' | 'EXTREME' | 'SUBGRAPH_URL' + | 'MAINNET_PK' export function useEnv(key: IEnvVars | IEnvVars[], _default = ''): string { if (typeof key === 'string') { From 7f538e9a76e5b1833f1e36cf29a9d107fb427799 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 16:32:00 -0400 Subject: [PATCH 075/462] rework FacadeRead revenue view functions --- contracts/facade/FacadeAct.sol | 67 +------- contracts/facade/FacadeRead.sol | 235 ++++++++++++++++----------- contracts/interfaces/IFacadeAct.sol | 14 +- contracts/interfaces/IFacadeRead.sol | 60 +++++-- contracts/interfaces/ITrade.sol | 4 + hardhat.config.ts | 6 +- test/FacadeAct.test.ts | 130 +-------------- test/FacadeRead.test.ts | 162 +++++++++++++----- 8 files changed, 318 insertions(+), 360 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 4d08276373..72325757ab 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -352,74 +352,9 @@ contract FacadeAct is IFacadeAct { return size > 0 ? size : 1; } - /// To use this, call via callStatic. - /// If canStart is true, proceed to runRecollateralizationAuctions - /// @return canStart true iff a recollateralization auction can be started - /// @custom:static-call - function canRunRecollateralizationAuctions(IBackingManager bm) - external - returns (bool canStart) - { - IERC20[] memory erc20s = bm.main().assetRegistry().erc20s(); - - // Settle all backingManager auctions - for (uint256 i = 0; i < erc20s.length; ++i) { - ITrade trade = bm.trades(erc20s[i]); - if (address(trade) != address(0) && trade.canSettle()) { - bm.settleTrade(erc20s[i]); - } - } - - uint256 tradesOpen = bm.tradesOpen(); - if (tradesOpen != 0) return false; - - // Try to launch auctions - bm.manageTokensSortedOrder(new IERC20[](0)); - return bm.tradesOpen() > 0; - } - - /// To use this, call via callStatic. - /// @return toStart The ERC20s that have auctions that can be started - /// @custom:static-call - function getRevenueAuctionERC20s(IRevenueTrader revenueTrader) - external - returns (IERC20[] memory toStart) - { - Registry memory reg = revenueTrader.main().assetRegistry().getRegistry(); - - // Forward ALL revenue - revenueTrader.main().backingManager().manageTokens(reg.erc20s); - - // Calculate which erc20s can have auctions started - uint256 num; - IERC20[] memory unfiltered = new IERC20[](reg.erc20s.length); // will filter down later - for (uint256 i = 0; i < reg.erc20s.length; ++i) { - // Settle first if possible. Required so we can assess full available balance - ITrade trade = revenueTrader.trades(reg.erc20s[i]); - if (address(trade) != address(0) && trade.canSettle()) { - revenueTrader.settleTrade(reg.erc20s[i]); - } - - uint256 tradesOpen = revenueTrader.tradesOpen(); - - try revenueTrader.manageToken(reg.erc20s[i]) { - if (revenueTrader.tradesOpen() - tradesOpen > 0) { - unfiltered[num] = reg.erc20s[i]; - ++num; - } - } catch {} - } - - // Filter down - toStart = new IERC20[](num); - for (uint256 i = 0; i < num; ++i) { - toStart[i] = unfiltered[i]; - } - } - /// To use this, first call: /// - FacadeRead.auctionsSettleable(revenueTrader) - /// - getRevenueAuctionERC20s(revenueTrader) + /// - revenueSurpluses(revenueTrader) /// If either arrays returned are non-empty, then can call this function. /// Logic: /// For each ERC20 in `toSettle`: diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index fd26c2e4ce..7bc4753341 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -9,10 +9,7 @@ import "../interfaces/IRToken.sol"; import "../interfaces/IStRSR.sol"; import "../libraries/Fixed.sol"; import "../p1/BasketHandler.sol"; -import "../p1/BackingManager.sol"; -import "../p1/Furnace.sol"; import "../p1/RToken.sol"; -import "../p1/RevenueTrader.sol"; import "../p1/StRSRVotes.sol"; /** @@ -133,7 +130,7 @@ contract FacadeRead is IFacadeRead { /// @return uoaShares {1} The proportion of the basket associated with each ERC20 /// @return targets The bytes32 representations of the target unit associated with each ERC20 /// @custom:static-call - function basketBreakdown(RTokenP1 rToken) + function basketBreakdown(IRToken rToken) external returns ( address[] memory erc20s, @@ -173,6 +170,144 @@ contract FacadeRead is IFacadeRead { } } + /// @return erc20s The registered ERC20s + /// @return balances {qTok} The held balances of each ERC20 across all traders + /// @return balancesNeededByBackingManager {qTok} The needed balance of each ERC20 at the BackingManager + /// @custom:static-call + function balancesAcrossAllTraders(IRToken rToken) + external + returns ( + IERC20[] memory erc20s, + uint256[] memory balances, + uint256[] memory balancesNeededByBackingManager + ) + { + IMain main = rToken.main(); + main.assetRegistry().refresh(); + main.furnace().melt(); + + erc20s = main.assetRegistry().erc20s(); + balances = new uint256[](erc20s.length); + balancesNeededByBackingManager = new uint256[](erc20s.length); + + uint192 basketsNeeded = rToken.basketsNeeded(); // {BU} + + for (uint256 i = 0; i < erc20s.length; ++i) { + balances[i] = erc20s[i].balanceOf(address(main.backingManager())); + balances[i] += erc20s[i].balanceOf(address(main.rTokenTrader())); + balances[i] += erc20s[i].balanceOf(address(main.rsrTrader())); + + // {qTok} = {tok/BU} * {BU} * {tok} * {qTok/tok} + uint192 balNeededFix = main.basketHandler().quantity(erc20s[i]).safeMul( + basketsNeeded, + RoundingMode.FLOOR // FLOOR to match redemption + ); + + balancesNeededByBackingManager[i] = balNeededFix.shiftl_toUint( + int8(IERC20Metadata(address(erc20s[i])).decimals()), + RoundingMode.FLOOR + ); + } + } + + /// To use this, call via callStatic. + /// If canStart is true, can run FacadeAct.runRecollateralizationAuctions + /// @return canStart true iff a recollateralization auction can be started + /// @return sell The sell token in the auction + /// @return buy The buy token in the auction + /// @return sellAmount {qSellTok} How much would be sold + /// @custom:static-call + function nextRecollateralizationAuction(IBackingManager bm) + external + returns ( + bool canStart, + IERC20 sell, + IERC20 buy, + uint256 sellAmount + ) + { + bm.main().assetRegistry().refresh(); + bm.main().furnace().melt(); + + IERC20[] memory erc20s = bm.main().assetRegistry().erc20s(); + + // Settle all backingManager auctions + for (uint256 i = 0; i < erc20s.length; ++i) { + ITrade trade = bm.trades(erc20s[i]); + if (address(trade) != address(0) && trade.canSettle()) { + bm.settleTrade(erc20s[i]); + } + } + + if (bm.tradesOpen() == 0) { + // Try to launch auctions + bm.manageTokensSortedOrder(new IERC20[](0)); + canStart = bm.tradesOpen() > 0; + + // Find the started auction + for (uint256 i = 0; i < erc20s.length; ++i) { + ITrade trade = bm.trades(erc20s[i]); + if (address(trade) != address(0)) { + sell = trade.sell(); + buy = trade.buy(); + sellAmount = trade.initBal(); + } + } + } + } + + /// To use this, call via callStatic. + /// @return erc20s The ERC20s that have auctions that can be started + /// @return canStart If the ERC20 auction can be started + /// @return surpluses {qTok} The surplus amount + /// @return minTradeAmounts {qTok} The minimum amount worth trading + /// @custom:static-call + function revenueSurpluses(IRevenueTrader revenueTrader) + external + returns ( + IERC20[] memory erc20s, + bool[] memory canStart, + uint256[] memory surpluses, + uint256[] memory minTradeAmounts + ) + { + uint192 minTradeVolume = revenueTrader.minTradeVolume(); // {UoA} + Registry memory reg = revenueTrader.main().assetRegistry().getRegistry(); + + // Forward ALL revenue + revenueTrader.main().backingManager().manageTokens(reg.erc20s); + + erc20s = new IERC20[](reg.erc20s.length); + canStart = new bool[](reg.erc20s.length); + surpluses = new uint256[](reg.erc20s.length); + minTradeAmounts = new uint256[](reg.erc20s.length); + // Calculate which erc20s can have auctions started + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + // Settle first if possible. Required so we can assess full available balance + ITrade trade = revenueTrader.trades(reg.erc20s[i]); + if (address(trade) != address(0) && trade.canSettle()) { + revenueTrader.settleTrade(reg.erc20s[i]); + } + + uint48 tradesOpen = revenueTrader.tradesOpen(); + erc20s[i] = reg.erc20s[i]; + surpluses[i] = reg.erc20s[i].balanceOf(address(revenueTrader)); + + (uint192 low, ) = reg.assets[i].price(); // {UoA/tok} + + // {qTok} = {UoA} / {UoA/tok} + minTradeAmounts[i] = minTradeVolume.div(low).shiftl_toUint( + int8(reg.assets[i].erc20Decimals()) + ); + + try revenueTrader.manageToken(reg.erc20s[i]) { + if (revenueTrader.tradesOpen() - tradesOpen > 0) { + canStart[i] = true; + } + } catch {} + } + } + // === Views === /// @param account The account for the query @@ -207,7 +342,7 @@ contract FacadeRead is IFacadeRead { /// @return erc20s The erc20s in the prime basket /// @return targetNames The bytes32 name identifier of the target unit, per ERC20 /// @return targetAmts {target/BU} The amount of the target unit in the basket, per ERC20 - function primeBasket(RTokenP1 rToken) + function primeBasket(IRToken rToken) external view returns ( @@ -228,7 +363,7 @@ contract FacadeRead is IFacadeRead { /// @param targetName The name of the target unit to lookup the backup for /// @return erc20s The backup erc20s for the target unit, in order of most to least desirable /// @return max The maximum number of tokens from the array to use at a single time - function backupConfig(RTokenP1 rToken, bytes32 targetName) + function backupConfig(IRToken rToken, bytes32 targetName) external view returns (IERC20[] memory erc20s, uint256 max) @@ -300,47 +435,6 @@ contract FacadeRead is IFacadeRead { overCollateralization = rsrUoA.div(uoaNeeded); } - /// @return erc20s The registered ERC20s - /// @return balances {qTok} The held balances of each ERC20 at the trader - /// @return balancesNeeded {qTok} The needed balance of each ERC20 at the trader - function traderBalances(IRToken rToken, ITrading trader) - external - view - returns ( - IERC20[] memory erc20s, - uint256[] memory balances, - uint256[] memory balancesNeeded - ) - { - IBackingManager backingManager = rToken.main().backingManager(); - IBasketHandler basketHandler = rToken.main().basketHandler(); - - erc20s = rToken.main().assetRegistry().erc20s(); - balances = new uint256[](erc20s.length); - balancesNeeded = new uint256[](erc20s.length); - - uint192 backingBuffer = TestIBackingManager(address(backingManager)).backingBuffer(); - uint192 basketsNeeded = rToken.basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); // {BU} - - bool isBackingManager = trader == backingManager; - for (uint256 i = 0; i < erc20s.length; ++i) { - balances[i] = erc20s[i].balanceOf(address(trader)); - - if (isBackingManager) { - // {qTok} = {tok/BU} * {BU} * {tok} * {qTok/tok} - uint192 balNeededFix = basketHandler.quantity(erc20s[i]).safeMul( - basketsNeeded, - RoundingMode.FLOOR // FLOOR to match redemption - ); - - balancesNeeded[i] = balNeededFix.shiftl_toUint( - int8(IERC20Metadata(address(erc20s[i])).decimals()), - RoundingMode.FLOOR - ); - } - } - } - /// @return low {UoA/tok} The low price of the RToken as given by the relevant RTokenAsset /// @return high {UoA/tok} The high price of the RToken as given by the relevant RTokenAsset function price(IRToken rToken) external view returns (uint192 low, uint192 high) { @@ -368,51 +462,4 @@ contract FacadeRead is IFacadeRead { erc20s[i] = unfiltered[i]; } } - - // === Private === - - function basketRange(IRToken rToken, uint192 basketsNeeded) - private - view - returns (BasketRange memory range) - { - IMain main = rToken.main(); - IBasketHandler bh = main.basketHandler(); - IBackingManager bm = main.backingManager(); - BasketRange memory basketsHeld = bh.basketsHeldBy(address(bm)); - - // if (bh.fullyCollateralized()) - if (basketsHeld.bottom >= basketsNeeded) { - range.bottom = basketsNeeded; - range.top = basketsNeeded; - } else { - // Note: Extremely this is extremely wasteful in terms of gas. This only exists so - // there is _some_ asset to represent the RToken itself when it is deployed, in - // the absence of an external price feed. Any RToken that gets reasonably big - // should switch over to an asset with a price feed. - - TradingContext memory ctx = TradingContext({ - basketsHeld: basketsHeld, - bm: bm, - ar: main.assetRegistry(), - stRSR: main.stRSR(), - rsr: main.rsr(), - rToken: main.rToken(), - minTradeVolume: bm.minTradeVolume(), - maxTradeSlippage: bm.maxTradeSlippage() - }); - - Registry memory reg = ctx.ar.getRegistry(); - - uint192[] memory quantities = new uint192[](reg.erc20s.length); - for (uint256 i = 0; i < reg.erc20s.length; ++i) { - quantities[i] = bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); - } - - (Price memory buPrice, ) = bh.prices(); - - // will exclude UoA value from RToken balances at BackingManager - range = RecollateralizationLibP1.basketRange(ctx, reg, quantities, buPrice); - } - } } diff --git a/contracts/interfaces/IFacadeAct.sol b/contracts/interfaces/IFacadeAct.sol index 8a36ea08c9..4964a1acfd 100644 --- a/contracts/interfaces/IFacadeAct.sol +++ b/contracts/interfaces/IFacadeAct.sol @@ -20,21 +20,9 @@ interface IFacadeAct { /// Claims rewards from all places they can accrue. function claimRewards(RTokenP1 rToken) external; - /// To use this, call via callStatic. - /// @return canStart true iff a recollateralization auction can be started - /// @custom:static-call - function canRunRecollateralizationAuctions(IBackingManager bm) external returns (bool canStart); - - /// To use this, call via callStatic. - /// @return toStart The ERC20s that have auctions that can be started - /// @custom:static-call - function getRevenueAuctionERC20s(IRevenueTrader revenueTrader) - external - returns (IERC20[] memory toStart); - /// To use this, first call: /// - FacadeRead.auctionsSettleable(revenueTrader) - /// - getRevenueAuctionERC20s(revenueTrader) + /// - revenueSurpluses(revenueTrader) /// If either arrays returned are non-empty, then can call this function. /// Logic: /// For each ERC20 in `toSettle`: diff --git a/contracts/interfaces/IFacadeRead.sol b/contracts/interfaces/IFacadeRead.sol index 35859b919d..56ade46e7a 100644 --- a/contracts/interfaces/IFacadeRead.sol +++ b/contracts/interfaces/IFacadeRead.sol @@ -51,7 +51,7 @@ interface IFacadeRead { /// @return uoaShares The proportion of the basket associated with each ERC20 /// @return targets The bytes32 representations of the target unit associated with each ERC20 /// @custom:static-call - function basketBreakdown(RTokenP1 rToken) + function basketBreakdown(IRToken rToken) external returns ( address[] memory erc20s, @@ -59,6 +59,48 @@ interface IFacadeRead { bytes32[] memory targets ); + /// @return erc20s The registered ERC20s + /// @return balances {qTok} The held balances of each ERC20 across all traders + /// @return balancesNeededByBackingManager {qTok} The needed balance of each ERC20 at the BackingManager + /// @custom:static-call + function balancesAcrossAllTraders(IRToken rToken) + external + returns ( + IERC20[] memory erc20s, + uint256[] memory balances, + uint256[] memory balancesNeededByBackingManager + ); + + /// To use this, call via callStatic. + /// @return canStart true iff a recollateralization auction can be started + /// @return sell The sell token in the auction + /// @return buy The buy token in the auction + /// @return sellAmount {qSellTok} How much would be sold + /// @custom:static-call + function nextRecollateralizationAuction(IBackingManager bm) + external + returns ( + bool canStart, + IERC20 sell, + IERC20 buy, + uint256 sellAmount + ); + + /// To use this, call via callStatic. + /// @return erc20s The ERC20s that have auctions that can be started + /// @return canStart If the ERC20 auction can be started + /// @return surpluses {qTok} The surplus amount + /// @return minTradeAmounts {qTok} The minimum amount worth trading + /// @custom:static-call + function revenueSurpluses(IRevenueTrader revenueTrader) + external + returns ( + IERC20[] memory erc20s, + bool[] memory canStart, + uint256[] memory surpluses, + uint256[] memory minTradeAmounts + ); + // === Views === struct Pending { @@ -80,7 +122,7 @@ interface IFacadeRead { /// @return erc20s The erc20s in the prime basket /// @return targetNames The bytes32 name identifier of the target unit, per ERC20 /// @return targetAmts {target/BU} The amount of the target unit in the basket, per ERC20 - function primeBasket(RTokenP1 rToken) + function primeBasket(IRToken rToken) external view returns ( @@ -93,7 +135,7 @@ interface IFacadeRead { /// @param targetName The name of the target unit to lookup the backup for /// @return erc20s The backup erc20s for the target unit, in order of most to least desirable /// @return max The maximum number of tokens from the array to use at a single time - function backupConfig(RTokenP1 rToken, bytes32 targetName) + function backupConfig(IRToken rToken, bytes32 targetName) external view returns (IERC20[] memory erc20s, uint256 max); @@ -114,18 +156,6 @@ interface IFacadeRead { view returns (uint192 backing, uint192 overCollateralization); - /// @return erc20s The registered ERC20s - /// @return balances {qTok} The held balances of each ERC20 at the trader - /// @return balancesNeeded {qTok} The needed balance of each ERC20 at the trader - function traderBalances(IRToken rToken, ITrading trader) - external - view - returns ( - IERC20[] memory erc20s, - uint256[] memory balances, - uint256[] memory balancesNeeded - ); - /// @return low {UoA/tok} The low price of the RToken as given by the relevant RTokenAsset /// @return high {UoA/tok} The high price of the RToken as given by the relevant RTokenAsset function price(IRToken rToken) external view returns (uint192 low, uint192 high); diff --git a/contracts/interfaces/ITrade.sol b/contracts/interfaces/ITrade.sol index 99736768c6..9dfbbd010b 100644 --- a/contracts/interfaces/ITrade.sol +++ b/contracts/interfaces/ITrade.sol @@ -13,6 +13,10 @@ interface ITrade { function buy() external view returns (IERC20Metadata); + /// The sell amount + /// @return {qSellTok} + function initBal() external view returns (uint256); + /// @return The timestamp at which the trade is projected to become settle-able function endTime() external view returns (uint48); diff --git a/hardhat.config.ts b/hardhat.config.ts index f463f81e1c..7eed62d6f3 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -85,7 +85,11 @@ const config: HardhatUserConfig = { overrides: { 'contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol': { version: '0.6.12', - settings: { optimizer: { enabled: true, runs: 1 } }, // to fit ContexStakingWrapper + settings: { optimizer: { enabled: true, runs: 1 } }, // contract over-size + }, + 'contracts/facade/FacadeRead.sol': { + version: '0.8.17', + settings: { optimizer: { enabled: true, runs: 1 } }, // contract over-size }, }, }, diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts index e1ce54fa8c..f4497f71cd 100644 --- a/test/FacadeAct.test.ts +++ b/test/FacadeAct.test.ts @@ -11,7 +11,7 @@ import { ZERO_ADDRESS, BN_SCALE_FACTOR, } from '../common/constants' -import { bn, fp, toBNDecimals, divCeil } from '../common/numbers' +import { bn, fp, divCeil } from '../common/numbers' import { IConfig } from '../common/configuration' import { advanceTime } from './utils/time' import { withinQuad } from './utils/matchers' @@ -22,7 +22,6 @@ import { CTokenMock, ERC20Mock, FacadeAct, - IFacadeRead, FiatCollateral, GnosisMock, IAssetRegistry, @@ -88,7 +87,6 @@ describe('FacadeAct contract', () => { // Facade let facadeAct: FacadeAct - let facade: IFacadeRead // Main let rToken: TestIRToken @@ -141,7 +139,6 @@ describe('FacadeAct contract', () => { collateral, basket, facadeAct, - facade, rToken, stRSR, config, @@ -285,86 +282,6 @@ describe('FacadeAct contract', () => { expect(data).to.equal('0x') }) - it('Trades in Backing Manager', async () => { - // Setup prime basket - await basketHandler.connect(owner).setPrimeBasket([usdc.address], [fp('1')]) - - // Switch Basket - await expect(basketHandler.connect(owner).refreshBasket()) - .to.emit(basketHandler, 'BasketSet') - .withArgs(2, [usdc.address], [fp('1')], false) - - // Trigger recollateralization - const sellAmt: BigNumber = await token.balanceOf(backingManager.address) - const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) - - // Confirm canRunRecollateralizationAuctions is true - expect( - await facadeAct.callStatic.canRunRecollateralizationAuctions(backingManager.address) - ).to.equal(true) - - // Run auction via Facade - let [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - await expect( - owner.sendTransaction({ - to: addr, - data, - }) - ) - .to.emit(backingManager, 'TradeStarted') - .withArgs( - anyValue, - token.address, - usdc.address, - sellAmt, - toBNDecimals(minBuyAmt, 6).add(1) - ) - - // Confirm canRunRecollateralizationAuctions is false - expect( - await facadeAct.callStatic.canRunRecollateralizationAuctions(backingManager.address) - ).to.equal(false) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - - // Perform Mock Bids for the new Token (addr1 has balance) - // Get fair price - all tokens - await usdc.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6)) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: toBNDecimals(sellAmt, 6), - }) - - // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) - - // Confirm auctionsSettleable returns trade - const settleable = await facade.auctionsSettleable(backingManager.address) - expect(settleable.length).to.equal(1) - expect(settleable[0]).to.equal(token.address) - - // Trade is ready to be settled - Call settle trade via Facade - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - // End current auction - await expect( - owner.sendTransaction({ - to: addr, - data, - }) - ) - .to.emit(backingManager, 'TradeSettled') - .withArgs(anyValue, token.address, usdc.address, sellAmt, toBNDecimals(sellAmt, 6)) - }) - it('Revenues/Rewards', async () => { const rewardAmountAAVE = bn('0.5e18') @@ -892,51 +809,6 @@ describe('FacadeAct contract', () => { expect(await rToken.balanceOf(rTokenTrader.address)).to.equal(hndAmt) }) }) - - context('getRevenueAuctionERC20s/runRevenueAuctions', () => { - it('Revenues/Rewards', async () => { - const rewardAmountAAVE = bn('0.5e18') - const rewardAmountCOMP = bn('1e18') - - // Setup AAVE + COMP rewards - await aToken.setRewards(backingManager.address, rewardAmountAAVE) - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) - await backingManager.claimRewards() - - // getRevenueAuctionERC20s should return reward token - const rTokenERC20s = await facadeAct.callStatic.getRevenueAuctionERC20s( - rTokenTrader.address - ) - expect(rTokenERC20s.length).to.equal(2) - expect(rTokenERC20s[0]).to.equal(aaveToken.address) - expect(rTokenERC20s[1]).to.equal(compToken.address) - const rsrERC20s = await facadeAct.callStatic.getRevenueAuctionERC20s(rsrTrader.address) - expect(rsrERC20s.length).to.equal(2) - expect(rsrERC20s[0]).to.equal(aaveToken.address) - expect(rsrERC20s[1]).to.equal(compToken.address) - - // Run revenue auctions for both traders - await facadeAct.runRevenueAuctions(rTokenTrader.address, [], rTokenERC20s) - await facadeAct.runRevenueAuctions(rsrTrader.address, [], rsrERC20s) - - // Nothing should be settleable - expect((await facade.auctionsSettleable(rTokenTrader.address)).length).to.equal(0) - expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(0) - - // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) - - // Now both should be settleable - const rTokenSettleable = await facade.auctionsSettleable(rTokenTrader.address) - expect(rTokenSettleable.length).to.equal(2) - expect(rTokenSettleable[0]).to.equal(aaveToken.address) - expect(rTokenSettleable[1]).to.equal(compToken.address) - const rsrSettleable = await facade.auctionsSettleable(rsrTrader.address) - expect(rsrSettleable.length).to.equal(2) - expect(rsrSettleable[0]).to.equal(aaveToken.address) - expect(rsrSettleable[1]).to.equal(compToken.address) - }) - }) }) describeGas('Gas Reporting', () => { diff --git a/test/FacadeRead.test.ts b/test/FacadeRead.test.ts index 0b2ed0c76a..e0802a2ca8 100644 --- a/test/FacadeRead.test.ts +++ b/test/FacadeRead.test.ts @@ -9,17 +9,23 @@ import { Asset, CTokenMock, ERC20Mock, + FacadeAct, FacadeRead, FacadeTest, MockV3Aggregator, StaticATokenMock, StRSRP1, - TestIBasketHandler, + IAssetRegistry, + IBackingManager, + IBasketHandler, + TestIBroker, + TestIRevenueTrader, TestIMain, TestIStRSR, TestIRToken, USDCMock, } from '../typechain' +import { advanceTime } from './utils/time' import { Collateral, Implementation, @@ -54,12 +60,18 @@ describe('FacadeRead contract', () => { // Facade let facade: FacadeRead let facadeTest: FacadeTest + let facadeAct: FacadeAct // Main let rToken: TestIRToken let main: TestIMain let stRSR: TestIStRSR - let basketHandler: TestIBasketHandler + let basketHandler: IBasketHandler + let rTokenTrader: TestIRevenueTrader + let rsrTrader: TestIRevenueTrader + let backingManager: IBackingManager + let broker: TestIBroker + let assetRegistry: IAssetRegistry // RSR let rsrAsset: Asset @@ -68,8 +80,23 @@ describe('FacadeRead contract', () => { ;[owner, addr1, addr2, other] = await ethers.getSigners() // Deploy fixture - ;({ stRSR, rsr, rsrAsset, basket, facade, facadeTest, rToken, main, basketHandler } = - await loadFixture(defaultFixture)) + ;({ + stRSR, + rsr, + rsrAsset, + basket, + facade, + facadeAct, + facadeTest, + rToken, + main, + basketHandler, + backingManager, + rTokenTrader, + rsrTrader, + broker, + assetRegistry, + } = await loadFixture(defaultFixture)) // Get assets and tokens ;[tokenAsset, usdcAsset, aTokenAsset, cTokenAsset] = basket @@ -345,60 +372,111 @@ describe('FacadeRead contract', () => { expect(overCollateralization).to.equal(0) }) - it('Should return traderBalances correctly', async () => { - // BackingManager - const backingManager = await ethers.getContractAt( - 'TestIBackingManager', - await main.backingManager() - ) - let [erc20s, balances, balancesNeeded] = await facade.traderBalances( - rToken.address, - backingManager.address - ) + it('Should return balancesAcrossAllTraders correctly', async () => { + // Send 1 token to rTokenTrader; 2 to rsrTrader + await token.connect(addr1).transfer(rTokenTrader.address, 1) + await token.connect(addr1).transfer(rsrTrader.address, 2) + await usdc.connect(addr1).transfer(rTokenTrader.address, 1) + await usdc.connect(addr1).transfer(rsrTrader.address, 2) + await aToken.connect(addr1).transfer(rTokenTrader.address, 1) + await aToken.connect(addr1).transfer(rsrTrader.address, 2) + await cToken.connect(addr1).transfer(rTokenTrader.address, 1) + await cToken.connect(addr1).transfer(rsrTrader.address, 2) + + // Balances + const [erc20s, balances, balancesNeededByBackingManager] = + await facade.callStatic.balancesAcrossAllTraders(rToken.address) expect(erc20s.length).to.equal(8) expect(balances.length).to.equal(8) - expect(balancesNeeded.length).to.equal(8) + expect(balancesNeededByBackingManager.length).to.equal(8) - const backingBuffer = await backingManager.backingBuffer() for (let i = 0; i < 8; i++) { let bal = bn('0') if (erc20s[i] == token.address) bal = issueAmount.div(4) if (erc20s[i] == usdc.address) bal = issueAmount.div(4).div(bn('1e12')) if (erc20s[i] == aToken.address) bal = issueAmount.div(4) if (erc20s[i] == cToken.address) bal = issueAmount.div(4).mul(50).div(bn('1e10')) - expect(balances[i]).to.equal(bal) - const balNeeded = bal.add(bal.mul(backingBuffer).div(fp('1'))) - expect(balancesNeeded[i]).to.equal(balNeeded) + if ([token.address, usdc.address, aToken.address, cToken.address].indexOf(erc20s[i]) >= 0) { + expect(balances[i]).to.equal(bal.add(3)) // expect 3 more + expect(balancesNeededByBackingManager[i]).to.equal(bal) + } else { + expect(balances[i]).to.equal(0) + expect(balancesNeededByBackingManager[i]).to.equal(0) + } } + }) - // RTokenTrader - ;[erc20s, balances, balancesNeeded] = await facade.traderBalances( - rToken.address, - await main.rTokenTrader() - ) - expect(erc20s.length).to.equal(8) - expect(balances.length).to.equal(8) - expect(balancesNeeded.length).to.equal(8) - for (let i = 0; i < 8; i++) { - expect(balances[i]).to.equal(0) - expect(balancesNeeded[i]).to.equal(0) - } + it('Should return revenueSurpluses + chain into FacadeAct.runRevenueAuctions', async () => { + const traders = [rTokenTrader, rsrTrader] + for (let traderIndex = 0; traderIndex < traders.length; traderIndex++) { + const trader = traders[traderIndex] + + const minTradeVolume = await trader.minTradeVolume() + const auctionLength = await broker.auctionLength() + const tokenSurplus = bn('0.5e18') + await token.connect(addr1).transfer(trader.address, tokenSurplus) + + // revenueSurpluses + const [erc20s, canStart, surpluses, minTradeAmounts] = + await facade.callStatic.revenueSurpluses(trader.address) + expect(erc20s.length).to.equal(8) // should be full set of registered ERC20s + + const erc20sToStart = [] + for (let i = 0; i < 8; i++) { + if (erc20s[i] == token.address) { + erc20sToStart.push(erc20s[i]) + expect(canStart[i]).to.equal(true) + expect(surpluses[i]).to.equal(tokenSurplus) + } else { + expect(canStart[i]).to.equal(false) + expect(surpluses[i]).to.equal(0) + } + + const asset = await ethers.getContractAt('IAsset', await assetRegistry.toAsset(erc20s[i])) + const [low] = await asset.price() + expect(minTradeAmounts[i]).to.equal( + minTradeVolume.mul(bn('10').pow(await asset.erc20Decimals())).div(low) + ) // 1% oracleError + } - // RSRTrader - ;[erc20s, balances, balancesNeeded] = await facade.traderBalances( - rToken.address, - await main.rsrTrader() - ) - expect(erc20s.length).to.equal(8) - expect(balances.length).to.equal(8) - expect(balancesNeeded.length).to.equal(8) - for (let i = 0; i < 8; i++) { - expect(balances[i]).to.equal(0) - expect(balancesNeeded[i]).to.equal(0) + // Run revenue auctions + await facadeAct.runRevenueAuctions(trader.address, [], erc20sToStart) + + // Nothing should be settleable + expect((await facade.auctionsSettleable(trader.address)).length).to.equal(0) + + // Advance time till auction ended + await advanceTime(auctionLength + 100) + + // Now should be settleable + const settleable = await facade.auctionsSettleable(trader.address) + expect(settleable.length).to.equal(1) + expect(settleable[0]).to.equal(token.address) } }) + it('Should return nextRecollateralizationAuction', async () => { + // Setup prime basket + await basketHandler.connect(owner).setPrimeBasket([usdc.address], [fp('1')]) + + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(2, [usdc.address], [fp('1')], false) + + // Trigger recollateralization + const sellAmt: BigNumber = await token.balanceOf(backingManager.address) + + // Confirm nextRecollateralizationAuction is true + const [canStart, sell, buy, sellAmount] = + await facade.callStatic.nextRecollateralizationAuction(backingManager.address) + expect(canStart).to.equal(true) + expect(sell).to.equal(token.address) + expect(buy).to.equal(usdc.address) + expect(sellAmount).to.equal(sellAmt) + }) + it('Should return basketBreakdown correctly for paused token', async () => { await main.connect(owner).pauseTrading() await expectValidBasketBreakdown(rToken) From fa832bea68783c1b425f5370c6b5769cd8d3a3ef Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 16:40:22 -0400 Subject: [PATCH 076/462] revenueSurpluses => revenue --- contracts/facade/FacadeAct.sol | 2 +- contracts/facade/FacadeRead.sol | 2 +- contracts/interfaces/IFacadeAct.sol | 2 +- contracts/interfaces/IFacadeRead.sol | 2 +- test/FacadeRead.test.ts | 9 +++++---- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 72325757ab..e9caea014f 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -354,7 +354,7 @@ contract FacadeAct is IFacadeAct { /// To use this, first call: /// - FacadeRead.auctionsSettleable(revenueTrader) - /// - revenueSurpluses(revenueTrader) + /// - revenue(revenueTrader) /// If either arrays returned are non-empty, then can call this function. /// Logic: /// For each ERC20 in `toSettle`: diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 7bc4753341..e1b62afaea 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -262,7 +262,7 @@ contract FacadeRead is IFacadeRead { /// @return surpluses {qTok} The surplus amount /// @return minTradeAmounts {qTok} The minimum amount worth trading /// @custom:static-call - function revenueSurpluses(IRevenueTrader revenueTrader) + function revenue(IRevenueTrader revenueTrader) external returns ( IERC20[] memory erc20s, diff --git a/contracts/interfaces/IFacadeAct.sol b/contracts/interfaces/IFacadeAct.sol index 4964a1acfd..c673f61275 100644 --- a/contracts/interfaces/IFacadeAct.sol +++ b/contracts/interfaces/IFacadeAct.sol @@ -22,7 +22,7 @@ interface IFacadeAct { /// To use this, first call: /// - FacadeRead.auctionsSettleable(revenueTrader) - /// - revenueSurpluses(revenueTrader) + /// - revenue(revenueTrader) /// If either arrays returned are non-empty, then can call this function. /// Logic: /// For each ERC20 in `toSettle`: diff --git a/contracts/interfaces/IFacadeRead.sol b/contracts/interfaces/IFacadeRead.sol index 56ade46e7a..063b000c35 100644 --- a/contracts/interfaces/IFacadeRead.sol +++ b/contracts/interfaces/IFacadeRead.sol @@ -92,7 +92,7 @@ interface IFacadeRead { /// @return surpluses {qTok} The surplus amount /// @return minTradeAmounts {qTok} The minimum amount worth trading /// @custom:static-call - function revenueSurpluses(IRevenueTrader revenueTrader) + function revenue(IRevenueTrader revenueTrader) external returns ( IERC20[] memory erc20s, diff --git a/test/FacadeRead.test.ts b/test/FacadeRead.test.ts index e0802a2ca8..4961c56831 100644 --- a/test/FacadeRead.test.ts +++ b/test/FacadeRead.test.ts @@ -407,7 +407,7 @@ describe('FacadeRead contract', () => { } }) - it('Should return revenueSurpluses + chain into FacadeAct.runRevenueAuctions', async () => { + it('Should return revenue + chain into FacadeAct.runRevenueAuctions', async () => { const traders = [rTokenTrader, rsrTrader] for (let traderIndex = 0; traderIndex < traders.length; traderIndex++) { const trader = traders[traderIndex] @@ -417,9 +417,10 @@ describe('FacadeRead contract', () => { const tokenSurplus = bn('0.5e18') await token.connect(addr1).transfer(trader.address, tokenSurplus) - // revenueSurpluses - const [erc20s, canStart, surpluses, minTradeAmounts] = - await facade.callStatic.revenueSurpluses(trader.address) + // revenue + const [erc20s, canStart, surpluses, minTradeAmounts] = await facade.callStatic.revenue( + trader.address + ) expect(erc20s.length).to.equal(8) // should be full set of registered ERC20s const erc20sToStart = [] From ae1a9bd3a2368b94638539f614585b00e522f531 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 16:42:14 -0400 Subject: [PATCH 077/462] revenue => revenueOverview --- contracts/facade/FacadeRead.sol | 2 +- contracts/interfaces/IFacadeRead.sol | 2 +- test/FacadeRead.test.ts | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index e1b62afaea..7772817b8b 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -262,7 +262,7 @@ contract FacadeRead is IFacadeRead { /// @return surpluses {qTok} The surplus amount /// @return minTradeAmounts {qTok} The minimum amount worth trading /// @custom:static-call - function revenue(IRevenueTrader revenueTrader) + function revenueOverview(IRevenueTrader revenueTrader) external returns ( IERC20[] memory erc20s, diff --git a/contracts/interfaces/IFacadeRead.sol b/contracts/interfaces/IFacadeRead.sol index 063b000c35..ccb323c608 100644 --- a/contracts/interfaces/IFacadeRead.sol +++ b/contracts/interfaces/IFacadeRead.sol @@ -92,7 +92,7 @@ interface IFacadeRead { /// @return surpluses {qTok} The surplus amount /// @return minTradeAmounts {qTok} The minimum amount worth trading /// @custom:static-call - function revenue(IRevenueTrader revenueTrader) + function revenueOverview(IRevenueTrader revenueTrader) external returns ( IERC20[] memory erc20s, diff --git a/test/FacadeRead.test.ts b/test/FacadeRead.test.ts index 4961c56831..dd7b3bd646 100644 --- a/test/FacadeRead.test.ts +++ b/test/FacadeRead.test.ts @@ -418,9 +418,8 @@ describe('FacadeRead contract', () => { await token.connect(addr1).transfer(trader.address, tokenSurplus) // revenue - const [erc20s, canStart, surpluses, minTradeAmounts] = await facade.callStatic.revenue( - trader.address - ) + const [erc20s, canStart, surpluses, minTradeAmounts] = + await facade.callStatic.revenueOverview(trader.address) expect(erc20s.length).to.equal(8) // should be full set of registered ERC20s const erc20sToStart = [] From 4f4159bd098f9c94a96fdc60e230f931df0818fc Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 16:46:04 -0400 Subject: [PATCH 078/462] deploy to goerli --- scripts/addresses/5-tmp-deployments.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/addresses/5-tmp-deployments.json b/scripts/addresses/5-tmp-deployments.json index bbb6e29231..ae3fa1caf9 100644 --- a/scripts/addresses/5-tmp-deployments.json +++ b/scripts/addresses/5-tmp-deployments.json @@ -5,7 +5,7 @@ "GNOSIS_EASY_AUCTION": "0x1fbab40c338e2e7243da945820ba680c92ef8281" }, "tradingLib": "0xCFA67f42A0fDe4F0Fb612ea5e66170B0465B84c1", - "facadeRead": "0x9E50605e02483c273F7B5A5826f363F3a46DCA79", + "facadeRead": "0x3C8cD9FCa9925780598eB097D9718dF3da482C2F", "facadeAct": "0xafd16aFdE22D42038223A6FfDF00ee49c8fDa985", "facadeWriteLib": "0xCBcd605088D5A5Da9ceEb3618bc01BFB87387423", "facadeWrite": "0x97C75046CE7Ea5253d20A35B3138699865E8813f", From b214da37859a46b80460016b2484492ec24c490f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 17:24:32 -0400 Subject: [PATCH 079/462] lint clean --- contracts/facade/FacadeRead.sol | 3 ++- contracts/interfaces/IFacadeRead.sol | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 7772817b8b..cbf6d746d0 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -172,7 +172,7 @@ contract FacadeRead is IFacadeRead { /// @return erc20s The registered ERC20s /// @return balances {qTok} The held balances of each ERC20 across all traders - /// @return balancesNeededByBackingManager {qTok} The needed balance of each ERC20 at the BackingManager + /// @return balancesNeededByBackingManager {qTok} does not account for backingBuffer /// @custom:static-call function balancesAcrossAllTraders(IRToken rToken) external @@ -304,6 +304,7 @@ contract FacadeRead is IFacadeRead { if (revenueTrader.tradesOpen() - tradesOpen > 0) { canStart[i] = true; } + // solhint-disable-next-line no-empty-blocks } catch {} } } diff --git a/contracts/interfaces/IFacadeRead.sol b/contracts/interfaces/IFacadeRead.sol index ccb323c608..492c7b844f 100644 --- a/contracts/interfaces/IFacadeRead.sol +++ b/contracts/interfaces/IFacadeRead.sol @@ -61,7 +61,7 @@ interface IFacadeRead { /// @return erc20s The registered ERC20s /// @return balances {qTok} The held balances of each ERC20 across all traders - /// @return balancesNeededByBackingManager {qTok} The needed balance of each ERC20 at the BackingManager + /// @return balancesNeededByBackingManager {qTok} does not account for backingBuffer /// @custom:static-call function balancesAcrossAllTraders(IRToken rToken) external From 9eda311961601b800b2e939679ce8f9dd0fe9ad4 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 13:39:04 -0400 Subject: [PATCH 080/462] revert changes that were picked up from the vault branch somehow? --- contracts/facade/FacadeAct.sol | 2 +- contracts/interfaces/IFacadeAct.sol | 2 +- contracts/interfaces/ITrade.sol | 4 ---- .../convex/vendor/ConvexStakingWrapper.sol | 9 --------- .../mainnet-2.1.0/1-tmp-assets-collateral.json | 6 +++--- .../convex/CvxStableMetapoolSuite.test.ts | 18 +----------------- .../CvxStableRTokenMetapoolTestSuite.test.ts | 18 +----------------- .../convex/CvxStableTestSuite.test.ts | 18 +----------------- .../convex/CvxVolatileTestSuite.test.ts | 18 +----------------- 9 files changed, 9 insertions(+), 86 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index e9caea014f..4f98a1b647 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -354,7 +354,7 @@ contract FacadeAct is IFacadeAct { /// To use this, first call: /// - FacadeRead.auctionsSettleable(revenueTrader) - /// - revenue(revenueTrader) + /// - FacadeRead.revenueOverview(revenueTrader) /// If either arrays returned are non-empty, then can call this function. /// Logic: /// For each ERC20 in `toSettle`: diff --git a/contracts/interfaces/IFacadeAct.sol b/contracts/interfaces/IFacadeAct.sol index c673f61275..d2c7637fc5 100644 --- a/contracts/interfaces/IFacadeAct.sol +++ b/contracts/interfaces/IFacadeAct.sol @@ -22,7 +22,7 @@ interface IFacadeAct { /// To use this, first call: /// - FacadeRead.auctionsSettleable(revenueTrader) - /// - revenue(revenueTrader) + /// - FacadeRead.revenueOverview(revenueTrader) /// If either arrays returned are non-empty, then can call this function. /// Logic: /// For each ERC20 in `toSettle`: diff --git a/contracts/interfaces/ITrade.sol b/contracts/interfaces/ITrade.sol index 9dfbbd010b..99736768c6 100644 --- a/contracts/interfaces/ITrade.sol +++ b/contracts/interfaces/ITrade.sol @@ -13,10 +13,6 @@ interface ITrade { function buy() external view returns (IERC20Metadata); - /// The sell amount - /// @return {qSellTok} - function initBal() external view returns (uint256); - /// @return The timestamp at which the trade is projected to become settle-able function endTime() external view returns (uint48); diff --git a/contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol b/contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol index 71caaf5035..c2984c5861 100644 --- a/contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol +++ b/contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol @@ -92,7 +92,6 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { ); event Withdrawn(address indexed _user, uint256 _amount, bool _unwrapped); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - event RewardsClaimed(IERC20 indexed erc20, uint256 indexed amount); constructor() public ERC20("StakedConvexToken", "stkCvx") {} @@ -405,14 +404,6 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { return claimable; } - function claimRewards() external { - uint256 cvxOldBal = IERC20(cvx).balanceOf(msg.sender); - uint256 crvOldBal = IERC20(crv).balanceOf(msg.sender); - _checkpointAndClaim([address(msg.sender), address(msg.sender)]); - emit RewardsClaimed(IERC20(cvx), IERC20(cvx).balanceOf(msg.sender) - cvxOldBal); - emit RewardsClaimed(IERC20(crv), IERC20(crv).balanceOf(msg.sender) - crvOldBal); - } - function getReward(address _account) external { //claim directly in checkpoint logic to save a bit of gas _checkpointAndClaim([_account, _account]); diff --git a/scripts/addresses/mainnet-2.1.0/1-tmp-assets-collateral.json b/scripts/addresses/mainnet-2.1.0/1-tmp-assets-collateral.json index 38d5a5a24b..8a23ad0ef4 100644 --- a/scripts/addresses/mainnet-2.1.0/1-tmp-assets-collateral.json +++ b/scripts/addresses/mainnet-2.1.0/1-tmp-assets-collateral.json @@ -33,9 +33,9 @@ "fDAI": "0xA4410B71033fFE8fA41c6096332Be58E3641326d", "fFRAX": "0xcd46Ff27c0d6F088FB94896dcE8F17491BD84c75", "cUSDCv3": "0x615D92fAF203Faa9ea7a4D8cdDC49b2Ad0702a1f", - "cvx3Pool": "0xC34E547D66B5a57B370217aAe4F34b882a9933Dc", + "cvx3Pool": "0x14548a0aEcA46418cD9cFd08c6Bf8E02FbE53B5E", "cvxTriCrypto": "0xb2EeD19C381b71d0f54327D61596312144f66fA7", - "cvxeUSDFRAXBP": "0x0D41E86D019cadaAA32a5a12A35d456711879770", - "cvxMIM3Pool": "0x9866020B7A59022C2F017C6d358868cB11b86E2d" + "cvxeUSDFRAXBP": "0x8DC1750B1fe69e940f570c021d658C14D8041834", + "cvxMIM3Pool": "0xD5BE0AeC2b537481A4fE2EcF52422a24644e1EF3" } } diff --git a/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts b/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts index 163fc010fb..dc16d43a2b 100644 --- a/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts +++ b/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts @@ -450,7 +450,7 @@ describeFork(`Collateral: Convex - Stable Metapool (MIM+3Pool)`, () => { await expect(collateral.claimRewards()).to.not.be.reverted }) - it('claims rewards (plugin)', async () => { + it('claims rewards', async () => { const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) await mintCollateralTo(ctx, amount, alice, collateral.address) @@ -465,22 +465,6 @@ describeFork(`Collateral: Convex - Stable Metapool (MIM+3Pool)`, () => { expect(crvAfter).gt(crvBefore) expect(cvxAfter).gt(cvxBefore) }) - - it('claims rewards (wrapper)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, alice.address) - - await advanceBlocks(1000) - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) - - const crvBefore = await crv.balanceOf(alice.address) - const cvxBefore = await cvx.balanceOf(alice.address) - await expect(ctx.wPool.connect(alice).claimRewards()).to.emit(ctx.wPool, 'RewardsClaimed') - const crvAfter = await crv.balanceOf(alice.address) - const cvxAfter = await cvx.balanceOf(alice.address) - expect(crvAfter).gt(crvBefore) - expect(cvxAfter).gt(cvxBefore) - }) }) describe('prices', () => { diff --git a/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts index e240dfe99f..44cac85b53 100644 --- a/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts @@ -435,7 +435,7 @@ describeFork(`Collateral: Convex - RToken Metapool (eUSD/fraxBP)`, () => { await expect(collateral.claimRewards()).to.not.be.reverted }) - it('claims rewards (plugin)', async () => { + it('claims rewards', async () => { const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) await mintCollateralTo(ctx, amount, alice, collateral.address) @@ -450,22 +450,6 @@ describeFork(`Collateral: Convex - RToken Metapool (eUSD/fraxBP)`, () => { expect(crvAfter).gt(crvBefore) expect(cvxAfter).gt(cvxBefore) }) - - it('claims rewards (wrapper)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, alice.address) - - await advanceBlocks(1000) - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) - - const crvBefore = await crv.balanceOf(alice.address) - const cvxBefore = await cvx.balanceOf(alice.address) - await expect(ctx.wPool.connect(alice).claimRewards()).to.emit(ctx.wPool, 'RewardsClaimed') - const crvAfter = await crv.balanceOf(alice.address) - const cvxAfter = await cvx.balanceOf(alice.address) - expect(crvAfter).gt(crvBefore) - expect(cvxAfter).gt(cvxBefore) - }) }) describe('prices', () => { diff --git a/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts b/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts index 84ef545663..35e331c292 100644 --- a/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts +++ b/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts @@ -442,7 +442,7 @@ describeFork(`Collateral: Convex - Stable (3Pool)`, () => { await expect(collateral.claimRewards()).to.not.be.reverted }) - it('claims rewards (plugin)', async () => { + it('claims rewards', async () => { const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) await mintCollateralTo(ctx, amount, alice, collateral.address) @@ -457,22 +457,6 @@ describeFork(`Collateral: Convex - Stable (3Pool)`, () => { expect(crvAfter).gt(crvBefore) expect(cvxAfter).gt(cvxBefore) }) - - it('claims rewards (wrapper)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, alice.address) - - await advanceBlocks(1000) - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) - - const crvBefore = await crv.balanceOf(alice.address) - const cvxBefore = await cvx.balanceOf(alice.address) - await expect(ctx.w3Pool.connect(alice).claimRewards()).to.emit(ctx.w3Pool, 'RewardsClaimed') - const crvAfter = await crv.balanceOf(alice.address) - const cvxAfter = await cvx.balanceOf(alice.address) - expect(crvAfter).gt(crvBefore) - expect(cvxAfter).gt(cvxBefore) - }) }) describe('prices', () => { diff --git a/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts b/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts index 749f5b29df..6fea7aa79f 100644 --- a/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts +++ b/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts @@ -472,7 +472,7 @@ describeFork(`Collateral: Convex - Volatile`, () => { await expect(collateral.claimRewards()).to.not.be.reverted }) - it('claims rewards (plugin)', async () => { + it('claims rewards', async () => { const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) await mintCollateralTo(ctx, amount, alice, collateral.address) @@ -487,22 +487,6 @@ describeFork(`Collateral: Convex - Volatile`, () => { expect(crvAfter).gt(crvBefore) expect(cvxAfter).gt(cvxBefore) }) - - it('claims rewards (wrapper)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, alice.address) - - await advanceBlocks(1000) - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) - - const crvBefore = await crv.balanceOf(alice.address) - const cvxBefore = await cvx.balanceOf(alice.address) - await expect(ctx.w3Pool.connect(alice).claimRewards()).to.emit(ctx.w3Pool, 'RewardsClaimed') - const crvAfter = await crv.balanceOf(alice.address) - const cvxAfter = await cvx.balanceOf(alice.address) - expect(crvAfter).gt(crvBefore) - expect(cvxAfter).gt(cvxBefore) - }) }) describe('prices', () => { From ca50c45cb3266d18bdcd46a1555feef4291bc590 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 13:41:05 -0400 Subject: [PATCH 081/462] ITrade.initBal() --- contracts/interfaces/ITrade.sol | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/contracts/interfaces/ITrade.sol b/contracts/interfaces/ITrade.sol index 99736768c6..b790b5b5f9 100644 --- a/contracts/interfaces/ITrade.sol +++ b/contracts/interfaces/ITrade.sol @@ -9,6 +9,11 @@ import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; * Usage: if (canSettle()) settle() */ interface ITrade { + /// Complete the trade and transfer tokens back to the origin trader + /// @return soldAmt {qSellTok} The quantity of tokens sold + /// @return boughtAmt {qBuyTok} The quantity of tokens bought + function settle() external returns (uint256 soldAmt, uint256 boughtAmt); + function sell() external view returns (IERC20Metadata); function buy() external view returns (IERC20Metadata); @@ -20,8 +25,6 @@ interface ITrade { /// @dev Should be guaranteed to be true eventually as an invariant function canSettle() external view returns (bool); - /// Complete the trade and transfer tokens back to the origin trader - /// @return soldAmt {qSellTok} The quantity of tokens sold - /// @return boughtAmt {qBuyTok} The quantity of tokens bought - function settle() external returns (uint256 soldAmt, uint256 boughtAmt); + /// @return {qSellTok} + function initBal() external view returns (uint256); } From 277b6cd43582da783e01a8d6d3b56f59512b048a Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 13:42:07 -0400 Subject: [PATCH 082/462] utils/env.ts --- utils/env.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/env.ts b/utils/env.ts index 9d5e776239..ea18051802 100644 --- a/utils/env.ts +++ b/utils/env.ts @@ -19,7 +19,6 @@ type IEnvVars = | 'JOBS' | 'EXTREME' | 'SUBGRAPH_URL' - | 'MAINNET_PK' export function useEnv(key: IEnvVars | IEnvVars[], _default = ''): string { if (typeof key === 'string') { From ed3a1c2337ad8a9eee1096f92b9a936a8deac9e5 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 18:49:25 -0400 Subject: [PATCH 083/462] implement DutchTrade --- contracts/interfaces/ITrade.sol | 8 + contracts/plugins/trading/DutchTrade.sol | 201 ++++++++++++++++++++++ contracts/plugins/trading/GnosisTrade.sol | 7 - 3 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 contracts/plugins/trading/DutchTrade.sol diff --git a/contracts/interfaces/ITrade.sol b/contracts/interfaces/ITrade.sol index 99736768c6..64de66ae3e 100644 --- a/contracts/interfaces/ITrade.sol +++ b/contracts/interfaces/ITrade.sol @@ -3,6 +3,14 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +enum TradeStatus { + NOT_STARTED, // before init() + OPEN, // after init() and before settle() + CLOSED, // after settle() + // === Intermediate-tx state === + PENDING // during init() or settle() (reentrancy protection) +} + /** * Simple generalized trading interface for all Trade contracts to obey * diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol new file mode 100644 index 0000000000..224fb1ad23 --- /dev/null +++ b/contracts/plugins/trading/DutchTrade.sol @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.17; + +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "../../libraries/Fixed.sol"; +import "../../interfaces/IAsset.sol"; +import "../../interfaces/ITrade.sol"; + +uint192 constant FIFTEEN_PERCENT = 15e16; // {1} +uint192 constant FIFTY_PERCENT = 50e16; // {1} +uint192 constant EIGHTY_FIVE_PERCENT = 85e16; // {1} + +/** + * @title DutchTrade + * @notice Implements a wholesale dutch auction via a piecewise falling-price mechansim. + * Over the first 15% of the auction the price falls from the ~150% pricepoint to the + * best price, as given by the price range. Over the last 85% of the auction it falls + * from the best price to the worst price. The worst price is additionally discounted by + * the maxTradeSlippage based on how far between minTradeVolume and maxTradeVolume the trade is. + * + * Flow for bidding: + * - Call `bidAmount()` to check price at various timestamps + * - Wait until desirable block is reached + * - Provide approval for `buy` token in the correct amount + * - Call `bid()`. Receive payment in sell tokens atomically + */ +contract DutchTrade is ITrade { + using FixLib for uint192; + using SafeERC20 for IERC20Metadata; + + TradeStatus public status; // reentrancy protection + + ITrading public origin; // creator + + // === Auction === + IERC20Metadata public sell; + IERC20Metadata public buy; + uint192 public sellAmount; // {sellTok} + + uint48 public startTime; // timestamp at which the dutch auction began + uint48 public endTime; // timestamp the dutch auction ends, if no bids have been received + + uint192 public middlePrice; // {buyTok/sellTok} The price at which the function is piecewise + uint192 public lowPrice; // {buyTok/sellTok} The price the auction ends at + // highPrice is always 1.5x the middlePrice + + // === Bid === + address public bidder; + // the bid amount is just whatever token balance is in the contract at settlement time + + // This modifier both enforces the state-machine pattern and guards against reentrancy. + modifier stateTransition(TradeStatus begin, TradeStatus end) { + require(status == begin, "Invalid trade state"); + status = TradeStatus.PENDING; + _; + assert(status == TradeStatus.PENDING); + status = end; + } + + /// @param sell_ The asset being sold by the protocol + /// @param buy_ The asset being bought by the protocol + /// @param sellAmount_ {sellTok} The amount to sell in the auction, in whole tokens + /// @param minTradeVolume_ {UoA} The mimimum amount to trade + /// @param maxTradeSlippage_ {1} An additional discount applied to the auction low price + /// @param auctionLength {1} An additional discount applied to the auction low price + function init( + IAsset sell_, + IAsset buy_, + uint192 sellAmount_, + uint192 minTradeVolume_, + uint192 maxTradeSlippage_, + uint48 auctionLength + ) external stateTransition(TradeStatus.NOT_STARTED, TradeStatus.OPEN) { + require(address(sell) != address(0) || address(buy) != address(0), "zero address token"); + + // uint256 sellAmountQ = sellAmount_.shiftl_toUint(int8(sell_.erc20Decimals())); + + // Only start an auction with well-defined prices + // + // In the BackingManager this may end up recalculating the RToken price + (uint192 sellLow, uint192 sellHigh) = sell_.price(); // {UoA/sellTok} + (uint192 buyLow, uint192 buyHigh) = buy_.price(); // {UoA/buyTok} + require(sellLow > 0 && sellHigh < FIX_MAX, "bad sell pricing"); + require(buyLow > 0 && buyHigh < FIX_MAX, "bad buy pricing"); + + // {UoA} + uint192 maxTradeVolume = fixMin(sell_.maxTradeVolume(), buy_.maxTradeVolume()); + + origin = ITrading(msg.sender); + sell = sell_.erc20(); + buy = buy_.erc20(); + sellAmount = fixMin(sellAmount_, maxTradeVolume.div(sellHigh, FLOOR)); + startTime = uint48(block.timestamp); + endTime = uint48(block.timestamp) + auctionLength; + + // {UoA} = {sellTok} * {UoA/sellTok} + uint192 auctionVolume = sellAmount.mul(sellHigh, FLOOR); + require(auctionVolume >= minTradeVolume_, "auction too small"); + + // {1} = {1} * ({UoA} - {UoA}} / ({UoA} - {UoA}) + uint192 slippage = maxTradeSlippage_.mul( + FIX_ONE - divuu(auctionVolume - minTradeVolume_, maxTradeVolume - minTradeVolume_) + ); + + // {buyTok/sellTok} = {1} * {UoA/sellTok} / {UoA/buyTok} + lowPrice = sellLow.mulDiv(FIX_ONE - slippage, buyHigh, FLOOR); + middlePrice = sellHigh.div(buyLow, CEIL); // no additional slippage + // highPrice = 1.5 * middlePrice + + require(lowPrice <= middlePrice, "asset inverted pricing"); + } + + /// Calculates how much buy token is needed to purchase the lot, at a particular timestamp + /// Price Curve: + /// - 1.5 * middlePrice down to the middlePrice for first 15% of auction + /// - middlePrice down to lowPrice for the last 80% of auction + /// @param timestamp {s} The block timestamp to get price for + /// @return {qBuyTok} The amount of buy tokens required to purchase the lot + function bidAmount(uint48 timestamp) public view returns (uint256) { + require(timestamp < endTime, "auction over"); + + uint192 progression = divuu(uint48(block.timestamp) - startTime, endTime - startTime); + // assert(progression <= FIX_ONE); + + // {buyTok/sellTok} + uint192 price; + + if (progression < FIFTEEN_PERCENT) { + // Fast decay -- 15th percentile case + + // highPrice is 1.5x middlePrice + uint192 highPrice = middlePrice + middlePrice.mul(FIFTY_PERCENT); + price = highPrice - (highPrice - middlePrice).mulDiv(progression, FIFTEEN_PERCENT); + } else { + // Slow decay -- 85th percentile case + price = + middlePrice - + (middlePrice - lowPrice).mulDiv(progression - FIFTEEN_PERCENT, EIGHTY_FIVE_PERCENT); + } + + // {qBuyTok} = {sellTok} * {buyTok/sellTok} + return sellAmount.mul(price, CEIL).shiftl_toUint(int8(buy.decimals()), CEIL); + } + + /// Bid for the auction lot at the current price; settling atomically via a callback + /// @dev Caller must have provided approval + function bid() external { + require(bidder == address(0), "bid received"); + + // {qBuyTok} + uint256 buyAmount = bidAmount(uint48(block.timestamp)); + + // Transfer in buy tokens + bidder = msg.sender; + buy.safeTransferFrom(bidder, address(this), buyAmount); + // TODO examine reentrancy - should be okay + + // Settle via callback + origin.settleTrade(sell); + } + + /// Settle the auction, emptying the contract of balances + /// @dev Buyer must have transferred buy tokens into the contract ahead of time + function settle() + external + stateTransition(TradeStatus.OPEN, TradeStatus.CLOSED) + returns (uint256 soldAmt, uint256 boughtAmt) + { + require(msg.sender == address(origin), "only origin can settle"); // via origin.settleTrade() + + // Received bid + if (bidder != address(0)) { + sell.safeTransfer(bidder, sellAmount); + } else { + require(block.timestamp >= endTime, "auction not over"); + } + + // {qBuyTok} + uint256 boughtAmount = buy.balanceOf(address(this)); + + // Transfer balances back to origin + buy.safeTransfer(address(origin), boughtAmount); + sell.safeTransfer(address(origin), sell.balanceOf(address(this))); + return (sellAmount, boughtAmount); + } + + /// Anyone can transfer any ERC20 back to the origin after the trade has been closed + /// @dev Escape hatch in case of accidentally transferred tokens after auction end + /// @custom:interaction CEI (and respects the state lock) + function transferToOriginAfterTradeComplete(IERC20Metadata erc20) external { + require(status == TradeStatus.CLOSED, "only after trade is closed"); + erc20.safeTransfer(address(origin), erc20.balanceOf(address(this))); + } + + /// @return True if the trade can be settled. + // Guaranteed to be true some time after init(), until settle() is called + function canSettle() external view returns (bool) { + return status == TradeStatus.OPEN && (bidder != address(0) || block.timestamp >= endTime); + } +} diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index c9258f500b..1d5e8ac96d 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -9,13 +9,6 @@ import "../../interfaces/IBroker.sol"; import "../../interfaces/IGnosis.sol"; import "../../interfaces/ITrade.sol"; -enum TradeStatus { - NOT_STARTED, // before init() - OPEN, // after init() and before settle() - CLOSED, // after settle() - PENDING // during init() or settle() (reentrancy protection) -} - // Modifications to this contract's state must only ever be made when status=PENDING! /// Trade contract against the Gnosis EasyAuction mechanism From 2b179489f0d58ffccd633e9f107d976c20958a08 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 19:04:27 -0400 Subject: [PATCH 084/462] add hardhat-storage-layout plugin --- hardhat.config.ts | 1 + package.json | 1 + yarn.lock | 28 ++++++++++++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/hardhat.config.ts b/hardhat.config.ts index f463f81e1c..b4e964b673 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -7,6 +7,7 @@ import '@openzeppelin/hardhat-upgrades' import '@typechain/hardhat' import 'hardhat-contract-sizer' import 'hardhat-gas-reporter' +import 'hardhat-storage-layout' import 'solidity-coverage' import { useEnv } from '#/utils/env' diff --git a/package.json b/package.json index e4402c9013..2e4d6c546c 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "hardhat": "^2.12.2", "hardhat-contract-sizer": "^2.4.0", "hardhat-gas-reporter": "^1.0.8", + "hardhat-storage-layout": "^0.1.7", "husky": "^7.0.0", "lodash": "^4.17.21", "lodash.get": "^4.4.2", diff --git a/yarn.lock b/yarn.lock index d59002b339..ccf2644e47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3667,6 +3667,15 @@ __metadata: languageName: node linkType: hard +"console-table-printer@npm:^2.9.0": + version: 2.11.1 + resolution: "console-table-printer@npm:2.11.1" + dependencies: + simple-wcswidth: ^1.0.1 + checksum: 1c5ec6c5edb8f454ac78ce2708c96b29986db4c1f369b32acc1931f87658a65984a8cf8731ca548dea670ff04119c740a7d658eeba9633d0202983c71d957096 + languageName: node + linkType: hard + "convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.7.0": version: 1.8.0 resolution: "convert-source-map@npm:1.8.0" @@ -5614,6 +5623,17 @@ fsevents@~2.1.1: languageName: node linkType: hard +"hardhat-storage-layout@npm:^0.1.7": + version: 0.1.7 + resolution: "hardhat-storage-layout@npm:0.1.7" + dependencies: + console-table-printer: ^2.9.0 + peerDependencies: + hardhat: ^2.0.3 + checksum: 8d27d6b16c1ebdffa032ba6b99c61996df4601dcbaf7d770c474806b492a56de9d21b4190086ab40f50a3a1f2e9851cc81034a9cfd2e21368941977324f96fd4 + languageName: node + linkType: hard + "hardhat@npm:^2.12.2": version: 2.12.2 resolution: "hardhat@npm:2.12.2" @@ -8286,6 +8306,7 @@ fsevents@~2.1.1: hardhat: ^2.12.2 hardhat-contract-sizer: ^2.4.0 hardhat-gas-reporter: ^1.0.8 + hardhat-storage-layout: ^0.1.7 husky: ^7.0.0 lodash: ^4.17.21 lodash.get: ^4.4.2 @@ -8723,6 +8744,13 @@ resolve@1.17.0: languageName: node linkType: hard +"simple-wcswidth@npm:^1.0.1": + version: 1.0.1 + resolution: "simple-wcswidth@npm:1.0.1" + checksum: dc5bf4cb131d9c386825d1355add2b1ecc408b37dc2c2334edd7a1a4c9f527e6b594dedcdbf6d949bce2740c3a332e39af1183072a2d068e40d9e9146067a37f + languageName: node + linkType: hard + "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" From 39b08ffb2f0479d8fec330bcafb55254d43c6bd0 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 20:56:15 -0400 Subject: [PATCH 085/462] first compile --- contracts/facade/FacadeAct.sol | 59 +++++--- contracts/facade/FacadeMonitor.sol | 4 +- contracts/facade/FacadeTest.sol | 9 +- contracts/interfaces/IBackingManager.sol | 18 +-- contracts/interfaces/IBroker.sol | 17 ++- contracts/interfaces/IDeployer.sol | 10 +- contracts/interfaces/IRevenueTrader.sol | 4 +- contracts/p0/BackingManager.sol | 125 ++++++++-------- contracts/p0/Broker.sol | 120 +++++++++++---- contracts/p0/Deployer.sol | 9 +- contracts/p0/RevenueTrader.sol | 5 +- contracts/p0/mixins/Trading.sol | 5 +- contracts/p0/mixins/TradingLib.sol | 18 ++- contracts/p1/BackingManager.sol | 138 ++++++++--------- contracts/p1/Broker.sol | 140 +++++++++++++----- contracts/p1/Deployer.sol | 12 +- contracts/p1/RevenueTrader.sol | 11 +- contracts/p1/mixins/TradeLib.sol | 16 +- contracts/p1/mixins/Trading.sol | 6 +- contracts/plugins/mocks/InvalidBrokerMock.sol | 18 ++- contracts/plugins/trading/DutchTrade.sol | 48 +++--- contracts/plugins/trading/GnosisTrade.sol | 4 +- 22 files changed, 509 insertions(+), 287 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 4d08276373..506833aed6 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -92,11 +92,10 @@ contract FacadeAct is IFacadeAct { ) { // try: backingManager.manageTokens([]) - IERC20[] memory empty = new IERC20[](0); - try cache.bm.manageTokens(empty) { + try cache.bm.rebalance(TradeKind.DUTCH_AUCTION) { return ( address(cache.bm), - abi.encodeWithSelector(cache.bm.manageTokens.selector, empty) + abi.encodeWithSelector(cache.bm.rebalance.selector, TradeKind.DUTCH_AUCTION) ); } catch {} } else { @@ -138,14 +137,15 @@ contract FacadeAct is IFacadeAct { // rTokenTrader: check if we can start any trades uint48 tradesOpen = cache.rTokenTrader.tradesOpen(); - try cache.rTokenTrader.manageToken(erc20s[i]) { + try cache.rTokenTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) { if (cache.rTokenTrader.tradesOpen() - tradesOpen > 0) { // A trade started; do cache.rTokenTrader.manageToken return ( address(cache.rTokenTrader), abi.encodeWithSelector( cache.rTokenTrader.manageToken.selector, - erc20s[i] + erc20s[i], + TradeKind.DUTCH_AUCTION ) ); } @@ -153,14 +153,15 @@ contract FacadeAct is IFacadeAct { // rsrTrader: check if we can start any trades tradesOpen = cache.rsrTrader.tradesOpen(); - try cache.rsrTrader.manageToken(erc20s[i]) { + try cache.rsrTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) { if (cache.rsrTrader.tradesOpen() - tradesOpen > 0) { // A trade started; do cache.rsrTrader.manageToken return ( address(cache.rsrTrader), abi.encodeWithSelector( cache.rsrTrader.manageToken.selector, - erc20s[i] + erc20s[i], + TradeKind.DUTCH_AUCTION ) ); } @@ -172,7 +173,7 @@ contract FacadeAct is IFacadeAct { if (cache.bh.status() == CollateralStatus.SOUND) { IAsset rsrAsset = cache.reg.toAsset(cache.rsr); uint192 initialStRSRBal = rsrAsset.bal(address(cache.stRSR)); // {RSR} - try cache.bm.manageTokens(erc20s) { + try cache.bm.forwardRevenue(erc20s) { // if this unblocked an auction in either revenue trader, // then prepare backingManager.manageTokens for (uint256 i = 0; i < erc20s.length; i++) { @@ -183,7 +184,12 @@ contract FacadeAct is IFacadeAct { if (address(erc20s[i]) != address(rToken)) { // rTokenTrader: check if we can start any trades uint48 tradesOpen = cache.rTokenTrader.tradesOpen(); - try cache.rTokenTrader.manageToken(erc20s[i]) { + try + cache.rTokenTrader.manageToken( + erc20s[i], + TradeKind.DUTCH_AUCTION + ) + { if (cache.rTokenTrader.tradesOpen() - tradesOpen > 0) { // always forward RToken + the ERC20 twoERC20s[0] = address(rToken); @@ -193,7 +199,7 @@ contract FacadeAct is IFacadeAct { return ( address(cache.bm), abi.encodeWithSelector( - cache.bm.manageTokens.selector, + cache.bm.forwardRevenue.selector, twoERC20s ) ); @@ -207,7 +213,12 @@ contract FacadeAct is IFacadeAct { if (erc20s[i] != cache.rsr) { // rsrTrader: check if we can start any trades uint48 tradesOpen = cache.rsrTrader.tradesOpen(); - try cache.rsrTrader.manageToken(erc20s[i]) { + try + cache.rsrTrader.manageToken( + erc20s[i], + TradeKind.DUTCH_AUCTION + ) + { if (cache.rsrTrader.tradesOpen() - tradesOpen > 0) { // always forward RSR + the ERC20 twoERC20s[0] = address(cache.rsr); @@ -217,7 +228,7 @@ contract FacadeAct is IFacadeAct { return ( address(cache.bm), abi.encodeWithSelector( - cache.bm.manageTokens.selector, + cache.bm.forwardRevenue.selector, twoERC20s ) ); @@ -236,13 +247,18 @@ contract FacadeAct is IFacadeAct { rTokenAsset.bal(address(cache.rTokenTrader)) > minTradeSize(cache.rTokenTrader.minTradeVolume(), lotLow) ) { - try cache.rTokenTrader.manageToken(IERC20(address(rToken))) { + try + cache.rTokenTrader.manageToken( + IERC20(address(rToken)), + TradeKind.DUTCH_AUCTION + ) + { address[] memory oneERC20 = new address[](1); oneERC20[0] = address(rToken); return ( address(cache.bm), abi.encodeWithSelector( - cache.bm.manageTokens.selector, + cache.bm.forwardRevenue.selector, oneERC20 ) ); @@ -262,7 +278,7 @@ contract FacadeAct is IFacadeAct { IERC20[] memory empty = new IERC20[](0); return ( address(cache.bm), - abi.encodeWithSelector(cache.bm.manageTokens.selector, empty) + abi.encodeWithSelector(cache.bm.forwardRevenue.selector, empty) ); } } @@ -374,7 +390,10 @@ contract FacadeAct is IFacadeAct { if (tradesOpen != 0) return false; // Try to launch auctions - bm.manageTokensSortedOrder(new IERC20[](0)); + // solhint-disable-next-line no-empty-blocks + try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch { + return false; + } return bm.tradesOpen() > 0; } @@ -388,7 +407,7 @@ contract FacadeAct is IFacadeAct { Registry memory reg = revenueTrader.main().assetRegistry().getRegistry(); // Forward ALL revenue - revenueTrader.main().backingManager().manageTokens(reg.erc20s); + revenueTrader.main().backingManager().forwardRevenue(reg.erc20s); // Calculate which erc20s can have auctions started uint256 num; @@ -402,7 +421,7 @@ contract FacadeAct is IFacadeAct { uint256 tradesOpen = revenueTrader.tradesOpen(); - try revenueTrader.manageToken(reg.erc20s[i]) { + try revenueTrader.manageToken(reg.erc20s[i], TradeKind.DUTCH_AUCTION) { if (revenueTrader.tradesOpen() - tradesOpen > 0) { unfiltered[num] = reg.erc20s[i]; ++num; @@ -438,11 +457,11 @@ contract FacadeAct is IFacadeAct { } // Transfer revenue backingManager -> revenueTrader - revenueTrader.main().backingManager().manageTokens(toStart); + revenueTrader.main().backingManager().forwardRevenue(toStart); // Start auctions for (uint256 i = 0; i < toStart.length; ++i) { - revenueTrader.manageToken(toStart[i]); + revenueTrader.manageToken(toStart[i], TradeKind.DUTCH_AUCTION); } } } diff --git a/contracts/facade/FacadeMonitor.sol b/contracts/facade/FacadeMonitor.sol index 70f6b97dd5..eedfb30858 100644 --- a/contracts/facade/FacadeMonitor.sol +++ b/contracts/facade/FacadeMonitor.sol @@ -58,7 +58,7 @@ contract FacadeMonitor { // Let's check if there are any trades we can start. uint48 tradesOpen = backingManager.tradesOpen(); - backingManager.manageTokens(erc20s); + backingManager.rebalance(TradeKind.DUTCH_AUCTION); if (backingManager.tradesOpen() - tradesOpen != 0) { response.tradesToBeStarted = erc20s; } @@ -116,7 +116,7 @@ contract FacadeMonitor { IERC20[] memory tradesToBeStarted = new IERC20[](erc20Count); for (uint256 i = 0; i < erc20Count; ) { - trader.manageToken(erc20s[i]); + trader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION); uint48 newTradesOpen = trader.tradesOpen(); diff --git a/contracts/facade/FacadeTest.sol b/contracts/facade/FacadeTest.sol index cfb30f2a6b..746386c1a1 100644 --- a/contracts/facade/FacadeTest.sol +++ b/contracts/facade/FacadeTest.sol @@ -50,11 +50,14 @@ contract FacadeTest is IFacadeTest { } } - main.backingManager().manageTokens(erc20s); + // solhint-disable no-empty-blocks + try main.backingManager().rebalance(TradeKind.DUTCH_AUCTION) {} catch {} + try main.backingManager().forwardRevenue(erc20s) {} catch {} for (uint256 i = 0; i < erc20s.length; i++) { - rsrTrader.manageToken(erc20s[i]); - rTokenTrader.manageToken(erc20s[i]); + try rsrTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} + try rTokenTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} } + // solhint-enable no-empty-blocks } /// Prompt all traders and the RToken itself to claim rewards and sweep to BackingManager diff --git a/contracts/interfaces/IBackingManager.sol b/contracts/interfaces/IBackingManager.sol index 169607fff3..e1161079d6 100644 --- a/contracts/interfaces/IBackingManager.sol +++ b/contracts/interfaces/IBackingManager.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "./IBroker.sol"; import "./IComponent.sol"; import "./ITrading.sol"; @@ -41,16 +42,15 @@ interface IBackingManager is IComponent, ITrading { /// @custom:interaction function grantRTokenAllowance(IERC20) external; - /// Maintain the overall backing policy; handout assets otherwise - /// @dev Performs a uniqueness check on the erc20s list in O(n^2) - /// @custom:interaction - function manageTokens(IERC20[] memory erc20s) external; + /// Apply the overall backing policy using the specified TradeKind, taking a haircut if unable + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION + /// @custom:interaction RCEI + function rebalance(TradeKind kind) external; - /// Maintain the overall backing policy; handout assets otherwise - /// @dev Tokens must be in sorted order! - /// @dev Performs a uniqueness check on the erc20s list in O(n) - /// @custom:interaction - function manageTokensSortedOrder(IERC20[] memory erc20s) external; + /// Forward revenue to RevenueTraders; reverts if not fully collateralized + /// @param erc20s The tokens to forward + /// @custom:interaction RCEI + function forwardRevenue(IERC20[] calldata erc20s) external; } interface TestIBackingManager is IBackingManager, TestITrading { diff --git a/contracts/interfaces/IBroker.sol b/contracts/interfaces/IBroker.sol index 81b0293862..53e4e65f0d 100644 --- a/contracts/interfaces/IBroker.sol +++ b/contracts/interfaces/IBroker.sol @@ -6,6 +6,11 @@ import "./IComponent.sol"; import "./IGnosis.sol"; import "./ITrade.sol"; +enum TradeKind { + DUTCH_AUCTION, + BATCH_AUCTION +} + /// The data format that describes a request for trade with the Broker struct TradeRequest { IAsset sell; @@ -29,14 +34,16 @@ interface IBroker is IComponent { function init( IMain main_, IGnosis gnosis_, - ITrade tradeImplementation_, - uint48 auctionLength_ + ITrade batchTradeImplemention_, + uint48 batchAuctionLength_, + ITrade dutchTradeImplemention_, + uint48 dutchAuctionLength_ ) external; /// Request a trade from the broker /// @dev Requires setting an allowance in advance /// @custom:interaction - function openTrade(TradeRequest memory req) external returns (ITrade); + function openTrade(TradeRequest memory req, TradeKind kind) external returns (ITrade); /// Only callable by one of the trading contracts the broker deploys function reportViolation() external; @@ -49,13 +56,13 @@ interface TestIBroker is IBroker { function tradeImplementation() external view returns (ITrade); - function auctionLength() external view returns (uint48); + function batchAuctionLength() external view returns (uint48); function setGnosis(IGnosis newGnosis) external; function setTradeImplementation(ITrade newTradeImplementation) external; - function setAuctionLength(uint48 newAuctionLength) external; + function setBatchAuctionLength(uint48 newAuctionLength) external; function setDisabled(bool disabled_) external; } diff --git a/contracts/interfaces/IDeployer.sol b/contracts/interfaces/IDeployer.sol index 4e4477ef18..d7cd56a555 100644 --- a/contracts/interfaces/IDeployer.sol +++ b/contracts/interfaces/IDeployer.sol @@ -40,7 +40,8 @@ struct DeploymentParams { // // === BackingManager === uint48 tradingDelay; // {s} how long to wait until starting auctions after switching basket - uint48 auctionLength; // {s} the length of an auction + uint48 batchAuctionLength; // {s} the length of a Gnosis EasyAuction + uint48 dutchAuctionLength; // {s} the length of a falling-price dutch auction uint192 backingBuffer; // {1} how much extra backing collateral to keep uint192 maxTradeSlippage; // {1} max slippage acceptable in a trade // @@ -56,7 +57,12 @@ struct DeploymentParams { struct Implementations { IMain main; Components components; - ITrade trade; + TradePlugins trading; +} + +struct TradePlugins { + ITrade gnosisTrade; + ITrade dutchTrade; } /** diff --git a/contracts/interfaces/IRevenueTrader.sol b/contracts/interfaces/IRevenueTrader.sol index abe3399bab..2c77bd9db6 100644 --- a/contracts/interfaces/IRevenueTrader.sol +++ b/contracts/interfaces/IRevenueTrader.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.17; +import "./IBroker.sol"; import "./IComponent.sol"; import "./ITrading.sol"; @@ -21,8 +22,9 @@ interface IRevenueTrader is IComponent, ITrading { /// Processes a single token; unpermissioned /// @dev Intended to be used with multicall + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction - function manageToken(IERC20 sell) external; + function manageToken(IERC20 sell, TradeKind kind) external; } // solhint-disable-next-line no-empty-blocks diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index c552a05e44..39700d42d2 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -48,77 +48,82 @@ contract BackingManagerP0 is TradingP0, IBackingManager { erc20.safeApprove(address(main.rToken()), type(uint256).max); } - /// Maintain the overall backing policy; handout assets otherwise - /// @custom:interaction - function manageTokens(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { - // Token list must not contain duplicates - require(ArrayLib.allUnique(erc20s), "duplicate tokens"); - _manageTokens(erc20s); - } + /// Apply the overall backing policy using the specified TradeKind, taking a haircut if unable + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION + /// @custom:interaction RCEI + function rebalance(TradeKind kind) external notTradingPausedOrFrozen { + // == Refresh == + main.assetRegistry().refresh(); + main.furnace().melt(); + + require(tradesOpen == 0, "trade open"); + require(main.basketHandler().isReady(), "basket not ready"); + require( + block.timestamp >= main.basketHandler().timestamp() + tradingDelay, + "trading delayed" + ); + require(!main.basketHandler().fullyCollateralized(), "already collateralized"); + + /* + * Recollateralization + * + * Strategy: iteratively move the system on a forgiving path towards collateralization + * through a narrowing BU price band. The initial large spread reflects the + * uncertainty associated with the market price of defaulted/volatile collateral, as + * well as potential losses due to trading slippage. In the absence of further + * collateral default, the size of the BU price band should decrease with each trade + * until it is 0, at which point collateralization is restored. + * + * If we run out of capital and are still undercollateralized, we compromise + * rToken.basketsNeeded to the current basket holdings. Haircut time. + */ - /// Maintain the overall backing policy; handout assets otherwise - /// @dev Tokens must be in sorted order! - /// @dev Performs a uniqueness check on the erc20s list in O(n) - /// @custom:interaction - function manageTokensSortedOrder(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { - // Token list must not contain duplicates - require(ArrayLib.sortedAndAllUnique(erc20s), "duplicate/unsorted tokens"); - _manageTokens(erc20s); + BasketRange memory basketsHeld = main.basketHandler().basketsHeldBy(address(this)); + (bool doTrade, TradeRequest memory req) = TradingLibP0.prepareRecollateralizationTrade( + this, + basketsHeld + ); + + if (doTrade) { + // Seize RSR if needed + if (req.sell.erc20() == main.rsr()) { + uint256 bal = req.sell.erc20().balanceOf(address(this)); + if (req.sellAmount > bal) main.stRSR().seizeRSR(req.sellAmount - bal); + } + + tryTrade(req, kind); + } else { + // Haircut time + compromiseBasketsNeeded(basketsHeld.bottom); + } } - function _manageTokens(IERC20[] calldata erc20s) private { - // Call keepers before - main.poke(); + /// Forward revenue to RevenueTraders; reverts if not fully collateralized + /// @param erc20s The tokens to forward + /// @custom:interaction RCEI + function forwardRevenue(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { + require(ArrayLib.allUnique(erc20s), "duplicate tokens"); - if (tradesOpen > 0) return; + // == Refresh == + main.assetRegistry().refresh(); + main.furnace().melt(); - // Ensure basket is ready, SOUND and not in warmup period + require(tradesOpen == 0, "trade open"); require(main.basketHandler().isReady(), "basket not ready"); - - uint48 basketTimestamp = main.basketHandler().timestamp(); - require(block.timestamp >= basketTimestamp + tradingDelay, "trading delayed"); + require( + block.timestamp >= main.basketHandler().timestamp() + tradingDelay, + "trading delayed" + ); + require(main.basketHandler().fullyCollateralized(), "undercollateralized"); BasketRange memory basketsHeld = main.basketHandler().basketsHeldBy(address(this)); - if (main.basketHandler().fullyCollateralized()) { - handoutExcessAssets(erc20s, basketsHeld.bottom); - } else { - /* - * Recollateralization - * - * Strategy: iteratively move the system on a forgiving path towards capitalization - * through a narrowing BU price band. The initial large spread reflects the - * uncertainty associated with the market price of defaulted/volatile collateral, as - * well as potential losses due to trading slippage. In the absence of further - * collateral default, the size of the BU price band should decrease with each trade - * until it is 0, at which point capitalization is restored. - * - * ====== - * - * If we run out of capital and are still undercollateralized, we compromise - * rToken.basketsNeeded to the current basket holdings. Haircut time. - */ - - (bool doTrade, TradeRequest memory req) = TradingLibP0.prepareRecollateralizationTrade( - this, - basketsHeld - ); - - if (doTrade) { - // Seize RSR if needed - if (req.sell.erc20() == main.rsr()) { - uint256 bal = req.sell.erc20().balanceOf(address(this)); - if (req.sellAmount > bal) main.stRSR().seizeRSR(req.sellAmount - bal); - } - - tryTrade(req); - } else { - // Haircut time - compromiseBasketsNeeded(basketsHeld.bottom); - } - } + // == Interaction (then return) == + handoutExcessAssets(erc20s, basketsHeld.bottom); } + // === Private === + /// Send excess assets to the RSR and RToken traders /// @param wholeBasketsHeld {BU} The number of full basket units held by the BackingManager function handoutExcessAssets(IERC20[] calldata erc20s, uint192 wholeBasketsHeld) private { diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 9eb5e3ee85..921abb6909 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "../plugins/trading/DutchTrade.sol"; import "../plugins/trading/GnosisTrade.sol"; import "../interfaces/IBroker.sol"; import "../interfaces/IMain.sol"; @@ -25,32 +26,43 @@ contract BrokerP0 is ComponentP0, IBroker { uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration -1 week // Added for interface compatibility with P1 - ITrade public tradeImplementation; + ITrade public batchTradeImplementation; + ITrade public dutchTradeImplementation; IGnosis public gnosis; mapping(address => bool) private trades; - uint48 public auctionLength; // {s} the length of an auction + uint48 public batchAuctionLength; // {s} the length of a Gnosis EasyAuction + uint48 public dutchAuctionLength; // {s} the length of a Dutch Auction bool public disabled; function init( IMain main_, IGnosis gnosis_, - ITrade tradeImplementation_, // Added for Interface compatibility with P1 - uint48 auctionLength_ + ITrade batchTradeImplementation_, // Added for Interface compatibility with P1 + uint48 batchAuctionLength_, + ITrade dutchTradeImplementation_, // Added for Interface compatibility with P1 + uint48 dutchAuctionLength_ ) public initializer { __Component_init(main_); setGnosis(gnosis_); - setTradeImplementation(tradeImplementation_); - setAuctionLength(auctionLength_); + setBatchTradeImplementation(batchTradeImplementation_); + setBatchAuctionLength(batchAuctionLength_); + setDutchTradeImplementation(dutchTradeImplementation_); + setDutchAuctionLength(dutchAuctionLength_); } /// Handle a trade request by deploying a customized disposable trading contract + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @dev Requires setting an allowance in advance /// @custom:protected - function openTrade(TradeRequest memory req) external notTradingPausedOrFrozen returns (ITrade) { + function openTrade(TradeRequest memory req, TradeKind kind) + external + notTradingPausedOrFrozen + returns (ITrade) + { require(!disabled, "broker disabled"); assert(req.sellAmount > 0); @@ -62,17 +74,13 @@ contract BrokerP0 is ComponentP0, IBroker { "only traders" ); - // In the future we'll have more sophisticated choice logic here, probably by trade size - GnosisTrade trade = new GnosisTrade(); - trades[address(trade)] = true; - - // Apply Gnosis EasyAuction-specific resizing - req = resizeTrade(req, GNOSIS_MAX_TOKENS); - - req.sell.erc20().safeTransferFrom(caller, address(trade), req.sellAmount); - - trade.init(this, caller, gnosis, auctionLength, req); - return trade; + // Must be updated when new TradeKinds are created + if (kind == TradeKind.BATCH_AUCTION) { + return newBatchAuction(req, caller); + } else { + // kind == TradeKind.DUTCH_AUCTION + return newDutchAuction(req, ITrading(caller)); + } } /// Disable the broker until re-enabled by governance @@ -114,29 +122,85 @@ contract BrokerP0 is ComponentP0, IBroker { } /// @custom:governance - function setTradeImplementation(ITrade newTradeImplementation) public governance { + function setBatchTradeImplementation(ITrade newTradeImplementation) public governance { require( address(newTradeImplementation) != address(0), "invalid Trade Implementation address" ); - emit TradeImplementationSet(tradeImplementation, newTradeImplementation); - tradeImplementation = newTradeImplementation; + emit TradeImplementationSet(batchTradeImplementation, newTradeImplementation); + batchTradeImplementation = newTradeImplementation; } /// @custom:governance - function setAuctionLength(uint48 newAuctionLength) public governance { + function setBatchAuctionLength(uint48 newAuctionLength) public governance { require( newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, - "invalid auctionLength" + "invalid batchAuctionLength" ); - emit AuctionLengthSet(auctionLength, newAuctionLength); - auctionLength = newAuctionLength; + emit AuctionLengthSet(batchAuctionLength, newAuctionLength); + batchAuctionLength = newAuctionLength; } /// @custom:governance - function setDisabled(bool disabled_) external governance { - emit DisabledSet(disabled, disabled_); - disabled = disabled_; + function setDutchTradeImplementation(ITrade newTradeImplementation) public governance { + require( + address(newTradeImplementation) != address(0), + "invalid Trade Implementation address" + ); + + emit TradeImplementationSet(dutchTradeImplementation, newTradeImplementation); + dutchTradeImplementation = newTradeImplementation; + } + + /// @custom:governance + function setDutchAuctionLength(uint48 newAuctionLength) public governance { + require( + newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, + "invalid dutchAuctionLength" + ); + emit AuctionLengthSet(dutchAuctionLength, newAuctionLength); + dutchAuctionLength = newAuctionLength; + } + + // === Private === + + function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { + GnosisTrade trade = new GnosisTrade(); + trades[address(trade)] = true; + + // Apply Gnosis EasyAuction-specific resizing of req, if needed: Ensure that + // max(sellAmount, minBuyAmount) <= maxTokensAllowed, while maintaining their proportion + uint256 maxQty = (req.minBuyAmount > req.sellAmount) ? req.minBuyAmount : req.sellAmount; + + if (maxQty > GNOSIS_MAX_TOKENS) { + req.sellAmount = mulDiv256(req.sellAmount, GNOSIS_MAX_TOKENS, maxQty, CEIL); + req.minBuyAmount = mulDiv256(req.minBuyAmount, GNOSIS_MAX_TOKENS, maxQty, FLOOR); + } + + // == Interactions == + IERC20Metadata(address(req.sell.erc20())).safeTransferFrom( + caller, + address(trade), + req.sellAmount + ); + + trade.init(this, caller, gnosis, batchAuctionLength, req); + return trade; + } + + function newDutchAuction(TradeRequest memory req, ITrading caller) private returns (ITrade) { + DutchTrade trade = new DutchTrade(); + trades[address(trade)] = true; + + // == Interactions == + IERC20Metadata(address(req.sell.erc20())).safeTransferFrom( + address(caller), + address(trade), + req.sellAmount + ); + + trade.init(caller, req.sell, req.buy, req.sellAmount, dutchAuctionLength); + return trade; } } diff --git a/contracts/p0/Deployer.sol b/contracts/p0/Deployer.sol index e1de9653e2..7380a62a84 100644 --- a/contracts/p0/Deployer.sol +++ b/contracts/p0/Deployer.sol @@ -110,7 +110,14 @@ contract DeployerP0 is IDeployer, Versioned { // Init Furnace main.furnace().init(main, params.rewardRatio); - main.broker().init(main, gnosis, ITrade(address(1)), params.auctionLength); + main.broker().init( + main, + gnosis, + ITrade(address(1)), + params.batchAuctionLength, + ITrade(address(1)), + params.dutchAuctionLength + ); // Init StRSR { diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index fb06ece1e4..1634292edd 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -30,8 +30,9 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { /// Processes a single token; unpermissioned /// @dev Intended to be used with multicall + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction - function manageToken(IERC20 erc20) external notTradingPausedOrFrozen { + function manageToken(IERC20 erc20, TradeKind kind) external notTradingPausedOrFrozen { if (address(trades[erc20]) != address(0)) return; uint256 bal = erc20.balanceOf(address(this)); @@ -70,7 +71,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { ); if (launch) { - tryTrade(req); + tryTrade(req, kind); } } } diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index 3f6c27494a..c591320399 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -53,7 +53,8 @@ abstract contract TradingP0 is RewardableP0, ITrading { } /// Try to initiate a trade with a trading partner provided by the broker - function tryTrade(TradeRequest memory req) internal { + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION + function tryTrade(TradeRequest memory req, TradeKind kind) internal { IBroker broker = main.broker(); assert(address(trades[req.sell.erc20()]) == address(0)); require(!broker.disabled(), "broker disabled"); @@ -61,7 +62,7 @@ abstract contract TradingP0 is RewardableP0, ITrading { req.sell.erc20().safeApprove(address(broker), 0); req.sell.erc20().safeApprove(address(broker), req.sellAmount); - ITrade trade = broker.openTrade(req); + ITrade trade = broker.openTrade(req, kind); trades[req.sell.erc20()] = trade; tradesOpen++; diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index dc37cff06b..0254cffd16 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -56,7 +56,7 @@ library TradingLibP0 { } // Cap sell amount - uint192 maxSell = maxTradeSize(trade.sell, lotHigh); // {sellTok} + uint192 maxSell = maxTradeSize(trade.sell, trade.buy, lotHigh); // {sellTok} uint192 s = trade.sellAmount > maxSell ? maxSell : trade.sellAmount; // {sellTok} // Calculate equivalent buyAmount within [0, FIX_MAX] @@ -577,10 +577,18 @@ library TradingLibP0 { return size > 0 ? size : 1; } - /// Calculates the maxTradeSize for an asset based on the asset's maxTradeVolume and price - /// @return {tok} The max trade size for the asset in whole tokens - function maxTradeSize(IAsset asset, uint192 price) private view returns (uint192) { - uint192 size = price == 0 ? FIX_MAX : asset.maxTradeVolume().div(price, FLOOR); + /// Calculates the maximum trade size for a trade pair of tokens + /// @return {tok} The max trade size for the trade overall + function maxTradeSize( + IAsset sell, + IAsset buy, + uint192 price + ) private view returns (uint192) { + // untestable: + // Price cannot be 0, it would've been filtered before in `prepareTradeSell` + uint192 size = price == 0 + ? FIX_MAX + : fixMin(sell.maxTradeVolume(), buy.maxTradeVolume()).div(price, FLOOR); return size > 0 ? size : 1; } } diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index db0035b05a..f238c160b4 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -36,6 +36,9 @@ contract BackingManagerP1 is TradingP1, IBackingManager { uint48 public tradingDelay; // {s} how long to wait until resuming trading after switching uint192 public backingBuffer; // {%} how much extra backing collateral to keep + // === 3.0.0 === + IFurnace private furnace; + // ==== Invariants ==== // tradingDelay <= MAX_TRADING_DELAY and backingBuffer <= MAX_BACKING_BUFFER // @@ -59,6 +62,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { rTokenTrader = main_.rTokenTrader(); rToken = main_.rToken(); stRSR = main_.stRSR(); + furnace = main_.furnace(); setTradingDelay(tradingDelay_); setBackingBuffer(backingBuffer_); @@ -76,84 +80,77 @@ contract BackingManagerP1 is TradingP1, IBackingManager { IERC20Upgradeable(address(erc20)).safeApprove(address(main.rToken()), type(uint256).max); } - /// Maintain the overall backing policy; handout assets otherwise - /// @custom:interaction - // checks: the addresses in `erc20s` are unique - // effect: _manageTokens(erc20s) - function manageTokens(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { - // Token list must not contain duplicates - require(ArrayLib.allUnique(erc20s), "duplicate tokens"); - _manageTokens(erc20s); - } - - /// Maintain the overall backing policy; handout assets otherwise - /// @dev Tokens must be in sorted order! - /// @dev Performs a uniqueness check on the erc20s list in O(n) - /// @custom:interaction - // checks: the addresses in `erc20s` are unique (and sorted) - // effect: _manageTokens(erc20s) - function manageTokensSortedOrder(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { - // Token list must not contain duplicates - require(ArrayLib.sortedAndAllUnique(erc20s), "duplicate/unsorted tokens"); - _manageTokens(erc20s); - } - - /// Maintain the overall backing policy; handout assets otherwise + /// Apply the overall backing policy using the specified TradeKind, taking a haircut if unable + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction RCEI - // only called internally, from manageTokens*, so erc20s has no duplicates unique - // (but not necessarily all registered or valid!) - function _manageTokens(IERC20[] calldata erc20s) private { + function rebalance(TradeKind kind) external notTradingPausedOrFrozen { // == Refresh == assetRegistry.refresh(); + furnace.melt(); - if (tradesOpen > 0) return; + BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(this)); - // Ensure basket is ready, SOUND and not in warmup period + require(tradesOpen == 0, "trade open"); require(basketHandler.isReady(), "basket not ready"); + require(block.timestamp >= basketHandler.timestamp() + tradingDelay, "trading delayed"); + require(basketsHeld.bottom < rToken.basketsNeeded(), "already collateralized"); + // require(!basketHandler.fullyCollateralized()) - uint48 basketTimestamp = basketHandler.timestamp(); - require(block.timestamp >= basketTimestamp + tradingDelay, "trading delayed"); + /* + * Recollateralization + * + * Strategy: iteratively move the system on a forgiving path towards collateralization + * through a narrowing BU price band. The initial large spread reflects the + * uncertainty associated with the market price of defaulted/volatile collateral, as + * well as potential losses due to trading slippage. In the absence of further + * collateral default, the size of the BU price band should decrease with each trade + * until it is 0, at which point collateralization is restored. + * + * If we run out of capital and are still undercollateralized, we compromise + * rToken.basketsNeeded to the current basket holdings. Haircut time. + */ - BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(this)); - uint192 basketsNeeded = rToken.basketsNeeded(); // {BU} + (bool doTrade, TradeRequest memory req) = RecollateralizationLibP1 + .prepareRecollateralizationTrade(this, basketsHeld); - // if (basketHandler.fullyCollateralized()) - if (basketsHeld.bottom >= basketsNeeded) { - // == Interaction (then return) == - handoutExcessAssets(erc20s, basketsHeld.bottom); - } else { - /* - * Recollateralization - * - * Strategy: iteratively move the system on a forgiving path towards collateralization - * through a narrowing BU price band. The initial large spread reflects the - * uncertainty associated with the market price of defaulted/volatile collateral, as - * well as potential losses due to trading slippage. In the absence of further - * collateral default, the size of the BU price band should decrease with each trade - * until it is 0, at which point collateralization is restored. - * - * If we run out of capital and are still undercollateralized, we compromise - * rToken.basketsNeeded to the current basket holdings. Haircut time. - */ - - (bool doTrade, TradeRequest memory req) = RecollateralizationLibP1 - .prepareRecollateralizationTrade(this, basketsHeld); - - if (doTrade) { - // Seize RSR if needed - if (req.sell.erc20() == rsr) { - uint256 bal = req.sell.erc20().balanceOf(address(this)); - if (req.sellAmount > bal) stRSR.seizeRSR(req.sellAmount - bal); - } - - tryTrade(req); - } else { - // Haircut time - compromiseBasketsNeeded(basketsHeld.bottom, basketsNeeded); + if (doTrade) { + // Seize RSR if needed + if (req.sell.erc20() == rsr) { + uint256 bal = req.sell.erc20().balanceOf(address(this)); + if (req.sellAmount > bal) stRSR.seizeRSR(req.sellAmount - bal); } + + tryTrade(req, kind); + } else { + // Haircut time + compromiseBasketsNeeded(basketsHeld.bottom); } } + /// Forward revenue to RevenueTraders; reverts if not fully collateralized + /// @param erc20s The tokens to forward + /// @custom:interaction RCEI + function forwardRevenue(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { + require(ArrayLib.allUnique(erc20s), "duplicate tokens"); + + // == Refresh == + assetRegistry.refresh(); + furnace.melt(); + + BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(this)); + + require(tradesOpen == 0, "trade open"); + require(basketHandler.isReady(), "basket not ready"); + require(block.timestamp >= basketHandler.timestamp() + tradingDelay, "trading delayed"); + require(basketsHeld.bottom >= rToken.basketsNeeded(), "undercollateralized"); + // require(basketHandler.fullyCollateralized()) + + // == Interaction (then return) == + handoutExcessAssets(erc20s, basketsHeld.bottom); + } + + // === Private === + /// Send excess assets to the RSR and RToken traders /// @param basketsHeldBottom {BU} The number of full basket units held by the BackingManager /// @custom:interaction CEI @@ -251,10 +248,8 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// Compromise on how many baskets are needed in order to recollateralize-by-accounting /// @param basketsHeldBottom {BU} The number of full basket units held by the BackingManager - /// @param basketsNeeded {BU} RToken.basketsNeeded() - function compromiseBasketsNeeded(uint192 basketsHeldBottom, uint192 basketsNeeded) private { + function compromiseBasketsNeeded(uint192 basketsHeldBottom) private { // assert(tradesOpen == 0 && !basketHandler.fullyCollateralized()); - assert(tradesOpen == 0 && basketsHeldBottom < basketsNeeded); rToken.setBasketsNeeded(basketsHeldBottom); } @@ -274,10 +269,15 @@ contract BackingManagerP1 is TradingP1, IBackingManager { backingBuffer = val; } + /// Call after upgrade to >= 3.0.0 + function cacheFurnace() public { + furnace = main.furnace(); + } + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[41] private __gap; + uint256[40] private __gap; } diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 36c57a2695..358053c895 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -9,6 +9,7 @@ import "../interfaces/IMain.sol"; import "../interfaces/ITrade.sol"; import "../libraries/Fixed.sol"; import "./mixins/Component.sol"; +import "../plugins/trading/DutchTrade.sol"; import "../plugins/trading/GnosisTrade.sol"; // Gnosis: uint96 ~= 7e28 @@ -27,14 +28,14 @@ contract BrokerP1 is ComponentP1, IBroker { IRevenueTrader private rsrTrader; IRevenueTrader private rTokenTrader; - // The trade contract to clone on openTrade(). Governance parameter. - ITrade public tradeImplementation; + // The Batch Auction Trade contract to clone on openTrade(). Governance parameter. + ITrade public batchTradeImplementation; // The Gnosis contract to init each trade with. Governance parameter. IGnosis public gnosis; - // {s} the length of an auction. Governance parameter. - uint48 public auctionLength; + // {s} the length of a Gnosis EasyAuction. Governance parameter. + uint48 public batchAuctionLength; // Whether trading is disabled. // Initially false. Settable by OWNER. A trade clone can set it to true via reportViolation() @@ -43,6 +44,14 @@ contract BrokerP1 is ComponentP1, IBroker { // The set of ITrade (clone) addresses this contract has created mapping(address => bool) private trades; + // === 3.0.0 === + + // The Dutch Auction Trade contract to clone on openTrade(). Governance parameter. + ITrade public dutchTradeImplementation; + + // {s} the length of a Dutch Auction. Governance parameter. + uint48 public dutchAuctionLength; + // ==== Invariant ==== // (trades[addr] == true) iff this contract has created an ITrade clone at addr @@ -50,8 +59,10 @@ contract BrokerP1 is ComponentP1, IBroker { function init( IMain main_, IGnosis gnosis_, - ITrade tradeImplementation_, - uint48 auctionLength_ + ITrade batchTradeImplementation_, + uint48 batchAuctionLength_, + ITrade dutchTradeImplementation_, + uint48 dutchAuctionLength_ ) external initializer { __Component_init(main_); @@ -60,11 +71,14 @@ contract BrokerP1 is ComponentP1, IBroker { rTokenTrader = main_.rTokenTrader(); setGnosis(gnosis_); - setTradeImplementation(tradeImplementation_); - setAuctionLength(auctionLength_); + setBatchTradeImplementation(batchTradeImplementation_); + setBatchAuctionLength(batchAuctionLength_); + setDutchTradeImplementation(dutchTradeImplementation_); + setDutchAuctionLength(dutchAuctionLength_); } /// Handle a trade request by deploying a customized disposable trading contract + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @dev Requires setting an allowance in advance /// @custom:interaction CEI // checks: @@ -76,7 +90,11 @@ contract BrokerP1 is ComponentP1, IBroker { // actions: // Transfers req.sellAmount of req.sell.erc20 from caller to `trade` // Calls trade.init() with appropriate parameters - function openTrade(TradeRequest memory req) external notTradingPausedOrFrozen returns (ITrade) { + function openTrade(TradeRequest memory req, TradeKind kind) + external + notTradingPausedOrFrozen + returns (ITrade) + { require(!disabled, "broker disabled"); address caller = _msgSender(); @@ -87,28 +105,11 @@ contract BrokerP1 is ComponentP1, IBroker { "only traders" ); - // In the future we'll have more sophisticated choice logic here, probably by trade size - GnosisTrade trade = GnosisTrade(address(tradeImplementation).clone()); - trades[address(trade)] = true; - - // Apply Gnosis EasyAuction-specific resizing of req, if needed: Ensure that - // max(sellAmount, minBuyAmount) <= maxTokensAllowed, while maintaining their proportion - uint256 maxQty = (req.minBuyAmount > req.sellAmount) ? req.minBuyAmount : req.sellAmount; - - if (maxQty > GNOSIS_MAX_TOKENS) { - req.sellAmount = mulDiv256(req.sellAmount, GNOSIS_MAX_TOKENS, maxQty, CEIL); - req.minBuyAmount = mulDiv256(req.minBuyAmount, GNOSIS_MAX_TOKENS, maxQty, FLOOR); + // Must be updated when new TradeKinds are created + if (kind == TradeKind.BATCH_AUCTION) { + return newBatchAuction(req, caller); } - - // == Interactions == - IERC20Upgradeable(address(req.sell.erc20())).safeTransferFrom( - caller, - address(trade), - req.sellAmount - ); - - trade.init(this, caller, gnosis, auctionLength, req); - return trade; + return newDutchAuction(req, ITrading(caller)); } /// Disable the broker until re-enabled by governance @@ -132,24 +133,45 @@ contract BrokerP1 is ComponentP1, IBroker { } /// @custom:governance - function setTradeImplementation(ITrade newTradeImplementation) public governance { + function setBatchTradeImplementation(ITrade newTradeImplementation) public governance { + require( + address(newTradeImplementation) != address(0), + "invalid Trade Implementation address" + ); + + emit TradeImplementationSet(batchTradeImplementation, newTradeImplementation); + batchTradeImplementation = newTradeImplementation; + } + + /// @custom:governance + function setBatchAuctionLength(uint48 newAuctionLength) public governance { + require( + newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, + "invalid batchAuctionLength" + ); + emit AuctionLengthSet(batchAuctionLength, newAuctionLength); + batchAuctionLength = newAuctionLength; + } + + /// @custom:governance + function setDutchTradeImplementation(ITrade newTradeImplementation) public governance { require( address(newTradeImplementation) != address(0), "invalid Trade Implementation address" ); - emit TradeImplementationSet(tradeImplementation, newTradeImplementation); - tradeImplementation = newTradeImplementation; + emit TradeImplementationSet(dutchTradeImplementation, newTradeImplementation); + dutchTradeImplementation = newTradeImplementation; } /// @custom:governance - function setAuctionLength(uint48 newAuctionLength) public governance { + function setDutchAuctionLength(uint48 newAuctionLength) public governance { require( newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, - "invalid auctionLength" + "invalid dutchAuctionLength" ); - emit AuctionLengthSet(auctionLength, newAuctionLength); - auctionLength = newAuctionLength; + emit AuctionLengthSet(dutchAuctionLength, newAuctionLength); + dutchAuctionLength = newAuctionLength; } /// @custom:governance @@ -158,10 +180,52 @@ contract BrokerP1 is ComponentP1, IBroker { disabled = disabled_; } + // === Private === + + function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { + // In the future we'll have more sophisticated choice logic here, probably by trade size + GnosisTrade trade = GnosisTrade(address(batchTradeImplementation).clone()); + trades[address(trade)] = true; + + // Apply Gnosis EasyAuction-specific resizing of req, if needed: Ensure that + // max(sellAmount, minBuyAmount) <= maxTokensAllowed, while maintaining their proportion + uint256 maxQty = (req.minBuyAmount > req.sellAmount) ? req.minBuyAmount : req.sellAmount; + + if (maxQty > GNOSIS_MAX_TOKENS) { + req.sellAmount = mulDiv256(req.sellAmount, GNOSIS_MAX_TOKENS, maxQty, CEIL); + req.minBuyAmount = mulDiv256(req.minBuyAmount, GNOSIS_MAX_TOKENS, maxQty, FLOOR); + } + + // == Interactions == + IERC20Upgradeable(address(req.sell.erc20())).safeTransferFrom( + caller, + address(trade), + req.sellAmount + ); + + trade.init(this, caller, gnosis, batchAuctionLength, req); + return trade; + } + + function newDutchAuction(TradeRequest memory req, ITrading caller) private returns (ITrade) { + DutchTrade trade = DutchTrade(address(dutchTradeImplementation).clone()); + trades[address(trade)] = true; + + // == Interactions == + IERC20Upgradeable(address(req.sell.erc20())).safeTransferFrom( + address(caller), + address(trade), + req.sellAmount + ); + + trade.init(caller, req.sell, req.buy, req.sellAmount, dutchAuctionLength); + return trade; + } + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[44] private __gap; + uint256[43] private __gap; } diff --git a/contracts/p1/Deployer.sol b/contracts/p1/Deployer.sol index 7f16093bfc..9b61c65031 100644 --- a/contracts/p1/Deployer.sol +++ b/contracts/p1/Deployer.sol @@ -50,7 +50,8 @@ contract DeployerP1 is IDeployer, Versioned { address(gnosis_) != address(0) && address(rsrAsset_) != address(0) && address(implementations_.main) != address(0) && - address(implementations_.trade) != address(0) && + address(implementations_.trading.gnosisTrade) != address(0) && + address(implementations_.trading.dutchTrade) != address(0) && address(implementations_.components.assetRegistry) != address(0) && address(implementations_.components.backingManager) != address(0) && address(implementations_.components.basketHandler) != address(0) && @@ -199,7 +200,14 @@ contract DeployerP1 is IDeployer, Versioned { // Init Furnace components.furnace.init(main, params.rewardRatio); - components.broker.init(main, gnosis, implementations.trade, params.auctionLength); + components.broker.init( + main, + gnosis, + implementations.trading.gnosisTrade, + params.batchAuctionLength, + implementations.trading.dutchTrade, + params.dutchAuctionLength + ); // Init StRSR { diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 28c09281a7..ba96bba353 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -36,6 +36,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { /// If erc20 is tokenToBuy, distribute it; else, sell it for tokenToBuy /// @dev Intended to be used with multicall + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction CEI // let bal = this contract's balance of erc20 // checks: !paused (trading), !frozen @@ -50,7 +51,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { // actions: // tryTrade(prepareTradeSell(toAsset(erc20), toAsset(tokenToBuy), bal)) // (i.e, start a trade, selling as much of our bal of erc20 as we can, to buy tokenToBuy) - function manageToken(IERC20 erc20) external notTradingPausedOrFrozen { + function manageToken(IERC20 erc20, TradeKind kind) external notTradingPausedOrFrozen { if (address(trades[erc20]) != address(0)) return; uint256 bal = erc20.balanceOf(address(this)); @@ -89,10 +90,16 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { ); if (launch) { - tryTrade(req); + tryTrade(req, kind); } } + // // {UoA} + // uint192 maxTradeVolume = fixMin(sell_.maxTradeVolume(), buy_.maxTradeVolume()); + + // sellAmount = shiftl_toFix(sellAmount_, -int8(sell.decimals())); + // sellAmount = fixMin(sellAmount, maxTradeVolume.div(sellHigh, FLOOR)); + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. diff --git a/contracts/p1/mixins/TradeLib.sol b/contracts/p1/mixins/TradeLib.sol index 5a634fbe97..fe225d6be4 100644 --- a/contracts/p1/mixins/TradeLib.sol +++ b/contracts/p1/mixins/TradeLib.sol @@ -59,7 +59,7 @@ library TradeLib { } // Cap sell amount - uint192 maxSell = maxTradeSize(trade.sell, lotHigh); // {sellTok} + uint192 maxSell = maxTradeSize(trade.sell, trade.buy, lotHigh); // {sellTok} uint192 s = trade.sellAmount > maxSell ? maxSell : trade.sellAmount; // {sellTok} // Calculate equivalent buyAmount within [0, FIX_MAX] @@ -185,12 +185,18 @@ library TradeLib { return size > 0 ? size : 1; } - /// Calculates the maxTradeSize for an asset based on the asset's maxTradeVolume and price - /// @return {tok} The max trade size for the asset in whole tokens - function maxTradeSize(IAsset asset, uint192 price) private view returns (uint192) { + /// Calculates the maximum trade size for a trade pair of tokens + /// @return {tok} The max trade size for the trade overall + function maxTradeSize( + IAsset sell, + IAsset buy, + uint192 price + ) private view returns (uint192) { // untestable: // Price cannot be 0, it would've been filtered before in `prepareTradeSell` - uint192 size = price == 0 ? FIX_MAX : asset.maxTradeVolume().div(price, FLOOR); + uint192 size = price == 0 + ? FIX_MAX + : fixMin(sell.maxTradeVolume(), buy.maxTradeVolume()).div(price, FLOOR); return size > 0 ? size : 1; } } diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 8a162be298..18bca29552 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -92,6 +92,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl } /// Try to initiate a trade with a trading partner provided by the broker + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction (only reads or writes `trades`, and is marked `nonReentrant`) // checks: // (not external, so we don't need auth or pause checks) @@ -108,14 +109,15 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // This is reentrancy-safe because we're using the `nonReentrant` modifier on every method of // this contract that changes state this function refers to. // slither-disable-next-line reentrancy-vulnerabilities-1 - function tryTrade(TradeRequest memory req) internal nonReentrant { + function tryTrade(TradeRequest memory req, TradeKind kind) internal nonReentrant { /* */ IERC20 sell = req.sell.erc20(); assert(address(trades[sell]) == address(0)); IERC20Upgradeable(address(sell)).safeApprove(address(broker), 0); IERC20Upgradeable(address(sell)).safeApprove(address(broker), req.sellAmount); - ITrade trade = broker.openTrade(req); + + ITrade trade = broker.openTrade(req, kind); trades[sell] = trade; tradesOpen++; diff --git a/contracts/plugins/mocks/InvalidBrokerMock.sol b/contracts/plugins/mocks/InvalidBrokerMock.sol index c0ccb988b0..86e0bb756a 100644 --- a/contracts/plugins/mocks/InvalidBrokerMock.sol +++ b/contracts/plugins/mocks/InvalidBrokerMock.sol @@ -19,7 +19,8 @@ contract InvalidBrokerMock is ComponentP0, IBroker { mapping(address => bool) private trades; - uint48 public auctionLength; // {s} the length of an auction + uint48 public batchAuctionLength; // {s} the length of a batch auction + uint48 public dutchAuctionLength; // {s} the length of a dutch auction bool public disabled = false; @@ -27,15 +28,18 @@ contract InvalidBrokerMock is ComponentP0, IBroker { IMain main_, IGnosis gnosis_, ITrade, - uint48 auctionLength_ + uint48 batchAuctionLength_, + ITrade, + uint48 dutchAuctionLength_ ) public initializer { __Component_init(main_); gnosis = gnosis_; - auctionLength = auctionLength_; + batchAuctionLength = batchAuctionLength_; + dutchAuctionLength = dutchAuctionLength_; } /// Invalid implementation - Reverts - function openTrade(TradeRequest memory req) + function openTrade(TradeRequest memory req, TradeKind) external view notTradingPausedOrFrozen @@ -54,7 +58,11 @@ contract InvalidBrokerMock is ComponentP0, IBroker { /// Dummy implementation /* solhint-disable no-empty-blocks */ - function setAuctionLength(uint48 newAuctionLength) external governance {} + function setBatchAuctionLength(uint48 newAuctionLength) external governance {} + + /// Dummy implementation + /* solhint-disable no-empty-blocks */ + function setDutchAuctionLength(uint48 newAuctionLength) external governance {} /// Dummy implementation /* solhint-disable no-empty-blocks */ diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 224fb1ad23..fa216fb3d5 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -58,24 +58,20 @@ contract DutchTrade is ITrade { status = end; } + /// @param origin_ The Trader that originated the trade /// @param sell_ The asset being sold by the protocol /// @param buy_ The asset being bought by the protocol - /// @param sellAmount_ {sellTok} The amount to sell in the auction, in whole tokens - /// @param minTradeVolume_ {UoA} The mimimum amount to trade - /// @param maxTradeSlippage_ {1} An additional discount applied to the auction low price - /// @param auctionLength {1} An additional discount applied to the auction low price + /// @param sellAmount_ {qSellTok} The amount to sell in the auction, in token quanta + /// @param auctionLength {s} How many seconds the dutch auction should run for function init( + ITrading origin_, IAsset sell_, IAsset buy_, - uint192 sellAmount_, - uint192 minTradeVolume_, - uint192 maxTradeSlippage_, + uint256 sellAmount_, uint48 auctionLength ) external stateTransition(TradeStatus.NOT_STARTED, TradeStatus.OPEN) { require(address(sell) != address(0) || address(buy) != address(0), "zero address token"); - // uint256 sellAmountQ = sellAmount_.shiftl_toUint(int8(sell_.erc20Decimals())); - // Only start an auction with well-defined prices // // In the BackingManager this may end up recalculating the RToken price @@ -84,24 +80,32 @@ contract DutchTrade is ITrade { require(sellLow > 0 && sellHigh < FIX_MAX, "bad sell pricing"); require(buyLow > 0 && buyHigh < FIX_MAX, "bad buy pricing"); - // {UoA} - uint192 maxTradeVolume = fixMin(sell_.maxTradeVolume(), buy_.maxTradeVolume()); - - origin = ITrading(msg.sender); + origin = origin_; sell = sell_.erc20(); buy = buy_.erc20(); - sellAmount = fixMin(sellAmount_, maxTradeVolume.div(sellHigh, FLOOR)); + sellAmount = shiftl_toFix(sellAmount_, -int8(sell.decimals())); // {sellTok} startTime = uint48(block.timestamp); endTime = uint48(block.timestamp) + auctionLength; - // {UoA} = {sellTok} * {UoA/sellTok} - uint192 auctionVolume = sellAmount.mul(sellHigh, FLOOR); - require(auctionVolume >= minTradeVolume_, "auction too small"); - - // {1} = {1} * ({UoA} - {UoA}} / ({UoA} - {UoA}) - uint192 slippage = maxTradeSlippage_.mul( - FIX_ONE - divuu(auctionVolume - minTradeVolume_, maxTradeVolume - minTradeVolume_) - ); + // {UoA} + uint192 maxTradeVolume = fixMin(sell_.maxTradeVolume(), buy_.maxTradeVolume()); + uint192 minTradeVolume = origin.minTradeVolume(); + + // Apply sliding slippage from 0% at maxTradeVolume to maxTradeSlippage() at minTradeVolume + uint192 slippage = origin.maxTradeSlippage(); // {1} + if (minTradeVolume < maxTradeVolume) { + // {UoA} = {sellTok} * {UoA/sellTok} + uint192 auctionVolume = sellAmount.mul(sellHigh, FLOOR); + + if (auctionVolume > minTradeVolume && auctionVolume <= maxTradeVolume) { + // {1} = {1} * ({UoA} - {UoA}} / ({UoA} - {UoA}) + slippage = slippage.mul( + FIX_ONE - divuu(auctionVolume - minTradeVolume, maxTradeVolume - minTradeVolume) + ); + } else if (auctionVolume > maxTradeVolume) { + slippage = 0; + } + } // {buyTok/sellTok} = {1} * {UoA/sellTok} / {UoA/buyTok} lowPrice = sellLow.mulDiv(FIX_ONE - slippage, buyHigh, FLOOR); diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index 1d5e8ac96d..b7a3188068 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -75,7 +75,7 @@ contract GnosisTrade is ITrade { IBroker broker_, address origin_, IGnosis gnosis_, - uint48 auctionLength, + uint48 batchAuctionLength, TradeRequest calldata req ) external stateTransition(TradeStatus.NOT_STARTED, TradeStatus.OPEN) { require(req.sellAmount <= type(uint96).max, "sellAmount too large"); @@ -93,7 +93,7 @@ contract GnosisTrade is ITrade { broker = broker_; origin = origin_; gnosis = gnosis_; - endTime = uint48(block.timestamp) + auctionLength; + endTime = uint48(block.timestamp) + batchAuctionLength; // {buyTok/sellTok} worstCasePrice = shiftl_toFix(req.minBuyAmount, -int8(buy.decimals())).div( From d5be78a285e525aadff69ae5913a4bb7caa283a9 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 22:06:00 -0400 Subject: [PATCH 086/462] tuning --- contracts/interfaces/ITrade.sol | 12 ++-- contracts/p0/Broker.sol | 4 +- contracts/p0/RevenueTrader.sol | 30 +++++++-- contracts/p0/mixins/Trading.sol | 17 ++++- contracts/p1/Broker.sol | 5 +- contracts/p1/RevenueTrader.sol | 34 +++++++--- contracts/p1/mixins/Trading.sol | 20 ++++-- contracts/plugins/trading/DutchTrade.sol | 82 ++++++++++++----------- contracts/plugins/trading/GnosisTrade.sol | 1 + 9 files changed, 136 insertions(+), 69 deletions(-) diff --git a/contracts/interfaces/ITrade.sol b/contracts/interfaces/ITrade.sol index 64de66ae3e..31e9c6740b 100644 --- a/contracts/interfaces/ITrade.sol +++ b/contracts/interfaces/ITrade.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "./IBroker.sol"; enum TradeStatus { NOT_STARTED, // before init() @@ -17,6 +18,11 @@ enum TradeStatus { * Usage: if (canSettle()) settle() */ interface ITrade { + /// Complete the trade and transfer tokens back to the origin trader + /// @return soldAmt {qSellTok} The quantity of tokens sold + /// @return boughtAmt {qBuyTok} The quantity of tokens bought + function settle() external returns (uint256 soldAmt, uint256 boughtAmt); + function sell() external view returns (IERC20Metadata); function buy() external view returns (IERC20Metadata); @@ -28,8 +34,6 @@ interface ITrade { /// @dev Should be guaranteed to be true eventually as an invariant function canSettle() external view returns (bool); - /// Complete the trade and transfer tokens back to the origin trader - /// @return soldAmt {qSellTok} The quantity of tokens sold - /// @return boughtAmt {qBuyTok} The quantity of tokens bought - function settle() external returns (uint256 soldAmt, uint256 boughtAmt); + /// @return TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION + function kind() external view returns (TradeKind); } diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 921abb6909..bff9704456 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -166,6 +166,7 @@ contract BrokerP0 is ComponentP0, IBroker { // === Private === function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { + require(batchAuctionLength > 0, "batchAuctionLength unset"); GnosisTrade trade = new GnosisTrade(); trades[address(trade)] = true; @@ -178,7 +179,6 @@ contract BrokerP0 is ComponentP0, IBroker { req.minBuyAmount = mulDiv256(req.minBuyAmount, GNOSIS_MAX_TOKENS, maxQty, FLOOR); } - // == Interactions == IERC20Metadata(address(req.sell.erc20())).safeTransferFrom( caller, address(trade), @@ -190,10 +190,10 @@ contract BrokerP0 is ComponentP0, IBroker { } function newDutchAuction(TradeRequest memory req, ITrading caller) private returns (ITrade) { + require(dutchAuctionLength > 0, "dutchAuctionLength unset"); DutchTrade trade = new DutchTrade(); trades[address(trade)] = true; - // == Interactions == IERC20Metadata(address(req.sell.erc20())).safeTransferFrom( address(caller), address(trade), diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index 1634292edd..b77ac2bdc4 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -28,20 +28,27 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { tokenToBuy = tokenToBuy_; } + /// Settle a single trade + distribute revenue + /// @custom:interaction + function settleTrade(IERC20 sell) + public + override(ITrading, TradingP0) + notTradingPausedOrFrozen + { + super.settleTrade(sell); + distributeRevenue(); + } + /// Processes a single token; unpermissioned /// @dev Intended to be used with multicall /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction function manageToken(IERC20 erc20, TradeKind kind) external notTradingPausedOrFrozen { - if (address(trades[erc20]) != address(0)) return; - - uint256 bal = erc20.balanceOf(address(this)); - if (bal == 0) return; + require(address(trades[erc20]) == address(0), "trade open"); + require(erc20.balanceOf(address(this)) > 0, "0 balance"); if (erc20 == tokenToBuy) { - erc20.safeApprove(address(main.distributor()), 0); - erc20.safeApprove(address(main.distributor()), bal); - main.distributor().distribute(erc20, bal); + distributeRevenue(); return; } @@ -74,4 +81,13 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { tryTrade(req, kind); } } + + // === Private === + + function distributeRevenue() private { + uint256 bal = tokenToBuy.balanceOf(address(this)); + tokenToBuy.safeApprove(address(main.distributor()), 0); + tokenToBuy.safeApprove(address(main.distributor()), bal); + main.distributor().distribute(tokenToBuy, bal); + } } diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index c591320399..7c708b4078 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -26,6 +26,8 @@ abstract contract TradingP0 is RewardableP0, ITrading { uint192 public minTradeVolume; // {UoA} + mapping(TradeKind => uint48) public lastSettlement; // {block} + // untestable: // `else` branch of `onlyInitializing` (ie. revert) is currently untestable. // This function is only called inside other `init` functions, each of which is wrapped @@ -41,13 +43,14 @@ abstract contract TradingP0 is RewardableP0, ITrading { /// Settle a single trade, expected to be used with multicall for efficient mass settlement /// @custom:interaction - function settleTrade(IERC20 sell) public notTradingPausedOrFrozen { + function settleTrade(IERC20 sell) public virtual notTradingPausedOrFrozen { ITrade trade = trades[sell]; - if (address(trade) == address(0)) return; + require(address(trade) != address(0), "no trade open"); require(trade.canSettle(), "cannot settle yet"); delete trades[sell]; tradesOpen--; + lastSettlement[trade.kind()] = uint48(block.number); (uint256 soldAmt, uint256 boughtAmt) = trade.settle(); emit TradeSettled(trade, trade.sell(), trade.buy(), soldAmt, boughtAmt); } @@ -62,8 +65,16 @@ abstract contract TradingP0 is RewardableP0, ITrading { req.sell.erc20().safeApprove(address(broker), 0); req.sell.erc20().safeApprove(address(broker), req.sellAmount); - ITrade trade = broker.openTrade(req, kind); + // Require at least 1 empty block between auctions of the same kind + // This gives space for someone to start one of the opposite kinds of auctions + if (kind == TradeKind.DUTCH_AUCTION) { + require(block.timestamp > lastSettlement[TradeKind.DUTCH_AUCTION] + 1, "wait 1 block"); + } else { + // kind == TradeKind.BATCH_AUCTION + require(block.timestamp > lastSettlement[TradeKind.BATCH_AUCTION] + 1, "wait 1 block"); + } + ITrade trade = broker.openTrade(req, kind); trades[req.sell.erc20()] = trade; tradesOpen++; emit TradeStarted( diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 358053c895..183714be17 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -31,7 +31,7 @@ contract BrokerP1 is ComponentP1, IBroker { // The Batch Auction Trade contract to clone on openTrade(). Governance parameter. ITrade public batchTradeImplementation; - // The Gnosis contract to init each trade with. Governance parameter. + // The Gnosis contract to init batch auction trades with. Governance parameter. IGnosis public gnosis; // {s} the length of a Gnosis EasyAuction. Governance parameter. @@ -183,7 +183,7 @@ contract BrokerP1 is ComponentP1, IBroker { // === Private === function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { - // In the future we'll have more sophisticated choice logic here, probably by trade size + require(batchAuctionLength > 0, "batchAuctionLength unset"); GnosisTrade trade = GnosisTrade(address(batchTradeImplementation).clone()); trades[address(trade)] = true; @@ -208,6 +208,7 @@ contract BrokerP1 is ComponentP1, IBroker { } function newDutchAuction(TradeRequest memory req, ITrading caller) private returns (ITrade) { + require(dutchAuctionLength > 0, "dutchAuctionLength unset"); DutchTrade trade = DutchTrade(address(dutchTradeImplementation).clone()); trades[address(trade)] = true; diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index ba96bba353..4e1832a430 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.17; -import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../interfaces/IMain.sol"; import "../interfaces/IAssetRegistry.sol"; @@ -13,7 +13,7 @@ import "./mixins/TradeLib.sol"; /// @custom:oz-upgrades-unsafe-allow external-library-linking contract RevenueTraderP1 is TradingP1, IRevenueTrader { using FixLib for uint192; - using SafeERC20Upgradeable for IERC20Upgradeable; + using SafeERC20 for IERC20; // Immutable after init() IERC20 public tokenToBuy; @@ -34,6 +34,17 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { tokenToBuy = tokenToBuy_; } + /// Settle a single trade + distribute revenue + /// @custom:interaction + function settleTrade(IERC20 sell) + public + override(ITrading, TradingP1) + notTradingPausedOrFrozen + { + super.settleTrade(sell); + distributeRevenue(); + } + /// If erc20 is tokenToBuy, distribute it; else, sell it for tokenToBuy /// @dev Intended to be used with multicall /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION @@ -52,16 +63,12 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { // tryTrade(prepareTradeSell(toAsset(erc20), toAsset(tokenToBuy), bal)) // (i.e, start a trade, selling as much of our bal of erc20 as we can, to buy tokenToBuy) function manageToken(IERC20 erc20, TradeKind kind) external notTradingPausedOrFrozen { - if (address(trades[erc20]) != address(0)) return; - - uint256 bal = erc20.balanceOf(address(this)); - if (bal == 0) return; + require(address(trades[erc20]) == address(0), "trade open"); + require(erc20.balanceOf(address(this)) > 0, "0 balance"); if (erc20 == tokenToBuy) { // == Interactions then return == - IERC20Upgradeable(address(erc20)).safeApprove(address(distributor), 0); - IERC20Upgradeable(address(erc20)).safeApprove(address(distributor), bal); - distributor.distribute(erc20, bal); + distributeRevenue(); return; } @@ -94,6 +101,15 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { } } + // === Private === + + function distributeRevenue() private { + uint256 bal = tokenToBuy.balanceOf(address(this)); + tokenToBuy.safeApprove(address(distributor), 0); + tokenToBuy.safeApprove(address(distributor), bal); + distributor.distribute(tokenToBuy, bal); + } + // // {UoA} // uint192 maxTradeVolume = fixMin(sell_.maxTradeVolume(), buy_.maxTradeVolume()); diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 18bca29552..21dc19a7ec 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -32,6 +32,9 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl uint192 public minTradeVolume; // {UoA} + // === 3.0.0 === + mapping(TradeKind => uint48) public lastSettlement; // {block} + // ==== Invariants ==== // tradesOpen = len(values(trades)) // trades[sell] != 0 iff trade[sell] has been opened and not yet settled @@ -63,13 +66,14 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // tradesOpen' = tradesOpen - 1 // untested: // OZ nonReentrant line is assumed to be working. cost/benefit of direct testing is high - function settleTrade(IERC20 sell) external notTradingPausedOrFrozen nonReentrant { + function settleTrade(IERC20 sell) public virtual notTradingPausedOrFrozen nonReentrant { ITrade trade = trades[sell]; - if (address(trade) == address(0)) return; + require(address(trade) != address(0), "no trade open"); require(trade.canSettle(), "cannot settle yet"); delete trades[sell]; tradesOpen--; + lastSettlement[trade.kind()] = uint48(block.number); // == Interactions == (uint256 soldAmt, uint256 boughtAmt) = trade.settle(); @@ -117,8 +121,16 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl IERC20Upgradeable(address(sell)).safeApprove(address(broker), 0); IERC20Upgradeable(address(sell)).safeApprove(address(broker), req.sellAmount); - ITrade trade = broker.openTrade(req, kind); + // Require at least 1 empty block between auctions of the same kind + // This gives space for someone to start one of the opposite kinds of auctions + if (kind == TradeKind.DUTCH_AUCTION) { + require(block.number > lastSettlement[TradeKind.DUTCH_AUCTION] + 1, "wait 1 block"); + } else { + // kind == TradeKind.BATCH_AUCTION + require(block.number > lastSettlement[TradeKind.BATCH_AUCTION] + 1, "wait 1 block"); + } + ITrade trade = broker.openTrade(req, kind); trades[sell] = trade; tradesOpen++; emit TradeStarted(trade, sell, req.buy.erc20(), req.sellAmount, req.minBuyAmount); @@ -156,5 +168,5 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[46] private __gap; + uint256[44] private __gap; } diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index fa216fb3d5..5eb20a4812 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -19,16 +19,17 @@ uint192 constant EIGHTY_FIVE_PERCENT = 85e16; // {1} * from the best price to the worst price. The worst price is additionally discounted by * the maxTradeSlippage based on how far between minTradeVolume and maxTradeVolume the trade is. * - * Flow for bidding: + * To bid: * - Call `bidAmount()` to check price at various timestamps * - Wait until desirable block is reached - * - Provide approval for `buy` token in the correct amount - * - Call `bid()`. Receive payment in sell tokens atomically + * - Provide approval of buy tokens and call bid(). Swap will be atomic */ contract DutchTrade is ITrade { using FixLib for uint192; using SafeERC20 for IERC20Metadata; + TradeKind public constant kind = TradeKind.DUTCH_AUCTION; + TradeStatus public status; // reentrancy protection ITrading public origin; // creator @@ -115,38 +116,6 @@ contract DutchTrade is ITrade { require(lowPrice <= middlePrice, "asset inverted pricing"); } - /// Calculates how much buy token is needed to purchase the lot, at a particular timestamp - /// Price Curve: - /// - 1.5 * middlePrice down to the middlePrice for first 15% of auction - /// - middlePrice down to lowPrice for the last 80% of auction - /// @param timestamp {s} The block timestamp to get price for - /// @return {qBuyTok} The amount of buy tokens required to purchase the lot - function bidAmount(uint48 timestamp) public view returns (uint256) { - require(timestamp < endTime, "auction over"); - - uint192 progression = divuu(uint48(block.timestamp) - startTime, endTime - startTime); - // assert(progression <= FIX_ONE); - - // {buyTok/sellTok} - uint192 price; - - if (progression < FIFTEEN_PERCENT) { - // Fast decay -- 15th percentile case - - // highPrice is 1.5x middlePrice - uint192 highPrice = middlePrice + middlePrice.mul(FIFTY_PERCENT); - price = highPrice - (highPrice - middlePrice).mulDiv(progression, FIFTEEN_PERCENT); - } else { - // Slow decay -- 85th percentile case - price = - middlePrice - - (middlePrice - lowPrice).mulDiv(progression - FIFTEEN_PERCENT, EIGHTY_FIVE_PERCENT); - } - - // {qBuyTok} = {sellTok} * {buyTok/sellTok} - return sellAmount.mul(price, CEIL).shiftl_toUint(int8(buy.decimals()), CEIL); - } - /// Bid for the auction lot at the current price; settling atomically via a callback /// @dev Caller must have provided approval function bid() external { @@ -158,14 +127,17 @@ contract DutchTrade is ITrade { // Transfer in buy tokens bidder = msg.sender; buy.safeTransferFrom(bidder, address(this), buyAmount); - // TODO examine reentrancy - should be okay - // Settle via callback + // TODO examine reentrancy - think it's probably ok + // the other candidate design is ditch the bid() function entirely and have them transfer + // tokens directly into this contract followed by origin.settleTrade(), but I don't like + // that pattern because it means humans cannot bid without a smart contract helper. + + // settle() via callback origin.settleTrade(sell); } /// Settle the auction, emptying the contract of balances - /// @dev Buyer must have transferred buy tokens into the contract ahead of time function settle() external stateTransition(TradeStatus.OPEN, TradeStatus.CLOSED) @@ -202,4 +174,38 @@ contract DutchTrade is ITrade { function canSettle() external view returns (bool) { return status == TradeStatus.OPEN && (bidder != address(0) || block.timestamp >= endTime); } + + // === Bid Helper === + + /// Calculates how much buy token is needed to purchase the lot, at a particular timestamp + /// Price Curve: + /// - 1.5 * middlePrice down to the middlePrice for first 15% of auction + /// - middlePrice down to lowPrice for the last 80% of auction + /// @param timestamp {s} The block timestamp to get price for + /// @return {qBuyTok} The amount of buy tokens required to purchase the lot + function bidAmount(uint48 timestamp) public view returns (uint256) { + require(timestamp < endTime, "auction over"); + + uint192 progression = divuu(uint48(block.timestamp) - startTime, endTime - startTime); + // assert(progression <= FIX_ONE); + + // {buyTok/sellTok} + uint192 price; + + if (progression < FIFTEEN_PERCENT) { + // Fast decay -- 15th percentile case + + // highPrice is 1.5x middlePrice + uint192 highPrice = middlePrice + middlePrice.mul(FIFTY_PERCENT); + price = highPrice - (highPrice - middlePrice).mulDiv(progression, FIFTEEN_PERCENT); + } else { + // Slow decay -- 85th percentile case + price = + middlePrice - + (middlePrice - lowPrice).mulDiv(progression - FIFTEEN_PERCENT, EIGHTY_FIVE_PERCENT); + } + + // {qBuyTok} = {sellTok} * {buyTok/sellTok} + return sellAmount.mul(price, CEIL).shiftl_toUint(int8(buy.decimals()), CEIL); + } } diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index b7a3188068..aff7ee90ec 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -17,6 +17,7 @@ contract GnosisTrade is ITrade { using SafeERC20Upgradeable for IERC20Upgradeable; // ==== Constants + TradeKind public constant kind = TradeKind.BATCH_AUCTION; uint256 public constant FEE_DENOMINATOR = 1000; // Upper bound for the max number of orders we're happy to have the auction clear in; From 7b0132e4ddbfd8c4d5d21b4ec902193d9dd6622b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 22:30:17 -0400 Subject: [PATCH 087/462] prevent bidding in same block as auction creation --- contracts/plugins/trading/DutchTrade.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 5eb20a4812..6d80989b39 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -184,6 +184,7 @@ contract DutchTrade is ITrade { /// @param timestamp {s} The block timestamp to get price for /// @return {qBuyTok} The amount of buy tokens required to purchase the lot function bidAmount(uint48 timestamp) public view returns (uint256) { + require(timestamp > startTime, "cannot bid block auction was created"); require(timestamp < endTime, "auction over"); uint192 progression = divuu(uint48(block.timestamp) - startTime, endTime - startTime); From 68ce8c8fb25916f7628306d12138834ad4bd53e1 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 22:31:11 -0400 Subject: [PATCH 088/462] chain bid into new auction --- contracts/interfaces/ITrading.sol | 3 ++- contracts/p0/BackingManager.sol | 21 +++++++++++++++++++++ contracts/p0/RevenueTrader.sol | 4 +++- contracts/p0/mixins/Trading.sol | 26 ++++++++++++++++---------- contracts/p1/BackingManager.sol | 16 ++++++++++++++++ contracts/p1/RevenueTrader.sol | 4 +++- contracts/p1/mixins/Trading.sol | 27 +++++++++++++++++---------- 7 files changed, 78 insertions(+), 23 deletions(-) diff --git a/contracts/interfaces/ITrading.sol b/contracts/interfaces/ITrading.sol index 6dc58ef41d..3a5ab9a5c7 100644 --- a/contracts/interfaces/ITrading.sol +++ b/contracts/interfaces/ITrading.sol @@ -45,8 +45,9 @@ interface ITrading is IComponent, IRewardableComponent { ); /// Settle a single trade, expected to be used with multicall for efficient mass settlement + /// @return The ITrade contract used /// @custom:refresher - function settleTrade(IERC20 sell) external; + function settleTrade(IERC20 sell) external returns (ITrade); /// @return {%} The maximum trade slippage acceptable function maxTradeSlippage() external view returns (uint192); diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 39700d42d2..47f8cb5b7e 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -48,6 +48,27 @@ contract BackingManagerP0 is TradingP0, IBackingManager { erc20.safeApprove(address(main.rToken()), type(uint256).max); } + /// Settle a single trade. If DUTCH_AUCTION, try rebalance() + /// @return trade The ITrade contract settled + /// @custom:interaction + function settleTrade(IERC20 sell) + public + override(ITrading, TradingP0) + notTradingPausedOrFrozen + returns (ITrade trade) + { + trade = super.settleTrade(sell); // the super call is nonReentrant + + // if Dutch: optimistically open another trade + if (trade.kind() == TradeKind.DUTCH_AUCTION) { + try this.rebalance(TradeKind.DUTCH_AUCTION) {} catch (bytes memory errData) { + // prevent MEV searchers from providing less gas on purpose by reverting if OOG + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + } + } + } + /// Apply the overall backing policy using the specified TradeKind, taking a haircut if unable /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction RCEI diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index b77ac2bdc4..a551c34005 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -29,13 +29,15 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { } /// Settle a single trade + distribute revenue + /// @return trade The ITrade contract settled /// @custom:interaction function settleTrade(IERC20 sell) public override(ITrading, TradingP0) notTradingPausedOrFrozen + returns (ITrade trade) { - super.settleTrade(sell); + trade = super.settleTrade(sell); distributeRevenue(); } diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index 7c708b4078..882cc4e524 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -42,9 +42,15 @@ abstract contract TradingP0 is RewardableP0, ITrading { } /// Settle a single trade, expected to be used with multicall for efficient mass settlement + /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) public virtual notTradingPausedOrFrozen { - ITrade trade = trades[sell]; + function settleTrade(IERC20 sell) + public + virtual + notTradingPausedOrFrozen + returns (ITrade trade) + { + trade = trades[sell]; require(address(trade) != address(0), "no trade open"); require(trade.canSettle(), "cannot settle yet"); @@ -65,14 +71,14 @@ abstract contract TradingP0 is RewardableP0, ITrading { req.sell.erc20().safeApprove(address(broker), 0); req.sell.erc20().safeApprove(address(broker), req.sellAmount); - // Require at least 1 empty block between auctions of the same kind - // This gives space for someone to start one of the opposite kinds of auctions - if (kind == TradeKind.DUTCH_AUCTION) { - require(block.timestamp > lastSettlement[TradeKind.DUTCH_AUCTION] + 1, "wait 1 block"); - } else { - // kind == TradeKind.BATCH_AUCTION - require(block.timestamp > lastSettlement[TradeKind.BATCH_AUCTION] + 1, "wait 1 block"); - } + // // Require at least 1 empty block between auctions of the same kind + // // This gives space for someone to start one of the opposite kinds of auctions + // if (kind == TradeKind.DUTCH_AUCTION) { + // require(block.timestamp > lastSettlement[TradeKind.DUTCH_AUCTION] + 1, "wait 1 block"); + // } else { + // // kind == TradeKind.BATCH_AUCTION + // require(block.timestamp > lastSettlement[TradeKind.BATCH_AUCTION] + 1, "wait 1 block"); + // } ITrade trade = broker.openTrade(req, kind); trades[req.sell.erc20()] = trade; diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index f238c160b4..c25d98bbf6 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -80,6 +80,22 @@ contract BackingManagerP1 is TradingP1, IBackingManager { IERC20Upgradeable(address(erc20)).safeApprove(address(main.rToken()), type(uint256).max); } + /// Settle a single trade. If DUTCH_AUCTION, try rebalance() + /// @return trade The ITrade contract settled + /// @custom:interaction + function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { + trade = super.settleTrade(sell); // the super call is nonReentrant and checks paused state + + // if Dutch: optimistically open another trade + if (trade.kind() == TradeKind.DUTCH_AUCTION) { + try this.rebalance(TradeKind.DUTCH_AUCTION) {} catch (bytes memory errData) { + // prevent MEV searchers from providing less gas on purpose by reverting if OOG + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + } + } + } + /// Apply the overall backing policy using the specified TradeKind, taking a haircut if unable /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction RCEI diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 4e1832a430..a42a758781 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -35,13 +35,15 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { } /// Settle a single trade + distribute revenue + /// @return trade The ITrade contract settled /// @custom:interaction function settleTrade(IERC20 sell) public override(ITrading, TradingP1) notTradingPausedOrFrozen + returns (ITrade trade) { - super.settleTrade(sell); + trade = super.settleTrade(sell); distributeRevenue(); } diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 21dc19a7ec..d82a5cc1be 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -55,6 +55,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl } /// Settle a single trade, expected to be used with multicall for efficient mass settlement + /// @return trade The ITrade contract settled /// @custom:interaction (only reads or writes trades, and is marked `nonReentrant`) // checks: // !paused (trading), !frozen @@ -66,8 +67,14 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // tradesOpen' = tradesOpen - 1 // untested: // OZ nonReentrant line is assumed to be working. cost/benefit of direct testing is high - function settleTrade(IERC20 sell) public virtual notTradingPausedOrFrozen nonReentrant { - ITrade trade = trades[sell]; + function settleTrade(IERC20 sell) + public + virtual + notTradingPausedOrFrozen + nonReentrant + returns (ITrade trade) + { + trade = trades[sell]; require(address(trade) != address(0), "no trade open"); require(trade.canSettle(), "cannot settle yet"); @@ -121,14 +128,14 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl IERC20Upgradeable(address(sell)).safeApprove(address(broker), 0); IERC20Upgradeable(address(sell)).safeApprove(address(broker), req.sellAmount); - // Require at least 1 empty block between auctions of the same kind - // This gives space for someone to start one of the opposite kinds of auctions - if (kind == TradeKind.DUTCH_AUCTION) { - require(block.number > lastSettlement[TradeKind.DUTCH_AUCTION] + 1, "wait 1 block"); - } else { - // kind == TradeKind.BATCH_AUCTION - require(block.number > lastSettlement[TradeKind.BATCH_AUCTION] + 1, "wait 1 block"); - } + // // Require at least 1 empty block between auctions of the same kind + // // This gives space for someone to start one of the opposite kinds of auctions + // if (kind == TradeKind.DUTCH_AUCTION) { + // require(block.number > lastSettlement[TradeKind.DUTCH_AUCTION] + 1, "wait 1 block"); + // } else { + // // kind == TradeKind.BATCH_AUCTION + // require(block.number > lastSettlement[TradeKind.BATCH_AUCTION] + 1, "wait 1 block"); + // } ITrade trade = broker.openTrade(req, kind); trades[sell] = trade; From 7bbcbf648696f3060faf87b7eb231d8240f4a1a9 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 25 Apr 2023 22:46:35 -0400 Subject: [PATCH 089/462] add 1-block cooldown between trades of the same type --- contracts/p0/BackingManager.sol | 6 +++--- contracts/p0/mixins/Trading.sol | 25 +++++++++++++++++-------- contracts/p1/BackingManager.sol | 8 ++++---- contracts/p1/mixins/Trading.sol | 19 +++++++++++-------- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 47f8cb5b7e..4121f6326b 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -59,9 +59,9 @@ contract BackingManagerP0 is TradingP0, IBackingManager { { trade = super.settleTrade(sell); // the super call is nonReentrant - // if Dutch: optimistically open another trade - if (trade.kind() == TradeKind.DUTCH_AUCTION) { - try this.rebalance(TradeKind.DUTCH_AUCTION) {} catch (bytes memory errData) { + // if the caller is the trade contract, try chaining with another rebalance() + if (_msgSender() == address(trade)) { + try this.rebalance(trade.kind()) {} catch (bytes memory errData) { // prevent MEV searchers from providing less gas on purpose by reverting if OOG // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index 882cc4e524..eeaf25146a 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -71,14 +71,23 @@ abstract contract TradingP0 is RewardableP0, ITrading { req.sell.erc20().safeApprove(address(broker), 0); req.sell.erc20().safeApprove(address(broker), req.sellAmount); - // // Require at least 1 empty block between auctions of the same kind - // // This gives space for someone to start one of the opposite kinds of auctions - // if (kind == TradeKind.DUTCH_AUCTION) { - // require(block.timestamp > lastSettlement[TradeKind.DUTCH_AUCTION] + 1, "wait 1 block"); - // } else { - // // kind == TradeKind.BATCH_AUCTION - // require(block.timestamp > lastSettlement[TradeKind.BATCH_AUCTION] + 1, "wait 1 block"); - // } + // Only start the next auction back-to-back if msgSender is self + if (_msgSender() != address(this)) { + // Require at least 1 empty block between auctions of the same kind + // This gives space for someone to start one of the opposite kinds of auctions + if (kind == TradeKind.DUTCH_AUCTION) { + require( + block.timestamp > lastSettlement[TradeKind.DUTCH_AUCTION] + 1, + "wait 1 block" + ); + } else { + // kind == TradeKind.BATCH_AUCTION + require( + block.timestamp > lastSettlement[TradeKind.BATCH_AUCTION] + 1, + "wait 1 block" + ); + } + } ITrade trade = broker.openTrade(req, kind); trades[req.sell.erc20()] = trade; diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index c25d98bbf6..bb92323654 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -84,11 +84,11 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// @return trade The ITrade contract settled /// @custom:interaction function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { - trade = super.settleTrade(sell); // the super call is nonReentrant and checks paused state + trade = super.settleTrade(sell); // modifiers: notTradingPausedOrFrozen nonReentrant - // if Dutch: optimistically open another trade - if (trade.kind() == TradeKind.DUTCH_AUCTION) { - try this.rebalance(TradeKind.DUTCH_AUCTION) {} catch (bytes memory errData) { + // if the caller is the trade contract, try chaining with another rebalance() + if (_msgSender() == address(trade)) { + try this.rebalance(trade.kind()) {} catch (bytes memory errData) { // prevent MEV searchers from providing less gas on purpose by reverting if OOG // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index d82a5cc1be..6d927eee36 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -128,14 +128,17 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl IERC20Upgradeable(address(sell)).safeApprove(address(broker), 0); IERC20Upgradeable(address(sell)).safeApprove(address(broker), req.sellAmount); - // // Require at least 1 empty block between auctions of the same kind - // // This gives space for someone to start one of the opposite kinds of auctions - // if (kind == TradeKind.DUTCH_AUCTION) { - // require(block.number > lastSettlement[TradeKind.DUTCH_AUCTION] + 1, "wait 1 block"); - // } else { - // // kind == TradeKind.BATCH_AUCTION - // require(block.number > lastSettlement[TradeKind.BATCH_AUCTION] + 1, "wait 1 block"); - // } + // Only start the next auction back-to-back if msgSender is self + if (_msgSender() != address(this)) { + // Require at least 1 empty block between auctions of the same kind + // This gives space for someone to start one of the opposite kinds of auctions + if (kind == TradeKind.DUTCH_AUCTION) { + require(block.number > lastSettlement[TradeKind.DUTCH_AUCTION] + 1, "wait 1 block"); + } else { + // kind == TradeKind.BATCH_AUCTION + require(block.number > lastSettlement[TradeKind.BATCH_AUCTION] + 1, "wait 1 block"); + } + } ITrade trade = broker.openTrade(req, kind); trades[sell] = trade; From bf60e1abf476f62c363b04b7462cbcc9458835b3 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 10:56:05 -0400 Subject: [PATCH 090/462] more tuning --- contracts/p0/BackingManager.sol | 33 ++++++++++++----------- contracts/p0/RevenueTrader.sol | 6 ++--- contracts/p0/mixins/Trading.sol | 11 +++----- contracts/p1/BackingManager.sol | 34 ++++++++++++------------ contracts/p1/Broker.sol | 2 ++ contracts/p1/RevenueTrader.sol | 18 ++++++------- contracts/p1/mixins/Trading.sol | 2 ++ contracts/plugins/trading/DutchTrade.sol | 3 ++- 8 files changed, 55 insertions(+), 54 deletions(-) diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 4121f6326b..e5a375779e 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -138,16 +138,6 @@ contract BackingManagerP0 is TradingP0, IBackingManager { require(main.basketHandler().fullyCollateralized(), "undercollateralized"); BasketRange memory basketsHeld = main.basketHandler().basketsHeldBy(address(this)); - - // == Interaction (then return) == - handoutExcessAssets(erc20s, basketsHeld.bottom); - } - - // === Private === - - /// Send excess assets to the RSR and RToken traders - /// @param wholeBasketsHeld {BU} The number of full basket units held by the BackingManager - function handoutExcessAssets(IERC20[] calldata erc20s, uint192 wholeBasketsHeld) private { assert(main.basketHandler().status() == CollateralStatus.SOUND); // Special-case RSR to forward to StRSR pool @@ -161,18 +151,18 @@ contract BackingManagerP0 is TradingP0, IBackingManager { { IRToken rToken = main.rToken(); needed = rToken.basketsNeeded(); // {BU} - if (wholeBasketsHeld.gt(needed)) { + if (basketsHeld.bottom.gt(needed)) { int8 decimals = int8(rToken.decimals()); uint192 totalSupply = shiftl_toFix(rToken.totalSupply(), -decimals); // {rTok} // {BU} = {BU} - {BU} - uint192 extraBUs = wholeBasketsHeld.minus(needed); + uint192 extraBUs = basketsHeld.bottom.minus(needed); // {qRTok: Fix} = {BU} * {qRTok / BU} (if needed == 0, conv rate is 1 qRTok/BU) uint192 rTok = (needed > 0) ? extraBUs.mulDiv(totalSupply, needed) : extraBUs; rToken.mint(address(this), rTok); - rToken.setBasketsNeeded(wholeBasketsHeld); + rToken.setBasketsNeeded(basketsHeld.bottom); } } @@ -192,15 +182,28 @@ contract BackingManagerP0 is TradingP0, IBackingManager { uint256 delta = bal.minus(req).shiftl_toUint(int8(asset.erc20Decimals())); uint256 tokensPerShare = delta / (totals.rTokenTotal + totals.rsrTotal); + // solhint-disable no-empty-blocks { uint256 toRSR = tokensPerShare * totals.rsrTotal; - if (toRSR > 0) erc20s[i].safeTransfer(address(main.rsrTrader()), toRSR); + if (toRSR > 0) { + erc20s[i].safeTransfer(address(main.rsrTrader()), toRSR); + try + main.rsrTrader().manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) + {} catch {} + // no need to revert during OOG because forwardRevenue() is already altruistic + } } { uint256 toRToken = tokensPerShare * totals.rTokenTotal; - if (toRToken > 0) + if (toRToken > 0) { erc20s[i].safeTransfer(address(main.rTokenTrader()), toRToken); + try + main.rTokenTrader().manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) + {} catch {} + // no need to revert during OOG because forwardRevenue() is already altruistic + } } + // solhint-enable no-empty-blocks } } } diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index a551c34005..b8e24056cc 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -38,7 +38,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { returns (ITrade trade) { trade = super.settleTrade(sell); - distributeRevenue(); + distributeTokenToBuy(); } /// Processes a single token; unpermissioned @@ -50,7 +50,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { require(erc20.balanceOf(address(this)) > 0, "0 balance"); if (erc20 == tokenToBuy) { - distributeRevenue(); + distributeTokenToBuy(); return; } @@ -86,7 +86,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { // === Private === - function distributeRevenue() private { + function distributeTokenToBuy() private { uint256 bal = tokenToBuy.balanceOf(address(this)); tokenToBuy.safeApprove(address(main.distributor()), 0); tokenToBuy.safeApprove(address(main.distributor()), bal); diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index eeaf25146a..907e2bc9f3 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -72,20 +72,15 @@ abstract contract TradingP0 is RewardableP0, ITrading { req.sell.erc20().safeApprove(address(broker), req.sellAmount); // Only start the next auction back-to-back if msgSender is self + // TODO is there a better way to do this? if (_msgSender() != address(this)) { // Require at least 1 empty block between auctions of the same kind // This gives space for someone to start one of the opposite kinds of auctions if (kind == TradeKind.DUTCH_AUCTION) { - require( - block.timestamp > lastSettlement[TradeKind.DUTCH_AUCTION] + 1, - "wait 1 block" - ); + require(block.number > lastSettlement[TradeKind.DUTCH_AUCTION] + 1, "wait 1 block"); } else { // kind == TradeKind.BATCH_AUCTION - require( - block.timestamp > lastSettlement[TradeKind.BATCH_AUCTION] + 1, - "wait 1 block" - ); + require(block.number > lastSettlement[TradeKind.BATCH_AUCTION] + 1, "wait 1 block"); } } diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index bb92323654..ace0251a70 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -161,16 +161,6 @@ contract BackingManagerP1 is TradingP1, IBackingManager { require(basketsHeld.bottom >= rToken.basketsNeeded(), "undercollateralized"); // require(basketHandler.fullyCollateralized()) - // == Interaction (then return) == - handoutExcessAssets(erc20s, basketsHeld.bottom); - } - - // === Private === - - /// Send excess assets to the RSR and RToken traders - /// @param basketsHeldBottom {BU} The number of full basket units held by the BackingManager - /// @custom:interaction CEI - function handoutExcessAssets(IERC20[] calldata erc20s, uint192 basketsHeldBottom) private { /** * Assumptions: * - Fully collateralized. All collateral meet balance requirements. @@ -202,25 +192,25 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // where rate(R) == R.basketsNeeded / R.totalSupply, // rate(rToken') >== rate(rToken) // (>== is "no less than, and nearly equal to") - // and rToken'.basketsNeeded <= basketsHeldBottom + // and rToken'.basketsNeeded <= basketsHeld.bottom // and rToken'.totalSupply is maximal satisfying this. uint192 needed; // {BU} { needed = rToken.basketsNeeded(); // {BU} - if (basketsHeldBottom.gt(needed)) { + if (basketsHeld.bottom.gt(needed)) { // gas-optimization: RToken is known to have 18 decimals, the same as FixLib uint192 totalSupply = _safeWrap(rToken.totalSupply()); // {rTok} // {BU} = {BU} - {BU} - uint192 extraBUs = basketsHeldBottom.minus(needed); + uint192 extraBUs = basketsHeld.bottom.minus(needed); // {rTok} = {BU} * {rTok / BU} (if needed == 0, conv rate is 1 rTok/BU) uint192 rTok = (needed > 0) ? extraBUs.mulDiv(totalSupply, needed) : extraBUs; // gas-optimization: RToken is known to have 18 decimals, same as FixLib rToken.mint(address(this), uint256(rTok)); - rToken.setBasketsNeeded(basketsHeldBottom); - needed = basketsHeldBottom; + rToken.setBasketsNeeded(basketsHeld.bottom); + needed = basketsHeld.bottom; } } @@ -255,8 +245,18 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // == Interactions == for (uint256 i = 0; i < length; ++i) { IERC20Upgradeable erc20 = IERC20Upgradeable(address(erc20s[i])); - if (toRToken[i] > 0) erc20.safeTransfer(address(rTokenTrader), toRToken[i]); - if (toRSR[i] > 0) erc20.safeTransfer(address(rsrTrader), toRSR[i]); + if (toRToken[i] > 0) { + erc20.safeTransfer(address(rTokenTrader), toRToken[i]); + // solhint-disable-next-line no-empty-blocks + try rTokenTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} + // no need to revert during OOG because forwardRevenue() is already altruistic + } + if (toRSR[i] > 0) { + erc20.safeTransfer(address(rsrTrader), toRSR[i]); + // solhint-disable-next-line no-empty-blocks + try rsrTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} + // no need to revert during OOG because forwardRevenue() is already altruistic + } } // It's okay if there is leftover dust for RToken or a surplus asset (not RSR) diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 183714be17..e74c0ec627 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -28,12 +28,14 @@ contract BrokerP1 is ComponentP1, IBroker { IRevenueTrader private rsrTrader; IRevenueTrader private rTokenTrader; + /// @custom:oz-renamed-from tradeImplementation // The Batch Auction Trade contract to clone on openTrade(). Governance parameter. ITrade public batchTradeImplementation; // The Gnosis contract to init batch auction trades with. Governance parameter. IGnosis public gnosis; + /// @custom:oz-renamed-from auctionLength // {s} the length of a Gnosis EasyAuction. Governance parameter. uint48 public batchAuctionLength; diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index a42a758781..0aac359e90 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -37,14 +37,12 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { /// Settle a single trade + distribute revenue /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) - public - override(ITrading, TradingP1) - notTradingPausedOrFrozen - returns (ITrade trade) - { - trade = super.settleTrade(sell); - distributeRevenue(); + function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { + trade = super.settleTrade(sell); // modifiers: notTradingPausedOrFrozen nonReentrant + distributeTokenToBuy(); + + // no need to try to start another auction + // back-to-back revenue auctions for the same sell token are unlikely } /// If erc20 is tokenToBuy, distribute it; else, sell it for tokenToBuy @@ -70,7 +68,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { if (erc20 == tokenToBuy) { // == Interactions then return == - distributeRevenue(); + distributeTokenToBuy(); return; } @@ -105,7 +103,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { // === Private === - function distributeRevenue() private { + function distributeTokenToBuy() private { uint256 bal = tokenToBuy.balanceOf(address(this)); tokenToBuy.safeApprove(address(distributor), 0); tokenToBuy.safeApprove(address(distributor), bal); diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 6d927eee36..b5adb2bd2d 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -129,6 +129,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl IERC20Upgradeable(address(sell)).safeApprove(address(broker), req.sellAmount); // Only start the next auction back-to-back if msgSender is self + // TODO is there a better way to do this? if (_msgSender() != address(this)) { // Require at least 1 empty block between auctions of the same kind // This gives space for someone to start one of the opposite kinds of auctions @@ -138,6 +139,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // kind == TradeKind.BATCH_AUCTION require(block.number > lastSettlement[TradeKind.BATCH_AUCTION] + 1, "wait 1 block"); } + // TODO this prevents revenue auctions from being chained unless do it the same way as the BackingManager } ITrade trade = broker.openTrade(req, kind); diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 6d80989b39..23e6867933 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -73,7 +73,7 @@ contract DutchTrade is ITrade { ) external stateTransition(TradeStatus.NOT_STARTED, TradeStatus.OPEN) { require(address(sell) != address(0) || address(buy) != address(0), "zero address token"); - // Only start an auction with well-defined prices + // Only start dutch auctions under well-defined prices // // In the BackingManager this may end up recalculating the RToken price (uint192 sellLow, uint192 sellHigh) = sell_.price(); // {UoA/sellTok} @@ -132,6 +132,7 @@ contract DutchTrade is ITrade { // the other candidate design is ditch the bid() function entirely and have them transfer // tokens directly into this contract followed by origin.settleTrade(), but I don't like // that pattern because it means humans cannot bid without a smart contract helper. + // also requires changing the function signature of settle() to accept the caller address // settle() via callback origin.settleTrade(sell); From 58f9a276ab5bd7ac01c695b479a20f7b0381ec3b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 11:07:15 -0400 Subject: [PATCH 091/462] rate-limit same-kind auctions based on endTime(), not time of settlement --- contracts/p0/mixins/Trading.sol | 18 +++++++++++++----- contracts/p1/mixins/Trading.sol | 19 +++++++++++++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index 907e2bc9f3..6f60b56e04 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -26,7 +26,7 @@ abstract contract TradingP0 is RewardableP0, ITrading { uint192 public minTradeVolume; // {UoA} - mapping(TradeKind => uint48) public lastSettlement; // {block} + mapping(TradeKind => uint48) public lastEndTime; // {s} block timestamp // untestable: // `else` branch of `onlyInitializing` (ie. revert) is currently untestable. @@ -56,7 +56,6 @@ abstract contract TradingP0 is RewardableP0, ITrading { delete trades[sell]; tradesOpen--; - lastSettlement[trade.kind()] = uint48(block.number); (uint256 soldAmt, uint256 boughtAmt) = trade.settle(); emit TradeSettled(trade, trade.sell(), trade.buy(), soldAmt, boughtAmt); } @@ -74,17 +73,26 @@ abstract contract TradingP0 is RewardableP0, ITrading { // Only start the next auction back-to-back if msgSender is self // TODO is there a better way to do this? if (_msgSender() != address(this)) { - // Require at least 1 empty block between auctions of the same kind + // Require more than 12s between auctions of the same kind // This gives space for someone to start one of the opposite kinds of auctions if (kind == TradeKind.DUTCH_AUCTION) { - require(block.number > lastSettlement[TradeKind.DUTCH_AUCTION] + 1, "wait 1 block"); + require( + block.timestamp > lastEndTime[TradeKind.DUTCH_AUCTION] + 12, + "wait 1 block" + ); } else { // kind == TradeKind.BATCH_AUCTION - require(block.number > lastSettlement[TradeKind.BATCH_AUCTION] + 1, "wait 1 block"); + require( + block.timestamp > lastEndTime[TradeKind.BATCH_AUCTION] + 12, + "wait 1 block" + ); } } ITrade trade = broker.openTrade(req, kind); + uint48 endTime = trade.endTime(); + if (endTime > lastEndTime[kind]) lastEndTime[kind] = endTime; + trades[req.sell.erc20()] = trade; tradesOpen++; emit TradeStarted( diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index b5adb2bd2d..488dcb9fd2 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -33,7 +33,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl uint192 public minTradeVolume; // {UoA} // === 3.0.0 === - mapping(TradeKind => uint48) public lastSettlement; // {block} + mapping(TradeKind => uint48) public lastEndTime; // {s} block timestamp // ==== Invariants ==== // tradesOpen = len(values(trades)) @@ -80,7 +80,6 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl delete trades[sell]; tradesOpen--; - lastSettlement[trade.kind()] = uint48(block.number); // == Interactions == (uint256 soldAmt, uint256 boughtAmt) = trade.settle(); @@ -131,18 +130,26 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // Only start the next auction back-to-back if msgSender is self // TODO is there a better way to do this? if (_msgSender() != address(this)) { - // Require at least 1 empty block between auctions of the same kind + // Require more than 12s between auctions of the same kind // This gives space for someone to start one of the opposite kinds of auctions if (kind == TradeKind.DUTCH_AUCTION) { - require(block.number > lastSettlement[TradeKind.DUTCH_AUCTION] + 1, "wait 1 block"); + require( + block.timestamp > lastEndTime[TradeKind.DUTCH_AUCTION] + 12, + "wait 1 block" + ); } else { // kind == TradeKind.BATCH_AUCTION - require(block.number > lastSettlement[TradeKind.BATCH_AUCTION] + 1, "wait 1 block"); + require( + block.timestamp > lastEndTime[TradeKind.BATCH_AUCTION] + 12, + "wait 1 block" + ); } - // TODO this prevents revenue auctions from being chained unless do it the same way as the BackingManager } ITrade trade = broker.openTrade(req, kind); + uint48 endTime = trade.endTime(); + if (endTime > lastEndTime[kind]) lastEndTime[kind] = endTime; + trades[sell] = trade; tradesOpen++; emit TradeStarted(trade, sell, req.buy.erc20(), req.sellAmount, req.minBuyAmount); From 0bf847fb9f2b2b9c99565d4b741781d2a157a855 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 17:27:05 -0400 Subject: [PATCH 092/462] switch order of TradeKind and TradeRequest --- contracts/interfaces/IBroker.sol | 2 +- contracts/p0/BackingManager.sol | 2 +- contracts/p0/Broker.sol | 2 +- contracts/p0/RevenueTrader.sol | 2 +- contracts/p0/mixins/Trading.sol | 8 +++++--- contracts/p1/BackingManager.sol | 2 +- contracts/p1/Broker.sol | 2 +- contracts/p1/RevenueTrader.sol | 2 +- contracts/p1/mixins/Trading.sol | 8 +++++--- contracts/plugins/mocks/InvalidBrokerMock.sol | 2 +- 10 files changed, 18 insertions(+), 14 deletions(-) diff --git a/contracts/interfaces/IBroker.sol b/contracts/interfaces/IBroker.sol index 53e4e65f0d..4998853f70 100644 --- a/contracts/interfaces/IBroker.sol +++ b/contracts/interfaces/IBroker.sol @@ -43,7 +43,7 @@ interface IBroker is IComponent { /// Request a trade from the broker /// @dev Requires setting an allowance in advance /// @custom:interaction - function openTrade(TradeRequest memory req, TradeKind kind) external returns (ITrade); + function openTrade(TradeKind kind, TradeRequest memory req) external returns (ITrade); /// Only callable by one of the trading contracts the broker deploys function reportViolation() external; diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index e5a375779e..7a84452871 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -112,7 +112,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { if (req.sellAmount > bal) main.stRSR().seizeRSR(req.sellAmount - bal); } - tryTrade(req, kind); + tryTrade(kind, req); } else { // Haircut time compromiseBasketsNeeded(basketsHeld.bottom); diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index bff9704456..f70d354dc7 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -58,7 +58,7 @@ contract BrokerP0 is ComponentP0, IBroker { /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @dev Requires setting an allowance in advance /// @custom:protected - function openTrade(TradeRequest memory req, TradeKind kind) + function openTrade(TradeKind kind, TradeRequest memory req) external notTradingPausedOrFrozen returns (ITrade) diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index b8e24056cc..fce5a56231 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -80,7 +80,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { ); if (launch) { - tryTrade(req, kind); + tryTrade(kind, req); } } diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index 6f60b56e04..6585fd0869 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -62,7 +62,7 @@ abstract contract TradingP0 is RewardableP0, ITrading { /// Try to initiate a trade with a trading partner provided by the broker /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION - function tryTrade(TradeRequest memory req, TradeKind kind) internal { + function tryTrade(TradeKind kind, TradeRequest memory req) internal { IBroker broker = main.broker(); assert(address(trades[req.sell.erc20()]) == address(0)); require(!broker.disabled(), "broker disabled"); @@ -71,9 +71,11 @@ abstract contract TradingP0 is RewardableP0, ITrading { req.sell.erc20().safeApprove(address(broker), req.sellAmount); // Only start the next auction back-to-back if msgSender is self + // Only time this happens is in BackingManager.settleTrade() -> BackingManager.rebalance() // TODO is there a better way to do this? if (_msgSender() != address(this)) { - // Require more than 12s between auctions of the same kind + // Warning, Assumption: blocktime <= 12s + // Require at least 1 block between auctions of the same kind // This gives space for someone to start one of the opposite kinds of auctions if (kind == TradeKind.DUTCH_AUCTION) { require( @@ -89,7 +91,7 @@ abstract contract TradingP0 is RewardableP0, ITrading { } } - ITrade trade = broker.openTrade(req, kind); + ITrade trade = broker.openTrade(kind, req); uint48 endTime = trade.endTime(); if (endTime > lastEndTime[kind]) lastEndTime[kind] = endTime; diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index ace0251a70..7eb15f3f22 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -136,7 +136,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { if (req.sellAmount > bal) stRSR.seizeRSR(req.sellAmount - bal); } - tryTrade(req, kind); + tryTrade(kind, req); } else { // Haircut time compromiseBasketsNeeded(basketsHeld.bottom); diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index e74c0ec627..00a9b01d76 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -92,7 +92,7 @@ contract BrokerP1 is ComponentP1, IBroker { // actions: // Transfers req.sellAmount of req.sell.erc20 from caller to `trade` // Calls trade.init() with appropriate parameters - function openTrade(TradeRequest memory req, TradeKind kind) + function openTrade(TradeKind kind, TradeRequest memory req) external notTradingPausedOrFrozen returns (ITrade) diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 0aac359e90..92b910a9da 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -97,7 +97,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { ); if (launch) { - tryTrade(req, kind); + tryTrade(kind, req); } } diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 488dcb9fd2..1940a46b07 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -119,7 +119,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // This is reentrancy-safe because we're using the `nonReentrant` modifier on every method of // this contract that changes state this function refers to. // slither-disable-next-line reentrancy-vulnerabilities-1 - function tryTrade(TradeRequest memory req, TradeKind kind) internal nonReentrant { + function tryTrade(TradeKind kind, TradeRequest memory req) internal nonReentrant { /* */ IERC20 sell = req.sell.erc20(); assert(address(trades[sell]) == address(0)); @@ -128,9 +128,11 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl IERC20Upgradeable(address(sell)).safeApprove(address(broker), req.sellAmount); // Only start the next auction back-to-back if msgSender is self + // Only time this happens is in BackingManager.settleTrade() -> BackingManager.rebalance() // TODO is there a better way to do this? if (_msgSender() != address(this)) { - // Require more than 12s between auctions of the same kind + // Warning, Assumption: blocktime <= 12s + // Require at least 1 block between auctions of the same kind // This gives space for someone to start one of the opposite kinds of auctions if (kind == TradeKind.DUTCH_AUCTION) { require( @@ -146,7 +148,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl } } - ITrade trade = broker.openTrade(req, kind); + ITrade trade = broker.openTrade(kind, req); uint48 endTime = trade.endTime(); if (endTime > lastEndTime[kind]) lastEndTime[kind] = endTime; diff --git a/contracts/plugins/mocks/InvalidBrokerMock.sol b/contracts/plugins/mocks/InvalidBrokerMock.sol index 86e0bb756a..7bbe3b7284 100644 --- a/contracts/plugins/mocks/InvalidBrokerMock.sol +++ b/contracts/plugins/mocks/InvalidBrokerMock.sol @@ -39,7 +39,7 @@ contract InvalidBrokerMock is ComponentP0, IBroker { } /// Invalid implementation - Reverts - function openTrade(TradeRequest memory req, TradeKind) + function openTrade(TradeKind, TradeRequest memory req) external view notTradingPausedOrFrozen From 48901e789b4073bb5c4c8ff1c6809f33ad7bd41f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 17:43:43 -0400 Subject: [PATCH 093/462] RevenueTrader --- contracts/interfaces/IRevenueTrader.sol | 3 ++- contracts/p0/RevenueTrader.sol | 15 ++++++++++++--- contracts/p0/mixins/Trading.sol | 4 ++-- contracts/p1/RevenueTrader.sol | 15 ++++++++++----- contracts/p1/mixins/Trading.sol | 4 ++-- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/contracts/interfaces/IRevenueTrader.sol b/contracts/interfaces/IRevenueTrader.sol index 2c77bd9db6..15f1fd8b4a 100644 --- a/contracts/interfaces/IRevenueTrader.sol +++ b/contracts/interfaces/IRevenueTrader.sol @@ -22,9 +22,10 @@ interface IRevenueTrader is IComponent, ITrading { /// Processes a single token; unpermissioned /// @dev Intended to be used with multicall + /// @param erc20 The ERC20 token to manage; can be tokenToBuy or anything registered /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction - function manageToken(IERC20 sell, TradeKind kind) external; + function manageToken(IERC20 erc20, TradeKind kind) external; } // solhint-disable-next-line no-empty-blocks diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index fce5a56231..5d262400fe 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -39,6 +39,9 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { { trade = super.settleTrade(sell); distributeTokenToBuy(); + + // no need to try to start another auction + // back-to-back revenue auctions for the same sell token are unlikely } /// Processes a single token; unpermissioned @@ -46,14 +49,20 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction function manageToken(IERC20 erc20, TradeKind kind) external notTradingPausedOrFrozen { - require(address(trades[erc20]) == address(0), "trade open"); - require(erc20.balanceOf(address(this)) > 0, "0 balance"); - if (erc20 == tokenToBuy) { distributeTokenToBuy(); return; } + // if open trade: settle or revert + if (address(trades[erc20]) != address(0)) { + settleTrade(erc20); + } + + if (erc20.balanceOf(address(this)) == 0) return; + + // Try to launch another auction + IAssetRegistry reg = main.assetRegistry(); IAsset sell = reg.toAsset(erc20); IAsset buy = reg.toAsset(tokenToBuy); diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index 6585fd0869..abb3d65c8c 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -70,8 +70,8 @@ abstract contract TradingP0 is RewardableP0, ITrading { req.sell.erc20().safeApprove(address(broker), 0); req.sell.erc20().safeApprove(address(broker), req.sellAmount); - // Only start the next auction back-to-back if msgSender is self - // Only time this happens is in BackingManager.settleTrade() -> BackingManager.rebalance() + // Only allow starting the next auction back-to-back if msgSender is self + // Only time this happens is BackingManager.settleTrade() -> BackingManager.rebalance() // TODO is there a better way to do this? if (_msgSender() != address(this)) { // Warning, Assumption: blocktime <= 12s diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 92b910a9da..59dab6a568 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -60,18 +60,23 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { // // If erc20 is any other registered asset (checked): // actions: - // tryTrade(prepareTradeSell(toAsset(erc20), toAsset(tokenToBuy), bal)) + // tryTrade(kind, prepareTradeSell(toAsset(erc20), toAsset(tokenToBuy), bal)) // (i.e, start a trade, selling as much of our bal of erc20 as we can, to buy tokenToBuy) function manageToken(IERC20 erc20, TradeKind kind) external notTradingPausedOrFrozen { - require(address(trades[erc20]) == address(0), "trade open"); - require(erc20.balanceOf(address(this)) > 0, "0 balance"); - if (erc20 == tokenToBuy) { - // == Interactions then return == distributeTokenToBuy(); return; } + // if open trade: settle or revert + if (address(trades[erc20]) != address(0)) { + settleTrade(erc20); + } + + if (erc20.balanceOf(address(this)) == 0) return; + + // Try to launch another auction + IAsset sell = assetRegistry.toAsset(erc20); IAsset buy = assetRegistry.toAsset(tokenToBuy); (uint192 sellPrice, ) = sell.price(); // {UoA/tok} diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 1940a46b07..ea7a02a7e3 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -127,8 +127,8 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl IERC20Upgradeable(address(sell)).safeApprove(address(broker), 0); IERC20Upgradeable(address(sell)).safeApprove(address(broker), req.sellAmount); - // Only start the next auction back-to-back if msgSender is self - // Only time this happens is in BackingManager.settleTrade() -> BackingManager.rebalance() + // Only allow starting the next auction back-to-back if msgSender is self + // Only time this happens is BackingManager.settleTrade() -> BackingManager.rebalance() // TODO is there a better way to do this? if (_msgSender() != address(this)) { // Warning, Assumption: blocktime <= 12s From dbbd570c02501134e27bf519df21a6a59313b311 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 17:48:36 -0400 Subject: [PATCH 094/462] lint clean --- contracts/interfaces/ITrade.sol | 3 ++- contracts/p0/BackingManager.sol | 7 ++++--- contracts/p1/BackingManager.sol | 7 ++++--- contracts/plugins/trading/DutchTrade.sol | 4 ++-- contracts/plugins/trading/GnosisTrade.sol | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/contracts/interfaces/ITrade.sol b/contracts/interfaces/ITrade.sol index 31e9c6740b..57ae23b501 100644 --- a/contracts/interfaces/ITrade.sol +++ b/contracts/interfaces/ITrade.sol @@ -35,5 +35,6 @@ interface ITrade { function canSettle() external view returns (bool); /// @return TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION - function kind() external view returns (TradeKind); + // solhint-disable-next-line func-name-mixedcase + function KIND() external view returns (TradeKind); } diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 7a84452871..91ed920524 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -61,7 +61,8 @@ contract BackingManagerP0 is TradingP0, IBackingManager { // if the caller is the trade contract, try chaining with another rebalance() if (_msgSender() == address(trade)) { - try this.rebalance(trade.kind()) {} catch (bytes memory errData) { + // solhint-disable-next-line no-empty-blocks + try this.rebalance(trade.KIND()) {} catch (bytes memory errData) { // prevent MEV searchers from providing less gas on purpose by reverting if OOG // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string @@ -190,7 +191,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { try main.rsrTrader().manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} - // no need to revert during OOG because forwardRevenue() is already altruistic + // no need to revert during OOG because caller is already altruistic } } { @@ -200,7 +201,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { try main.rTokenTrader().manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} - // no need to revert during OOG because forwardRevenue() is already altruistic + // no need to revert during OOG because caller is already altruistic } } // solhint-enable no-empty-blocks diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 7eb15f3f22..2d85e53a47 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -88,7 +88,8 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // if the caller is the trade contract, try chaining with another rebalance() if (_msgSender() == address(trade)) { - try this.rebalance(trade.kind()) {} catch (bytes memory errData) { + // solhint-disable-next-line no-empty-blocks + try this.rebalance(trade.KIND()) {} catch (bytes memory errData) { // prevent MEV searchers from providing less gas on purpose by reverting if OOG // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string @@ -249,13 +250,13 @@ contract BackingManagerP1 is TradingP1, IBackingManager { erc20.safeTransfer(address(rTokenTrader), toRToken[i]); // solhint-disable-next-line no-empty-blocks try rTokenTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} - // no need to revert during OOG because forwardRevenue() is already altruistic + // no need to revert during OOG because caller is already altruistic } if (toRSR[i] > 0) { erc20.safeTransfer(address(rsrTrader), toRSR[i]); // solhint-disable-next-line no-empty-blocks try rsrTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} - // no need to revert during OOG because forwardRevenue() is already altruistic + // no need to revert during OOG because caller is already altruistic } } diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 23e6867933..0db975a6a1 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -28,7 +28,7 @@ contract DutchTrade is ITrade { using FixLib for uint192; using SafeERC20 for IERC20Metadata; - TradeKind public constant kind = TradeKind.DUTCH_AUCTION; + TradeKind public constant KIND = TradeKind.DUTCH_AUCTION; TradeStatus public status; // reentrancy protection @@ -144,7 +144,7 @@ contract DutchTrade is ITrade { stateTransition(TradeStatus.OPEN, TradeStatus.CLOSED) returns (uint256 soldAmt, uint256 boughtAmt) { - require(msg.sender == address(origin), "only origin can settle"); // via origin.settleTrade() + require(msg.sender == address(origin), "only origin can settle"); // origin.settleTrade() // Received bid if (bidder != address(0)) { diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index aff7ee90ec..56bc5c5592 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -17,7 +17,7 @@ contract GnosisTrade is ITrade { using SafeERC20Upgradeable for IERC20Upgradeable; // ==== Constants - TradeKind public constant kind = TradeKind.BATCH_AUCTION; + TradeKind public constant KIND = TradeKind.BATCH_AUCTION; uint256 public constant FEE_DENOMINATOR = 1000; // Upper bound for the max number of orders we're happy to have the auction clear in; From 2626ef9db706c3716827823012681b83c6af3356 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 18:05:09 -0400 Subject: [PATCH 095/462] RevenueTrader: expose distributeTokenToBuy directly --- contracts/interfaces/IRevenueTrader.sol | 7 ++++++- contracts/p0/Broker.sol | 4 ++-- contracts/p0/RevenueTrader.sol | 19 ++++++++++--------- contracts/p1/Broker.sol | 4 ++-- contracts/p1/RevenueTrader.sol | 25 ++++++++++--------------- 5 files changed, 30 insertions(+), 29 deletions(-) diff --git a/contracts/interfaces/IRevenueTrader.sol b/contracts/interfaces/IRevenueTrader.sol index 15f1fd8b4a..7f90f20b89 100644 --- a/contracts/interfaces/IRevenueTrader.sol +++ b/contracts/interfaces/IRevenueTrader.sol @@ -20,12 +20,17 @@ interface IRevenueTrader is IComponent, ITrading { uint192 minTradeVolume_ ) external; - /// Processes a single token; unpermissioned + /// Process a single token /// @dev Intended to be used with multicall /// @param erc20 The ERC20 token to manage; can be tokenToBuy or anything registered /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction function manageToken(IERC20 erc20, TradeKind kind) external; + + /// Distribute tokenToBuy to its destinations + /// @dev Special-case of manageToken(tokenToBuy, *) + /// @custom:interaction + function distributeTokenToBuy() external; } // solhint-disable-next-line no-empty-blocks diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index f70d354dc7..6ea7d50f92 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -125,7 +125,7 @@ contract BrokerP0 is ComponentP0, IBroker { function setBatchTradeImplementation(ITrade newTradeImplementation) public governance { require( address(newTradeImplementation) != address(0), - "invalid Trade Implementation address" + "invalid batchTradeImplementation address" ); emit TradeImplementationSet(batchTradeImplementation, newTradeImplementation); @@ -146,7 +146,7 @@ contract BrokerP0 is ComponentP0, IBroker { function setDutchTradeImplementation(ITrade newTradeImplementation) public governance { require( address(newTradeImplementation) != address(0), - "invalid Trade Implementation address" + "invalid dutchTradeImplementation address" ); emit TradeImplementationSet(dutchTradeImplementation, newTradeImplementation); diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index 5d262400fe..2846c37bef 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -44,6 +44,16 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { // back-to-back revenue auctions for the same sell token are unlikely } + /// Distribute tokenToBuy to its destinations + /// @dev Special-case of manageToken(tokenToBuy, *) + /// @custom:interaction + function distributeTokenToBuy() public { + uint256 bal = tokenToBuy.balanceOf(address(this)); + tokenToBuy.safeApprove(address(main.distributor()), 0); + tokenToBuy.safeApprove(address(main.distributor()), bal); + main.distributor().distribute(tokenToBuy, bal); + } + /// Processes a single token; unpermissioned /// @dev Intended to be used with multicall /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION @@ -92,13 +102,4 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { tryTrade(kind, req); } } - - // === Private === - - function distributeTokenToBuy() private { - uint256 bal = tokenToBuy.balanceOf(address(this)); - tokenToBuy.safeApprove(address(main.distributor()), 0); - tokenToBuy.safeApprove(address(main.distributor()), bal); - main.distributor().distribute(tokenToBuy, bal); - } } diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 00a9b01d76..86dd193389 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -138,7 +138,7 @@ contract BrokerP1 is ComponentP1, IBroker { function setBatchTradeImplementation(ITrade newTradeImplementation) public governance { require( address(newTradeImplementation) != address(0), - "invalid Trade Implementation address" + "invalid batchTradeImplementation address" ); emit TradeImplementationSet(batchTradeImplementation, newTradeImplementation); @@ -159,7 +159,7 @@ contract BrokerP1 is ComponentP1, IBroker { function setDutchTradeImplementation(ITrade newTradeImplementation) public governance { require( address(newTradeImplementation) != address(0), - "invalid Trade Implementation address" + "invalid dutchTradeImplementation address" ); emit TradeImplementationSet(dutchTradeImplementation, newTradeImplementation); diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 59dab6a568..04875ebc55 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -45,6 +45,16 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { // back-to-back revenue auctions for the same sell token are unlikely } + /// Distribute tokenToBuy to its destinations + /// @dev Special-case of manageToken(tokenToBuy, *) + /// @custom:interaction + function distributeTokenToBuy() public { + uint256 bal = tokenToBuy.balanceOf(address(this)); + tokenToBuy.safeApprove(address(distributor), 0); + tokenToBuy.safeApprove(address(distributor), bal); + distributor.distribute(tokenToBuy, bal); + } + /// If erc20 is tokenToBuy, distribute it; else, sell it for tokenToBuy /// @dev Intended to be used with multicall /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION @@ -106,21 +116,6 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { } } - // === Private === - - function distributeTokenToBuy() private { - uint256 bal = tokenToBuy.balanceOf(address(this)); - tokenToBuy.safeApprove(address(distributor), 0); - tokenToBuy.safeApprove(address(distributor), bal); - distributor.distribute(tokenToBuy, bal); - } - - // // {UoA} - // uint192 maxTradeVolume = fixMin(sell_.maxTradeVolume(), buy_.maxTradeVolume()); - - // sellAmount = shiftl_toFix(sellAmount_, -int8(sell.decimals())); - // sellAmount = fixMin(sellAmount, maxTradeVolume.div(sellHigh, FLOOR)); - /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. From dc9ced547a9079aec3ca711e56956b8a3bf0c156 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 19:46:59 -0400 Subject: [PATCH 096/462] refresh()/melt() before RevenueTrader manageToken --- contracts/interfaces/ITrading.sol | 3 +- contracts/p0/BackingManager.sol | 14 ++++----- contracts/p0/RevenueTrader.sol | 17 ++++++----- contracts/p0/mixins/Trading.sol | 1 + contracts/p1/BackingManager.sol | 11 ++++---- contracts/p1/RevenueTrader.sol | 36 ++++++++++++++++-------- contracts/p1/mixins/Trading.sol | 31 ++++++++++---------- contracts/plugins/trading/DutchTrade.sol | 2 +- 8 files changed, 64 insertions(+), 51 deletions(-) diff --git a/contracts/interfaces/ITrading.sol b/contracts/interfaces/ITrading.sol index 3a5ab9a5c7..4f76877f07 100644 --- a/contracts/interfaces/ITrading.sol +++ b/contracts/interfaces/ITrading.sol @@ -45,7 +45,8 @@ interface ITrading is IComponent, IRewardableComponent { ); /// Settle a single trade, expected to be used with multicall for efficient mass settlement - /// @return The ITrade contract used + /// @param sell The sell token in the trade + /// @return The trade settled /// @custom:refresher function settleTrade(IERC20 sell) external returns (ITrade); diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 91ed920524..1179552697 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -49,17 +49,13 @@ contract BackingManagerP0 is TradingP0, IBackingManager { } /// Settle a single trade. If DUTCH_AUCTION, try rebalance() + /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) - public - override(ITrading, TradingP0) - notTradingPausedOrFrozen - returns (ITrade trade) - { - trade = super.settleTrade(sell); // the super call is nonReentrant - - // if the caller is the trade contract, try chaining with another rebalance() + function settleTrade(IERC20 sell) public override(ITrading, TradingP0) returns (ITrade trade) { + trade = super.settleTrade(sell); + + // if the settler is the trade contract itself, try chaining with another rebalance() if (_msgSender() == address(trade)) { // solhint-disable-next-line no-empty-blocks try this.rebalance(trade.KIND()) {} catch (bytes memory errData) { diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index 2846c37bef..7572937bc3 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -29,6 +29,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { } /// Settle a single trade + distribute revenue + /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction function settleTrade(IERC20 sell) @@ -39,9 +40,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { { trade = super.settleTrade(sell); distributeTokenToBuy(); - - // no need to try to start another auction - // back-to-back revenue auctions for the same sell token are unlikely + // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } /// Distribute tokenToBuy to its destinations @@ -64,14 +63,14 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { return; } - // if open trade: settle or revert - if (address(trades[erc20]) != address(0)) { - settleTrade(erc20); - } + // === Try to launch another auction === - if (erc20.balanceOf(address(this)) == 0) return; + // refresh() + main.assetRegistry().refresh(); + main.furnace().melt(); - // Try to launch another auction + require(address(trades[erc20]) == address(0), "trade open"); + require(erc20.balanceOf(address(this)) == 0, "0 balance"); IAssetRegistry reg = main.assetRegistry(); IAsset sell = reg.toAsset(erc20); diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index abb3d65c8c..ff8b0dd975 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -42,6 +42,7 @@ abstract contract TradingP0 is RewardableP0, ITrading { } /// Settle a single trade, expected to be used with multicall for efficient mass settlement + /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction function settleTrade(IERC20 sell) diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 2d85e53a47..6f2ba4c5fb 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -81,12 +81,13 @@ contract BackingManagerP1 is TradingP1, IBackingManager { } /// Settle a single trade. If DUTCH_AUCTION, try rebalance() + /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { - trade = super.settleTrade(sell); // modifiers: notTradingPausedOrFrozen nonReentrant + trade = super.settleTrade(sell); // modifier: notTradingPausedOrFrozen - // if the caller is the trade contract, try chaining with another rebalance() + // if the settler is the trade contract itself, try chaining with another rebalance() if (_msgSender() == address(trade)) { // solhint-disable-next-line no-empty-blocks try this.rebalance(trade.KIND()) {} catch (bytes memory errData) { @@ -105,11 +106,11 @@ contract BackingManagerP1 is TradingP1, IBackingManager { assetRegistry.refresh(); furnace.melt(); - BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(this)); - require(tradesOpen == 0, "trade open"); require(basketHandler.isReady(), "basket not ready"); require(block.timestamp >= basketHandler.timestamp() + tradingDelay, "trading delayed"); + + BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(this)); require(basketsHeld.bottom < rToken.basketsNeeded(), "already collateralized"); // require(!basketHandler.fullyCollateralized()) @@ -296,5 +297,5 @@ contract BackingManagerP1 is TradingP1, IBackingManager { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[40] private __gap; + uint256[41] private __gap; } diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 04875ebc55..f62ce07e40 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -19,6 +19,8 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { IERC20 public tokenToBuy; IAssetRegistry private assetRegistry; IDistributor private distributor; + IBackingManager private backingManager; + IFurnace private furnace; function init( IMain main_, @@ -31,18 +33,18 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { __Trading_init(main_, maxTradeSlippage_, minTradeVolume_); assetRegistry = main_.assetRegistry(); distributor = main_.distributor(); + backingManager = main_.backingManager(); tokenToBuy = tokenToBuy_; } /// Settle a single trade + distribute revenue + /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { - trade = super.settleTrade(sell); // modifiers: notTradingPausedOrFrozen nonReentrant + trade = super.settleTrade(sell); // modifier: notTradingPausedOrFrozen distributeTokenToBuy(); - - // no need to try to start another auction - // back-to-back revenue auctions for the same sell token are unlikely + // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } /// Distribute tokenToBuy to its destinations @@ -78,14 +80,16 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { return; } - // if open trade: settle or revert - if (address(trades[erc20]) != address(0)) { - settleTrade(erc20); - } + // === Try to launch another auction === - if (erc20.balanceOf(address(this)) == 0) return; + // refresh() if not called by BackingManager -- gas optimization + if (_msgSender() != address(backingManager)) { + assetRegistry.refresh(); + furnace.melt(); + } - // Try to launch another auction + require(address(trades[erc20]) == address(0), "trade open"); + require(erc20.balanceOf(address(this)) == 0, "0 balance"); IAsset sell = assetRegistry.toAsset(erc20); IAsset buy = assetRegistry.toAsset(tokenToBuy); @@ -116,10 +120,20 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { } } + /// Call after upgrade to >= 3.0.0 + function cacheBackingManager() public { + backingManager = main.backingManager(); + } + + /// Call after upgrade to >= 3.0.0 + function cacheFurnace() public { + furnace = main.furnace(); + } + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[47] private __gap; + uint256[45] private __gap; } diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index ea7a02a7e3..f924871e24 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -54,7 +54,23 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl setMinTradeVolume(minTradeVolume_); } + /// Claim all rewards + /// Collective Action + /// @custom:interaction CEI + function claimRewards() external notTradingPausedOrFrozen { + RewardableLibP1.claimRewards(main.assetRegistry()); + } + + /// Claim rewards for a single asset + /// Collective Action + /// @param erc20 The ERC20 to claimRewards on + /// @custom:interaction CEI + function claimRewardsSingle(IERC20 erc20) external notTradingPausedOrFrozen { + RewardableLibP1.claimRewardsSingle(main.assetRegistry().toAsset(erc20)); + } + /// Settle a single trade, expected to be used with multicall for efficient mass settlement + /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction (only reads or writes trades, and is marked `nonReentrant`) // checks: @@ -86,21 +102,6 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl emit TradeSettled(trade, trade.sell(), trade.buy(), soldAmt, boughtAmt); } - /// Claim all rewards - /// Collective Action - /// @custom:interaction CEI - function claimRewards() external notTradingPausedOrFrozen { - RewardableLibP1.claimRewards(main.assetRegistry()); - } - - /// Claim rewards for a single asset - /// Collective Action - /// @param erc20 The ERC20 to claimRewards on - /// @custom:interaction CEI - function claimRewardsSingle(IERC20 erc20) external notTradingPausedOrFrozen { - RewardableLibP1.claimRewardsSingle(main.assetRegistry().toAsset(erc20)); - } - /// Try to initiate a trade with a trading partner provided by the broker /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction (only reads or writes `trades`, and is marked `nonReentrant`) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 0db975a6a1..2360637e9f 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -134,7 +134,7 @@ contract DutchTrade is ITrade { // that pattern because it means humans cannot bid without a smart contract helper. // also requires changing the function signature of settle() to accept the caller address - // settle() via callback + // settle() via callback; may also start a new Trade origin.settleTrade(sell); } From 657c1b14bb696220033926bf929c4c9e1793834b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 19:47:07 -0400 Subject: [PATCH 097/462] update scripts --- .../phase1-common/0_setup_deployments.ts | 7 ++++- .../phase1-common/2_deploy_implementations.ts | 28 ++++++++++++++----- .../phase1-common/3_deploy_rsrAsset.ts | 7 +---- .../deployment/phase3-rtoken/rTokenConfig.ts | 6 ++-- scripts/deployment/utils.ts | 9 ++++-- .../verification/1_verify_implementations.ts | 12 ++++++-- 6 files changed, 47 insertions(+), 22 deletions(-) diff --git a/scripts/deployment/phase1-common/0_setup_deployments.ts b/scripts/deployment/phase1-common/0_setup_deployments.ts index 7203616726..603da02a07 100644 --- a/scripts/deployment/phase1-common/0_setup_deployments.ts +++ b/scripts/deployment/phase1-common/0_setup_deployments.ts @@ -54,15 +54,20 @@ async function main() { GNOSIS_EASY_AUCTION: gnosisAddr, }, tradingLib: '', + cvxMiningLib: '', facadeRead: '', facadeAct: '', facadeWriteLib: '', facadeWrite: '', + facadeMonitor: '', deployer: '', rsrAsset: '', implementations: { main: '', - trade: '', + trading: { + gnosisTrade: '', + dutchTrade: '', + }, components: { assetRegistry: '', backingManager: '', diff --git a/scripts/deployment/phase1-common/2_deploy_implementations.ts b/scripts/deployment/phase1-common/2_deploy_implementations.ts index ec1442314c..311ac237a3 100644 --- a/scripts/deployment/phase1-common/2_deploy_implementations.ts +++ b/scripts/deployment/phase1-common/2_deploy_implementations.ts @@ -10,6 +10,7 @@ import { BasketHandlerP1, BrokerP1, DistributorP1, + DutchTrade, FurnaceP1, GnosisTrade, MainP1, @@ -28,7 +29,8 @@ let mainImpl: MainP1 let revTraderImpl: RevenueTraderP1 let rTokenImpl: RTokenP1 let stRSRImpl: StRSRP1Votes -let tradeImpl: GnosisTrade +let gnosisTradeImpl: GnosisTrade +let dutchTradeImpl: DutchTrade const writeComponentDeployment = ( deployments: IDeployments, @@ -80,17 +82,29 @@ async function main() { console.log(`Deployed to ${hre.network.name} (${chainId}): Main Implementation: ${mainImpl.address}`) - // ******************** Deploy Trade ********************************/ + // ******************** Deploy GnosisTrade ********************************/ - const TradeImplFactory = await ethers.getContractFactory('GnosisTrade') - tradeImpl = await TradeImplFactory.connect(burner).deploy() - await tradeImpl.deployed() + const GnosisTradeImplFactory = await ethers.getContractFactory('GnosisTrade') + gnosisTradeImpl = await GnosisTradeImplFactory.connect(burner).deploy() + await gnosisTradeImpl.deployed() // Write temporary deployments file - deployments.implementations.trade = tradeImpl.address + deployments.implementations.trading.gnosisTrade = gnosisTradeImpl.address fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) - console.log(` Trade Implementation: ${tradeImpl.address}`) + console.log(`GnosisTrade Implementation: ${gnosisTradeImpl.address}`) + + // ******************** Deploy DutchTrade ********************************/ + + const DutchTradeImplFactory = await ethers.getContractFactory('DutchTrade') + dutchTradeImpl = await DutchTradeImplFactory.connect(burner).deploy() + await dutchTradeImpl.deployed() + + // Write temporary deployments file + deployments.implementations.trading.dutchTrade = dutchTradeImpl.address + fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) + + console.log(`DutchTrade Implementation: ${dutchTradeImpl.address}`) // ******************** Deploy Components ********************************/ diff --git a/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts b/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts index 5a80bd25a9..e67a33f602 100644 --- a/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts +++ b/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts @@ -6,12 +6,7 @@ import { networkConfig } from '../../../common/configuration' import { ZERO_ADDRESS } from '../../../common/constants' import { fp } from '../../../common/numbers' import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../../deployment/common' -import { - priceTimeout, - longOracleTimeout, - oracleTimeout, - validateImplementations, -} from '../../deployment/utils' +import { priceTimeout, oracleTimeout, validateImplementations } from '../../deployment/utils' import { Asset } from '../../../typechain' let rsrAsset: Asset diff --git a/scripts/deployment/phase3-rtoken/rTokenConfig.ts b/scripts/deployment/phase3-rtoken/rTokenConfig.ts index 4fb1b47e8c..bac340d02a 100644 --- a/scripts/deployment/phase3-rtoken/rTokenConfig.ts +++ b/scripts/deployment/phase3-rtoken/rTokenConfig.ts @@ -21,7 +21,7 @@ export const rTokenConfig: { [key: string]: IRToken } = { rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks tradingDelay: bn('14400'), // (the delay _after_ default has been confirmed) 4 hours - auctionLength: bn('900'), // 15 minutes + batchAuctionLength: bn('900'), // 15 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days @@ -58,7 +58,7 @@ export const rTokenConfig: { [key: string]: IRToken } = { rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks tradingDelay: bn('14400'), // (the delay _after_ default has been confirmed) 4 hours - auctionLength: bn('900'), // 15 minutes + batchAuctionLength: bn('900'), // 15 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days @@ -94,7 +94,7 @@ export const rTokenConfig: { [key: string]: IRToken } = { rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) - auctionLength: bn('900'), // 15 minutes + batchAuctionLength: bn('900'), // 15 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days diff --git a/scripts/deployment/utils.ts b/scripts/deployment/utils.ts index 140b5534a1..9679c0b15d 100644 --- a/scripts/deployment/utils.ts +++ b/scripts/deployment/utils.ts @@ -56,7 +56,8 @@ export const validateImplementations = async (deployments: IDeployments) => { // Check implementations if ( !deployments.implementations.main || - !deployments.implementations.trade || + !deployments.implementations.trading.gnosisTrade || + !deployments.implementations.trading.dutchTrade || !deployments.implementations.components.assetRegistry || !deployments.implementations.components.backingManager || !deployments.implementations.components.basketHandler || @@ -71,8 +72,10 @@ export const validateImplementations = async (deployments: IDeployments) => { throw new Error(`Missing deployed implementations in network ${hre.network.name}`) } else if (!(await isValidContract(hre, deployments.implementations.main))) { throw new Error(`Main implementation not found in network ${hre.network.name}`) - } else if (!(await isValidContract(hre, deployments.implementations.trade))) { - throw new Error(`Trade implementation not found in network ${hre.network.name}`) + } else if (!(await isValidContract(hre, deployments.implementations.trading.gnosisTrade))) { + throw new Error(`GnosisTrade implementation not found in network ${hre.network.name}`) + } else if (!(await isValidContract(hre, deployments.implementations.trading.dutchTrade))) { + throw new Error(`DutchTrade implementation not found in network ${hre.network.name}`) } else if (!(await validComponents(deployments.implementations.components))) { throw new Error(`Component implementation(s) not found in network ${hre.network.name}`) } diff --git a/scripts/verification/1_verify_implementations.ts b/scripts/verification/1_verify_implementations.ts index f79e8a8a6f..1e5094c1b1 100644 --- a/scripts/verification/1_verify_implementations.ts +++ b/scripts/verification/1_verify_implementations.ts @@ -28,14 +28,22 @@ async function main() { 'contracts/p1/Main.sol:MainP1' ) - // /** ******************** Verify Trade implementation ****************************************/ + // /** ******************** Verify GnosisTrade implementation ****************************************/ await verifyContract( chainId, - deployments.implementations.trade, + deployments.implementations.trading.gnosisTrade, [], 'contracts/plugins/trading/GnosisTrade.sol:GnosisTrade' ) + // /** ******************** Verify DutchTrade implementation ****************************************/ + await verifyContract( + chainId, + deployments.implementations.trading.dutchTrade, + [], + 'contracts/plugins/trading/DutchTrade.sol:DutchTrade' + ) + /** ******************** Verify Components ****************************************/ // Define interface required for each component interface ComponentInfo { From 99a41d06fcc2cddd4834c52e579ad3b1dd4030ce Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 20:14:51 -0400 Subject: [PATCH 098/462] fix gap --- contracts/p1/mixins/Trading.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index f924871e24..f6bcb13d8e 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -190,5 +190,5 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[44] private __gap; + uint256[45] private __gap; } From bfc81c2b7892ee5257e778a8d8f95fa70939a081 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 20:17:18 -0400 Subject: [PATCH 099/462] DutchTrade asserts --- contracts/plugins/trading/DutchTrade.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 2360637e9f..ff5a9995d9 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -135,7 +135,9 @@ contract DutchTrade is ITrade { // also requires changing the function signature of settle() to accept the caller address // settle() via callback; may also start a new Trade + assert(status == TradeStatus.OPEN); origin.settleTrade(sell); + assert(status == TradeStatus.CLOSED); } /// Settle the auction, emptying the contract of balances From 87a5a8c20d956783198c56d0de2708bd3113d787 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 20:17:34 -0400 Subject: [PATCH 100/462] Broker events/governance setters --- contracts/interfaces/IBroker.sol | 18 ++++++++++++++---- contracts/p0/Broker.sol | 8 ++++---- contracts/p1/Broker.sol | 8 ++++---- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/contracts/interfaces/IBroker.sol b/contracts/interfaces/IBroker.sol index 4998853f70..13a8031098 100644 --- a/contracts/interfaces/IBroker.sol +++ b/contracts/interfaces/IBroker.sol @@ -26,8 +26,10 @@ struct TradeRequest { */ interface IBroker is IComponent { event GnosisSet(IGnosis indexed oldVal, IGnosis indexed newVal); - event TradeImplementationSet(ITrade indexed oldVal, ITrade indexed newVal); - event AuctionLengthSet(uint48 indexed oldVal, uint48 indexed newVal); + event BatchTradeImplementationSet(ITrade indexed oldVal, ITrade indexed newVal); + event DutchTradeImplementationSet(ITrade indexed oldVal, ITrade indexed newVal); + event BatchAuctionLengthSet(uint48 indexed oldVal, uint48 indexed newVal); + event DutchAuctionLengthSet(uint48 indexed oldVal, uint48 indexed newVal); event DisabledSet(bool indexed prevVal, bool indexed newVal); // Initialization @@ -54,15 +56,23 @@ interface IBroker is IComponent { interface TestIBroker is IBroker { function gnosis() external view returns (IGnosis); - function tradeImplementation() external view returns (ITrade); + function batchTradeImplementation() external view returns (ITrade); + + function dutchTradeImplementation() external view returns (ITrade); function batchAuctionLength() external view returns (uint48); + function dutchAuctionLength() external view returns (uint48); + function setGnosis(IGnosis newGnosis) external; - function setTradeImplementation(ITrade newTradeImplementation) external; + function setBatchTradeImplementation(ITrade newTradeImplementation) external; function setBatchAuctionLength(uint48 newAuctionLength) external; + function setDutchTradeImplementation(ITrade newTradeImplementation) external; + + function setDutchAuctionLength(uint48 newAuctionLength) external; + function setDisabled(bool disabled_) external; } diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 6ea7d50f92..5f78cf3f81 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -128,7 +128,7 @@ contract BrokerP0 is ComponentP0, IBroker { "invalid batchTradeImplementation address" ); - emit TradeImplementationSet(batchTradeImplementation, newTradeImplementation); + emit BatchTradeImplementationSet(batchTradeImplementation, newTradeImplementation); batchTradeImplementation = newTradeImplementation; } @@ -138,7 +138,7 @@ contract BrokerP0 is ComponentP0, IBroker { newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, "invalid batchAuctionLength" ); - emit AuctionLengthSet(batchAuctionLength, newAuctionLength); + emit BatchAuctionLengthSet(batchAuctionLength, newAuctionLength); batchAuctionLength = newAuctionLength; } @@ -149,7 +149,7 @@ contract BrokerP0 is ComponentP0, IBroker { "invalid dutchTradeImplementation address" ); - emit TradeImplementationSet(dutchTradeImplementation, newTradeImplementation); + emit DutchTradeImplementationSet(dutchTradeImplementation, newTradeImplementation); dutchTradeImplementation = newTradeImplementation; } @@ -159,7 +159,7 @@ contract BrokerP0 is ComponentP0, IBroker { newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, "invalid dutchAuctionLength" ); - emit AuctionLengthSet(dutchAuctionLength, newAuctionLength); + emit DutchAuctionLengthSet(dutchAuctionLength, newAuctionLength); dutchAuctionLength = newAuctionLength; } diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 86dd193389..78380189d1 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -141,7 +141,7 @@ contract BrokerP1 is ComponentP1, IBroker { "invalid batchTradeImplementation address" ); - emit TradeImplementationSet(batchTradeImplementation, newTradeImplementation); + emit BatchTradeImplementationSet(batchTradeImplementation, newTradeImplementation); batchTradeImplementation = newTradeImplementation; } @@ -151,7 +151,7 @@ contract BrokerP1 is ComponentP1, IBroker { newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, "invalid batchAuctionLength" ); - emit AuctionLengthSet(batchAuctionLength, newAuctionLength); + emit BatchAuctionLengthSet(batchAuctionLength, newAuctionLength); batchAuctionLength = newAuctionLength; } @@ -162,7 +162,7 @@ contract BrokerP1 is ComponentP1, IBroker { "invalid dutchTradeImplementation address" ); - emit TradeImplementationSet(dutchTradeImplementation, newTradeImplementation); + emit DutchTradeImplementationSet(dutchTradeImplementation, newTradeImplementation); dutchTradeImplementation = newTradeImplementation; } @@ -172,7 +172,7 @@ contract BrokerP1 is ComponentP1, IBroker { newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, "invalid dutchAuctionLength" ); - emit AuctionLengthSet(dutchAuctionLength, newAuctionLength); + emit DutchAuctionLengthSet(dutchAuctionLength, newAuctionLength); dutchAuctionLength = newAuctionLength; } From eb661aca1c4377f80dfee94b93d12764c8cb0e14 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 20:20:50 -0400 Subject: [PATCH 101/462] Trading minor ternary expression optimization --- contracts/p0/mixins/Trading.sol | 16 ++++------------ contracts/p1/mixins/Trading.sol | 21 +++++++++------------ 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index ff8b0dd975..dd160150d2 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -78,18 +78,10 @@ abstract contract TradingP0 is RewardableP0, ITrading { // Warning, Assumption: blocktime <= 12s // Require at least 1 block between auctions of the same kind // This gives space for someone to start one of the opposite kinds of auctions - if (kind == TradeKind.DUTCH_AUCTION) { - require( - block.timestamp > lastEndTime[TradeKind.DUTCH_AUCTION] + 12, - "wait 1 block" - ); - } else { - // kind == TradeKind.BATCH_AUCTION - require( - block.timestamp > lastEndTime[TradeKind.BATCH_AUCTION] + 12, - "wait 1 block" - ); - } + uint48 lastEnd = lastEndTime[ + kind == TradeKind.DUTCH_AUCTION ? TradeKind.BATCH_AUCTION : TradeKind.DUTCH_AUCTION + ]; + require(block.timestamp > lastEnd, "wait 1 block"); } ITrade trade = broker.openTrade(kind, req); diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index f6bcb13d8e..c1bb7f9ed3 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -135,18 +135,15 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // Warning, Assumption: blocktime <= 12s // Require at least 1 block between auctions of the same kind // This gives space for someone to start one of the opposite kinds of auctions - if (kind == TradeKind.DUTCH_AUCTION) { - require( - block.timestamp > lastEndTime[TradeKind.DUTCH_AUCTION] + 12, - "wait 1 block" - ); - } else { - // kind == TradeKind.BATCH_AUCTION - require( - block.timestamp > lastEndTime[TradeKind.BATCH_AUCTION] + 12, - "wait 1 block" - ); - } + require( + block.timestamp > + lastEndTime[ + kind == TradeKind.DUTCH_AUCTION + ? TradeKind.BATCH_AUCTION + : TradeKind.DUTCH_AUCTION + ], + "wait 1 block" + ); } ITrade trade = broker.openTrade(kind, req); From f242b861a390b933a665554aabd09f4e4f956798 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 20:54:08 -0400 Subject: [PATCH 102/462] RevenueTrader require bugfix --- contracts/facade/FacadeTest.sol | 6 +++--- contracts/p0/Broker.sol | 6 ++++++ contracts/p0/RevenueTrader.sol | 2 +- contracts/p1/RevenueTrader.sol | 14 +++++--------- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/contracts/facade/FacadeTest.sol b/contracts/facade/FacadeTest.sol index 746386c1a1..de71f7542f 100644 --- a/contracts/facade/FacadeTest.sol +++ b/contracts/facade/FacadeTest.sol @@ -51,11 +51,11 @@ contract FacadeTest is IFacadeTest { } // solhint-disable no-empty-blocks - try main.backingManager().rebalance(TradeKind.DUTCH_AUCTION) {} catch {} + try main.backingManager().rebalance(TradeKind.BATCH_AUCTION) {} catch {} try main.backingManager().forwardRevenue(erc20s) {} catch {} for (uint256 i = 0; i < erc20s.length; i++) { - try rsrTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} - try rTokenTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} + try rsrTrader.manageToken(erc20s[i], TradeKind.BATCH_AUCTION) {} catch {} + try rTokenTrader.manageToken(erc20s[i], TradeKind.BATCH_AUCTION) {} catch {} } // solhint-enable no-empty-blocks } diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 5f78cf3f81..fadb582231 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -203,4 +203,10 @@ contract BrokerP0 is ComponentP0, IBroker { trade.init(caller, req.sell, req.buy, req.sellAmount, dutchAuctionLength); return trade; } + + /// @custom:governance + function setDisabled(bool disabled_) external governance { + emit DisabledSet(disabled, disabled_); + disabled = disabled_; + } } diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index 7572937bc3..4656d35ae9 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -70,7 +70,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { main.furnace().melt(); require(address(trades[erc20]) == address(0), "trade open"); - require(erc20.balanceOf(address(this)) == 0, "0 balance"); + require(erc20.balanceOf(address(this)) > 0, "0 balance"); IAssetRegistry reg = main.assetRegistry(); IAsset sell = reg.toAsset(erc20); diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index f62ce07e40..fc1f01fa85 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -31,10 +31,8 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { require(address(tokenToBuy_) != address(0), "invalid token address"); __Component_init(main_); __Trading_init(main_, maxTradeSlippage_, minTradeVolume_); - assetRegistry = main_.assetRegistry(); - distributor = main_.distributor(); - backingManager = main_.backingManager(); tokenToBuy = tokenToBuy_; + cacheComponents(); } /// Settle a single trade + distribute revenue @@ -89,7 +87,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { } require(address(trades[erc20]) == address(0), "trade open"); - require(erc20.balanceOf(address(this)) == 0, "0 balance"); + require(erc20.balanceOf(address(this)) > 0, "0 balance"); IAsset sell = assetRegistry.toAsset(erc20); IAsset buy = assetRegistry.toAsset(tokenToBuy); @@ -121,12 +119,10 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { } /// Call after upgrade to >= 3.0.0 - function cacheBackingManager() public { + function cacheComponents() public { + assetRegistry = main.assetRegistry(); + distributor = main.distributor(); backingManager = main.backingManager(); - } - - /// Call after upgrade to >= 3.0.0 - function cacheFurnace() public { furnace = main.furnace(); } From 67901ea6fe417f4564d0b3f5320e090b9285f0fa Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 21:03:35 -0400 Subject: [PATCH 103/462] RevenueTrader: minimize melting + asset refresh() --- contracts/p0/RevenueTrader.sol | 3 --- contracts/p1/RevenueTrader.sol | 25 +++++++++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index 4656d35ae9..04617bd45f 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -63,9 +63,6 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { return; } - // === Try to launch another auction === - - // refresh() main.assetRegistry().refresh(); main.furnace().melt(); diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index fc1f01fa85..d30fb6e8af 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -21,6 +21,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { IDistributor private distributor; IBackingManager private backingManager; IFurnace private furnace; + IRToken private rToken; function init( IMain main_, @@ -58,7 +59,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { /// If erc20 is tokenToBuy, distribute it; else, sell it for tokenToBuy /// @dev Intended to be used with multicall /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION - /// @custom:interaction CEI + /// @custom:interaction RCEI // let bal = this contract's balance of erc20 // checks: !paused (trading), !frozen // does nothing if erc20 == addr(0) or bal == 0 @@ -78,19 +79,26 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { return; } - // === Try to launch another auction === + IAsset sell = assetRegistry.toAsset(erc20); + IAsset buy = assetRegistry.toAsset(tokenToBuy); - // refresh() if not called by BackingManager -- gas optimization + // === Refresh === + // do not need to refresh when caller is BackingManager if (_msgSender() != address(backingManager)) { - assetRegistry.refresh(); - furnace.melt(); + if (erc20 == IERC20(address(rToken)) || tokenToBuy == IERC20(address(rToken))) { + // if either token is the RToken, refresh everything + assetRegistry.refresh(); + furnace.melt(); + } else { + // otherwise, refresh just buy + sell + sell.refresh(); + buy.refresh(); + } } require(address(trades[erc20]) == address(0), "trade open"); require(erc20.balanceOf(address(this)) > 0, "0 balance"); - IAsset sell = assetRegistry.toAsset(erc20); - IAsset buy = assetRegistry.toAsset(tokenToBuy); (uint192 sellPrice, ) = sell.price(); // {UoA/tok} (, uint192 buyPrice) = buy.price(); // {UoA/tok} @@ -124,6 +132,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { distributor = main.distributor(); backingManager = main.backingManager(); furnace = main.furnace(); + rToken = main.rToken(); } /** @@ -131,5 +140,5 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[45] private __gap; + uint256[44] private __gap; } From bbdd30407e11a31baf6d20943221acea6b11bc8a Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 26 Apr 2023 22:38:46 -0400 Subject: [PATCH 104/462] keep RToken up to backingBuffer as well --- contracts/p0/BackingManager.sol | 5 ++- contracts/p1/BackingManager.sol | 73 ++++++++++++++++++--------------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 1179552697..2f6e7417e7 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -163,7 +163,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { } } - // Keep a small surplus of individual collateral + // BackingBuffer for non-RTokens needed = main.rToken().basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); // Handout excess assets above what is needed, including any newly minted RToken @@ -173,6 +173,9 @@ contract BackingManagerP0 is TradingP0, IBackingManager { uint192 bal = asset.bal(address(this)); // {tok} uint192 req = needed.mul(main.basketHandler().quantity(erc20s[i]), CEIL); + if (address(erc20s[i]) == address(main.rToken())) { + req = backingBuffer.mul(_safeWrap(main.rToken().totalSupply())); + } if (bal.gt(req)) { // delta: {qTok} diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 6f2ba4c5fb..872785c09c 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.17; -import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../interfaces/IAsset.sol"; import "../interfaces/IBackingManager.sol"; @@ -19,7 +19,7 @@ import "./mixins/RecollateralizationLib.sol"; /// @custom:oz-upgrades-unsafe-allow external-library-linking contract BackingManagerP1 is TradingP1, IBackingManager { using FixLib for uint192; - using SafeERC20Upgradeable for IERC20Upgradeable; + using SafeERC20 for IERC20; // Cache of peer components IAssetRegistry private assetRegistry; @@ -76,8 +76,8 @@ contract BackingManagerP1 is TradingP1, IBackingManager { function grantRTokenAllowance(IERC20 erc20) external notFrozen { require(assetRegistry.isRegistered(erc20), "erc20 unregistered"); // == Interaction == - IERC20Upgradeable(address(erc20)).safeApprove(address(main.rToken()), 0); - IERC20Upgradeable(address(erc20)).safeApprove(address(main.rToken()), type(uint256).max); + IERC20(address(erc20)).safeApprove(address(main.rToken()), 0); + IERC20(address(erc20)).safeApprove(address(main.rToken()), type(uint256).max); } /// Settle a single trade. If DUTCH_AUCTION, try rebalance() @@ -176,17 +176,15 @@ contract BackingManagerP1 is TradingP1, IBackingManager { * 2. Using whatever balances of collateral are there, fast-issue all RToken possible. * (in detail: mint RToken and set basketsNeeded so that the BU/rtok exchange rate is * roughly constant, and strictly does not decrease, - * 3. Handout all surplus asset balances (including collateral and RToken) to the - * RSR and RToken traders according to the distribution totals. + * 3. Handout all RToken held above the backingBuffer portion of the supply, and all + * non-RToken surplus asset balances to the RSR and + * RToken traders according to the distribution totals. */ // Forward any RSR held to StRSR pool; RSR should never be sold for RToken yield if (rsr.balanceOf(address(this)) > 0) { // For CEI, this is an interaction "within our system" even though RSR is already live - IERC20Upgradeable(address(rsr)).safeTransfer( - address(stRSR), - rsr.balanceOf(address(this)) - ); + IERC20(address(rsr)).safeTransfer(address(stRSR), rsr.balanceOf(address(this))); } // Mint revenue RToken and update `basketsNeeded` @@ -196,24 +194,25 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // (>== is "no less than, and nearly equal to") // and rToken'.basketsNeeded <= basketsHeld.bottom // and rToken'.totalSupply is maximal satisfying this. - uint192 needed; // {BU} - { - needed = rToken.basketsNeeded(); // {BU} - if (basketsHeld.bottom.gt(needed)) { - // gas-optimization: RToken is known to have 18 decimals, the same as FixLib - uint192 totalSupply = _safeWrap(rToken.totalSupply()); // {rTok} - - // {BU} = {BU} - {BU} - uint192 extraBUs = basketsHeld.bottom.minus(needed); - - // {rTok} = {BU} * {rTok / BU} (if needed == 0, conv rate is 1 rTok/BU) - uint192 rTok = (needed > 0) ? extraBUs.mulDiv(totalSupply, needed) : extraBUs; - - // gas-optimization: RToken is known to have 18 decimals, same as FixLib - rToken.mint(address(this), uint256(rTok)); - rToken.setBasketsNeeded(basketsHeld.bottom); - needed = basketsHeld.bottom; - } + uint192 rTokenBuffer; // {rTok} + uint192 needed = rToken.basketsNeeded(); // {BU} + if (basketsHeld.bottom.gt(needed)) { + // gas-optimization: RToken is known to have 18 decimals, the same as FixLib + uint192 totalSupply = _safeWrap(rToken.totalSupply()); // {rTok} + + // {BU} = {BU} - {BU} + uint192 extraBUs = basketsHeld.bottom.minus(needed); + + // {rTok} = {BU} * {rTok / BU} (if needed == 0, conv rate is 1 rTok/BU) + uint192 rTok = (needed > 0) ? extraBUs.mulDiv(totalSupply, needed) : extraBUs; + + // gas-optimization: RToken is known to have 18 decimals, same as FixLib + rToken.mint(address(this), uint256(rTok)); + rToken.setBasketsNeeded(basketsHeld.bottom); + needed = basketsHeld.bottom; + + // {rTok} = {1} * ({rTok} + {rTok}) + rTokenBuffer = backingBuffer.mul(totalSupply + rTok); } // At this point, even though basketsNeeded may have changed: @@ -223,7 +222,9 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // Keep a small buffer of individual collateral; "excess" assets are beyond the buffer. needed = needed.mul(FIX_ONE.plus(backingBuffer)); - // Handout excess assets above what is needed, including any recently minted RToken + // Calculate all balances above the backingBuffer: + // - rToken balances above the rTokenBuffer + // - non-RToken balances above the backingBuffer uint256 length = erc20s.length; RevenueTotals memory totals = distributor.totals(); uint256[] memory toRSR = new uint256[](length); @@ -231,10 +232,15 @@ contract BackingManagerP1 is TradingP1, IBackingManager { for (uint256 i = 0; i < length; ++i) { IAsset asset = assetRegistry.toAsset(erc20s[i]); - uint192 req = needed.mul(basketHandler.quantity(erc20s[i]), CEIL); - if (asset.bal(address(this)).gt(req)) { + // {tok} = {BU} * {tok/BU} + uint192 req = erc20s[i] != IERC20(address(rToken)) + ? needed.mul(basketHandler.quantity(erc20s[i]), CEIL) + : rTokenBuffer; + + uint192 bal = asset.bal(address(this)); + if (bal.gt(req)) { // delta: {qTok}, the excess quantity of this asset that we hold - uint256 delta = asset.bal(address(this)).minus(req).shiftl_toUint( + uint256 delta = bal.minus(req).shiftl_toUint( int8(IERC20Metadata(address(erc20s[i])).decimals()) ); // no div-by-0: Distributor guarantees (totals.rTokenTotal + totals.rsrTotal) > 0 @@ -246,7 +252,8 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // == Interactions == for (uint256 i = 0; i < length; ++i) { - IERC20Upgradeable erc20 = IERC20Upgradeable(address(erc20s[i])); + if (erc20s[i] == IERC20(address(rToken))) continue; + IERC20 erc20 = IERC20(address(erc20s[i])); if (toRToken[i] > 0) { erc20.safeTransfer(address(rTokenTrader), toRToken[i]); // solhint-disable-next-line no-empty-blocks From 7a261d380f5af874eb81eeaaa063456a77d86b9d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 27 Apr 2023 14:14:16 -0400 Subject: [PATCH 105/462] draft: sol compiling --- contracts/facade/FacadeRead.sol | 12 +-- contracts/interfaces/IBasketHandler.sol | 3 +- contracts/interfaces/IRToken.sol | 11 ++- contracts/p0/AssetRegistry.sol | 5 ++ contracts/p0/BasketHandler.sol | 96 +++++++++++++++++++++ contracts/p0/RToken.sol | 107 +++++++++++++++++++----- contracts/p1/BasketHandler.sol | 55 ++++++------ contracts/p1/RToken.sol | 105 ++++++++++++----------- hardhat.config.ts | 4 +- 9 files changed, 288 insertions(+), 110 deletions(-) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index fd26c2e4ce..6e1014fca0 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -221,7 +221,8 @@ contract FacadeRead is IFacadeRead { /// @return tokens The ERC20s backing the RToken function basketTokens(IRToken rToken) external view returns (address[] memory tokens) { - (tokens, ) = rToken.main().basketHandler().quote(FIX_ONE, RoundingMode.FLOOR); + IBasketHandler bh = rToken.main().basketHandler(); + (tokens, ) = bh.quote(FIX_ONE, RoundingMode.FLOOR); } /// Returns the backup configuration for a given targetName @@ -250,6 +251,7 @@ contract FacadeRead is IFacadeRead { view returns (uint192 backing, uint192 overCollateralization) { + IBasketHandler bh = rToken.main().basketHandler(); uint256 supply = rToken.totalSupply(); if (supply == 0) return (0, 0); @@ -257,10 +259,10 @@ contract FacadeRead is IFacadeRead { uint192 uoaNeeded; // {UoA} uint192 uoaHeldInBaskets; // {UoA} { - (address[] memory basketERC20s, uint256[] memory quantities) = rToken - .main() - .basketHandler() - .quote(basketsNeeded, FLOOR); + (address[] memory basketERC20s, uint256[] memory quantities) = bh.quote( + basketsNeeded, + FLOOR + ); IAssetRegistry reg = rToken.main().assetRegistry(); IBackingManager bm = rToken.main().backingManager(); diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index f3030c7995..36fc8b6631 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -120,13 +120,12 @@ interface IBasketHandler is IComponent { returns (address[] memory erc20s, uint256[] memory quantities); /// Return the redemption value of `amount` BUs for a linear combination of historical baskets - /// Requires `portions` sums to FIX_ONE + /// Checks `portions` sum to FIX_ONE /// @param basketNonces An array of basket nonces to do redemption from /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE /// @param amount {BU} /// @return erc20s The backing collateral erc20s /// @return quantities {qTok} ERC20 token quantities equal to `amount` BUs - // Returns (erc20s, [quantity(e) * amount {as qTok} for e in erc20s]) function quoteHistoricalRedemption( uint48[] memory basketNonces, uint192[] memory portions, diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index 23c65931b7..0f772f940c 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -79,12 +79,14 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea function issueTo(address recipient, uint256 amount) external; /// Redeem RToken for basket collateral + /// @dev Reverts if partial redemption /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @param basketNonce The nonce of the basket the redemption should be from; else reverts /// @custom:interaction function redeem(uint256 amount, uint48 basketNonce) external; /// Redeem RToken for basket collateral to a particular recipient + /// @dev Reverts if partial redemption /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @param basketNonce The nonce of the basket the redemption should be from; else reverts @@ -96,16 +98,21 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea ) external; /// Redeem RToken for a linear combination of historical baskets, to a particular recipient + /// @dev Allows partial redemptions up to the minAmounts /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @param basketNonces An array of basket nonces to do redemption from /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE + /// @param minERC20s An array of ERC20 addresses to require minAmounts for + /// @param minAmounts {qTok} The minimum ERC20 quantities the caller should receive /// @custom:interaction - function historicalRedeemTo( + function customRedemption( address recipient, uint256 amount, uint48[] memory basketNonces, - uint192[] memory portions + uint192[] memory portions, + IERC20[] memory minERC20s, + uint256[] memory minAmounts ) external; /// Mints a quantity of RToken to the `recipient`, callable only by the BackingManager diff --git a/contracts/p0/AssetRegistry.sol b/contracts/p0/AssetRegistry.sol index 5ae56c22fd..2f105886da 100644 --- a/contracts/p0/AssetRegistry.sol +++ b/contracts/p0/AssetRegistry.sol @@ -122,6 +122,11 @@ contract AssetRegistryP0 is ComponentP0, IAssetRegistry { assert(reg.erc20s.length == reg.assets.length); } + /// @return The number of registered ERC20s + function size() external view returns (uint256) { + return _erc20s.length(); + } + // /// Forbids registering a different asset for an ERC20 that is already registered diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 1810df299b..67691f1c29 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -140,6 +140,14 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { uint48 private lastStatusTimestamp; CollateralStatus private lastStatus; + // Nonce of the first reference basket from the current history + // A new historical record begins whenever the prime basket is changed + // There can be 0 to any number of reference baskets from the current history + uint48 private historicalNonce; // {nonce} + + // The historical baskets by basket nonce; includes current basket + mapping(uint48 => Basket) private historicalBaskets; + // ==== Invariants ==== // basket is a valid Basket: // basket.erc20s is a valid collateral array and basket.erc20s == keys(basket.refAmts) @@ -430,6 +438,94 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } } + /// Return the redemption value of `amount` BUs for a linear combination of historical baskets + /// Checks `portions` sum to FIX_ONE + /// @param basketNonces An array of basket nonces to do redemption from + /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE + /// @param amount {BU} + /// @return erc20s The backing collateral erc20s + /// @return quantities {qTok} ERC20 token quantities equal to `amount` BUs + // Returns (erc20s, [quantity(e) * amount {as qTok} for e in erc20s]) + function quoteHistoricalRedemption( + uint48[] memory basketNonces, + uint192[] memory portions, + uint192 amount + ) external view returns (address[] memory erc20s, uint256[] memory quantities) { + // directly after upgrade the historicalNonce will be 0, which is not a valid value + require(historicalNonce > 0, "historicalNonce uninitialized"); + require(basketNonces.length == portions.length, "portions does not mirror basketNonces"); + + // Confirm portions sum to FIX_ONE + { + uint256 portionsSum; + for (uint256 i = 0; i < portions.length; ++i) portionsSum += portions[i]; + require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); + } + + IERC20[] memory erc20sAll = new IERC20[](main.assetRegistry().size()); + uint192[] memory refAmtsAll = new uint192[](erc20sAll.length); + + uint256 len; // length of return arrays + + // Calculate the linear combination basket + for (uint48 i = 0; i < basketNonces.length; ++i) { + require( + basketNonces[i] >= historicalNonce && basketNonces[i] <= nonce, + "invalid basketNonce" + ); // will always revert directly after setPrimeBasket() + Basket storage b = historicalBaskets[i]; + + // Add-in refAmts contribution from historical basket + for (uint256 j = 0; j < b.erc20s.length; ++j) { + IERC20 erc20 = b.erc20s[j]; + if (address(erc20) == address(0)) continue; + + // Ugly search through erc20sAll + uint256 erc20Index = type(uint256).max; + for (uint256 k = 0; k < len; ++k) { + if (erc20 == erc20sAll[k]) { + erc20Index = k; + continue; + } + } + + // Add new ERC20 entry if not found + if (erc20Index == type(uint256).max) { + erc20sAll[len] = erc20; + + // {ref} = {1} * {ref} + refAmtsAll[len] = portions[j].mul(b.refAmts[erc20], FLOOR); + ++len; + } else { + // {ref} = {1} * {ref} + refAmtsAll[erc20Index] += portions[j].mul(b.refAmts[erc20], FLOOR); + } + } + } + + erc20s = new address[](len); + quantities = new uint256[](len); + + // Calculate quantities + for (uint256 i = 0; i < len; ++i) { + erc20s[i] = address(erc20sAll[i]); + IAsset asset = main.assetRegistry().toAsset(IERC20(erc20s[i])); + if (!asset.isCollateral()) continue; // skip token if no longer registered + + // prevent div-by-zero + uint192 refPerTok = ICollateral(address(asset)).refPerTok(); + if (refPerTok == 0) continue; // quantities[i] = 0; + + // {tok} = {BU} * {ref/BU} / {ref/tok} + quantities[i] = amount.mulDiv(refAmtsAll[i], refPerTok, FLOOR).shiftl_toUint( + int8(asset.erc20Decimals()), + FLOOR + ); + // marginally more penalizing than its sibling calculation that uses _quantity() + // because does not intermediately CEIL as part of the division + } + } + /// @return baskets {BU} /// .top The number of partial basket units: e.g max(coll.map((c) => c.balAsBUs()) /// .bottom The number of whole basket units held by the account diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 7f281c6832..d71338fb35 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -139,12 +139,43 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// Redeem RToken for basket collateral to a particular recipient /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem - /// @param basketNonce The nonce of the basket the redemption should be from; else reverts + /// @param basketNonce The nonce of the basket the redemption should be from /// @custom:interaction function redeemTo( address recipient, uint256 amount, uint48 basketNonce + ) public { + uint48[] memory basketNonces = new uint48[](1); + basketNonces[0] = basketNonce; + uint192[] memory portions = new uint192[](1); + portions[0] = FIX_ONE; + + customRedemption( + recipient, + amount, + basketNonces, + portions, + new IERC20[](0), + new uint256[](0) + ); + } + + /// Redeem RToken for basket collateral to a particular recipient + /// @param recipient The address to receive the backing collateral tokens + /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem + /// @param basketNonces An array of basket nonces to do redemption from + /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE + /// @param minERC20s An array of ERC20 addresses to require minAmounts for + /// @param minAmounts {qTok} The minimum ERC20 quantities the caller should receive + /// @custom:interaction + function customRedemption( + address recipient, + uint256 amount, + uint48[] memory basketNonces, + uint192[] memory portions, + IERC20[] memory minERC20s, + uint256[] memory minAmounts ) public notFrozen exchangeRateIsValidAfter { require(amount > 0, "Cannot redeem zero"); require(amount <= balanceOf(_msgSender()), "insufficient balance"); @@ -166,38 +197,72 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { assert(basketsRedeemed.lte(basketsNeeded)); emit Redemption(_msgSender(), recipient, amount, basketsRedeemed); - require(main.basketHandler().nonce() == basketNonce, "non-current basket nonce"); - (address[] memory erc20s, uint256[] memory amounts) = main.basketHandler().quote( - basketsRedeemed, - FLOOR - ); + address[] memory erc20s; + uint256[] memory amounts; + if (basketNonces.length == 1 && main.basketHandler().nonce() == basketNonces[0]) { + // Current-basket redemption + + require(portions.length == 1, "portions does not mirror basketNonces"); + (erc20s, amounts) = main.basketHandler().quote(basketsRedeemed, FLOOR); + } else { + // Historical basket redemption + + // BasketHandler will handle the require that portions sum to FIX_ZERO + (erc20s, amounts) = main.basketHandler().quoteHistoricalRedemption( + basketNonces, + portions, + basketsRedeemed + ); + } emit BasketsNeededChanged(basketsNeeded, basketsNeeded.minus(basketsRedeemed)); basketsNeeded = basketsNeeded.minus(basketsRedeemed); - // ==== Send back collateral tokens ==== - IBackingManager backingMgr = main.backingManager(); - - bool allZero = true; - // Bound each withdrawal by the prorata share, in case we're currently under-collateralized - for (uint256 i = 0; i < erc20s.length; i++) { - uint256 bal = IERC20Upgradeable(erc20s[i]).balanceOf(address(backingMgr)); // {qTok} + // === Save initial recipient balances === - // {qTok} = {qTok} * {qRTok} / {qRTok} - uint256 prorata = mulDiv256(bal, amount, totalSupply()); // FLOOR - if (prorata < amounts[i]) amounts[i] = prorata; + uint256[] memory initialMinERC20Balances = new uint256[](minERC20s.length); + for (uint256 i = 0; i < minERC20s.length; ++i) { + initialMinERC20Balances[i] = minERC20s[i].balanceOf(recipient); + } - // Send withdrawal - if (amounts[i] > 0) { - IERC20(erc20s[i]).safeTransferFrom(address(backingMgr), recipient, amounts[i]); - allZero = false; + // ==== Prorate redemption + send out balances ==== + { + bool allZero = true; + // Bound each withdrawal by the prorata share, in case we're currently under-collateralized + for (uint256 i = 0; i < erc20s.length; i++) { + uint256 bal = IERC20Upgradeable(erc20s[i]).balanceOf( + address(main.backingManager()) + ); // {qTok} + + // {qTok} = {qTok} * {qRTok} / {qRTok} + uint256 prorata = mulDiv256(bal, amount, totalSupply()); // FLOOR + if (prorata < amounts[i]) amounts[i] = prorata; + + // Send withdrawal + if (amounts[i] > 0) { + IERC20(erc20s[i]).safeTransferFrom( + address(main.backingManager()), + recipient, + amounts[i] + ); + allZero = false; + } } + if (allZero) revert("empty redemption"); } // Accept and burn RToken, reverts if not enough balance _burn(_msgSender(), amount); - if (allZero) revert("empty redemption"); + // === Post-checks === + + // Check post-balances + for (uint256 i = 0; i < minERC20s.length; ++i) { + require( + minERC20s[i].balanceOf(recipient) - initialMinERC20Balances[i] >= minAmounts[i], + "redemption below minimum" + ); + } } // === diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index b700286077..effee1ef98 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -165,21 +165,6 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // A new historical record begins whenever the prime basket is changed // There can be 0 to any number of reference baskets from the current history uint48 private historicalNonce; // {nonce} - // TODO double-check historicalNonce fits in the Warmup Period slot - - // The historical baskets by basket nonce; includes current basket - mapping(uint48 => Basket) private historicalBaskets; - - // === - - // === Historical basket nonces === - // Added in 3.0.0 - - // Nonce of the first reference basket from the current history - // A new historical record begins whenever the prime basket is changed - // There can be 0 to any number of reference baskets from the current history - uint48 private historicalNonce; // {nonce} - // TODO double-check historicalNonce fits in the Warmup Period slot // The historical baskets by basket nonce; includes current basket mapping(uint48 => Basket) private historicalBaskets; @@ -375,7 +360,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // Returns FIX_MAX (in lieu of +infinity) if Collateral.refPerTok() is 0. // Otherwise returns (token's basket.refAmts / token's Collateral.refPerTok()) function quantity(IERC20 erc20) public view returns (uint192) { - try main.assetRegistry().toColl(erc20) returns (ICollateral coll) { + try assetRegistry.toColl(erc20) returns (ICollateral coll) { return _quantity(erc20, coll); } catch { return FIX_ZERO; @@ -459,6 +444,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } /// Return the current issuance/redemption value of `amount` BUs + /// @dev Subset of logic with quoteHistoricalRedemption; more gas efficient for 1 basketNonce /// @param amount {BU} /// @return erc20s The backing collateral erc20s /// @return quantities {qTok} ERC20 token quantities equal to `amount` BUs @@ -474,17 +460,20 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { for (uint256 i = 0; i < length; ++i) { erc20s[i] = address(basket.erc20s[i]); + ICollateral coll = assetRegistry.toColl(IERC20(erc20s[i])); // {qTok} = {tok/BU} * {BU} * {tok} * {qTok/tok} - quantities[i] = quantity(basket.erc20s[i]).safeMul(amount, rounding).shiftl_toUint( - int8(IERC20Metadata(address(basket.erc20s[i])).decimals()), - rounding - ); + quantities[i] = _quantity(basket.erc20s[i], coll) + .safeMul(amount, rounding) + .shiftl_toUint( + int8(IERC20Metadata(address(basket.erc20s[i])).decimals()), + rounding + ); } } /// Return the redemption value of `amount` BUs for a linear combination of historical baskets - /// Requires `portions` sums to FIX_ONE + /// Checks `portions` sum to FIX_ONE /// @param basketNonces An array of basket nonces to do redemption from /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE /// @param amount {BU} @@ -496,10 +485,19 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { uint192[] memory portions, uint192 amount ) external view returns (address[] memory erc20s, uint256[] memory quantities) { + // directly after upgrade the historicalNonce will be 0, which is not a valid value + require(historicalNonce > 0, "historicalNonce uninitialized"); require(basketNonces.length == portions.length, "portions does not mirror basketNonces"); - uint256 lenAll = assetRegistry.size(); - IERC20[] memory erc20sAll = new IERC20[](lenAll); - uint192[] memory refAmtsAll = new uint192[](lenAll); + + // Confirm portions sum to FIX_ONE + { + uint256 portionsSum; + for (uint256 i = 0; i < portions.length; ++i) portionsSum += portions[i]; + require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); + } + + IERC20[] memory erc20sAll = new IERC20[](assetRegistry.size()); + uint192[] memory refAmtsAll = new uint192[](erc20sAll.length); uint256 len; // length of return arrays @@ -544,9 +542,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // Calculate quantities for (uint256 i = 0; i < len; ++i) { - IERC20Metadata erc20 = IERC20Metadata(address(erc20sAll[i])); - erc20s[i] = address(erc20); - IAsset asset = assetRegistry.toAsset(erc20); + erc20s[i] = address(erc20sAll[i]); + IAsset asset = assetRegistry.toAsset(IERC20(erc20s[i])); if (!asset.isCollateral()) continue; // skip token if no longer registered // prevent div-by-zero @@ -555,10 +552,10 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // {tok} = {BU} * {ref/BU} / {ref/tok} quantities[i] = amount.mulDiv(refAmtsAll[i], refPerTok, FLOOR).shiftl_toUint( - int8(erc20.decimals()), + int8(asset.erc20Decimals()), FLOOR ); - // slightly more penalizing than its sibling calculation that uses through _quantity() + // marginally more penalizing than its sibling calculation that uses _quantity() // because does not intermediately CEIL as part of the division } } diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 44cf4fcd18..cc0870daa9 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -178,7 +178,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// Redeem RToken for basket collateral /// @param amount {qTok} The quantity {qRToken} of RToken to redeem - /// @param basketNonce The nonce of the basket the redemption should be from; else reverts + /// @param basketNonce The nonce of the basket the redemption should be from /// @custom:interaction CEI function redeem(uint256 amount, uint48 basketNonce) external { redeemTo(_msgSender(), amount, basketNonce); @@ -187,7 +187,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// Redeem RToken for basket collateral to a particular recipient /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem - /// @param basketNonce The nonce of the basket the redemption should be from; else reverts + /// @param basketNonce The nonce of the basket the redemption should be from /// @custom:interaction function redeemTo( address recipient, @@ -199,22 +199,14 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { uint192[] memory portions = new uint192[](1); portions[0] = FIX_ONE; - _redeemTo(recipient, amount, basketNonces, portions); - } - - /// Redeem RToken for a linear combination of historical baskets, to a particular recipient - /// @param recipient The address to receive the backing collateral tokens - /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem - /// @param basketNonces An array of basket nonces to do redemption from - /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE - /// @custom:interaction - function historicalRedeemTo( - address recipient, - uint256 amount, - uint48[] memory basketNonces, - uint192[] memory portions - ) external { - _redeemTo(recipient, amount, basketNonces, portions); + customRedemption( + recipient, + amount, + basketNonces, + portions, + new IERC20[](0), + new uint256[](0) + ); } /// Redeem RToken for basket collateral to a particular recipient @@ -222,7 +214,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // checks: // amount > 0 // amount <= balanceOf(caller) - // basket is not DISABLED + // sum(portions) == FIX_ONE + // nonce >= basketHandler.historicalNonce() for nonce in basketNonces // // effects: // (so totalSupply -= amount and balanceOf(caller) -= amount) @@ -238,29 +231,28 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // untestable: // `else` branch of `exchangeRateIsValidAfter` (ie. revert) // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. + /// @dev Allows partial redemptions up to the minAmounts /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @param basketNonces An array of basket nonces to do redemption from /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE + /// @param minERC20s An array of ERC20 addresses to require minAmounts for + /// @param minAmounts {qTok} The minimum ERC20 quantities the caller should receive /// @custom:interaction - function _redeemTo( + function customRedemption( address recipient, uint256 amount, uint48[] memory basketNonces, - uint192[] memory portions - ) internal notFrozen exchangeRateIsValidAfter { + uint192[] memory portions, + IERC20[] memory minERC20s, + uint256[] memory minAmounts + ) public notFrozen exchangeRateIsValidAfter { // == Refresh == assetRegistry.refresh(); // == Checks and Effects == - address redeemer = _msgSender(); require(amount > 0, "Cannot redeem zero"); - require(amount <= balanceOf(redeemer), "insufficient balance"); - - // Confirm portions sums to FIX_ONE - uint256 portionsSum; - for (uint256 i = 0; i < portions.length; ++i) portionsSum += portions[i]; - require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); + require(amount <= balanceOf(_msgSender()), "insufficient balance"); // Failure to melt results in a lower redemption price, so we can allow it when paused // solhint-disable-next-line no-empty-blocks @@ -276,7 +268,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // D18{BU} = D18{BU} * {qRTok} / {qRTok} uint192 basketsRedeemed = basketsNeeded.muluDivu(amount, supply); // FLOOR - emit Redemption(redeemer, recipient, amount, basketsRedeemed); + emit Redemption(_msgSender(), recipient, amount, basketsRedeemed); address[] memory erc20s; uint256[] memory amounts; @@ -301,8 +293,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // where balances[i] = erc20s[i].balanceOf(this) // Bound each withdrawal by the prorata share, in case we're currently under-collateralized - uint256 erc20length = erc20s.length; - for (uint256 i = 0; i < erc20length; ++i) { + for (uint256 i = 0; i < erc20s.length; ++i) { // {qTok} = {qTok} * {qRTok} / {qRTok} uint256 prorata = mulDiv256( IERC20Upgradeable(erc20s[i]).balanceOf(address(backingManager)), @@ -313,28 +304,46 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { if (prorata < amounts[i]) amounts[i] = prorata; } - uint192 newBasketsNeeded = basketsNeeded - basketsRedeemed; - emit BasketsNeededChanged(basketsNeeded, newBasketsNeeded); - basketsNeeded = newBasketsNeeded; + emit BasketsNeededChanged(basketsNeeded, basketsNeeded - basketsRedeemed); + basketsNeeded = basketsNeeded - basketsRedeemed; + + // === Save initial recipient balances === - // == Interactions == + uint256[] memory initialMinERC20Balances = new uint256[](minERC20s.length); + for (uint256 i = 0; i < minERC20s.length; ++i) { + initialMinERC20Balances[i] = minERC20s[i].balanceOf(recipient); + } + + // === Interactions === // Accept and burn RToken, reverts if not enough balance to burn - _burn(redeemer, amount); + _burn(_msgSender(), amount); + + // Distribute tokens; revert if empty redemption + { + bool allZero = true; + for (uint256 i = 0; i < erc20s.length; ++i) { + if (amounts[i] == 0) continue; + if (allZero) allZero = false; + + // Send withdrawal + IERC20Upgradeable(erc20s[i]).safeTransferFrom( + address(backingManager), + recipient, + amounts[i] + ); + } + if (allZero) revert("empty redemption"); + } - bool allZero = true; - for (uint256 i = 0; i < erc20length; ++i) { - if (amounts[i] == 0) continue; - if (allZero) allZero = false; + // === Post-checks === - // Send withdrawal - IERC20Upgradeable(erc20s[i]).safeTransferFrom( - address(backingManager), - recipient, - amounts[i] + // Check post-balances + for (uint256 i = 0; i < minERC20s.length; ++i) { + require( + minERC20s[i].balanceOf(recipient) - initialMinERC20Balances[i] >= minAmounts[i], + "redemption below minimum" ); } - - if (allZero) revert("empty redemption"); } /// Mint a quantity of RToken to the `recipient`, decreasing the basket rate diff --git a/hardhat.config.ts b/hardhat.config.ts index e72723e734..f463f81e1c 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -22,9 +22,7 @@ const MNEMONIC = useEnv('MNEMONIC') ?? 'test test test test test test test test const TIMEOUT = useEnv('SLOW') ? 6_000_000 : 600_000 const src_dir = `./contracts/${useEnv('PROTO')}` -const settings = useEnv('NO_OPT') - ? { outputSelection: { '*': { '*': ['storage'] } } } - : { outputSelection: { '*': { '*': ['storage'] } }, optimizer: { enabled: true, runs: 200 } } +const settings = useEnv('NO_OPT') ? {} : { optimizer: { enabled: true, runs: 200 } } const config: HardhatUserConfig = { defaultNetwork: 'hardhat', From a3ad8fafebc61074f4fe017724ff4e68d946d611 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 27 Apr 2023 15:15:36 -0300 Subject: [PATCH 106/462] role changes in contracts --- contracts/facade/FacadeWrite.sol | 51 ++++++++++++++------------- contracts/interfaces/IFacadeWrite.sol | 16 +++++++-- contracts/mixins/Auth.sol | 13 ++----- contracts/p0/Deployer.sol | 6 ---- contracts/p1/Deployer.sol | 8 +---- 5 files changed, 44 insertions(+), 50 deletions(-) diff --git a/contracts/facade/FacadeWrite.sol b/contracts/facade/FacadeWrite.sol index 4ce6b2fb22..542fd04c08 100644 --- a/contracts/facade/FacadeWrite.sol +++ b/contracts/facade/FacadeWrite.sol @@ -111,8 +111,10 @@ contract FacadeWrite is IFacadeWrite { } // Pause until setupGovernance + main.grantRole(PAUSER, address(this)); main.pauseTrading(); main.pauseIssuance(); + main.revokeRole(PAUSER, address(this)); // Setup deployer as owner to complete next step - do not renounce roles yet main.grantRole(OWNER, msg.sender); @@ -128,9 +130,7 @@ contract FacadeWrite is IFacadeWrite { bool deployGovernance, bool unpause, GovernanceParams calldata govParams, - address owner, - address guardian, - address pauser + GovernanceRoles calldata govRoles ) external returns (address newOwner) { // Get Main IMain main = rToken.main(); @@ -142,7 +142,7 @@ contract FacadeWrite is IFacadeWrite { main.revokeRole(OWNER, msg.sender); if (deployGovernance) { - require(owner == address(0), "owner should be empty"); + require(govRoles.owner == address(0), "owner should be empty"); TimelockController timelock = new TimelockController( govParams.timelockDelay, @@ -163,45 +163,48 @@ contract FacadeWrite is IFacadeWrite { // Setup Roles timelock.grantRole(timelock.PROPOSER_ROLE(), governance); // Gov only proposer - timelock.grantRole(timelock.CANCELLER_ROLE(), guardian); // Guardian as canceller + timelock.grantRole(timelock.CANCELLER_ROLE(), govRoles.guardian); // Guardian as canceller timelock.grantRole(timelock.EXECUTOR_ROLE(), address(0)); // Anyone as executor timelock.revokeRole(timelock.TIMELOCK_ADMIN_ROLE(), address(this)); // Revoke admin role // Set new owner to timelock newOwner = address(timelock); } else { - require(owner != address(0), "owner not defined"); - newOwner = owner; + require(govRoles.owner != address(0), "owner not defined"); + newOwner = govRoles.owner; } - // Setup guardian as freeze starter / extender + pauser - if (guardian != address(0)) { - // As a further decentralization step it is suggested to further differentiate between - // these two roles. But this is what will make sense for simple system setup. - main.grantRole(SHORT_FREEZER, guardian); - main.grantRole(LONG_FREEZER, guardian); - main.grantRole(PAUSER, guardian); + // Setup pausers + for (uint256 i = 0; i < govRoles.pausers.length; ++i) { + if (govRoles.pausers[i] != address(0)) { + main.grantRole(PAUSER, govRoles.pausers[i]); + } } - // Setup Pauser - if (pauser != address(0)) { - main.grantRole(PAUSER, pauser); + // Setup short freezers + for (uint256 i = 0; i < govRoles.shortFreezers.length; ++i) { + if (govRoles.shortFreezers[i] != address(0)) { + main.grantRole(SHORT_FREEZER, govRoles.shortFreezers[i]); + } + } + + // Setup long freezers + for (uint256 i = 0; i < govRoles.longFreezers.length; ++i) { + if (govRoles.longFreezers[i] != address(0)) { + main.grantRole(LONG_FREEZER, govRoles.longFreezers[i]); + } } // Unpause if required if (unpause) { + main.grantRole(PAUSER, address(this)); main.unpauseTrading(); main.unpauseIssuance(); + main.revokeRole(PAUSER, address(this)); } - // Transfer Ownership and renounce roles + // Transfer Ownership and renounce owner role main.grantRole(OWNER, newOwner); - main.grantRole(SHORT_FREEZER, newOwner); - main.grantRole(LONG_FREEZER, newOwner); - main.grantRole(PAUSER, newOwner); main.renounceRole(OWNER, address(this)); - main.renounceRole(SHORT_FREEZER, address(this)); - main.renounceRole(LONG_FREEZER, address(this)); - main.renounceRole(PAUSER, address(this)); } } diff --git a/contracts/interfaces/IFacadeWrite.sol b/contracts/interfaces/IFacadeWrite.sol index f00903c9b8..04d4bd7bec 100644 --- a/contracts/interfaces/IFacadeWrite.sol +++ b/contracts/interfaces/IFacadeWrite.sol @@ -63,6 +63,18 @@ struct GovernanceParams { uint256 timelockDelay; // in seconds (used for timelock) } +/** + * @title GovernanceRoles + * @notice The set of roles required (owner, guardian, pausers, and freezers) + */ +struct GovernanceRoles { + address owner; + address guardian; + address[] pausers; + address[] shortFreezers; + address[] longFreezers; +} + /** * @title IFacadeWrite * @notice A UX-friendly layer for interactin with the protocol @@ -89,8 +101,6 @@ interface IFacadeWrite { bool deployGovernance, bool unfreeze, GovernanceParams calldata govParams, - address owner, - address guardian, - address pauser + GovernanceRoles calldata govRoles ) external returns (address); } diff --git a/contracts/mixins/Auth.sol b/contracts/mixins/Auth.sol index aeaca0544e..fa4735c285 100644 --- a/contracts/mixins/Auth.sol +++ b/contracts/mixins/Auth.sol @@ -21,8 +21,7 @@ abstract contract Auth is AccessControlUpgradeable, IAuth { * and rewards payout * - Issuance Paused: disallow issuance * - * Typically freezing thaws on its own in a predetemined number of blocks. - * However, OWNER can also freeze forever. + * Typically freezing thaws on its own in a predetermined number of blocks. */ /// The rest of the contract uses the shorthand; these declarations are here for getters @@ -55,9 +54,8 @@ abstract contract Auth is AccessControlUpgradeable, IAuth { // - 0 < shortFreeze_ <= MAX_SHORT_FREEZE // - 0 < longFreeze_ <= MAX_LONG_FREEZE // effects: - // - caller has all roles + // - caller has only the OWNER role // - OWNER is the admin role for all roles - // - longFreezes[caller] == LONG_FREEZE_CHARGES // - shortFreeze' == shortFreeze_ // - longFreeze' == longFreeze_ // questions: (what do I know about the values of paused and unfreezeAt?) @@ -75,12 +73,7 @@ abstract contract Auth is AccessControlUpgradeable, IAuth { _setRoleAdmin(LONG_FREEZER, OWNER); _setRoleAdmin(PAUSER, OWNER); - address msgSender = _msgSender(); - _grantRole(OWNER, msgSender); - _grantRole(SHORT_FREEZER, msgSender); - _grantRole(LONG_FREEZER, msgSender); - _grantRole(PAUSER, msgSender); - longFreezes[msgSender] = LONG_FREEZE_CHARGES; + _grantRole(OWNER, _msgSender()); setShortFreeze(shortFreeze_); setLongFreeze(longFreeze_); diff --git a/contracts/p0/Deployer.sol b/contracts/p0/Deployer.sol index e1de9653e2..ae4add1e54 100644 --- a/contracts/p0/Deployer.sol +++ b/contracts/p0/Deployer.sol @@ -145,13 +145,7 @@ contract DeployerP0 is IDeployer, Versioned { // Transfer Ownership main.grantRole(OWNER, owner); - main.grantRole(SHORT_FREEZER, owner); - main.grantRole(LONG_FREEZER, owner); - main.grantRole(PAUSER, owner); main.renounceRole(OWNER, address(this)); - main.renounceRole(SHORT_FREEZER, address(this)); - main.renounceRole(LONG_FREEZER, address(this)); - main.renounceRole(PAUSER, address(this)); emit RTokenCreated(main, components.rToken, components.stRSR, owner, version()); return (address(components.rToken)); diff --git a/contracts/p1/Deployer.sol b/contracts/p1/Deployer.sol index 7f16093bfc..29cf01ee8b 100644 --- a/contracts/p1/Deployer.sol +++ b/contracts/p1/Deployer.sol @@ -98,7 +98,7 @@ contract DeployerP1 is IDeployer, Versioned { // Deploy a proxy for Main and every component of Main // Call init() on Main and every component of Main, using `params` for needed parameters // While doing this, init assetRegistry with this.rsrAsset and a new rTokenAsset - // Set up Auth so that `owner` holds all roles and no one else has any + // Set up Auth so that `owner` holds the OWNER role and no one else has any function deploy( string memory name, string memory symbol, @@ -234,13 +234,7 @@ contract DeployerP1 is IDeployer, Versioned { // Transfer Ownership main.grantRole(OWNER, owner); - main.grantRole(SHORT_FREEZER, owner); - main.grantRole(LONG_FREEZER, owner); - main.grantRole(PAUSER, owner); main.renounceRole(OWNER, address(this)); - main.renounceRole(SHORT_FREEZER, address(this)); - main.renounceRole(LONG_FREEZER, address(this)); - main.renounceRole(PAUSER, address(this)); emit RTokenCreated(main, components.rToken, components.stRSR, owner, version()); return (address(components.rToken)); From e2b23306690114e686af80349be5d18ca1ba30a8 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 27 Apr 2023 15:16:15 -0300 Subject: [PATCH 107/462] update tests and scripts --- common/configuration.ts | 8 + .../phase3-rtoken/2_deploy_governance.ts | 14 +- test/FacadeWrite.test.ts | 230 +++++++++--------- test/fixtures.ts | 7 +- .../compoundv2/CTokenFiatCollateral.test.ts | 15 +- 5 files changed, 147 insertions(+), 127 deletions(-) diff --git a/common/configuration.ts b/common/configuration.ts index 411fb30432..9b8096a4a3 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -387,6 +387,14 @@ export interface IGovParams { timelockDelay: BigNumber } +export interface IGovRoles { + owner: string + guardian: string + pausers: string[] + shortFreezers: string[] + longFreezers: string[] +} + // System constants export const MAX_TRADE_SLIPPAGE = BigNumber.from(10).pow(18) export const MAX_BACKING_BUFFER = BigNumber.from(10).pow(18) diff --git a/scripts/deployment/phase3-rtoken/2_deploy_governance.ts b/scripts/deployment/phase3-rtoken/2_deploy_governance.ts index ba616cab58..578bf793dd 100644 --- a/scripts/deployment/phase3-rtoken/2_deploy_governance.ts +++ b/scripts/deployment/phase3-rtoken/2_deploy_governance.ts @@ -2,7 +2,7 @@ import fs from 'fs' import hre, { ethers } from 'hardhat' import { getChainId, isValidContract } from '../../../common/blockchain-utils' -import { IGovParams, networkConfig } from '../../../common/configuration' +import { IGovParams, IGovRoles, networkConfig } from '../../../common/configuration' import { ZERO_ADDRESS } from '../../../common/constants' import { expectInReceipt } from '../../../common/events' import { getRTokenConfig, RTOKEN_NAME } from './rTokenConfig' @@ -74,15 +74,21 @@ async function main() { } // Setup Governance in RToken + const govRoles: IGovRoles = { + owner: ZERO_ADDRESS, + guardian: ZERO_ADDRESS, + pausers: [], + shortFreezers: [], + longFreezers: [], + } + const receipt = await ( await facadeWrite.connect(deployerUser).setupGovernance( rToken.address, true, // deploy governance chainId != '1', // unpause if not mainnet govParams, - ZERO_ADDRESS, - ZERO_ADDRESS, - ZERO_ADDRESS // no other pauser + govRoles ) ).wait() diff --git a/test/FacadeWrite.test.ts b/test/FacadeWrite.test.ts index 5147331ec8..e5f0e5fb05 100644 --- a/test/FacadeWrite.test.ts +++ b/test/FacadeWrite.test.ts @@ -7,6 +7,7 @@ import { cloneDeep } from 'lodash' import { IConfig, IGovParams, + IGovRoles, IRevenueShare, IRTokenConfig, IRTokenSetup, @@ -67,6 +68,7 @@ describe('FacadeWrite contract', () => { let owner: SignerWithAddress let addr1: SignerWithAddress let addr2: SignerWithAddress + let addr3: SignerWithAddress let beneficiary1: SignerWithAddress let beneficiary2: SignerWithAddress @@ -122,12 +124,14 @@ describe('FacadeWrite contract', () => { let rTokenConfig: IRTokenConfig let rTokenSetup: IRTokenSetup let govParams: IGovParams + let govRoles: IGovRoles let revShare1: IRevenueShare let revShare2: IRevenueShare beforeEach(async () => { - ;[deployerUser, owner, addr1, addr2, beneficiary1, beneficiary2] = await ethers.getSigners() + ;[deployerUser, owner, addr1, addr2, addr3, beneficiary1, beneficiary2] = + await ethers.getSigners() // Deploy fixture ;({ rsr, compToken, compAsset, basket, config, facade, facadeTest, deployer } = @@ -195,6 +199,15 @@ describe('FacadeWrite contract', () => { quorumPercent: bn(4), // 4% timelockDelay: bn(60 * 60 * 24), // 1 day } + + // Set initial governance roles + govRoles = { + owner: owner.address, + guardian: ZERO_ADDRESS, + pausers: [], + shortFreezers: [], + longFreezers: [], + } }) it('Should validate parameters', async () => { @@ -367,9 +380,9 @@ describe('FacadeWrite contract', () => { expect(await main.hasRole(PAUSER, deployerUser.address)).to.equal(false) expect(await main.hasRole(OWNER, facadeWrite.address)).to.equal(true) - expect(await main.hasRole(SHORT_FREEZER, facadeWrite.address)).to.equal(true) - expect(await main.hasRole(LONG_FREEZER, facadeWrite.address)).to.equal(true) - expect(await main.hasRole(PAUSER, facadeWrite.address)).to.equal(true) + expect(await main.hasRole(SHORT_FREEZER, facadeWrite.address)).to.equal(false) + expect(await main.hasRole(LONG_FREEZER, facadeWrite.address)).to.equal(false) + expect(await main.hasRole(PAUSER, facadeWrite.address)).to.equal(false) expect(await main.frozen()).to.equal(false) expect(await main.tradingPaused()).to.equal(true) expect(await main.tradingPausedOrFrozen()).to.equal(true) @@ -458,15 +471,7 @@ describe('FacadeWrite contract', () => { await expect( facadeWrite .connect(addr1) - .setupGovernance( - rToken.address, - false, - false, - govParams, - owner.address, - ZERO_ADDRESS, - ZERO_ADDRESS - ) + .setupGovernance(rToken.address, false, false, govParams, govRoles) ).to.be.revertedWith('not initial deployer') }) @@ -474,29 +479,16 @@ describe('FacadeWrite contract', () => { await expect( facadeWrite .connect(deployerUser) - .setupGovernance( - rToken.address, - true, - false, - govParams, - owner.address, - ZERO_ADDRESS, - ZERO_ADDRESS - ) + .setupGovernance(rToken.address, true, false, govParams, govRoles) ).to.be.revertedWith('owner should be empty') - await expect( + // Remove owner + const noOwnerGovRoles = { ...govRoles } + noOwnerGovRoles.owner = ZERO_ADDRESS + govRoles.owner = await expect( facadeWrite .connect(deployerUser) - .setupGovernance( - rToken.address, - false, - false, - govParams, - ZERO_ADDRESS, - ZERO_ADDRESS, - ZERO_ADDRESS - ) + .setupGovernance(rToken.address, false, false, govParams, noOwnerGovRoles) ).to.be.revertedWith('owner not defined') }) }) @@ -504,23 +496,18 @@ describe('FacadeWrite contract', () => { describe('Phase 2 - Complete Setup', () => { context('Without deploying Governance - Paused', function () { beforeEach(async () => { + // Setup pauser + const newGovRoles = { ...govRoles } + newGovRoles.pausers.push(addr1.address) + await facadeWrite .connect(deployerUser) - .setupGovernance( - rToken.address, - false, - false, - govParams, - owner.address, - ZERO_ADDRESS, - ZERO_ADDRESS - ) + .setupGovernance(rToken.address, false, false, govParams, newGovRoles) }) it('Should register Basket correctly', async () => { - // Unpause - await main.connect(owner).unpauseTrading() - await main.connect(owner).unpauseIssuance() + await main.connect(addr1).unpauseTrading() + await main.connect(addr1).unpauseIssuance() // Basket expect(await basketHandler.fullyCollateralized()).to.equal(true) @@ -583,9 +570,15 @@ describe('FacadeWrite contract', () => { it('Should setup roles correctly', async () => { expect(await main.hasRole(OWNER, owner.address)).to.equal(true) - expect(await main.hasRole(SHORT_FREEZER, owner.address)).to.equal(true) - expect(await main.hasRole(LONG_FREEZER, owner.address)).to.equal(true) - expect(await main.hasRole(PAUSER, owner.address)).to.equal(true) + expect(await main.hasRole(SHORT_FREEZER, owner.address)).to.equal(false) + expect(await main.hasRole(LONG_FREEZER, owner.address)).to.equal(false) + expect(await main.hasRole(PAUSER, owner.address)).to.equal(false) + + // Pauser + expect(await main.hasRole(OWNER, addr1.address)).to.equal(false) + expect(await main.hasRole(SHORT_FREEZER, addr1.address)).to.equal(false) + expect(await main.hasRole(LONG_FREEZER, addr1.address)).to.equal(false) + expect(await main.hasRole(PAUSER, addr1.address)).to.equal(true) expect(await main.hasRole(OWNER, facadeWrite.address)).to.equal(false) expect(await main.hasRole(SHORT_FREEZER, facadeWrite.address)).to.equal(false) @@ -608,51 +601,48 @@ describe('FacadeWrite contract', () => { await expect( facadeWrite .connect(deployerUser) - .setupGovernance( - rToken.address, - false, - false, - govParams, - owner.address, - ZERO_ADDRESS, - ZERO_ADDRESS - ) + .setupGovernance(rToken.address, false, false, govParams, govRoles) ).to.be.revertedWith('ownership already transferred') }) }) context('Without deploying Governance - Unpaused', function () { beforeEach(async () => { + // Setup guardian, pauser, and freezers + const newGovRoles = { ...govRoles } + newGovRoles.guardian = addr1.address + newGovRoles.pausers.push(addr2.address) + newGovRoles.shortFreezers.push(addr2.address) + newGovRoles.longFreezers.push(owner.address) // make owner freezer + newGovRoles.longFreezers.push(addr3.address) // add another long freezer + // Deploy RToken via FacadeWrite await facadeWrite .connect(deployerUser) - .setupGovernance( - rToken.address, - false, - true, - govParams, - owner.address, - addr1.address, - addr2.address - ) + .setupGovernance(rToken.address, false, true, govParams, newGovRoles) }) it('Should setup owner, freezer and pauser correctly', async () => { expect(await main.hasRole(OWNER, owner.address)).to.equal(true) - expect(await main.hasRole(SHORT_FREEZER, owner.address)).to.equal(true) + expect(await main.hasRole(SHORT_FREEZER, owner.address)).to.equal(false) expect(await main.hasRole(LONG_FREEZER, owner.address)).to.equal(true) - expect(await main.hasRole(PAUSER, owner.address)).to.equal(true) + expect(await main.hasRole(PAUSER, owner.address)).to.equal(false) expect(await main.hasRole(OWNER, addr1.address)).to.equal(false) - expect(await main.hasRole(SHORT_FREEZER, addr1.address)).to.equal(true) - expect(await main.hasRole(LONG_FREEZER, addr1.address)).to.equal(true) - expect(await main.hasRole(PAUSER, addr1.address)).to.equal(true) + expect(await main.hasRole(SHORT_FREEZER, addr1.address)).to.equal(false) + expect(await main.hasRole(LONG_FREEZER, addr1.address)).to.equal(false) + expect(await main.hasRole(PAUSER, addr1.address)).to.equal(false) expect(await main.hasRole(OWNER, addr2.address)).to.equal(false) - expect(await main.hasRole(SHORT_FREEZER, addr2.address)).to.equal(false) + expect(await main.hasRole(SHORT_FREEZER, addr2.address)).to.equal(true) expect(await main.hasRole(LONG_FREEZER, addr2.address)).to.equal(false) expect(await main.hasRole(PAUSER, addr2.address)).to.equal(true) + expect(await main.hasRole(OWNER, addr3.address)).to.equal(false) + expect(await main.hasRole(SHORT_FREEZER, addr3.address)).to.equal(false) + expect(await main.hasRole(LONG_FREEZER, addr3.address)).to.equal(true) + expect(await main.hasRole(PAUSER, addr3.address)).to.equal(false) + expect(await main.hasRole(OWNER, facadeWrite.address)).to.equal(false) expect(await main.hasRole(SHORT_FREEZER, facadeWrite.address)).to.equal(false) expect(await main.hasRole(LONG_FREEZER, facadeWrite.address)).to.equal(false) @@ -673,19 +663,20 @@ describe('FacadeWrite contract', () => { context('Deploying Governance - Paused', function () { beforeEach(async () => { + // Setup guardian + const newGovRoles = { ...govRoles } + newGovRoles.owner = ZERO_ADDRESS + newGovRoles.guardian = addr1.address + newGovRoles.pausers.push(addr1.address) + newGovRoles.pausers.push(addr2.address) + newGovRoles.shortFreezers.push(addr2.address) + newGovRoles.shortFreezers.push(addr3.address) + // Deploy RToken via FacadeWrite const receipt = await ( await facadeWrite .connect(deployerUser) - .setupGovernance( - rToken.address, - true, - false, - govParams, - ZERO_ADDRESS, - addr1.address, - ZERO_ADDRESS - ) + .setupGovernance(rToken.address, true, false, govParams, newGovRoles) ).wait() // Get Governor and Timelock @@ -699,19 +690,24 @@ describe('FacadeWrite contract', () => { it('Should setup owner, freezer and pauser correctly', async () => { expect(await main.hasRole(OWNER, timelock.address)).to.equal(true) - expect(await main.hasRole(SHORT_FREEZER, timelock.address)).to.equal(true) - expect(await main.hasRole(LONG_FREEZER, timelock.address)).to.equal(true) - expect(await main.hasRole(PAUSER, timelock.address)).to.equal(true) + expect(await main.hasRole(SHORT_FREEZER, timelock.address)).to.equal(false) + expect(await main.hasRole(LONG_FREEZER, timelock.address)).to.equal(false) + expect(await main.hasRole(PAUSER, timelock.address)).to.equal(false) expect(await main.hasRole(OWNER, addr1.address)).to.equal(false) - expect(await main.hasRole(SHORT_FREEZER, addr1.address)).to.equal(true) - expect(await main.hasRole(LONG_FREEZER, addr1.address)).to.equal(true) + expect(await main.hasRole(SHORT_FREEZER, addr1.address)).to.equal(false) + expect(await main.hasRole(LONG_FREEZER, addr1.address)).to.equal(false) expect(await main.hasRole(PAUSER, addr1.address)).to.equal(true) expect(await main.hasRole(OWNER, addr2.address)).to.equal(false) - expect(await main.hasRole(SHORT_FREEZER, addr2.address)).to.equal(false) + expect(await main.hasRole(SHORT_FREEZER, addr2.address)).to.equal(true) expect(await main.hasRole(LONG_FREEZER, addr2.address)).to.equal(false) - expect(await main.hasRole(PAUSER, addr2.address)).to.equal(false) + expect(await main.hasRole(PAUSER, addr2.address)).to.equal(true) + + expect(await main.hasRole(OWNER, addr3.address)).to.equal(false) + expect(await main.hasRole(SHORT_FREEZER, addr3.address)).to.equal(true) + expect(await main.hasRole(LONG_FREEZER, addr3.address)).to.equal(false) + expect(await main.hasRole(PAUSER, addr3.address)).to.equal(false) expect(await main.hasRole(OWNER, facadeWrite.address)).to.equal(false) expect(await main.hasRole(SHORT_FREEZER, facadeWrite.address)).to.equal(false) @@ -751,18 +747,21 @@ describe('FacadeWrite contract', () => { context('Deploying Governance - Unpaused', function () { beforeEach(async () => { + // Remove owner + const newGovRoles = { ...govRoles } + newGovRoles.owner = ZERO_ADDRESS + newGovRoles.pausers.push(addr1.address) + newGovRoles.shortFreezers.push(addr1.address) + + // Should handle Zero addresses + newGovRoles.pausers.push(ZERO_ADDRESS) + newGovRoles.shortFreezers.push(ZERO_ADDRESS) + newGovRoles.longFreezers.push(ZERO_ADDRESS) + const receipt = await ( await facadeWrite .connect(deployerUser) - .setupGovernance( - rToken.address, - true, - true, - govParams, - ZERO_ADDRESS, - ZERO_ADDRESS, - ZERO_ADDRESS - ) + .setupGovernance(rToken.address, true, true, govParams, newGovRoles) ).wait() // Get Governor and Timelock @@ -776,14 +775,14 @@ describe('FacadeWrite contract', () => { it('Should setup owner, freezer and pauser correctly', async () => { expect(await main.hasRole(OWNER, timelock.address)).to.equal(true) - expect(await main.hasRole(SHORT_FREEZER, timelock.address)).to.equal(true) - expect(await main.hasRole(LONG_FREEZER, timelock.address)).to.equal(true) - expect(await main.hasRole(PAUSER, timelock.address)).to.equal(true) + expect(await main.hasRole(SHORT_FREEZER, timelock.address)).to.equal(false) + expect(await main.hasRole(LONG_FREEZER, timelock.address)).to.equal(false) + expect(await main.hasRole(PAUSER, timelock.address)).to.equal(false) expect(await main.hasRole(OWNER, addr1.address)).to.equal(false) - expect(await main.hasRole(SHORT_FREEZER, addr1.address)).to.equal(false) + expect(await main.hasRole(SHORT_FREEZER, addr1.address)).to.equal(true) expect(await main.hasRole(LONG_FREEZER, addr1.address)).to.equal(false) - expect(await main.hasRole(PAUSER, addr1.address)).to.equal(false) + expect(await main.hasRole(PAUSER, addr1.address)).to.equal(true) expect(await main.hasRole(OWNER, addr2.address)).to.equal(false) expect(await main.hasRole(SHORT_FREEZER, addr2.address)).to.equal(false) @@ -817,36 +816,29 @@ describe('FacadeWrite contract', () => { }) it('Phase 2 - Without governance', async () => { + const newGovRoles = { ...govRoles } + newGovRoles.guardian = addr1.address + newGovRoles.pausers.push(addr2.address) + // Deploy RToken via FacadeWrite await snapshotGasCost( await facadeWrite .connect(deployerUser) - .setupGovernance( - rToken.address, - false, - false, - govParams, - owner.address, - addr1.address, - addr2.address - ) + .setupGovernance(rToken.address, false, false, govParams, newGovRoles) ) }) it('Phase 2 - Deploy governance', async () => { + const newGovRoles = { ...govRoles } + newGovRoles.owner = ZERO_ADDRESS + newGovRoles.guardian = addr1.address + newGovRoles.pausers.push(addr2.address) + // Deploy RToken via FacadeWrite await snapshotGasCost( await facadeWrite .connect(deployerUser) - .setupGovernance( - rToken.address, - true, - true, - govParams, - ZERO_ADDRESS, - addr1.address, - addr2.address - ) + .setupGovernance(rToken.address, true, true, govParams, newGovRoles) ) }) }) diff --git a/test/fixtures.ts b/test/fixtures.ts index 7b578a1398..bdfbc2d132 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -5,7 +5,7 @@ import { getChainId } from '../common/blockchain-utils' import { IConfig, IImplementations, IRevenueShare, networkConfig } from '../common/configuration' import { expectInReceipt } from '../common/events' import { bn, fp } from '../common/numbers' -import { CollateralStatus } from '../common/constants' +import { CollateralStatus, PAUSER, LONG_FREEZER, SHORT_FREEZER } from '../common/constants' import { Asset, AssetRegistryP1, @@ -662,6 +662,11 @@ export const defaultFixture: Fixture = async function (): Promis // Charge throttle await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 3600) + // Set Owner as Pauser/Freezer for tests + await main.connect(owner).grantRole(PAUSER, owner.address) + await main.connect(owner).grantRole(SHORT_FREEZER, owner.address) + await main.connect(owner).grantRole(LONG_FREEZER, owner.address) + return { rsr, rsrAsset, diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 1c524e5569..d7f66cccfa 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -10,6 +10,7 @@ import forkBlockNumber from '../../../integration/fork-block-numbers' import { IConfig, IGovParams, + IGovRoles, IRevenueShare, IRTokenConfig, IRTokenSetup, @@ -97,6 +98,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi let facadeTest: FacadeTest let facadeWrite: FacadeWrite let govParams: IGovParams + let govRoles: IGovRoles // RToken Configuration const dist: IRevenueShare = { @@ -251,15 +253,22 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await ethers.getContractAt('RTokenAsset', await assetRegistry.toAsset(rToken.address)) ) + // Set initial governance roles + govRoles = { + owner: owner.address, + guardian: ZERO_ADDRESS, + pausers: [], + shortFreezers: [], + longFreezers: [], + } + // Setup owner and unpause await facadeWrite.connect(owner).setupGovernance( rToken.address, false, // do not deploy governance true, // unpaused govParams, // mock values, not relevant - owner.address, // owner - ZERO_ADDRESS, // no guardian - ZERO_ADDRESS // no pauser + govRoles ) // Setup mock chainlink feed for some of the tests (so we can change the value) From 0387853d09e221dddd671833ef8da39aaec040bc Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 27 Apr 2023 14:19:55 -0400 Subject: [PATCH 108/462] touch ups --- contracts/facade/FacadeRead.sol | 12 +++++------- contracts/interfaces/IRToken.sol | 4 ++-- contracts/p0/BasketHandler.sol | 3 ++- contracts/p1/BasketHandler.sol | 2 +- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 6e1014fca0..fd26c2e4ce 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -221,8 +221,7 @@ contract FacadeRead is IFacadeRead { /// @return tokens The ERC20s backing the RToken function basketTokens(IRToken rToken) external view returns (address[] memory tokens) { - IBasketHandler bh = rToken.main().basketHandler(); - (tokens, ) = bh.quote(FIX_ONE, RoundingMode.FLOOR); + (tokens, ) = rToken.main().basketHandler().quote(FIX_ONE, RoundingMode.FLOOR); } /// Returns the backup configuration for a given targetName @@ -251,7 +250,6 @@ contract FacadeRead is IFacadeRead { view returns (uint192 backing, uint192 overCollateralization) { - IBasketHandler bh = rToken.main().basketHandler(); uint256 supply = rToken.totalSupply(); if (supply == 0) return (0, 0); @@ -259,10 +257,10 @@ contract FacadeRead is IFacadeRead { uint192 uoaNeeded; // {UoA} uint192 uoaHeldInBaskets; // {UoA} { - (address[] memory basketERC20s, uint256[] memory quantities) = bh.quote( - basketsNeeded, - FLOOR - ); + (address[] memory basketERC20s, uint256[] memory quantities) = rToken + .main() + .basketHandler() + .quote(basketsNeeded, FLOOR); IAssetRegistry reg = rToken.main().assetRegistry(); IBackingManager bm = rToken.main().backingManager(); diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index 0f772f940c..448039d3ec 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -79,14 +79,14 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea function issueTo(address recipient, uint256 amount) external; /// Redeem RToken for basket collateral - /// @dev Reverts if partial redemption + /// @dev Use customRedemption to restrict min token amounts received /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @param basketNonce The nonce of the basket the redemption should be from; else reverts /// @custom:interaction function redeem(uint256 amount, uint48 basketNonce) external; /// Redeem RToken for basket collateral to a particular recipient - /// @dev Reverts if partial redemption + /// @dev Use customRedemption to restrict min token amounts received /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @param basketNonce The nonce of the basket the redemption should be from; else reverts diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 67691f1c29..53f3080158 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -731,8 +731,9 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // Update the basket if it's not disabled if (!disabled) { - basket.setFrom(newBasket); nonce += 1; + basket.setFrom(newBasket); + historicalBaskets[nonce].setFrom(_newBasket); timestamp = uint48(block.timestamp); } diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index effee1ef98..211b5b3e9d 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -444,7 +444,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } /// Return the current issuance/redemption value of `amount` BUs - /// @dev Subset of logic with quoteHistoricalRedemption; more gas efficient for 1 basketNonce + /// @dev Subset of logic with quoteHistoricalRedemption; more gas efficient for current nonce /// @param amount {BU} /// @return erc20s The backing collateral erc20s /// @return quantities {qTok} ERC20 token quantities equal to `amount` BUs From 44896bb78a7a8b2ad41fa92248e1f2df8ca282d6 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 27 Apr 2023 15:20:18 -0300 Subject: [PATCH 109/462] fix lint --- contracts/facade/FacadeWrite.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/facade/FacadeWrite.sol b/contracts/facade/FacadeWrite.sol index 542fd04c08..134fa7c702 100644 --- a/contracts/facade/FacadeWrite.sol +++ b/contracts/facade/FacadeWrite.sol @@ -163,7 +163,8 @@ contract FacadeWrite is IFacadeWrite { // Setup Roles timelock.grantRole(timelock.PROPOSER_ROLE(), governance); // Gov only proposer - timelock.grantRole(timelock.CANCELLER_ROLE(), govRoles.guardian); // Guardian as canceller + // Set Guardian as canceller, if address(0) then no one can cancel + timelock.grantRole(timelock.CANCELLER_ROLE(), govRoles.guardian); timelock.grantRole(timelock.EXECUTOR_ROLE(), address(0)); // Anyone as executor timelock.revokeRole(timelock.TIMELOCK_ADMIN_ROLE(), address(this)); // Revoke admin role From bb53b71f4ff26ec666cb6def71c00f19305eea00 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 27 Apr 2023 14:24:55 -0400 Subject: [PATCH 110/462] back to compiling --- contracts/p0/BasketHandler.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 53f3080158..be4c573986 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -266,6 +266,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { config.targetNames[erc20s[i]] = names[i]; } + historicalNonce = nonce + 1; // set historicalNonce to the next nonce emit PrimeBasketSet(erc20s, targetAmts, names); } @@ -733,7 +734,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { if (!disabled) { nonce += 1; basket.setFrom(newBasket); - historicalBaskets[nonce].setFrom(_newBasket); + historicalBaskets[nonce].setFrom(newBasket); timestamp = uint48(block.timestamp); } From b65d1091b45647e64cd7ddecbff963a5577732b4 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 27 Apr 2023 15:49:38 -0300 Subject: [PATCH 111/462] fix integration and extreme tests --- test/integration/EasyAuction.test.ts | 4 ++++ test/integration/fixtures.ts | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 39fc9d64f5..2bc56bee98 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -23,6 +23,7 @@ import { MAX_UINT192, MAX_UINT256, ONE_ADDRESS, + PAUSER, } from '../../common/constants' import { advanceTime, getLatestBlockTimestamp } from '../utils/time' import { expectTrade, getAuctionId, getTrade } from '../utils/trades' @@ -770,8 +771,11 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function 1, 1 ) + // Set pauser and unpause + await main.connect(owner).grantRole(PAUSER, owner.address) await main.connect(owner).unpauseTrading() await main.connect(owner).unpauseIssuance() + await broker.init(main.address, easyAuction.address, ONE_ADDRESS, config.auctionLength) const sellTok = await ERC20Factory.deploy('Sell Token', 'SELL', sellTokDecimals) const buyTok = await ERC20Factory.deploy('Buy Token', 'BUY', buyTokDecimals) diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index f6e3a8491e..86498f8e68 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -2,6 +2,7 @@ import { BigNumber, ContractFactory } from 'ethers' import hre, { ethers } from 'hardhat' import { getChainId } from '../../common/blockchain-utils' import { IConfig, IImplementations, IRevenueShare, networkConfig } from '../../common/configuration' +import { PAUSER, SHORT_FREEZER, LONG_FREEZER } from '../../common/constants' import { expectInReceipt } from '../../common/events' import { advanceTime } from '../utils/time' import { bn, fp } from '../../common/numbers' @@ -848,6 +849,11 @@ export const defaultFixture: Fixture = async function (): Promis await backingManager.grantRTokenAllowance(await basket[i].erc20()) } + // Set Owner as Pauser/Freezer for tests + await main.connect(owner).grantRole(PAUSER, owner.address) + await main.connect(owner).grantRole(SHORT_FREEZER, owner.address) + await main.connect(owner).grantRole(LONG_FREEZER, owner.address) + return { rsr, rsrAsset, From e35bceb8caadb29bb66af8bb11f672147acc8aa0 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 27 Apr 2023 15:46:54 -0400 Subject: [PATCH 112/462] do not manageToken() after handoutRevenue(); leave for facade --- contracts/facade/FacadeAct.sol | 29 ++++++++++++++--------------- contracts/p0/BackingManager.sol | 14 +------------- contracts/p1/BackingManager.sol | 27 +++++---------------------- 3 files changed, 20 insertions(+), 50 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 506833aed6..f6c01f13cc 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -92,10 +92,10 @@ contract FacadeAct is IFacadeAct { ) { // try: backingManager.manageTokens([]) - try cache.bm.rebalance(TradeKind.DUTCH_AUCTION) { + try cache.bm.rebalance(TradeKind.BATCH_AUCTION) { return ( address(cache.bm), - abi.encodeWithSelector(cache.bm.rebalance.selector, TradeKind.DUTCH_AUCTION) + abi.encodeWithSelector(cache.bm.rebalance.selector, TradeKind.BATCH_AUCTION) ); } catch {} } else { @@ -137,7 +137,7 @@ contract FacadeAct is IFacadeAct { // rTokenTrader: check if we can start any trades uint48 tradesOpen = cache.rTokenTrader.tradesOpen(); - try cache.rTokenTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) { + try cache.rTokenTrader.manageToken(erc20s[i], TradeKind.BATCH_AUCTION) { if (cache.rTokenTrader.tradesOpen() - tradesOpen > 0) { // A trade started; do cache.rTokenTrader.manageToken return ( @@ -145,7 +145,7 @@ contract FacadeAct is IFacadeAct { abi.encodeWithSelector( cache.rTokenTrader.manageToken.selector, erc20s[i], - TradeKind.DUTCH_AUCTION + TradeKind.BATCH_AUCTION ) ); } @@ -153,7 +153,7 @@ contract FacadeAct is IFacadeAct { // rsrTrader: check if we can start any trades tradesOpen = cache.rsrTrader.tradesOpen(); - try cache.rsrTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) { + try cache.rsrTrader.manageToken(erc20s[i], TradeKind.BATCH_AUCTION) { if (cache.rsrTrader.tradesOpen() - tradesOpen > 0) { // A trade started; do cache.rsrTrader.manageToken return ( @@ -161,7 +161,7 @@ contract FacadeAct is IFacadeAct { abi.encodeWithSelector( cache.rsrTrader.manageToken.selector, erc20s[i], - TradeKind.DUTCH_AUCTION + TradeKind.BATCH_AUCTION ) ); } @@ -187,7 +187,7 @@ contract FacadeAct is IFacadeAct { try cache.rTokenTrader.manageToken( erc20s[i], - TradeKind.DUTCH_AUCTION + TradeKind.BATCH_AUCTION ) { if (cache.rTokenTrader.tradesOpen() - tradesOpen > 0) { @@ -216,7 +216,7 @@ contract FacadeAct is IFacadeAct { try cache.rsrTrader.manageToken( erc20s[i], - TradeKind.DUTCH_AUCTION + TradeKind.BATCH_AUCTION ) { if (cache.rsrTrader.tradesOpen() - tradesOpen > 0) { @@ -242,15 +242,14 @@ contract FacadeAct is IFacadeAct { { IAsset rTokenAsset = cache.reg.toAsset(IERC20(address(rToken))); (uint192 lotLow, ) = rTokenAsset.lotPrice(); - if ( - rTokenAsset.bal(address(cache.rTokenTrader)) > + rTokenAsset.bal(address(cache.rTokenTrader)) >= minTradeSize(cache.rTokenTrader.minTradeVolume(), lotLow) ) { try cache.rTokenTrader.manageToken( IERC20(address(rToken)), - TradeKind.DUTCH_AUCTION + TradeKind.BATCH_AUCTION ) { address[] memory oneERC20 = new address[](1); @@ -272,7 +271,7 @@ contract FacadeAct is IFacadeAct { (uint192 lotLow, ) = rsrAsset.lotPrice(); if ( - rsrAsset.bal(address(cache.stRSR)) - initialStRSRBal > + rsrAsset.bal(address(cache.stRSR)) - initialStRSRBal >= minTradeSize(cache.rsrTrader.minTradeVolume(), lotLow) ) { IERC20[] memory empty = new IERC20[](0); @@ -391,7 +390,7 @@ contract FacadeAct is IFacadeAct { // Try to launch auctions // solhint-disable-next-line no-empty-blocks - try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch { + try bm.rebalance(TradeKind.BATCH_AUCTION) {} catch { return false; } return bm.tradesOpen() > 0; @@ -421,7 +420,7 @@ contract FacadeAct is IFacadeAct { uint256 tradesOpen = revenueTrader.tradesOpen(); - try revenueTrader.manageToken(reg.erc20s[i], TradeKind.DUTCH_AUCTION) { + try revenueTrader.manageToken(reg.erc20s[i], TradeKind.BATCH_AUCTION) { if (revenueTrader.tradesOpen() - tradesOpen > 0) { unfiltered[num] = reg.erc20s[i]; ++num; @@ -461,7 +460,7 @@ contract FacadeAct is IFacadeAct { // Start auctions for (uint256 i = 0; i < toStart.length; ++i) { - revenueTrader.manageToken(toStart[i], TradeKind.DUTCH_AUCTION); + revenueTrader.manageToken(toStart[i], TradeKind.BATCH_AUCTION); } } } diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 2f6e7417e7..e8fe076bcc 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -182,28 +182,16 @@ contract BackingManagerP0 is TradingP0, IBackingManager { uint256 delta = bal.minus(req).shiftl_toUint(int8(asset.erc20Decimals())); uint256 tokensPerShare = delta / (totals.rTokenTotal + totals.rsrTotal); - // solhint-disable no-empty-blocks { uint256 toRSR = tokensPerShare * totals.rsrTotal; - if (toRSR > 0) { - erc20s[i].safeTransfer(address(main.rsrTrader()), toRSR); - try - main.rsrTrader().manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) - {} catch {} - // no need to revert during OOG because caller is already altruistic - } + if (toRSR > 0) erc20s[i].safeTransfer(address(main.rsrTrader()), toRSR); } { uint256 toRToken = tokensPerShare * totals.rTokenTotal; if (toRToken > 0) { erc20s[i].safeTransfer(address(main.rTokenTrader()), toRToken); - try - main.rTokenTrader().manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) - {} catch {} - // no need to revert during OOG because caller is already altruistic } } - // solhint-enable no-empty-blocks } } } diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 872785c09c..8ac88adff1 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -194,7 +194,6 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // (>== is "no less than, and nearly equal to") // and rToken'.basketsNeeded <= basketsHeld.bottom // and rToken'.totalSupply is maximal satisfying this. - uint192 rTokenBuffer; // {rTok} uint192 needed = rToken.basketsNeeded(); // {BU} if (basketsHeld.bottom.gt(needed)) { // gas-optimization: RToken is known to have 18 decimals, the same as FixLib @@ -210,9 +209,6 @@ contract BackingManagerP1 is TradingP1, IBackingManager { rToken.mint(address(this), uint256(rTok)); rToken.setBasketsNeeded(basketsHeld.bottom); needed = basketsHeld.bottom; - - // {rTok} = {1} * ({rTok} + {rTok}) - rTokenBuffer = backingBuffer.mul(totalSupply + rTok); } // At this point, even though basketsNeeded may have changed: @@ -235,14 +231,13 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // {tok} = {BU} * {tok/BU} uint192 req = erc20s[i] != IERC20(address(rToken)) ? needed.mul(basketHandler.quantity(erc20s[i]), CEIL) - : rTokenBuffer; + : backingBuffer.mul(_safeWrap(rToken.totalSupply())); uint192 bal = asset.bal(address(this)); if (bal.gt(req)) { // delta: {qTok}, the excess quantity of this asset that we hold - uint256 delta = bal.minus(req).shiftl_toUint( - int8(IERC20Metadata(address(erc20s[i])).decimals()) - ); + uint256 delta = bal.minus(req).shiftl_toUint(int8(asset.erc20Decimals())); + // no div-by-0: Distributor guarantees (totals.rTokenTotal + totals.rsrTotal) > 0 // initial division is intentional here! We'd rather save the dust than be unfair toRSR[i] = (delta / (totals.rTokenTotal + totals.rsrTotal)) * totals.rsrTotal; @@ -252,20 +247,8 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // == Interactions == for (uint256 i = 0; i < length; ++i) { - if (erc20s[i] == IERC20(address(rToken))) continue; - IERC20 erc20 = IERC20(address(erc20s[i])); - if (toRToken[i] > 0) { - erc20.safeTransfer(address(rTokenTrader), toRToken[i]); - // solhint-disable-next-line no-empty-blocks - try rTokenTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} - // no need to revert during OOG because caller is already altruistic - } - if (toRSR[i] > 0) { - erc20.safeTransfer(address(rsrTrader), toRSR[i]); - // solhint-disable-next-line no-empty-blocks - try rsrTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} - // no need to revert during OOG because caller is already altruistic - } + if (toRToken[i] > 0) erc20s[i].safeTransfer(address(rTokenTrader), toRToken[i]); + if (toRSR[i] > 0) erc20s[i].safeTransfer(address(rsrTrader), toRSR[i]); } // It's okay if there is leftover dust for RToken or a surplus asset (not RSR) From 7834efb46b70b2a0dcb4a2ca7f3f8db9481aeb7a Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Thu, 27 Apr 2023 17:09:23 -0400 Subject: [PATCH 113/462] fix quote algo, add simple quote test. --- contracts/p1/BasketHandler.sol | 20 ++++++------ test/Main.test.ts | 57 ++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 211b5b3e9d..0461b5552d 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -161,10 +161,10 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // === Historical basket nonces === // Added in 3.0.0 - // Nonce of the first reference basket from the current history + // Nonce of the first reference basket from the current prime basket history // A new historical record begins whenever the prime basket is changed // There can be 0 to any number of reference baskets from the current history - uint48 private historicalNonce; // {nonce} + uint48 private primeNonce; // {nonce} // The historical baskets by basket nonce; includes current basket mapping(uint48 => Basket) private historicalBaskets; @@ -293,7 +293,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { config.targetNames[erc20s[i]] = names[i]; } - historicalNonce = nonce + 1; // set historicalNonce to the next nonce + primeNonce = nonce + 1; // set primeNonce to the next nonce emit PrimeBasketSet(erc20s, targetAmts, names); } @@ -485,8 +485,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { uint192[] memory portions, uint192 amount ) external view returns (address[] memory erc20s, uint256[] memory quantities) { - // directly after upgrade the historicalNonce will be 0, which is not a valid value - require(historicalNonce > 0, "historicalNonce uninitialized"); + // directly after upgrade the primeNonce will be 0, which is not a valid value + require(primeNonce > 0, "primeNonce uninitialized"); require(basketNonces.length == portions.length, "portions does not mirror basketNonces"); // Confirm portions sum to FIX_ONE @@ -504,11 +504,10 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // Calculate the linear combination basket for (uint48 i = 0; i < basketNonces.length; ++i) { require( - basketNonces[i] >= historicalNonce && basketNonces[i] <= nonce, + basketNonces[i] >= primeNonce && basketNonces[i] <= nonce, "invalid basketNonce" ); // will always revert directly after setPrimeBasket() - Basket storage b = historicalBaskets[i]; - + Basket storage b = historicalBaskets[basketNonces[i]]; // Add-in refAmts contribution from historical basket for (uint256 j = 0; j < b.erc20s.length; ++j) { IERC20 erc20 = b.erc20s[j]; @@ -524,15 +523,16 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } // Add new ERC20 entry if not found + uint192 amt = portions[i].mul(b.refAmts[erc20], FLOOR); if (erc20Index == type(uint256).max) { erc20sAll[len] = erc20; // {ref} = {1} * {ref} - refAmtsAll[len] = portions[j].mul(b.refAmts[erc20], FLOOR); + refAmtsAll[len] = amt; ++len; } else { // {ref} = {1} * {ref} - refAmtsAll[erc20Index] += portions[j].mul(b.refAmts[erc20], FLOOR); + refAmtsAll[erc20Index] += amt; } } } diff --git a/test/Main.test.ts b/test/Main.test.ts index ed775a2bfb..a25573b178 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -108,10 +108,12 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { let token1: USDCMock let token2: StaticATokenMock let token3: CTokenMock + let backupToken1: ERC20Mock let collateral0: FiatCollateral let collateral1: FiatCollateral let collateral2: ATokenFiatCollateral let collateral3: CTokenFiatCollateral + let backupCollateral1: FiatCollateral let erc20s: ERC20Mock[] let basketsNeededAmts: BigNumber[] @@ -170,6 +172,9 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { token2 = erc20s[collateral.indexOf(basket[2])] token3 = erc20s[collateral.indexOf(basket[3])] + backupToken1 = erc20s[2] // USDT + backupCollateral1 = collateral[2] + collateral0 = basket[0] collateral1 = basket[1] collateral2 = basket[2] @@ -1722,6 +1727,58 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { .withArgs([token0.address], [fp('1')], [ethers.utils.formatBytes32String('USD')]) }) + describe('Historical Redemptions', () => { + const issueAmount = fp('10000') + let usdcChainlink: MockV3Aggregator + + beforeEach(async () => { + usdcChainlink = await ethers.getContractAt('MockV3Aggregator', await collateral1.chainlinkFeed()) + + // register backups + await assetRegistry.connect(owner).register(backupCollateral1.address) + await basketHandler + .connect(owner) + .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [backupToken1.address]) + + // issue rTokens + await token0.connect(addr1).approve(rToken.address, issueAmount) + await token1.connect(addr1).approve(rToken.address, issueAmount) + await token2.connect(addr1).approve(rToken.address, issueAmount) + await token3.connect(addr1).approve(rToken.address, issueAmount) + await rToken.connect(addr1).issue(issueAmount) + }) + + it('Should correctly quote a historical redemption (simple)', async () => { + // default usdc & refresh basket to use backup collateral + await usdcChainlink.updateAnswer(bn('0.8e8')) + await basketHandler.refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + + const quote = await basketHandler.quoteHistoricalRedemption([1, 2], [fp('0.5'), fp('0.5')], fp('10000')) + + const expectedTokens = [token0.address, token1.address, token2.address, token3.address, backupToken1.address] + const expectedQuantities = [ + fp('0.25').mul(issueAmount).div(await collateral0.refPerTok()).div(bn(`1e${18 - (await token0.decimals())}`)), + fp('0.125').mul(issueAmount).div(await collateral1.refPerTok()).div(bn(`1e${18 - (await token1.decimals())}`)), + fp('0.25').mul(issueAmount).div(await collateral2.refPerTok()).div(bn(`1e${18 - (await token2.decimals())}`)), + fp('0.25').mul(issueAmount).div(await collateral3.refPerTok()).div(bn(`1e${18 - (await token3.decimals())}`)), + fp('0.125').mul(issueAmount).div(await backupCollateral1.refPerTok()).div(bn(`1e${18 - (await backupToken1.decimals())}`)), + ] + expect(quote.erc20s[0]).equal(expectedTokens[0]) + expect(quote.erc20s[1]).equal(expectedTokens[1]) + expect(quote.erc20s[2]).equal(expectedTokens[2]) + expect(quote.erc20s[3]).equal(expectedTokens[3]) + expect(quote.erc20s[4]).equal(expectedTokens[4]) + expect(quote.quantities[0]).equal(expectedQuantities[0]) + expect(quote.quantities[1]).equal(expectedQuantities[1]) + expect(quote.quantities[2]).equal(expectedQuantities[2]) + expect(quote.quantities[3]).equal(expectedQuantities[3]) + expect(quote.quantities[4]).equal(expectedQuantities[4]) + }) + }) + it('Should return (FIX_ZERO, FIX_MAX) for basketsHeldBy() if the basket is empty', async () => { // run a fresh deployment specifically for this test const receipt = await ( From 0397152cee1bd5a52e46cf7eeab4a1aceec42edc Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 27 Apr 2023 20:52:46 -0400 Subject: [PATCH 114/462] p0 + p1 tests --- common/configuration.ts | 10 +- common/constants.ts | 7 +- contracts/p1/BackingManager.sol | 5 +- test/Broker.test.ts | 243 +++++++++++++++++-------- test/Deployer.test.ts | 15 +- test/FacadeAct.test.ts | 18 +- test/FacadeMonitor.ts | 4 +- test/Main.test.ts | 16 +- test/Recollateralization.test.ts | 293 +++++++++++++++---------------- test/Revenues.test.ts | 215 +++++++++-------------- test/Upgradeability.test.ts | 24 ++- test/ZTradingExtremes.test.ts | 6 +- test/fixtures.ts | 16 +- 13 files changed, 490 insertions(+), 382 deletions(-) diff --git a/common/configuration.ts b/common/configuration.ts index 411fb30432..dcd8928808 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -317,7 +317,8 @@ export interface IConfig { unstakingDelay: BigNumber warmupPeriod: BigNumber tradingDelay: BigNumber - auctionLength: BigNumber + batchAuctionLength: BigNumber + dutchAuctionLength: BigNumber backingBuffer: BigNumber maxTradeSlippage: BigNumber issuanceThrottle: ThrottleParams @@ -347,9 +348,14 @@ export interface IComponents { stRSR: string } +export interface ITradePlugins { + gnosisTrade: string + dutchTrade: string +} + export interface IImplementations { main: string - trade: string + trading: ITradePlugins components: IComponents } diff --git a/common/constants.ts b/common/constants.ts index 5de1f65d9d..a11a2b4b80 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -44,13 +44,18 @@ export enum RoundingMode { CEIL, } -// @dev Must match `GnosisTrade.TradeStatus`. +// @dev Must match `ITrade.TradeStatus`. export enum TradeStatus { NOT_STARTED, OPEN, CLOSED, } +export enum TradeKind { + DUTCH_AUCTION, + BATCH_AUCTION, +} + export const FURNACE_DEST = '0x0000000000000000000000000000000000000001' export const STRSR_DEST = '0x0000000000000000000000000000000000000002' diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 8ac88adff1..5e0906dc6e 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -34,7 +34,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { uint192 public constant MAX_BACKING_BUFFER = FIX_ONE; // {1} 100% uint48 public tradingDelay; // {s} how long to wait until resuming trading after switching - uint192 public backingBuffer; // {%} how much extra backing collateral to keep + uint192 public backingBuffer; // {1} how much extra backing collateral to keep // === 3.0.0 === IFurnace private furnace; @@ -231,7 +231,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // {tok} = {BU} * {tok/BU} uint192 req = erc20s[i] != IERC20(address(rToken)) ? needed.mul(basketHandler.quantity(erc20s[i]), CEIL) - : backingBuffer.mul(_safeWrap(rToken.totalSupply())); + : backingBuffer.mul(_safeWrap(rToken.totalSupply()), CEIL); uint192 bal = asset.bal(address(this)); if (bal.gt(req)) { @@ -258,6 +258,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// @param basketsHeldBottom {BU} The number of full basket units held by the BackingManager function compromiseBasketsNeeded(uint192 basketsHeldBottom) private { // assert(tradesOpen == 0 && !basketHandler.fullyCollateralized()); + assert(tradesOpen == 0); rToken.setBasketsNeeded(basketsHeldBottom); } diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 34edb5af00..d013f2d6dd 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -5,9 +5,10 @@ import { expect } from 'chai' import { BigNumber, ContractFactory } from 'ethers' import { ethers, upgrades } from 'hardhat' import { IConfig, MAX_AUCTION_LENGTH } from '../common/configuration' -import { MAX_UINT96, TradeStatus, ZERO_ADDRESS } from '../common/constants' +import { MAX_UINT96, TradeKind, TradeStatus, ZERO_ADDRESS, ONE_ADDRESS } from '../common/constants' import { bn, toBNDecimals } from '../common/numbers' import { + DutchTrade, ERC20Mock, GnosisMock, GnosisMockReentrant, @@ -94,7 +95,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { describe('Deployment', () => { it('Should setup Broker correctly', async () => { expect(await broker.gnosis()).to.equal(gnosis.address) - expect(await broker.auctionLength()).to.equal(config.auctionLength) + expect(await broker.batchAuctionLength()).to.equal(config.batchAuctionLength) expect(await broker.disabled()).to.equal(false) expect(await broker.main()).to.equal(main.address) }) @@ -114,11 +115,28 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { } await expect( - newBroker.init(main.address, ZERO_ADDRESS, ZERO_ADDRESS, bn('100')) + newBroker.init(main.address, ZERO_ADDRESS, ZERO_ADDRESS, bn('100'), ZERO_ADDRESS, bn('100')) ).to.be.revertedWith('invalid Gnosis address') await expect( - newBroker.init(main.address, gnosis.address, ZERO_ADDRESS, bn('100')) - ).to.be.revertedWith('invalid Trade Implementation address') + newBroker.init( + main.address, + gnosis.address, + ZERO_ADDRESS, + bn('100'), + ZERO_ADDRESS, + bn('100') + ) + ).to.be.revertedWith('invalid batchTradeImplementation address') + await expect( + newBroker.init( + main.address, + gnosis.address, + ONE_ADDRESS, + bn('100'), + ZERO_ADDRESS, + bn('100') + ) + ).to.be.revertedWith('invalid dutchTradeImplementation address') }) }) @@ -149,77 +167,151 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await broker.gnosis()).to.equal(mock.address) }) - it('Should allow to update Trade Implementation if Owner and perform validations', async () => { + it('Should allow to update BatchTrade Implementation if Owner and perform validations', async () => { // Create a Trade const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') const tradeImpl: GnosisTrade = await TradeFactory.deploy() // Update to a trade implementation to use as baseline for tests - await expect(broker.connect(owner).setTradeImplementation(tradeImpl.address)) - .to.emit(broker, 'TradeImplementationSet') + await expect(broker.connect(owner).setBatchTradeImplementation(tradeImpl.address)) + .to.emit(broker, 'BatchTradeImplementationSet') .withArgs(anyValue, tradeImpl.address) // Check existing value - expect(await broker.tradeImplementation()).to.equal(tradeImpl.address) + expect(await broker.batchTradeImplementation()).to.equal(tradeImpl.address) // If not owner cannot update - await expect(broker.connect(other).setTradeImplementation(mock.address)).to.be.revertedWith( - 'governance only' - ) + await expect( + broker.connect(other).setBatchTradeImplementation(mock.address) + ).to.be.revertedWith('governance only') // Check value did not change - expect(await broker.tradeImplementation()).to.equal(tradeImpl.address) + expect(await broker.batchTradeImplementation()).to.equal(tradeImpl.address) // Attempt to update with Owner but zero address - not allowed - await expect(broker.connect(owner).setTradeImplementation(ZERO_ADDRESS)).to.be.revertedWith( - 'invalid Trade Implementation address' - ) + await expect( + broker.connect(owner).setBatchTradeImplementation(ZERO_ADDRESS) + ).to.be.revertedWith('invalid batchTradeImplementation address') + + // Update with owner + await expect(broker.connect(owner).setBatchTradeImplementation(mock.address)) + .to.emit(broker, 'BatchTradeImplementationSet') + .withArgs(tradeImpl.address, mock.address) + + // Check value was updated + expect(await broker.batchTradeImplementation()).to.equal(mock.address) + }) + + it('Should allow to update DutchTrade Implementation if Owner and perform validations', async () => { + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + const tradeImpl: DutchTrade = await TradeFactory.deploy() + + // Update to a trade implementation to use as baseline for tests + await expect(broker.connect(owner).setDutchTradeImplementation(tradeImpl.address)) + .to.emit(broker, 'DutchTradeImplementationSet') + .withArgs(anyValue, tradeImpl.address) + + // Check existing value + expect(await broker.dutchTradeImplementation()).to.equal(tradeImpl.address) + + // If not owner cannot update + await expect( + broker.connect(other).setDutchTradeImplementation(mock.address) + ).to.be.revertedWith('governance only') + + // Check value did not change + expect(await broker.dutchTradeImplementation()).to.equal(tradeImpl.address) + + // Attempt to update with Owner but zero address - not allowed + await expect( + broker.connect(owner).setDutchTradeImplementation(ZERO_ADDRESS) + ).to.be.revertedWith('invalid dutchTradeImplementation address') // Update with owner - await expect(broker.connect(owner).setTradeImplementation(mock.address)) - .to.emit(broker, 'TradeImplementationSet') + await expect(broker.connect(owner).setDutchTradeImplementation(mock.address)) + .to.emit(broker, 'DutchTradeImplementationSet') .withArgs(tradeImpl.address, mock.address) // Check value was updated - expect(await broker.tradeImplementation()).to.equal(mock.address) + expect(await broker.dutchTradeImplementation()).to.equal(mock.address) + }) + + it('Should allow to update batchAuctionLength if Owner', async () => { + const newValue: BigNumber = bn('360') + + // Check existing value + expect(await broker.batchAuctionLength()).to.equal(config.batchAuctionLength) + + // If not owner cannot update + await expect(broker.connect(other).setBatchAuctionLength(newValue)).to.be.revertedWith( + 'governance only' + ) + + // Check value did not change + expect(await broker.batchAuctionLength()).to.equal(config.batchAuctionLength) + + // Update with owner + await expect(broker.connect(owner).setBatchAuctionLength(newValue)) + .to.emit(broker, 'BatchAuctionLengthSet') + .withArgs(config.batchAuctionLength, newValue) + + // Check value was updated + expect(await broker.batchAuctionLength()).to.equal(newValue) }) - it('Should allow to update auctionLength if Owner', async () => { + it('Should allow to update dutchAuctionLength if Owner', async () => { const newValue: BigNumber = bn('360') // Check existing value - expect(await broker.auctionLength()).to.equal(config.auctionLength) + expect(await broker.dutchAuctionLength()).to.equal(config.dutchAuctionLength) // If not owner cannot update - await expect(broker.connect(other).setAuctionLength(newValue)).to.be.revertedWith( + await expect(broker.connect(other).setDutchAuctionLength(newValue)).to.be.revertedWith( 'governance only' ) // Check value did not change - expect(await broker.auctionLength()).to.equal(config.auctionLength) + expect(await broker.dutchAuctionLength()).to.equal(config.dutchAuctionLength) // Update with owner - await expect(broker.connect(owner).setAuctionLength(newValue)) - .to.emit(broker, 'AuctionLengthSet') - .withArgs(config.auctionLength, newValue) + await expect(broker.connect(owner).setDutchAuctionLength(newValue)) + .to.emit(broker, 'DutchAuctionLengthSet') + .withArgs(config.dutchAuctionLength, newValue) // Check value was updated - expect(await broker.auctionLength()).to.equal(newValue) + expect(await broker.dutchAuctionLength()).to.equal(newValue) + }) + + it('Should perform validations on batchAuctionLength', async () => { + let invalidValue: BigNumber = bn(0) + + // Attempt to update + await expect(broker.connect(owner).setBatchAuctionLength(invalidValue)).to.be.revertedWith( + 'invalid batchAuctionLength' + ) + + invalidValue = bn(MAX_AUCTION_LENGTH + 1) + + // Attempt to update + await expect(broker.connect(owner).setBatchAuctionLength(invalidValue)).to.be.revertedWith( + 'invalid batchAuctionLength' + ) }) - it('Should perform validations on auctionLength', async () => { + it('Should perform validations on dutchAuctionLength', async () => { let invalidValue: BigNumber = bn(0) // Attempt to update - await expect(broker.connect(owner).setAuctionLength(invalidValue)).to.be.revertedWith( - 'invalid auctionLength' + await expect(broker.connect(owner).setDutchAuctionLength(invalidValue)).to.be.revertedWith( + 'invalid dutchAuctionLength' ) invalidValue = bn(MAX_AUCTION_LENGTH + 1) // Attempt to update - await expect(broker.connect(owner).setAuctionLength(invalidValue)).to.be.revertedWith( - 'invalid auctionLength' + await expect(broker.connect(owner).setDutchAuctionLength(invalidValue)).to.be.revertedWith( + 'invalid dutchAuctionLength' ) }) @@ -267,13 +359,13 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { } await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(broker.connect(bmSigner).openTrade(tradeRequest)).to.be.revertedWith( - 'broker disabled' - ) + await expect( + broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ).to.be.revertedWith('broker disabled') }) }) - it('Should not allow to open trade if paused', async () => { + it('Should not allow to open trade if trading paused', async () => { await main.connect(owner).pauseTrading() // Attempt to open trade @@ -285,9 +377,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { } await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(broker.connect(bmSigner).openTrade(tradeRequest)).to.be.revertedWith( - 'frozen or trading paused' - ) + await expect( + broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ).to.be.revertedWith('frozen or trading paused') }) }) @@ -303,9 +395,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { } await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(broker.connect(bmSigner).openTrade(tradeRequest)).to.be.revertedWith( - 'frozen or trading paused' - ) + await expect( + broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ).to.be.revertedWith('frozen or trading paused') }) }) @@ -327,25 +419,30 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // Attempt to open trade from non-trader await token0.connect(addr1).approve(broker.address, amount) - await expect(broker.connect(addr1).openTrade(tradeRequest)).to.be.revertedWith('only traders') + await expect( + broker.connect(addr1).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ).to.be.revertedWith('only traders') // Open from traders - Should work // Backing Manager await whileImpersonating(backingManager.address, async (bmSigner) => { await token0.connect(bmSigner).approve(broker.address, amount) - await expect(broker.connect(bmSigner).openTrade(tradeRequest)).to.not.be.reverted + await expect(broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest)).to + .not.be.reverted }) // RSR Trader await whileImpersonating(rsrTrader.address, async (rsrSigner) => { await token0.connect(rsrSigner).approve(broker.address, amount) - await expect(broker.connect(rsrSigner).openTrade(tradeRequest)).to.not.be.reverted + await expect(broker.connect(rsrSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest)).to + .not.be.reverted }) // RToken Trader await whileImpersonating(rTokenTrader.address, async (rtokSigner) => { await token0.connect(rtokSigner).approve(broker.address, amount) - await expect(broker.connect(rtokSigner).openTrade(tradeRequest)).to.not.be.reverted + await expect(broker.connect(rtokSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest)).to + .not.be.reverted }) }) @@ -418,7 +515,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { broker.address, backingManager.address, gnosis.address, - config.auctionLength, + config.batchAuctionLength, tradeRequest ) ).to.not.be.reverted @@ -433,7 +530,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await trade.buy()).to.equal(token1.address) expect(await trade.initBal()).to.equal(amount) expect(await trade.endTime()).to.equal( - (await getLatestBlockTimestamp()) + Number(config.auctionLength) + (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength) ) expect(await trade.worstCasePrice()).to.equal(bn('0')) expect(await trade.canSettle()).to.equal(false) @@ -444,7 +541,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await trade.broker(), await trade.origin(), await trade.gnosis(), - await broker.auctionLength(), + await broker.batchAuctionLength(), tradeRequest ) ).to.be.revertedWith('Invalid trade state') @@ -475,7 +572,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { broker.address, backingManager.address, gnosis.address, - config.auctionLength, + config.batchAuctionLength, tradeRequest ) ).to.not.be.reverted @@ -490,7 +587,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await trade.buy()).to.equal(tokenZ.address) expect(await trade.initBal()).to.equal(amount) expect(await trade.endTime()).to.equal( - (await getLatestBlockTimestamp()) + Number(config.auctionLength) + (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength) ) expect(await trade.worstCasePrice()).to.equal(bn('0')) expect(await trade.canSettle()).to.equal(false) @@ -530,7 +627,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { broker.address, backingManager.address, reentrantGnosis.address, - config.auctionLength, + config.batchAuctionLength, tradeRequest ) ).to.be.revertedWith('Invalid trade state') @@ -563,7 +660,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { broker.address, backingManager.address, gnosis.address, - config.auctionLength, + config.batchAuctionLength, tradeRequest ) ).to.be.revertedWith('sellAmount too large') @@ -578,7 +675,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { broker.address, backingManager.address, gnosis.address, - config.auctionLength, + config.batchAuctionLength, tradeRequest ) ).to.be.revertedWith('minBuyAmount too large') @@ -595,7 +692,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { broker.address, backingManager.address, gnosis.address, - config.auctionLength, + config.batchAuctionLength, tradeRequest ) ).to.be.revertedWith('initBal too large') @@ -625,7 +722,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { broker.address, backingManager.address, gnosis.address, - config.auctionLength, + config.batchAuctionLength, tradeRequest ) ).to.be.revertedWith('unfunded trade') @@ -662,7 +759,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { broker.address, backingManager.address, gnosis.address, - config.auctionLength, + config.batchAuctionLength, tradeRequest ) ).to.not.be.reverted @@ -672,7 +769,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await trade.canSettle()).to.equal(false) // Advance time till trade can be settled - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Check status - can be settled now expect(await trade.status()).to.equal(TradeStatus.OPEN) @@ -723,13 +820,13 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { broker.address, backingManager.address, reentrantGnosis.address, - config.auctionLength, + config.batchAuctionLength, tradeRequest ) ).to.not.be.reverted // Advance time till trade can be settled - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Attempt Settle trade await whileImpersonating(backingManager.address, async (bmSigner) => { @@ -763,7 +860,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { broker.address, backingManager.address, gnosis.address, - config.auctionLength, + config.batchAuctionLength, tradeRequest ) ).to.not.be.reverted @@ -773,7 +870,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await trade.canSettle()).to.equal(false) // Advance time till trade can be settled - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Check status - can be settled now expect(await trade.status()).to.equal(TradeStatus.OPEN) @@ -843,7 +940,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { broker.address, backingManager.address, gnosis.address, - config.auctionLength, + config.batchAuctionLength, tradeRequest ) ).to.not.be.reverted @@ -858,7 +955,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ) // Advance time till trade can be settled - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Settle trade await whileImpersonating(backingManager.address, async (bmSigner) => { @@ -923,19 +1020,25 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // Backing Manager await whileImpersonating(backingManager.address, async (bmSigner) => { await token0.connect(bmSigner).approve(broker.address, amount) - await snapshotGasCost(broker.connect(bmSigner).openTrade(tradeRequest)) + await snapshotGasCost( + broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ) }) // RSR Trader await whileImpersonating(rsrTrader.address, async (rsrSigner) => { await token0.connect(rsrSigner).approve(broker.address, amount) - await snapshotGasCost(broker.connect(rsrSigner).openTrade(tradeRequest)) + await snapshotGasCost( + broker.connect(rsrSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ) }) // RToken Trader await whileImpersonating(rTokenTrader.address, async (rtokSigner) => { await token0.connect(rtokSigner).approve(broker.address, amount) - await snapshotGasCost(broker.connect(rtokSigner).openTrade(tradeRequest)) + await snapshotGasCost( + broker.connect(rtokSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ) }) }) @@ -947,7 +1050,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { broker.address, backingManager.address, gnosis.address, - config.auctionLength, + config.batchAuctionLength, tradeRequest ) ) @@ -960,12 +1063,12 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { broker.address, backingManager.address, gnosis.address, - config.auctionLength, + config.batchAuctionLength, tradeRequest ) // Advance time till trade can be settled - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Settle trade await whileImpersonating(backingManager.address, async (bmSigner) => { diff --git a/test/Deployer.test.ts b/test/Deployer.test.ts index c2764a5bc7..1f14da64ea 100644 --- a/test/Deployer.test.ts +++ b/test/Deployer.test.ts @@ -157,7 +157,7 @@ describe(`DeployerP${IMPLEMENTATION} contract #fast`, () => { rsrTrader: mock.address, rTokenTrader: mock.address, }, - trade: mock.address, + trading: { gnosisTrade: mock.address, dutchTrade: mock.address }, } await expect( @@ -180,12 +180,19 @@ describe(`DeployerP${IMPLEMENTATION} contract #fast`, () => { ).to.be.revertedWith('invalid address') implementations.main = mock.address - // Trade - implementations.trade = ZERO_ADDRESS + // GnosisTrade + implementations.trading.gnosisTrade = ZERO_ADDRESS await expect( deployNewDeployer(rsr.address, gnosis.address, rsrAsset.address, implementations) ).to.be.revertedWith('invalid address') - implementations.trade = mock.address + implementations.trading.gnosisTrade = mock.address + + // DutchTrade + implementations.trading.dutchTrade = ZERO_ADDRESS + await expect( + deployNewDeployer(rsr.address, gnosis.address, rsrAsset.address, implementations) + ).to.be.revertedWith('invalid address') + implementations.trading.dutchTrade = mock.address await validateComponent(implementations, 'assetRegistry') await validateComponent(implementations, 'backingManager') diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts index e1ce54fa8c..e6438ee28d 100644 --- a/test/FacadeAct.test.ts +++ b/test/FacadeAct.test.ts @@ -342,7 +342,7 @@ describe('FacadeAct contract', () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Confirm auctionsSettleable returns trade const settleable = await facade.auctionsSettleable(backingManager.address) @@ -441,7 +441,7 @@ describe('FacadeAct contract', () => { expect(await aaveToken.balanceOf(gnosis.address)).to.equal(rewardAmountAAVE) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction by minting the buy tokens (in this case RSR and RToken) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -493,7 +493,7 @@ describe('FacadeAct contract', () => { expect(data).to.equal('0x') // distribute Revenue from RToken trader - await rTokenTrader.manageToken(rToken.address) + await rTokenTrader.distributeTokenToBuy() // Claim additional Revenue but only send to RSR (to trigger RSR trader directly) // Set f = 1 @@ -678,7 +678,7 @@ describe('FacadeAct contract', () => { expect(await aaveToken.balanceOf(gnosis.address)).to.equal(rewardAmountAAVE) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction by minting the buy tokens (in this case RSR and RToken) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -888,8 +888,12 @@ describe('FacadeAct contract', () => { }) // RToken forwarded - expect(await rToken.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.balanceOf(rTokenTrader.address)).to.equal(hndAmt) + expect(await rToken.balanceOf(backingManager.address)).to.equal( + config.backingBuffer.mul(await rToken.totalSupply()).div(fp('1')) + ) + expect(await rToken.balanceOf(rTokenTrader.address)).to.equal( + hndAmt.sub(issueAmount.mul(config.backingBuffer).div(fp('1'))) + ) }) }) @@ -924,7 +928,7 @@ describe('FacadeAct contract', () => { expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(0) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Now both should be settleable const rTokenSettleable = await facade.auctionsSettleable(rTokenTrader.address) diff --git a/test/FacadeMonitor.ts b/test/FacadeMonitor.ts index 66f8525088..e64ac004c7 100644 --- a/test/FacadeMonitor.ts +++ b/test/FacadeMonitor.ts @@ -216,7 +216,7 @@ describe('FacadeMonitor Contract', () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) const response2 = await facadeMonitor.callStatic.getTradesForBackingManager(rToken.address) @@ -335,7 +335,7 @@ describe('FacadeMonitor Contract', () => { expect(await aaveToken.balanceOf(gnosis.address)).to.equal(rewardAmountAAVE) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction by minting the buy tokens (in this case RSR and RToken) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) diff --git a/test/Main.test.ts b/test/Main.test.ts index ed775a2bfb..9c001a6fbc 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -33,6 +33,7 @@ import { ATokenFiatCollateral, CTokenFiatCollateral, CTokenMock, + DutchTrade, ERC20Mock, FacadeRead, FacadeTest, @@ -385,10 +386,19 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { ) // Attempt to reinitialize - Broker - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + const GnosisTradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const gnosisTrade: GnosisTrade = await GnosisTradeFactory.deploy() + const DutchTradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + const dutchTrade: DutchTrade = await DutchTradeFactory.deploy() await expect( - broker.init(main.address, gnosis.address, trade.address, config.auctionLength) + broker.init( + main.address, + gnosis.address, + gnosisTrade.address, + config.batchAuctionLength, + dutchTrade.address, + config.dutchAuctionLength + ) ).to.be.revertedWith('Initializable: contract is already initialized') // Attempt to reinitialize - RToken diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 081e158845..5ec934cc5c 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -5,7 +5,7 @@ import { expect } from 'chai' import { BigNumber, ContractFactory } from 'ethers' import { ethers } from 'hardhat' import { IConfig } from '../common/configuration' -import { BN_SCALE_FACTOR, CollateralStatus, MAX_UINT256 } from '../common/constants' +import { BN_SCALE_FACTOR, CollateralStatus, TradeKind, MAX_UINT256 } from '../common/constants' import { expectEvents } from '../common/events' import { bn, fp, pow10, toBNDecimals, divCeil } from '../common/numbers' import { @@ -801,24 +801,21 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { it('Should not trade if paused', async () => { await main.connect(owner).pauseTrading() - await expect(backingManager.manageTokens([])).to.be.revertedWith('frozen or trading paused') - await expect(backingManager.manageTokensSortedOrder([])).to.be.revertedWith( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'frozen or trading paused' ) }) it('Should not trade if frozen', async () => { await main.connect(owner).freezeShort() - await expect(backingManager.manageTokens([])).to.be.revertedWith('frozen or trading paused') - await expect(backingManager.manageTokensSortedOrder([])).to.be.revertedWith( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'frozen or trading paused' ) }) it('Should not trade if UNPRICED', async () => { await advanceTime(ORACLE_TIMEOUT.toString()) - await expect(backingManager.manageTokens([])).to.be.revertedWith('basket not ready') - await expect(backingManager.manageTokensSortedOrder([])).to.be.revertedWith( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'basket not ready' ) }) @@ -829,8 +826,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Set warmup period await basketHandler.connect(owner).setWarmupPeriod(warmupPeriod) - await expect(backingManager.manageTokens([])).to.be.revertedWith('basket not ready') - await expect(backingManager.manageTokensSortedOrder([])).to.be.revertedWith( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'basket not ready' ) }) @@ -872,7 +868,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) }) @@ -900,7 +896,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('0.5'), fp('1')) // Attempt to trigger before warmup period - will revert - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.be.revertedWith( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'basket not ready' ) @@ -925,7 +921,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) }) @@ -948,7 +944,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) // Attempt to trigger before trading delay - Should revert - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.be.revertedWith( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'trading delayed' ) @@ -973,7 +969,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) }) @@ -1029,7 +1025,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1072,7 +1068,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expect(backingManager.settleTrade(token3.address)).to.not.emit // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should not start any new auctions await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1144,7 +1140,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1168,7 +1164,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should not start any new auctions await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1247,7 +1243,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1292,7 +1288,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should not start any new auctions await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1371,7 +1367,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1416,7 +1412,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should not start any new auctions await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1434,10 +1430,13 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await basketHandler.fullyCollateralized()).to.equal(true) expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( issueAmount, - issueAmount.div(1000000) // 1 part in a million + issueAmount.mul(520).div(100000) // 520 parts in 1 miliion + ) + expect(await token0.balanceOf(backingManager.address)).to.be.closeTo(0, 10) + expect(await token1.balanceOf(backingManager.address)).to.be.closeTo( + 0, + toBNDecimals(sellAmt, 6) ) - expect(await token0.balanceOf(backingManager.address)).to.equal(remainder) - expect(await token1.balanceOf(backingManager.address)).to.equal(toBNDecimals(sellAmt, 6)) expect(await rToken.totalSupply()).to.equal(issueAmount) // Supply remains constant // Check price in USD of the current RToken @@ -1501,7 +1500,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1547,7 +1546,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should not start any new auctions await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1564,8 +1563,11 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(true) const remainingValue = toBNDecimals(minBuyAmt, 6).mul(bn('1e12')).add(remainder) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(remainingValue) - expect(await token0.balanceOf(backingManager.address)).to.equal(remainder) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( + remainingValue, + remainingValue.div(bn('5e3')) + ) + expect(await token0.balanceOf(backingManager.address)).to.be.closeTo(0, 10) expect(await token1.balanceOf(backingManager.address)).to.equal(toBNDecimals(minBuyAmt, 6)) expect(await rToken.totalSupply()).to.equal(issueAmount) // Supply remains constant expect(await rToken.basketsNeeded()).to.be.closeTo( @@ -1646,7 +1648,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1678,7 +1680,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start a new one to sell RSR for collateral // ~3e18 Tokens left to buy - Sets Buy amount as independent value @@ -1716,7 +1718,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -1754,7 +1756,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start final dust auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1833,7 +1835,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1863,7 +1865,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start a new one to sell a new revenue token instead of RSR // About 3e18 Tokens left to buy - Sets Buy amount as independent value @@ -1906,7 +1908,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: aaveToken.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -1941,7 +1943,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2079,7 +2081,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2106,7 +2108,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current, and will open a new auction for the same amount await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2130,7 +2132,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.auctionLength), + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -2157,14 +2159,14 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Check staking situation remains unchanged expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current, and will open a new auction for the same amount const leftoverMinBuyAmt = await toMinBuyAmt(leftoverSellAmt, fp('0.5'), fp('1')) @@ -2194,7 +2196,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.auctionLength), + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), externalId: bn('2'), }) @@ -2221,7 +2223,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Check staking situation remains unchanged expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) @@ -2260,7 +2262,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('3'), }) @@ -2296,7 +2298,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction; should not start a new one await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2397,7 +2399,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2423,7 +2425,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Raise minTradeVolume to the whole issueAmount await backingManager.connect(owner).setMinTradeVolume(issueAmount) @@ -2475,7 +2477,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - NO RSR Auction launched but haircut taken await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2598,7 +2600,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2612,7 +2614,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current, and will open a new auction to buy the remaining backup tokens const buyAmtBidRSR: BigNumber = issueAmount.div(2).add(1) // other half to buy @@ -2638,7 +2640,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: backupToken2.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -2665,7 +2667,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2762,7 +2764,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2776,7 +2778,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current, and will open a new auction to sell RSR for collateral // 50e18 Tokens left to buy - Sets Buy amount as independent value @@ -2803,7 +2805,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -2828,7 +2830,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Settle auction with no bids - will return RSR to Backing Manager // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2854,7 +2856,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), }) @@ -2871,7 +2873,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions again - Will close the pending auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2939,7 +2941,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2969,7 +2971,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start a new one to sell a new revenue token instead of RSR // But the revenue token will have price = 0 so it wont be sold, will use RSR @@ -3013,7 +3015,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -3048,7 +3050,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -3245,7 +3247,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token2.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -3265,7 +3267,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start a new one to sell RSR for collateral // ~0.25 Tokens left to buy - Sets Buy amount as independent value @@ -3293,7 +3295,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -3336,7 +3338,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -3486,7 +3488,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -3505,7 +3507,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction for another token const minBuyAmt2 = await toMinBuyAmt(sellAmt2, fp('0.8'), fp('1')) @@ -3531,7 +3533,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token2.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -3561,7 +3563,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction const minBuyAmt3 = await toMinBuyAmt(sellAmt3, fp('0.8').div(50), fp('1')) @@ -3593,7 +3595,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token3.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), }) @@ -3624,7 +3626,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction with the rebalancing await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -3655,7 +3657,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token1.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('3'), }) const t = await getTrade(backingManager, token1.address) @@ -3695,7 +3697,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close 2nd to final auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -3720,7 +3722,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token1.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('4'), }) const t2 = await getTrade(backingManager, token1.address) @@ -3754,7 +3756,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close final auction - should take haircut await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -3774,23 +3776,18 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Check final state - Haircut taken, stable but price of RToken has been reduced expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - issueAmount - .div(4) - .add(minBuyAmt0) - .add(minBuyAmt2) - .add(minBuyAmt3) - .sub(sellAmt4.mul(bn('1e12')).sub(minBuyAmt4)) - .sub(sellAmt5.mul(bn('1e12')).sub(minBuyAmt5)) - ) - await expectCurrentBacking({ - tokens: newTokens, - quantities: [ - newQuantities[0].sub(sellAmt4).sub(sellAmt5), - minBuyAmt0.add(minBuyAmt2).add(minBuyAmt3).add(minBuyAmt4).add(minBuyAmt5), - ], - }) + const expAmount = issueAmount + .div(4) + .add(minBuyAmt0) + .add(minBuyAmt2) + .add(minBuyAmt3) + .sub(sellAmt4.mul(bn('1e12')).sub(minBuyAmt4)) + .sub(sellAmt5.mul(bn('1e12')).sub(minBuyAmt5)) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( + expAmount, + expAmount.div(bn('5e3')) // 1 part in 5000 + ) expect(await rToken.totalSupply()).to.equal(issueAmount) // Check price in USD of the current RToken @@ -3809,8 +3806,10 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(quotes[1]).to.be.closeTo(finalQuotes[1], finalQuotes[1].div(bn('1e5'))) // 1 part in 100k // Check Backup tokens available - expect(await backupToken1.balanceOf(backingManager.address)).to.equal( - minBuyAmt0.add(minBuyAmt2).add(minBuyAmt3).add(minBuyAmt4).add(minBuyAmt5) + const backup1 = minBuyAmt0.add(minBuyAmt2).add(minBuyAmt3).add(minBuyAmt4).add(minBuyAmt5) + expect(await backupToken1.balanceOf(backingManager.address)).to.be.closeTo( + backup1, + backup1.div(bn('1e5')) ) }) @@ -3897,7 +3896,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token2.address, buy: token0.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -3910,7 +3909,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction for token1 const sellAmt1: BigNumber = (await token1.balanceOf(backingManager.address)).mul(pow10(12)) // convert to 18 decimals @@ -3937,7 +3936,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token1.address, buy: token0.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -3964,7 +3963,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction for token3 // We only need now about 11.8 tokens for Token0 to be fully collateralized @@ -3990,7 +3989,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token3.address, buy: token0.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), }) @@ -4024,7 +4023,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close final auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -4175,7 +4174,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -4189,7 +4188,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -4214,7 +4213,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token2.address, buy: backupToken2.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -4248,7 +4247,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction const minBuyAmt3: BigNumber = await toMinBuyAmt(sellAmt3, fp('0.5').div(50), fp('1')) // sell cToken @@ -4280,7 +4279,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token3.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), }) @@ -4314,7 +4313,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction // We need to rebalance our backing, we have an excess of Token1 now and we need more backupToken2 @@ -4347,7 +4346,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token1.address, buy: backupToken2.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('3'), }) @@ -4391,7 +4390,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ { contract: backingManager, @@ -4414,7 +4413,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: backupToken1.address, buy: backupToken2.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('4'), }) @@ -4459,7 +4458,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -4494,7 +4493,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token1.address, buy: backupToken2.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('5'), }) @@ -4517,7 +4516,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close final auction - takes haircut await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -4545,7 +4544,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: backupToken1.address, buy: backupToken2.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('6'), }) @@ -4568,7 +4567,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close final auction - takes haircut await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -4599,31 +4598,17 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Check final state - Haircut taken, stable but price of RToken has been reduced expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(fp('62.5')) - - await expectCurrentBacking({ - tokens: newTokens, - quantities: [ - newQuantities[0].sub(sellAmt4).sub(sellAmt5), - sellAmt0 - .div(2) - .add(sellAmt3.div(50).div(2)) - .sub(sellAmtRebalance) - .sub(sellAmtRebalance2), - sellAmt2 - .div(2) - .add(buyAmt4) - .add(sellAmtRebalance) - .add(sellAmtRebalance2) - .add(minBuyAmt5), - ], - }) - - expect(await rToken.totalSupply()).to.equal(issueAmount) // Check price in USD of the current RToken - Haircut of ~37.52% taken // The default was for 37.5% of backing, so this is pretty awesome const exactRTokenPrice = fp('0.6247979797979798') + const totalAssetValue = issueAmount.mul(exactRTokenPrice).div(fp('1')) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( + totalAssetValue, + totalAssetValue.div(bn('1e6')) + ) + + expect(await rToken.totalSupply()).to.equal(issueAmount) await expectRTokenPrice( rTokenAsset.address, exactRTokenPrice, @@ -4640,11 +4625,24 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(quotes).to.eql(finalQuotes) // Check Backup tokens available - expect(await backupToken1.balanceOf(backingManager.address)).to.equal( - sellAmt0.div(2).add(sellAmt3.div(50).div(2)).sub(sellAmtRebalance).sub(sellAmtRebalance2) + const expBackup1 = sellAmt0 + .div(2) + .add(sellAmt3.div(50).div(2)) + .sub(sellAmtRebalance) + .sub(sellAmtRebalance2) + expect(await backupToken1.balanceOf(backingManager.address)).to.be.closeTo( + expBackup1, + expBackup1.div(bn('1e3')) // 1 part in a thousand ) - expect(await backupToken2.balanceOf(backingManager.address)).to.equal( - sellAmt2.div(2).add(buyAmt4).add(sellAmtRebalance).add(minBuyAmt5).add(sellAmtRebalance2) + const expBackup2 = sellAmt2 + .div(2) + .add(buyAmt4) + .add(sellAmtRebalance) + .add(minBuyAmt5) + .add(sellAmtRebalance2) + expect(await backupToken2.balanceOf(backingManager.address)).to.be.closeTo( + expBackup2, + expBackup2.div(bn('1e3')) // 1 part in a thousand ) }) }) @@ -4682,7 +4680,6 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Register Collateral await assetRegistry.connect(owner).register(backupCollateral1.address) await assetRegistry.connect(owner).register(backupCollateral2.address) - const registeredERC20s = await assetRegistry.erc20s() // Set backup configuration - USDT and aUSDT as backup await basketHandler @@ -4710,13 +4707,13 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Will sell all balance of token2 const sellAmt2 = await token2.balanceOf(backingManager.address) await snapshotGasCost(backingManager.settleTrade(token2.address)) - await snapshotGasCost(backingManager.manageTokens(registeredERC20s)) + await snapshotGasCost(backingManager.rebalance(TradeKind.BATCH_AUCTION)) // Another call should not create any new auctions if still ongoing await expect(backingManager.settleTrade(token2.address)).to.be.revertedWith( 'cannot settle yet' ) - await snapshotGasCost(backingManager.manageTokens(registeredERC20s)) + await snapshotGasCost(backingManager.rebalance(TradeKind.BATCH_AUCTION)) // Perform Mock Bids for the new Token (addr1 has balance) // Get minBuyAmt, we will have now surplus of backupToken1 @@ -4729,7 +4726,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start a new one to sell the new surplus of Backup Token 1 const requiredBkpToken: BigNumber = issueAmount.mul(bkpTokenRefAmt).div(BN_SCALE_FACTOR) @@ -4738,7 +4735,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Run auctions - First Settle trades then Manage Funds await snapshotGasCost(backingManager.settleTrade(token2.address)) - await snapshotGasCost(backingManager.manageTokens(registeredERC20s)) + await snapshotGasCost(backingManager.rebalance(TradeKind.BATCH_AUCTION)) // Perform Mock Bids for the new Token (addr1 has balance) // Assume fair price, get all of them @@ -4750,7 +4747,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start a new one to sell RSR for collateral const buyAmtBidRSR: BigNumber = requiredBkpToken.sub(minBuyAmtBkp1) @@ -4758,7 +4755,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Run auctions - First Settle trades then Manage Funds await snapshotGasCost(backingManager.settleTrade(backupToken1.address)) - await snapshotGasCost(backingManager.manageTokens(registeredERC20s)) + await snapshotGasCost(backingManager.rebalance(TradeKind.BATCH_AUCTION)) // Perform Mock Bids for RSR (addr1 has balance) // Assume fair price RSR = 1 get all of them - Leave a surplus of RSR to be returned @@ -4770,7 +4767,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) expect(await backingManager.tradesOpen()).to.equal(1) // End current auction diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 24156f4b30..4baec598e6 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -11,6 +11,7 @@ import { STRSR_DEST, ZERO_ADDRESS, CollateralStatus, + TradeKind, } from '../common/constants' import { expectEvents } from '../common/events' import { bn, divCeil, fp, near } from '../common/numbers' @@ -380,16 +381,16 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should not trade if paused', async () => { await main.connect(owner).pauseTrading() - await expect(rsrTrader.manageToken(ZERO_ADDRESS)).to.be.revertedWith( - 'frozen or trading paused' - ) + await expect( + rsrTrader.manageToken(ZERO_ADDRESS, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('frozen or trading paused') }) it('Should not trade if frozen', async () => { await main.connect(owner).freezeShort() - await expect(rTokenTrader.manageToken(ZERO_ADDRESS)).to.be.revertedWith( - 'frozen or trading paused' - ) + await expect( + rTokenTrader.manageToken(ZERO_ADDRESS, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('frozen or trading paused') }) it('Should not claim rewards if paused', async () => { @@ -446,7 +447,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const rtokenPrice = await basketHandler.price() const realRtokenPrice = rtokenPrice.low.add(rtokenPrice.high).div(2) const minBuyAmt = await toMinBuyAmt(issueAmount, fp('0.7'), realRtokenPrice) - await expect(rTokenTrader.manageToken(token0.address)) + await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)) .to.emit(rTokenTrader, 'TradeStarted') .withArgs(anyValue, token0.address, rToken.address, issueAmount, withinQuad(minBuyAmt)) }) @@ -454,9 +455,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should not launch revenue auction if UNPRICED', async () => { await advanceTime(ORACLE_TIMEOUT.toString()) await rsr.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await expect(rTokenTrader.manageToken(rsr.address)).to.be.revertedWith( - 'buy asset price unknown' - ) + await expect( + rTokenTrader.manageToken(rsr.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('buy asset price unknown') }) it('Should launch revenue auction if DISABLED with nonzero minBuyAmount', async () => { @@ -465,7 +466,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await advanceTime((await collateral0.delayUntilDefault()).toString()) expect(await collateral0.status()).to.equal(CollateralStatus.DISABLED) await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await expect(rTokenTrader.manageToken(token0.address)).to.emit(rTokenTrader, 'TradeStarted') + await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) // Trade should have extremely nonzero worst-case price const trade = await getTrade(rTokenTrader, token0.address) @@ -535,7 +539,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: compToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -543,7 +547,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: compToken.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -564,7 +568,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expect(rTokenTrader.settleTrade(aaveToken.address)).to.not.emit // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -930,7 +934,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: aaveToken.address, buy: rsr.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.auctionLength), + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -938,7 +942,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: aaveToken.address, buy: rToken.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.auctionLength), + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -946,7 +950,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await aaveToken.balanceOf(gnosis.address)).to.equal(rewardAmountAAVE) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction by minting the buy tokens (in this case RSR and RToken) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -1089,7 +1093,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: compToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1120,7 +1124,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions const remainderSellAmt = rewardAmountCOMP.sub(sellAmt) @@ -1156,7 +1160,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: compToken.address, buy: rsr.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.auctionLength), + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -1173,7 +1177,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close auctions await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1291,7 +1295,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: aaveToken.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1312,7 +1316,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Another call will create a new auction and close existing await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1347,7 +1351,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: aaveToken.address, buy: rToken.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.auctionLength), + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -1360,7 +1364,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1392,7 +1396,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // StRSR expect(await rsr.balanceOf(stRSR.address)).to.equal(0) // Furnace - expect(await rToken.balanceOf(furnace.address)).to.equal(minBuyAmt.add(minBuyAmtRemainder)) + expect(await rToken.balanceOf(furnace.address)).to.be.closeTo( + minBuyAmt.add(minBuyAmtRemainder), + minBuyAmt.add(minBuyAmtRemainder).div(bn('1e4')) // melting + ) }) it('Should handle large auctions using maxTradeVolume with revenue split RSR/RToken', async () => { @@ -1495,7 +1502,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: compToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1503,12 +1510,12 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: compToken.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) // Advance time till auctions ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -1575,7 +1582,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Run final auction until all funds are converted // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, minBuyAmtRemainder) @@ -1610,7 +1617,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { minBuyAmt.add(minBuyAmtRemainder), 15 ) - expect(await rToken.balanceOf(furnace.address)).to.be.closeTo(minBuyAmtRToken, 15) + expect(await rToken.balanceOf(furnace.address)).to.be.closeTo( + minBuyAmtRToken, + minBuyAmtRToken.div(bn('1e4')) // melting + ) }) it('Should not distribute if paused or frozen', async () => { @@ -1798,7 +1808,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await compToken.balanceOf(rTokenTrader.address)).to.equal(0) // Handout COMP tokens to Traders - await backingManager.manageTokens([compToken.address]) + await backingManager.forwardRevenue([compToken.address]) // Check funds sent to traders expect(await compToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader) @@ -1808,9 +1818,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await setOraclePrice(rsrAsset.address, bn('0')) // Should revert - await expect(rsrTrader.manageToken(compToken.address)).to.be.revertedWith( - 'buy asset price unknown' - ) + await expect( + rsrTrader.manageToken(compToken.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('buy asset price unknown') // Funds still in Trader expect(await compToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader) @@ -1880,7 +1890,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: aaveToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1888,12 +1898,12 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: aaveToken.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) // In order to force deactivation we provide an amount below minBuyAmt, this will represent for our tests an invalid behavior although in a real scenario would retrigger auction @@ -1991,11 +2001,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const sellAmtRToken: BigNumber = rewardAmountAAVE.sub(sellAmt) // Remainder // Attempt to run auctions - await backingManager.manageTokens([aaveToken.address]) - await expect(rsrTrader.manageToken(aaveToken.address)).to.be.revertedWith('broker disabled') - await expect(rTokenTrader.manageToken(aaveToken.address)).to.be.revertedWith( - 'broker disabled' - ) + await backingManager.forwardRevenue([aaveToken.address]) + await expect( + rsrTrader.manageToken(aaveToken.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('broker disabled') + await expect( + rTokenTrader.manageToken(aaveToken.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('broker disabled') // Check funds - remain in traders expect(await rsr.balanceOf(stRSR.address)).to.equal(0) @@ -2119,7 +2131,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: compToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2127,7 +2139,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: compToken.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -2135,7 +2147,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await compToken.balanceOf(gnosis.address)).to.equal(rewardAmountCOMP) // Advance time till auctions ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -2420,7 +2432,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: token2.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2428,7 +2440,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: token2.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -2440,7 +2452,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction by minting the buy tokens (in this case RSR and RToken) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -2580,7 +2592,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: token2.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2588,7 +2600,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: token2.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -2603,7 +2615,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await token2.balanceOf(rTokenTrader.address)).to.equal(0) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction by minting the buy tokens (in this case RSR and RToken) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -2669,11 +2681,11 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await token2.setExchangeRate(fp('1.2')) await expect( - backingManager.manageTokens([token2.address, token2.address]) + backingManager.forwardRevenue([token2.address, token2.address]) ).to.be.revertedWith('duplicate tokens') await expect( - backingManager.manageTokens([ + backingManager.forwardRevenue([ token2.address, token2.address, token2.address, @@ -2682,11 +2694,11 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ).to.be.revertedWith('duplicate tokens') await expect( - backingManager.manageTokens([token2.address, token1.address, token2.address]) + backingManager.forwardRevenue([token2.address, token1.address, token2.address]) ).to.be.revertedWith('duplicate tokens') await expect( - backingManager.manageTokens([ + backingManager.forwardRevenue([ token1.address, token2.address, token3.address, @@ -2695,7 +2707,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ).to.be.revertedWith('duplicate tokens') await expect( - backingManager.manageTokens([ + backingManager.forwardRevenue([ token1.address, token2.address, token3.address, @@ -2704,66 +2716,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ).to.be.revertedWith('duplicate tokens') // Remove duplicates, should work - await expect(backingManager.manageTokens([token1.address, token2.address, token3.address])) - .to.not.be.reverted - }) - - it('Should not overspend if backingManager.manageTokensSortedOrder() is called with duplicate tokens', async () => { - expect(await basketHandler.fullyCollateralized()).to.be.true - - // Change redemption rate for AToken and CToken to double - await token2.setExchangeRate(fp('1.2')) - await expect( - backingManager.manageTokensSortedOrder([token2.address, token2.address]) - ).to.be.revertedWith('duplicate/unsorted tokens') - - await expect( - backingManager.manageTokensSortedOrder([ - token2.address, - token2.address, - token2.address, - token2.address, - ]) - ).to.be.revertedWith('duplicate/unsorted tokens') - - await expect( - backingManager.manageTokensSortedOrder([token2.address, token1.address, token2.address]) - ).to.be.revertedWith('duplicate/unsorted tokens') - - await expect( - backingManager.manageTokensSortedOrder([ - token1.address, - token2.address, - token3.address, - token2.address, - ]) - ).to.be.revertedWith('duplicate/unsorted tokens') - - await expect( - backingManager.manageTokensSortedOrder([ - token1.address, - token2.address, - token3.address, - token3.address, - ]) - ).to.be.revertedWith('duplicate/unsorted tokens') - - // Remove duplicates but unsort - const sorted = [token1.address, token2.address, token3.address].sort((a, b) => { - const x = BigNumber.from(a) - const y = BigNumber.from(b) - if (x.lt(y)) return -1 - if (x.gt(y)) return 1 - return 0 - }) - const unsorted = [sorted[2], sorted[0], sorted[1]] - await expect(backingManager.manageTokensSortedOrder(unsorted)).to.be.revertedWith( - 'duplicate/unsorted tokens' - ) - - // Remove duplicates and sort, should work - await expect(backingManager.manageTokensSortedOrder(sorted)).to.not.be.reverted + backingManager.forwardRevenue([token1.address, token2.address, token3.address]) + ).to.not.be.reverted }) it('Should mint RTokens when collateral appreciates and handle revenue auction correctly - Even quantity', async () => { @@ -2838,7 +2793,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: rToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2851,7 +2806,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction - will not start new one await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -3025,7 +2980,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: rToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -3033,7 +2988,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: token2.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -3041,7 +2996,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: token2.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), }) @@ -3069,7 +3024,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start a new one with same amount await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -3167,7 +3122,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await token2.connect(owner).mint(backingManager.address, mintAmt) await token3.connect(owner).mint(backingManager.address, mintAmt) - await expect(backingManager.manageTokens([])).revertedWith('BU rate out of range') + await expect(backingManager.forwardRevenue([])).revertedWith('BU rate out of range') }) }) }) @@ -3270,12 +3225,12 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await backingManager.claimRewards() // Manage Funds - await backingManager.manageTokens([compToken.address]) - await snapshotGasCost(rsrTrader.manageToken(compToken.address)) - await snapshotGasCost(rTokenTrader.manageToken(compToken.address)) + await backingManager.forwardRevenue([compToken.address]) + await snapshotGasCost(rsrTrader.manageToken(compToken.address, TradeKind.BATCH_AUCTION)) + await snapshotGasCost(rTokenTrader.manageToken(compToken.address, TradeKind.BATCH_AUCTION)) // Advance time till auctions ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -3302,12 +3257,12 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await snapshotGasCost(rTokenTrader.settleTrade(compToken.address)) // Manage Funds - await snapshotGasCost(rsrTrader.manageToken(compToken.address)) - await snapshotGasCost(rTokenTrader.manageToken(compToken.address)) + await snapshotGasCost(rsrTrader.manageToken(compToken.address, TradeKind.BATCH_AUCTION)) + await snapshotGasCost(rTokenTrader.manageToken(compToken.address, TradeKind.BATCH_AUCTION)) // Run final auction until all funds are converted // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, minBuyAmtRemainder) diff --git a/test/Upgradeability.test.ts b/test/Upgradeability.test.ts index 6f8f3a9721..f72ce7e462 100644 --- a/test/Upgradeability.test.ts +++ b/test/Upgradeability.test.ts @@ -18,6 +18,7 @@ import { BrokerP1V2, DistributorP1, DistributorP1V2, + DutchTrade, ERC20Mock, FurnaceP1, FurnaceP1V2, @@ -86,7 +87,8 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { let BasketHandlerFactory: ContractFactory let DistributorFactory: ContractFactory let BrokerFactory: ContractFactory - let TradeFactory: ContractFactory + let GnosisTradeFactory: ContractFactory + let DutchTradeFactory: ContractFactory let StRSRFactory: ContractFactory let notWallet: Wallet @@ -138,7 +140,8 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { BasketHandlerFactory = await ethers.getContractFactory('BasketHandlerP1') DistributorFactory = await ethers.getContractFactory('DistributorP1') BrokerFactory = await ethers.getContractFactory('BrokerP1') - TradeFactory = await ethers.getContractFactory('GnosisTrade') + GnosisTradeFactory = await ethers.getContractFactory('GnosisTrade') + DutchTradeFactory = await ethers.getContractFactory('DutchTrade') StRSRFactory = await ethers.getContractFactory('StRSRP1Votes') // Import deployed proxies @@ -249,11 +252,19 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { }) it('Should deploy valid implementation - Broker / Trade', async () => { - const trade: GnosisTrade = await TradeFactory.deploy() + const gnosisTrade: GnosisTrade = await GnosisTradeFactory.deploy() + const dutchTrade: DutchTrade = await DutchTradeFactory.deploy() const newBroker: BrokerP1 = await upgrades.deployProxy( BrokerFactory, - [main.address, gnosis.address, trade.address, config.auctionLength], + [ + main.address, + gnosis.address, + gnosisTrade.address, + config.batchAuctionLength, + dutchTrade.address, + config.dutchAuctionLength, + ], { initializer: 'init', kind: 'uups', @@ -262,7 +273,8 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { await newBroker.deployed() expect(await newBroker.gnosis()).to.equal(gnosis.address) - expect(await newBroker.auctionLength()).to.equal(config.auctionLength) + expect(await newBroker.batchAuctionLength()).to.equal(config.batchAuctionLength) + expect(await newBroker.dutchAuctionLength()).to.equal(config.dutchAuctionLength) expect(await newBroker.disabled()).to.equal(false) expect(await newBroker.main()).to.equal(main.address) }) @@ -519,7 +531,7 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { // Check state is preserved expect(await brokerV2.gnosis()).to.equal(gnosis.address) - expect(await brokerV2.auctionLength()).to.equal(config.auctionLength) + expect(await brokerV2.batchAuctionLength()).to.equal(config.batchAuctionLength) expect(await brokerV2.disabled()).to.equal(false) expect(await brokerV2.main()).to.equal(main.address) diff --git a/test/ZTradingExtremes.test.ts b/test/ZTradingExtremes.test.ts index 2eff82ffa9..7250cab251 100644 --- a/test/ZTradingExtremes.test.ts +++ b/test/ZTradingExtremes.test.ts @@ -277,7 +277,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, } // Advance time till auction ends - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) } } @@ -346,7 +346,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, // Mint any excess possible before increasing exchange rate to avoid blowing through max BU exchange rate // Explanation: For low-decimal tokens it's possible to begin overcollateralized when // the amount transferred in on RToken minting is 1 attoToken - await backingManager.manageTokens([]) + await backingManager.forwardRevenue([]) // === Execution === @@ -575,7 +575,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, } // Advance time till auction ends - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) uncollateralized = !(await basketHandler.fullyCollateralized()) } diff --git a/test/fixtures.ts b/test/fixtures.ts index 7b578a1398..eadc142d4c 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -20,6 +20,7 @@ import { ERC20Mock, DeployerP0, DeployerP1, + DutchTrade, FacadeRead, FacadeAct, FacadeTest, @@ -412,7 +413,8 @@ export const defaultFixture: Fixture = async function (): Promis unstakingDelay: bn('1209600'), // 2 weeks warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) - auctionLength: bn('900'), // 15 minutes + batchAuctionLength: bn('900'), // 15 minutes + dutchAuctionLength: bn('600'), // 10 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { @@ -515,8 +517,11 @@ export const defaultFixture: Fixture = async function (): Promis const FurnaceImplFactory: ContractFactory = await ethers.getContractFactory('FurnaceP1') const furnaceImpl: FurnaceP1 = await FurnaceImplFactory.deploy() - const TradeImplFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const tradeImpl: GnosisTrade = await TradeImplFactory.deploy() + const GnosisTradeImplFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const gnosisTrade: GnosisTrade = await GnosisTradeImplFactory.deploy() + + const DutchTradeImplFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + const dutchTrade: DutchTrade = await DutchTradeImplFactory.deploy() const BrokerImplFactory: ContractFactory = await ethers.getContractFactory('BrokerP1') const brokerImpl: BrokerP1 = await BrokerImplFactory.deploy() @@ -542,7 +547,10 @@ export const defaultFixture: Fixture = async function (): Promis rsrTrader: revTraderImpl.address, rTokenTrader: revTraderImpl.address, }, - trade: tradeImpl.address, + trading: { + gnosisTrade: gnosisTrade.address, + dutchTrade: dutchTrade.address, + }, } const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP1') From 5edf60256b2980e986a32e11d3c62741a30349de Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 27 Apr 2023 20:52:55 -0400 Subject: [PATCH 115/462] docs/system-design.md --- docs/system-design.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/system-design.md b/docs/system-design.md index 33d6581233..ea7e42927b 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -158,7 +158,7 @@ The minimum sized trade that can be performed, in terms of the unit of account. Setting this too high will result in auctions happening infrequently or the RToken taking a haircut when it cannot be sure it has enough staked RSR to succeed in rebalancing at par. -Setting this too low may allow griefers to delay important auctions. The variable should be set such that donations of size `minTradeVolume` would be worth delaying trading `auctionLength` seconds. +Setting this too low may allow griefers to delay important auctions. The variable should be set such that donations of size `minTradeVolume` would be worth delaying trading `batchAuctionLength` seconds. This variable should NOT be interpreted to mean that auction sizes above this value will necessarily clear. It could be the case that gas frictions are so high that auctions launched at this size are not worthy of bids. @@ -217,7 +217,7 @@ The warmup period is how many seconds should pass after the basket regained the Default value: `900` = 15 minutes Mainnet reasonable range: 0 to 604800 -### `auctionLength` +### `batchAuctionLength` Dimension: `{seconds}` @@ -226,11 +226,20 @@ The auction length is how many seconds long Gnosis EasyAuctions should be. Default value: `900` = 15 minutes Mainnet reasonable range: 60 to 3600 +### `dutchAuctionLength` + +Dimension: `{seconds}` + +The dutch auction length is how many seconds long falling-price dutch auctions should be. A longer period will result in less slippage due to better price granularity. + +Default value: `600` = 10 minutes +Mainnet reasonable range: 120 to 3600 + ### `backingBuffer` Dimension: `{1}` -The backing buffer is a percentage value that describes how much additional collateral tokens to keep in the BackingManager before forwarding tokens to the RevenueTraders. This buffer allows collateral tokens to be periodically converted into the RToken, which is a more efficient form of revenue production than trading each individual collateral for the desired RToken. It also adds a small buffer that can prevent RSR from being seized when there are small losses due to slippage during rebalancing. +The backing buffer is a percentage value that describes how much overcollateralization to hold in the form of RToken. This buffer allows collateral tokens to be converted into RToken, which is a more efficient form of revenue production than trading each individual collateral for the desired RToken, and also adds a small buffer that can prevent RSR from being seized when there are small losses due to slippage during rebalancing. Default value: `1e15` = 0.1% Mainnet reasonable range: 1e12 to 1e18 From b0b8d2429ef5658d2732cd83f3bf824685f6f784 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 27 Apr 2023 21:28:56 -0400 Subject: [PATCH 116/462] scenario tests --- contracts/p0/RevenueTrader.sol | 5 +- contracts/p1/RevenueTrader.sol | 5 +- test/scenario/BadCollateralPlugin.test.ts | 17 +++---- test/scenario/BadERC20.test.ts | 22 ++++++--- test/scenario/ComplexBasket.test.ts | 56 +++++++++++++---------- test/scenario/EURT.test.ts | 26 +++++++---- test/scenario/MaxBasketSize.test.ts | 4 +- test/scenario/NestedRTokens.test.ts | 14 +++--- test/scenario/NontrivialPeg.test.ts | 13 +++--- test/scenario/RevenueHiding.test.ts | 14 ++++-- test/scenario/SetProtocol.test.ts | 14 +++--- test/scenario/WBTC.test.ts | 28 ++++++++---- test/scenario/WETH.test.ts | 42 +++++++++++------ test/scenario/cETH.test.ts | 44 ++++++++++++------ test/scenario/cWBTC.test.ts | 41 +++++++++++------ 15 files changed, 212 insertions(+), 133 deletions(-) diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index 04617bd45f..fec1c0233c 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -94,8 +94,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { maxTradeSlippage ); - if (launch) { - tryTrade(kind, req); - } + require(launch, "trade not worth launching"); + tryTrade(kind, req); } } diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index d30fb6e8af..d06959374f 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -121,9 +121,8 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { maxTradeSlippage ); - if (launch) { - tryTrade(kind, req); - } + require(launch, "trade not worth launching"); + tryTrade(kind, req); } /// Call after upgrade to >= 3.0.0 diff --git a/test/scenario/BadCollateralPlugin.test.ts b/test/scenario/BadCollateralPlugin.test.ts index 2202378e99..01892c2429 100644 --- a/test/scenario/BadCollateralPlugin.test.ts +++ b/test/scenario/BadCollateralPlugin.test.ts @@ -5,7 +5,7 @@ import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { IConfig } from '../../common/configuration' import { expectEvents } from '../../common/events' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { bn, fp, divCeil } from '../../common/numbers' import { BadCollateralPlugin, @@ -190,7 +190,10 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { }) it('should use RSR to recollateralize, breaking the economic model fundamentally', async () => { - await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) const trade = await getTrade(backingManager, rsr.address) expect(await trade.sell()).to.equal(rsr.address) expect(await trade.buy()).to.equal(token0.address) @@ -265,12 +268,10 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { expect(await basketHandler.fullyCollateralized()).to.equal(true) // Should not launch auctions or create revenue - await expectEvents(backingManager.manageTokens([token0.address]), [ - { - contract: backingManager, - name: 'TradeStarted', - emitted: false, - }, + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' + ) + await expectEvents(backingManager.forwardRevenue([token0.address]), [ { contract: token0, name: 'Transfer', diff --git a/test/scenario/BadERC20.test.ts b/test/scenario/BadERC20.test.ts index f9ba9e8762..0b1b604252 100644 --- a/test/scenario/BadERC20.test.ts +++ b/test/scenario/BadERC20.test.ts @@ -5,6 +5,7 @@ import { expect } from 'chai' import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { IConfig } from '../../common/configuration' +import { TradeKind } from '../../common/constants' import { bn, divCeil, fp } from '../../common/numbers' import { BadERC20, @@ -216,7 +217,8 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { await expect(basketHandler.refreshBasket()) .to.emit(basketHandler, 'BasketSet') .withArgs(3, [backupToken.address], [fp('1')], false) - await expect(backingManager.manageTokens([])).to.be.reverted // can't catch No Decimals + await expect(backingManager.forwardRevenue([])).to.be.reverted // can't catch No Decimals + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.reverted // can't catch No Decimals }) it('should keep collateral working', async () => { @@ -254,7 +256,10 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) - await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) // Should be trading RSR for backup token const trade = await getTrade(backingManager, rsr.address) @@ -318,11 +323,11 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) - await expect(backingManager.manageTokens([])).to.be.revertedWith('censored') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith('censored') // Should work now await token0.setCensored(backingManager.address, false) - await backingManager.manageTokens([]) + await backingManager.rebalance(TradeKind.BATCH_AUCTION) }) it('should keep collateral working', async () => { @@ -364,7 +369,10 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) - await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) // Should be trading RSR for backup token const trade = await getTrade(backingManager, rsr.address) @@ -376,10 +384,10 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { it('should be able to process any uncensored assets already accumulated at RevenueTraders', async () => { await rToken.connect(addr1).transfer(rTokenTrader.address, issueAmt.div(2)) await rToken.connect(addr1).transfer(rsrTrader.address, issueAmt.div(2)) - await expect(rTokenTrader.manageToken(rToken.address)) + await expect(rTokenTrader.manageToken(rToken.address, TradeKind.BATCH_AUCTION)) .to.emit(rToken, 'Transfer') .withArgs(rTokenTrader.address, furnace.address, issueAmt.div(2)) - await expect(rsrTrader.manageToken(rToken.address)) + await expect(rsrTrader.manageToken(rToken.address, TradeKind.BATCH_AUCTION)) .to.emit(rsrTrader, 'TradeStarted') .withArgs( anyValue, diff --git a/test/scenario/ComplexBasket.test.ts b/test/scenario/ComplexBasket.test.ts index 6aac8837ed..1dd55724d9 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -603,7 +603,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: compToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -611,7 +611,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await compToken.balanceOf(gnosis.address)).to.equal(rewardAmount) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -855,7 +855,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: cUSDToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -870,7 +870,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: cUSDToken.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -911,7 +911,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auctions await rsr.connect(addr1).approve(gnosis.address, auctionbuyAmt2) @@ -1017,7 +1017,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: cWBTC.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('6'), }) @@ -1032,7 +1032,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: cWBTC.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('7'), }) @@ -1054,7 +1054,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auctions await rsr.connect(addr1).approve(gnosis.address, auctionbuyAmt5) @@ -1139,7 +1139,10 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { origAssetValue, point5Pct(origAssetValue) ) - expect(await rToken.totalSupply()).to.equal(currentTotalSupply) + expect(await rToken.totalSupply()).to.be.closeTo( + currentTotalSupply, + currentTotalSupply.div(bn('1e9')) // within 1 billionth + ) // Check destinations at this stage - RSR and RTokens already in StRSR and Furnace expect(await rsr.balanceOf(stRSR.address)).to.be.closeTo( @@ -1164,7 +1167,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: cETH.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('10'), }) @@ -1179,7 +1182,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: cETH.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('11'), }) @@ -1201,7 +1204,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auctions await rsr.connect(addr1).approve(gnosis.address, auctionbuyAmt7) @@ -1256,7 +1259,10 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { origAssetValue, point5Pct(origAssetValue) ) - expect(await rToken.totalSupply()).to.equal(currentTotalSupply) + expect(await rToken.totalSupply()).to.be.closeTo( + currentTotalSupply, + currentTotalSupply.div(bn('1e5')) + ) // Check destinations at this stage - RSR and RTokens already in StRSR and Furnace expect(await rsr.balanceOf(stRSR.address)).to.be.closeTo( @@ -1366,7 +1372,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: cWBTC.address, buy: wbtc.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1381,7 +1387,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await cWBTC.balanceOf(backingManager.address)).to.equal(bn(0)) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction - Get 80% of value // 1600 cWTBC -> 80% = 1280 cWBTC @ 400 = 512K = 25 BTC @@ -1441,7 +1447,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: wbtc.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -1456,7 +1462,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await rsr.balanceOf(gnosis.address)).to.be.closeTo(sellAmtRSR, point5Pct(sellAmtRSR)) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction - Get all tokens await wbtc.connect(addr1).approve(gnosis.address, auctionBuyAmtRSR) @@ -1599,7 +1605,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: cETH.address, buy: weth.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1617,7 +1623,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction - Get 8.2K ETH const auctionbuyAmt = fp('8200') @@ -1664,7 +1670,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: cETH.address, buy: weth.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -1685,7 +1691,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await cETH.balanceOf(backingManager.address)).to.equal(bn(0)) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction // 438,000 cETH @ 12 = 5.25 M = approx 4255 ETH - Get 4400 WETH @@ -1759,7 +1765,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: weth.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), }) @@ -1771,7 +1777,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(auctionBuyAmtRSR1).to.be.closeTo(buyAmtBidRSR1, point5Pct(buyAmtBidRSR1)) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction - Get 8500 WETH tokens // const auctionbuyAmtRSR1 = fp('8500') @@ -1843,7 +1849,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: weth.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('3'), }) @@ -1855,7 +1861,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(auctionBuyAmtRSR2).to.be.closeTo(buyAmtRSR2, point5Pct(buyAmtRSR2)) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction - Get all tokens await weth.connect(addr1).approve(gnosis.address, auctionBuyAmtRSR2) diff --git a/test/scenario/EURT.test.ts b/test/scenario/EURT.test.ts index 46059f966e..2da3a3c75d 100644 --- a/test/scenario/EURT.test.ts +++ b/test/scenario/EURT.test.ts @@ -6,7 +6,7 @@ import { ethers } from 'hardhat' import { bn, fp } from '../../common/numbers' import { advanceTime } from '../utils/time' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { ERC20Mock, EURFiatCollateral, @@ -156,22 +156,32 @@ describe(`EUR fiatcoins (eg EURT) - P${IMPLEMENTATION}`, () => { it('should sell appreciating stable collateral and ignore eurt', async () => { await token0.setExchangeRate(fp('1.1')) // 10% appreciation - await expect(backingManager.manageTokens([token0.address])).to.not.emit( - backingManager, - 'TradeStarted' + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) + await backingManager.forwardRevenue([eurt.address, token0.address]) expect(await eurt.balanceOf(rTokenTrader.address)).to.equal(0) expect(await eurt.balanceOf(rsrTrader.address)).to.equal(0) - await expect(rTokenTrader.manageToken(eurt.address)).to.not.emit(rTokenTrader, 'TradeStarted') - await expect(rTokenTrader.manageToken(token0.address)).to.emit(rTokenTrader, 'TradeStarted') + await expect( + rTokenTrader.manageToken(eurt.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('0 balance') + await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) // RTokenTrader should be selling token0 and buying RToken const trade = await getTrade(rTokenTrader, token0.address) expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(rToken.address) - await expect(rsrTrader.manageToken(eurt.address)).to.not.emit(rsrTrader, 'TradeStarted') - await expect(rsrTrader.manageToken(token0.address)).to.emit(rsrTrader, 'TradeStarted') + await expect(rsrTrader.manageToken(eurt.address, TradeKind.BATCH_AUCTION)).to.be.revertedWith( + '0 balance' + ) + await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rsrTrader, + 'TradeStarted' + ) // RSRTrader should be selling token0 and buying RToken const trade2 = await getTrade(rsrTrader, token0.address) diff --git a/test/scenario/MaxBasketSize.test.ts b/test/scenario/MaxBasketSize.test.ts index 1bb50d0ac1..7f2ce9377b 100644 --- a/test/scenario/MaxBasketSize.test.ts +++ b/test/scenario/MaxBasketSize.test.ts @@ -419,7 +419,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: firstDefaultedToken.address, buy: backing[0], - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) }) @@ -557,7 +557,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: firstDefaultedToken.address, buy: backing[0], - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) }) diff --git a/test/scenario/NestedRTokens.test.ts b/test/scenario/NestedRTokens.test.ts index e75eb3859b..1645114118 100644 --- a/test/scenario/NestedRTokens.test.ts +++ b/test/scenario/NestedRTokens.test.ts @@ -3,7 +3,7 @@ import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { ethers } from 'hardhat' -import { ONE_PERIOD, ZERO_ADDRESS, CollateralStatus } from '../../common/constants' +import { ONE_PERIOD, ZERO_ADDRESS, CollateralStatus, TradeKind } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { withinQuad } from '../utils/matchers' import { toSellAmt, toMinBuyAmt } from '../utils/trades' @@ -207,9 +207,8 @@ describe(`Nested RTokens - P${IMPLEMENTATION}`, () => { await two.assetRegistry.refresh() expect(await two.basketHandler.fullyCollateralized()).to.equal(true) expect(await two.basketHandler.status()).to.equal(CollateralStatus.SOUND) - await expect(two.backingManager.manageTokens([])).to.not.emit( - two.backingManager, - 'TradeStarted' + await expect(two.backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) // Launch recollateralization auction in inner RToken @@ -221,7 +220,7 @@ describe(`Nested RTokens - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, await one.backingManager.maxTradeSlippage() ) - await expect(one.backingManager.manageTokens([])) + await expect(one.backingManager.rebalance(TradeKind.BATCH_AUCTION)) .to.emit(one.backingManager, 'TradeStarted') .withArgs( anyValue, @@ -235,9 +234,8 @@ describe(`Nested RTokens - P${IMPLEMENTATION}`, () => { await two.assetRegistry.refresh() expect(await two.basketHandler.fullyCollateralized()).to.equal(true) expect(await two.basketHandler.status()).to.equal(CollateralStatus.SOUND) - await expect(two.backingManager.manageTokens([])).to.not.emit( - two.backingManager, - 'TradeStarted' + await expect(two.backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) // Prices should be aware diff --git a/test/scenario/NontrivialPeg.test.ts b/test/scenario/NontrivialPeg.test.ts index 4c4d229560..2cc2d1ab1b 100644 --- a/test/scenario/NontrivialPeg.test.ts +++ b/test/scenario/NontrivialPeg.test.ts @@ -3,7 +3,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { ethers } from 'hardhat' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { expectRTokenPrice, setOraclePrice } from '../utils/oracles' import { expectEvents } from '../../common/events' @@ -171,16 +171,14 @@ describe(`The peg (target/ref) should be arbitrary - P${IMPLEMENTATION}`, () => await backingManager.maxTradeSlippage(), config.minTradeVolume.mul((await assetRegistry.erc20s()).length) ) + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' + ) await expectEvents( backingManager .connect(owner) - .manageTokens([token0.address, token1.address, rsr.address, rToken.address]), + .forwardRevenue([token0.address, token1.address, rsr.address, rToken.address]), [ - { - contract: backingManager, - name: 'TradeStarted', - emitted: false, - }, { contract: token0, name: 'Transfer', @@ -203,6 +201,7 @@ describe(`The peg (target/ref) should be arbitrary - P${IMPLEMENTATION}`, () => }, ] ) + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(true) // sum of target amounts diff --git a/test/scenario/RevenueHiding.test.ts b/test/scenario/RevenueHiding.test.ts index 4eecf8a61a..7e99757112 100644 --- a/test/scenario/RevenueHiding.test.ts +++ b/test/scenario/RevenueHiding.test.ts @@ -5,7 +5,7 @@ import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { bn, fp, divCeil } from '../../common/numbers' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { CTokenMock, CTokenFiatCollateral, @@ -257,9 +257,15 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME it('auction should be launched at low price ignoring revenueHiding', async () => { // Double exchange rate and launch auctions await cDAI.setExchangeRate(fp('2')) // double rate - await backingManager.manageTokens([cDAI.address]) // transfers tokens to Traders - await expect(rTokenTrader.manageToken(cDAI.address)).to.emit(rTokenTrader, 'TradeStarted') - await expect(rsrTrader.manageToken(cDAI.address)).to.emit(rsrTrader, 'TradeStarted') + await backingManager.forwardRevenue([cDAI.address]) // transfers tokens to Traders + await expect(rTokenTrader.manageToken(cDAI.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) + await expect(rsrTrader.manageToken(cDAI.address, TradeKind.BATCH_AUCTION)).to.emit( + rsrTrader, + 'TradeStarted' + ) // Auctions launched should be at discounted low price const t = await getTrade(rsrTrader, cDAI.address) diff --git a/test/scenario/SetProtocol.test.ts b/test/scenario/SetProtocol.test.ts index 614e7fc29f..e0d944e3e1 100644 --- a/test/scenario/SetProtocol.test.ts +++ b/test/scenario/SetProtocol.test.ts @@ -4,7 +4,7 @@ import { expect } from 'chai' import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { expectPrice, setOraclePrice } from '../utils/oracles' import { expectEvents } from '../../common/events' @@ -175,10 +175,13 @@ describe(`Linear combination of self-referential collateral - P${IMPLEMENTATION} await expectPrice(basketHandler.address, price, ORACLE_ERROR, true) expect(await basketHandler.fullyCollateralized()).to.equal(true) expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' + ) await expectEvents( backingManager .connect(owner) - .manageTokens([ + .forwardRevenue([ token0.address, token1.address, token2.address, @@ -186,11 +189,6 @@ describe(`Linear combination of self-referential collateral - P${IMPLEMENTATION} rToken.address, ]), [ - { - contract: backingManager, - name: 'TradeStarted', - emitted: false, - }, { contract: token0, name: 'Transfer', @@ -257,7 +255,7 @@ describe(`Linear combination of self-referential collateral - P${IMPLEMENTATION} // Should send donated token to revenue traders await expectEvents( - backingManager.manageTokens([ + backingManager.forwardRevenue([ token0.address, token1.address, token2.address, diff --git a/test/scenario/WBTC.test.ts b/test/scenario/WBTC.test.ts index 09781a15a3..da26e50680 100644 --- a/test/scenario/WBTC.test.ts +++ b/test/scenario/WBTC.test.ts @@ -6,7 +6,7 @@ import { ethers } from 'hardhat' import { bn, fp } from '../../common/numbers' import { advanceTime } from '../utils/time' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { ERC20Mock, IAssetRegistry, @@ -170,22 +170,32 @@ describe(`Non-fiat collateral (eg WBTC) - P${IMPLEMENTATION}`, () => { it('should sell appreciating stable collateral and ignore wbtc', async () => { await token0.setExchangeRate(fp('1.1')) // 10% appreciation - await expect(backingManager.manageTokens([token0.address])).to.not.emit( - backingManager, - 'TradeStarted' + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) + await backingManager.forwardRevenue([wbtc.address, token0.address]) expect(await wbtc.balanceOf(rTokenTrader.address)).to.equal(0) expect(await wbtc.balanceOf(rsrTrader.address)).to.equal(0) - await expect(rTokenTrader.manageToken(wbtc.address)).to.not.emit(rTokenTrader, 'TradeStarted') - await expect(rTokenTrader.manageToken(token0.address)).to.emit(rTokenTrader, 'TradeStarted') + await expect( + rTokenTrader.manageToken(wbtc.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('0 balance') + await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) // RTokenTrader should be selling token0 and buying RToken const trade = await getTrade(rTokenTrader, token0.address) expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(rToken.address) - await expect(rsrTrader.manageToken(wbtc.address)).to.not.emit(rsrTrader, 'TradeStarted') - await expect(rsrTrader.manageToken(token0.address)).to.emit(rsrTrader, 'TradeStarted') + await expect(rsrTrader.manageToken(wbtc.address, TradeKind.BATCH_AUCTION)).to.be.revertedWith( + '0 balance' + ) + await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rsrTrader, + 'TradeStarted' + ) // RSRTrader should be selling token0 and buying RToken const trade2 = await getTrade(rsrTrader, token0.address) @@ -200,7 +210,7 @@ describe(`Non-fiat collateral (eg WBTC) - P${IMPLEMENTATION}`, () => { // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) - await expect(backingManager.manageTokens([token0.address, wbtc.address])).to.emit( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( backingManager, 'TradeStarted' ) diff --git a/test/scenario/WETH.test.ts b/test/scenario/WETH.test.ts index 0d12c375fc..0e7015cedd 100644 --- a/test/scenario/WETH.test.ts +++ b/test/scenario/WETH.test.ts @@ -5,7 +5,7 @@ import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { bn, fp } from '../../common/numbers' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { CTokenMock, SelfReferentialCollateral, @@ -166,22 +166,32 @@ describe(`Self-referential collateral (eg ETH via WETH) - P${IMPLEMENTATION}`, ( it('should sell appreciating collateral and ignore WETH', async () => { await token0.setExchangeRate(fp('1.1')) // 10% appreciation - await expect(backingManager.manageTokens([token0.address])).to.not.emit( - backingManager, - 'TradeStarted' + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) + await backingManager.forwardRevenue([weth.address, token0.address]) expect(await weth.balanceOf(rTokenTrader.address)).to.equal(0) expect(await weth.balanceOf(rsrTrader.address)).to.equal(0) - await expect(rTokenTrader.manageToken(weth.address)).to.not.emit(rTokenTrader, 'TradeStarted') - await expect(rTokenTrader.manageToken(token0.address)).to.emit(rTokenTrader, 'TradeStarted') + await expect( + rTokenTrader.manageToken(weth.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('0 balance') + await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) // RTokenTrader should be selling token0 and buying RToken const trade = await getTrade(rTokenTrader, token0.address) expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(rToken.address) - await expect(rsrTrader.manageToken(weth.address)).to.not.emit(rsrTrader, 'TradeStarted') - await expect(rsrTrader.manageToken(token0.address)).to.emit(rsrTrader, 'TradeStarted') + await expect(rsrTrader.manageToken(weth.address, TradeKind.BATCH_AUCTION)).to.be.revertedWith( + '0 balance' + ) + await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rsrTrader, + 'TradeStarted' + ) // RSRTrader should be selling token0 and buying RToken const trade2 = await getTrade(rsrTrader, token0.address) @@ -196,7 +206,7 @@ describe(`Self-referential collateral (eg ETH via WETH) - P${IMPLEMENTATION}`, ( // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) - await expect(backingManager.manageTokens([token0.address, weth.address])).to.emit( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( backingManager, 'TradeStarted' ) @@ -252,12 +262,18 @@ describe(`Self-referential collateral (eg ETH via WETH) - P${IMPLEMENTATION}`, ( ) // Should view WETH as surplus - await expect(backingManager.manageTokens([token0.address, weth.address])).to.not.emit( - backingManager, + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' + ) + await backingManager.forwardRevenue([weth.address]) + await expect(rsrTrader.manageToken(weth.address, TradeKind.BATCH_AUCTION)).to.emit( + rsrTrader, + 'TradeStarted' + ) + await expect(rTokenTrader.manageToken(weth.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, 'TradeStarted' ) - await expect(rsrTrader.manageToken(weth.address)).to.emit(rsrTrader, 'TradeStarted') - await expect(rTokenTrader.manageToken(weth.address)).to.emit(rTokenTrader, 'TradeStarted') }) }) }) diff --git a/test/scenario/cETH.test.ts b/test/scenario/cETH.test.ts index fc85a62c9a..86ec6cd5b7 100644 --- a/test/scenario/cETH.test.ts +++ b/test/scenario/cETH.test.ts @@ -5,7 +5,7 @@ import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { bn, fp } from '../../common/numbers' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { CTokenMock, CTokenSelfReferentialCollateral, @@ -201,22 +201,32 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, it('should sell appreciating stable collateral and ignore cETH', async () => { await token0.setExchangeRate(fp('1.1')) // 10% appreciation - await expect(backingManager.manageTokens([token0.address])).to.not.emit( - backingManager, - 'TradeStarted' + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) + await backingManager.forwardRevenue([token0.address, cETH.address]) expect(await cETH.balanceOf(rTokenTrader.address)).to.equal(0) expect(await cETH.balanceOf(rsrTrader.address)).to.equal(0) - await expect(rTokenTrader.manageToken(cETH.address)).to.not.emit(rTokenTrader, 'TradeStarted') - await expect(rTokenTrader.manageToken(token0.address)).to.emit(rTokenTrader, 'TradeStarted') + await expect( + rTokenTrader.manageToken(cETH.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('0 balance') + await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) // RTokenTrader should be selling token0 and buying RToken const trade = await getTrade(rTokenTrader, token0.address) expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(rToken.address) - await expect(rsrTrader.manageToken(cETH.address)).to.not.emit(rsrTrader, 'TradeStarted') - await expect(rsrTrader.manageToken(token0.address)).to.emit(rsrTrader, 'TradeStarted') + await expect(rsrTrader.manageToken(cETH.address, TradeKind.BATCH_AUCTION)).to.be.revertedWith( + '0 balance' + ) + await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rsrTrader, + 'TradeStarted' + ) // RSRTrader should be selling token0 and buying RToken const trade2 = await getTrade(rsrTrader, token0.address) @@ -231,7 +241,7 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) - await expect(backingManager.manageTokens([token0.address, cETH.address])).to.emit( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( backingManager, 'TradeStarted' ) @@ -269,13 +279,16 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, it('should sell cETH for RToken after redemption rate increase', async () => { await cETH.setExchangeRate(fp('2')) // doubling of price await basketHandler.refreshBasket() - await expect(backingManager.manageTokens([cETH.address])).to.not.emit( - backingManager, - 'TradeStarted' + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) + await backingManager.forwardRevenue([cETH.address]) // RTokenTrader should be selling cETH and buying RToken - await expect(rTokenTrader.manageToken(cETH.address)).to.emit(rTokenTrader, 'TradeStarted') + await expect(rTokenTrader.manageToken(cETH.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) const trade = await getTrade(rTokenTrader, cETH.address) expect(await trade.sell()).to.equal(cETH.address) expect(await trade.buy()).to.equal(rToken.address) @@ -323,7 +336,10 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, ) // Should view WETH as surplus - await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) // BackingManager should be selling cETH and buying WETH const trade = await getTrade(backingManager, cETH.address) diff --git a/test/scenario/cWBTC.test.ts b/test/scenario/cWBTC.test.ts index 4427d595df..02b6e1867b 100644 --- a/test/scenario/cWBTC.test.ts +++ b/test/scenario/cWBTC.test.ts @@ -6,7 +6,7 @@ import { ethers } from 'hardhat' import { bn, fp } from '../../common/numbers' import { advanceTime } from '../utils/time' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { CTokenMock, CTokenNonFiatCollateral, @@ -210,25 +210,32 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => it('should sell appreciating stable collateral and ignore cWBTC', async () => { await token0.setExchangeRate(fp('1.1')) // 10% appreciation - await expect(backingManager.manageTokens([token0.address])).to.not.emit( - backingManager, - 'TradeStarted' + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) + await backingManager.forwardRevenue([cWBTC.address, token0.address]) expect(await cWBTC.balanceOf(rTokenTrader.address)).to.equal(0) expect(await cWBTC.balanceOf(rsrTrader.address)).to.equal(0) - await expect(rTokenTrader.manageToken(cWBTC.address)).to.not.emit( + await expect( + rTokenTrader.manageToken(cWBTC.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('0 balance') + await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( rTokenTrader, 'TradeStarted' ) - await expect(rTokenTrader.manageToken(token0.address)).to.emit(rTokenTrader, 'TradeStarted') // RTokenTrader should be selling token0 and buying RToken const trade = await getTrade(rTokenTrader, token0.address) expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(rToken.address) - await expect(rsrTrader.manageToken(cWBTC.address)).to.not.emit(rsrTrader, 'TradeStarted') - await expect(rsrTrader.manageToken(token0.address)).to.emit(rsrTrader, 'TradeStarted') + await expect( + rsrTrader.manageToken(cWBTC.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('0 balance') + await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rsrTrader, + 'TradeStarted' + ) // RSRTrader should be selling token0 and buying RToken const trade2 = await getTrade(rsrTrader, token0.address) @@ -243,7 +250,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) - await expect(backingManager.manageTokens([token0.address, cWBTC.address])).to.emit( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( backingManager, 'TradeStarted' ) @@ -296,13 +303,16 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => it('should sell cWBTC for RToken after redemption rate increase', async () => { await cWBTC.setExchangeRate(fp('2')) // doubling of price await basketHandler.refreshBasket() - await expect(backingManager.manageTokens([cWBTC.address])).to.not.emit( - backingManager, - 'TradeStarted' + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) + await backingManager.forwardRevenue([cWBTC.address]) // RTokenTrader should be selling cWBTC and buying RToken - await expect(rTokenTrader.manageToken(cWBTC.address)).to.emit(rTokenTrader, 'TradeStarted') + await expect(rTokenTrader.manageToken(cWBTC.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) const trade = await getTrade(rTokenTrader, cWBTC.address) expect(await trade.sell()).to.equal(cWBTC.address) expect(await trade.buy()).to.equal(rToken.address) @@ -351,7 +361,10 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => ) // Should view cWBTC as surplus - await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) // BackingManager should be selling cWBTC and buying cWBTC const trade = await getTrade(backingManager, cWBTC.address) From 6f5b22c866bf71e142d8edf6b3bf9027ce10f251 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 27 Apr 2023 21:42:44 -0400 Subject: [PATCH 117/462] integration tests --- test/integration/EasyAuction.test.ts | 72 ++++++++++++++++++---------- test/integration/fixtures.ts | 16 +++++-- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 39fc9d64f5..2eba6213bc 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -18,6 +18,7 @@ import { IConfig, networkConfig } from '../../common/configuration' import { BN_SCALE_FACTOR, CollateralStatus, + TradeKind, QUEUE_START, MAX_UINT48, MAX_UINT192, @@ -146,7 +147,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await token0.connect(owner).mint(addr2.address, issueAmount.mul(1e9)) // Create auction - await expect(backingManager.manageTokens([])) + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)) .to.emit(backingManager, 'TradeStarted') .withArgs(anyValue, rsr.address, token0.address, anyValue, withinQuad(buyAmt)) @@ -161,7 +162,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await expectTrade(backingManager, { sell: rsr.address, buy: token0.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: auctionId, }) @@ -174,7 +175,9 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function // Check Gnosis expect(await rsr.balanceOf(easyAuction.address)).to.equal(sellAmt) - await expect(backingManager.manageTokens([])).to.not.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'trade open' + ) // Auction should not be able to be settled await expect(easyAuction.settleAuction(auctionId)).to.be.reverted @@ -204,7 +207,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function it('no volume', async () => { // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should restart await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -237,7 +240,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(rsr.address), [ @@ -273,7 +276,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(rsr.address), [ @@ -302,7 +305,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function .placeSellOrders(auctionId, [sellAmt], [bidAmt], [QUEUE_START], ethers.constants.HashZero) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(rsr.address), [ @@ -332,7 +335,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function .placeSellOrders(auctionId, [sellAmt], [bidAmt], [QUEUE_START], ethers.constants.HashZero) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(rsr.address), [ @@ -362,7 +365,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function .placeSellOrders(auctionId, [sellAmt], [bidAmt], [QUEUE_START], ethers.constants.HashZero) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction -- should trade at lower worst-case price await expectEvents(backingManager.settleTrade(rsr.address), [ @@ -406,7 +409,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(rsr.address), [ @@ -430,7 +433,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function it('/w non-trivial prices', async () => { // End first auction, since it is at old prices - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) await backingManager.settleTrade(rsr.address) // $0.007 RSR at $4k ETH @@ -442,7 +445,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function buyAmt = issueAmount.add(1) // rounding up from prepareTradeToCoverDeficit // Start next auction - await expectEvents(backingManager.manageTokens([]), [ + await expectEvents(backingManager.rebalance(TradeKind.BATCH_AUCTION), [ { contract: backingManager, name: 'TradeStarted', @@ -468,7 +471,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(rsr.address), [ @@ -497,7 +500,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function .placeSellOrders(auctionId, [sellAmt], [bidAmt], [QUEUE_START], ethers.constants.HashZero) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Settle auction directly await easyAuction.connect(addr2).settleAuction(auctionId) @@ -573,7 +576,10 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await advanceTime(Number(config.warmupPeriod) + 1) // Should launch auction for token1 - await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) const auctionTimestamp: number = await getLatestBlockTimestamp() const auctionId = await getAuctionId(backingManager, token0.address) @@ -582,7 +588,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: auctionId, }) const trade = await getTrade(backingManager, token0.address) @@ -596,7 +602,9 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function // Check Gnosis expect(await token0.balanceOf(easyAuction.address)).to.equal(issueAmount) - await expect(backingManager.manageTokens([])).to.not.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'trade open' + ) // Auction should not be able to be settled await expect(easyAuction.settleAuction(auctionId)).to.be.reverted @@ -630,7 +638,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(token0.address), [ @@ -671,7 +679,10 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await advanceTime(Number(config.warmupPeriod) + 1) // Should launch auction for token1 - await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) const auctionTimestamp: number = await getLatestBlockTimestamp() const auctionId = await getAuctionId(backingManager, token0.address) @@ -680,7 +691,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: auctionId, }) const trade = await getTrade(backingManager, token0.address) @@ -694,7 +705,9 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function // Check Gnosis expect(await token0.balanceOf(easyAuction.address)).to.be.closeTo(issueAmount, 1) - await expect(backingManager.manageTokens([])).to.not.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'trade open' + ) // Auction should not be able to be settled await expect(easyAuction.settleAuction(auctionId)).to.be.reverted @@ -714,7 +727,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(token0.address), [ @@ -772,7 +785,14 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function ) await main.connect(owner).unpauseTrading() await main.connect(owner).unpauseIssuance() - await broker.init(main.address, easyAuction.address, ONE_ADDRESS, config.auctionLength) + await broker.init( + main.address, + easyAuction.address, + ONE_ADDRESS, + config.batchAuctionLength, + ONE_ADDRESS, + config.dutchAuctionLength + ) const sellTok = await ERC20Factory.deploy('Sell Token', 'SELL', sellTokDecimals) const buyTok = await ERC20Factory.deploy('Buy Token', 'BUY', buyTokDecimals) const sellColl = await CollFactory.deploy({ @@ -807,7 +827,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function // First simulate opening the trade to get where it will be deployed await sellTok.connect(addr1).approve(broker.address, auctionSellAmt) - const tradeAddr = await broker.connect(addr1).callStatic.openTrade({ + const tradeAddr = await broker.connect(addr1).callStatic.openTrade(TradeKind.BATCH_AUCTION, { sell: sellColl.address, buy: buyColl.address, sellAmount: auctionSellAmt, @@ -815,7 +835,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function }) // Start auction! - await broker.connect(addr1).openTrade({ + await broker.connect(addr1).openTrade(TradeKind.BATCH_AUCTION, { sell: sellColl.address, buy: buyColl.address, sellAmount: auctionSellAmt, @@ -850,7 +870,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function } // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End Auction await expect(trade.connect(addr1).settle()).to.not.emit(broker, 'DisabledSet') diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index f6e3a8491e..f46b0b8d00 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -21,6 +21,7 @@ import { DeployerP0, DeployerP1, DistributorP1, + DutchTrade, EasyAuction, ERC20Mock, EURFiatCollateral, @@ -617,7 +618,8 @@ export const defaultFixture: Fixture = async function (): Promis unstakingDelay: bn('1209600'), // 2 weeks warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) - auctionLength: bn('900'), // 15 minutes + batchAuctionLength: bn('900'), // 15 minutes + dutchAuctionLength: bn('600'), // 10 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { @@ -704,8 +706,11 @@ export const defaultFixture: Fixture = async function (): Promis const FurnaceImplFactory: ContractFactory = await ethers.getContractFactory('FurnaceP1') const furnaceImpl: FurnaceP1 = await FurnaceImplFactory.deploy() - const TradeImplFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const tradeImpl: GnosisTrade = await TradeImplFactory.deploy() + const GnosisTradeImplFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const gnosisTrade: GnosisTrade = await GnosisTradeImplFactory.deploy() + + const DutchTradeImplFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + const dutchTrade: DutchTrade = await DutchTradeImplFactory.deploy() const BrokerImplFactory: ContractFactory = await ethers.getContractFactory('BrokerP1') const brokerImpl: BrokerP1 = await BrokerImplFactory.deploy() @@ -719,7 +724,6 @@ export const defaultFixture: Fixture = async function (): Promis // Setup Implementation addresses const implementations: IImplementations = { main: mainImpl.address, - trade: tradeImpl.address, components: { assetRegistry: assetRegImpl.address, backingManager: backingMgrImpl.address, @@ -732,6 +736,10 @@ export const defaultFixture: Fixture = async function (): Promis rToken: rTokenImpl.address, stRSR: stRSRImpl.address, }, + trading: { + gnosisTrade: gnosisTrade.address, + dutchTrade: dutchTrade.address, + }, } const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP1') From 23a53de12768c7c94de804a1e8f00ea1a23a3e21 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 27 Apr 2023 21:54:04 -0400 Subject: [PATCH 118/462] reorder FacadeAct tests --- test/FacadeAct.test.ts | 90 +++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts index e6438ee28d..12b897ea14 100644 --- a/test/FacadeAct.test.ts +++ b/test/FacadeAct.test.ts @@ -203,6 +203,51 @@ describe('FacadeAct contract', () => { await rToken.connect(addr1).issue(issueAmount) }) + context('getRevenueAuctionERC20s/runRevenueAuctions', () => { + it('Revenues/Rewards', async () => { + const rewardAmountAAVE = bn('0.5e18') + const rewardAmountCOMP = bn('1e18') + + // Setup AAVE + COMP rewards + await aToken.setRewards(backingManager.address, rewardAmountAAVE) + await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + await backingManager.claimRewards() + + // getRevenueAuctionERC20s should return reward token + const rTokenERC20s = await facadeAct.callStatic.getRevenueAuctionERC20s( + rTokenTrader.address + ) + expect(rTokenERC20s.length).to.equal(2) + expect(rTokenERC20s[0]).to.equal(aaveToken.address) + expect(rTokenERC20s[1]).to.equal(compToken.address) + const rsrERC20s = await facadeAct.callStatic.getRevenueAuctionERC20s(rsrTrader.address) + expect(rsrERC20s.length).to.equal(2) + expect(rsrERC20s[0]).to.equal(aaveToken.address) + expect(rsrERC20s[1]).to.equal(compToken.address) + + // Run revenue auctions for both traders + await facadeAct.runRevenueAuctions(rTokenTrader.address, [], rTokenERC20s) + await facadeAct.runRevenueAuctions(rsrTrader.address, [], rsrERC20s) + + // Nothing should be settleable + expect((await facade.auctionsSettleable(rTokenTrader.address)).length).to.equal(0) + expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(0) + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Now both should be settleable + const rTokenSettleable = await facade.auctionsSettleable(rTokenTrader.address) + expect(rTokenSettleable.length).to.equal(2) + expect(rTokenSettleable[0]).to.equal(aaveToken.address) + expect(rTokenSettleable[1]).to.equal(compToken.address) + const rsrSettleable = await facade.auctionsSettleable(rsrTrader.address) + expect(rsrSettleable.length).to.equal(2) + expect(rsrSettleable[0]).to.equal(aaveToken.address) + expect(rsrSettleable[1]).to.equal(compToken.address) + }) + }) + context('getActCalldata', () => { it('No call required', async () => { // Via Facade get next call - No action required @@ -896,51 +941,6 @@ describe('FacadeAct contract', () => { ) }) }) - - context('getRevenueAuctionERC20s/runRevenueAuctions', () => { - it('Revenues/Rewards', async () => { - const rewardAmountAAVE = bn('0.5e18') - const rewardAmountCOMP = bn('1e18') - - // Setup AAVE + COMP rewards - await aToken.setRewards(backingManager.address, rewardAmountAAVE) - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) - await backingManager.claimRewards() - - // getRevenueAuctionERC20s should return reward token - const rTokenERC20s = await facadeAct.callStatic.getRevenueAuctionERC20s( - rTokenTrader.address - ) - expect(rTokenERC20s.length).to.equal(2) - expect(rTokenERC20s[0]).to.equal(aaveToken.address) - expect(rTokenERC20s[1]).to.equal(compToken.address) - const rsrERC20s = await facadeAct.callStatic.getRevenueAuctionERC20s(rsrTrader.address) - expect(rsrERC20s.length).to.equal(2) - expect(rsrERC20s[0]).to.equal(aaveToken.address) - expect(rsrERC20s[1]).to.equal(compToken.address) - - // Run revenue auctions for both traders - await facadeAct.runRevenueAuctions(rTokenTrader.address, [], rTokenERC20s) - await facadeAct.runRevenueAuctions(rsrTrader.address, [], rsrERC20s) - - // Nothing should be settleable - expect((await facade.auctionsSettleable(rTokenTrader.address)).length).to.equal(0) - expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(0) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // Now both should be settleable - const rTokenSettleable = await facade.auctionsSettleable(rTokenTrader.address) - expect(rTokenSettleable.length).to.equal(2) - expect(rTokenSettleable[0]).to.equal(aaveToken.address) - expect(rTokenSettleable[1]).to.equal(compToken.address) - const rsrSettleable = await facade.auctionsSettleable(rsrTrader.address) - expect(rsrSettleable.length).to.equal(2) - expect(rsrSettleable[0]).to.equal(aaveToken.address) - expect(rsrSettleable[1]).to.equal(compToken.address) - }) - }) }) describeGas('Gas Reporting', () => { From cd33bf4155d205b3afcca0aaacb00ff64547e273 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 27 Apr 2023 22:35:22 -0400 Subject: [PATCH 119/462] plugin integration tests --- test/plugins/Collateral.test.ts | 32 +++++++++---------- .../compoundv2/CTokenFiatCollateral.test.ts | 3 +- .../plugins/individual-collateral/fixtures.ts | 10 ++++-- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index dbbb3d419c..676e96a819 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -7,7 +7,6 @@ import { IConfig, MAX_DELAY_UNTIL_DEFAULT } from '../../common/configuration' import { CollateralStatus, MAX_UINT48, ZERO_ADDRESS } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { - AppreciatingFiatCollateral, ATokenFiatCollateral, ComptrollerMock, CTokenFiatCollateral, @@ -27,6 +26,7 @@ import { StaticATokenMock, TestIBackingManager, TestIRToken, + UnpricedAppreciatingFiatCollateralMock, USDCMock, WETH9, } from '../../typechain' @@ -713,21 +713,21 @@ describe('Collateral contracts', () => { const UnpricedAppreciatingFactory = await ethers.getContractFactory( 'UnpricedAppreciatingFiatCollateralMock' ) - const unpricedAppFiatCollateral: AppreciatingFiatCollateral = ( - await UnpricedAppreciatingFactory.deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: await aTokenCollateral.chainlinkFeed(), // reuse - mock - oracleError: ORACLE_ERROR, - erc20: aToken.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - }, - REVENUE_HIDING - ) + const unpricedAppFiatCollateral: UnpricedAppreciatingFiatCollateralMock = < + UnpricedAppreciatingFiatCollateralMock + >await UnpricedAppreciatingFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await aTokenCollateral.chainlinkFeed(), // reuse - mock + oracleError: ORACLE_ERROR, + erc20: aToken.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + REVENUE_HIDING ) // Save prices diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 1c524e5569..2a06b63a49 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -113,7 +113,8 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi unstakingDelay: bn('1209600'), // 2 weeks warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) - auctionLength: bn('900'), // 15 minutes + batchAuctionLength: bn('900'), // 15 minutes + dutchAuctionLength: bn('600'), // 10 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { diff --git a/test/plugins/individual-collateral/fixtures.ts b/test/plugins/individual-collateral/fixtures.ts index 3f790ad281..c54236dc30 100644 --- a/test/plugins/individual-collateral/fixtures.ts +++ b/test/plugins/individual-collateral/fixtures.ts @@ -13,6 +13,7 @@ import { DeployerP0, DeployerP1, DistributorP1, + DutchTrade, ERC20Mock, FacadeRead, FacadeAct, @@ -149,8 +150,11 @@ export const defaultFixture: Fixture = async function (): Promis const FurnaceImplFactory: ContractFactory = await ethers.getContractFactory('FurnaceP1') const furnaceImpl: FurnaceP1 = await FurnaceImplFactory.deploy() - const TradeImplFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const tradeImpl: GnosisTrade = await TradeImplFactory.deploy() + const GnosisTradeImplFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const gnosisTrade: GnosisTrade = await GnosisTradeImplFactory.deploy() + + const DutchTradeImplFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + const dutchTrade: DutchTrade = await DutchTradeImplFactory.deploy() const BrokerImplFactory: ContractFactory = await ethers.getContractFactory('BrokerP1') const brokerImpl: BrokerP1 = await BrokerImplFactory.deploy() @@ -164,7 +168,7 @@ export const defaultFixture: Fixture = async function (): Promis // Setup Implementation addresses const implementations: IImplementations = { main: mainImpl.address, - trade: tradeImpl.address, + trading: { gnosisTrade: gnosisTrade.address, dutchTrade: dutchTrade.address }, components: { assetRegistry: assetRegImpl.address, backingManager: backingMgrImpl.address, From 01ad759e771498bf2b6d7c47e77d959723459bb4 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 28 Apr 2023 00:14:10 -0400 Subject: [PATCH 120/462] make backingBuffer handling more straightforward --- contracts/p0/BackingManager.sol | 8 +------- contracts/p1/BackingManager.sol | 17 +++++++---------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index e8fe076bcc..1dd3527d4e 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -144,7 +144,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { } // Mint revenue RToken - uint192 needed; // {BU} + uint192 needed = main.rToken().basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); // {BU} { IRToken rToken = main.rToken(); needed = rToken.basketsNeeded(); // {BU} @@ -163,9 +163,6 @@ contract BackingManagerP0 is TradingP0, IBackingManager { } } - // BackingBuffer for non-RTokens - needed = main.rToken().basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); - // Handout excess assets above what is needed, including any newly minted RToken RevenueTotals memory totals = main.distributor().totals(); for (uint256 i = 0; i < erc20s.length; i++) { @@ -173,9 +170,6 @@ contract BackingManagerP0 is TradingP0, IBackingManager { uint192 bal = asset.bal(address(this)); // {tok} uint192 req = needed.mul(main.basketHandler().quantity(erc20s[i]), CEIL); - if (address(erc20s[i]) == address(main.rToken())) { - req = backingBuffer.mul(_safeWrap(main.rToken().totalSupply())); - } if (bal.gt(req)) { // delta: {qTok} diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 5e0906dc6e..76aa3166fc 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -194,7 +194,10 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // (>== is "no less than, and nearly equal to") // and rToken'.basketsNeeded <= basketsHeld.bottom // and rToken'.totalSupply is maximal satisfying this. - uint192 needed = rToken.basketsNeeded(); // {BU} + + // Keep backingBuffer worth of collateral before recognizing revenue + uint192 needed = rToken.basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); // {BU} + if (basketsHeld.bottom.gt(needed)) { // gas-optimization: RToken is known to have 18 decimals, the same as FixLib uint192 totalSupply = _safeWrap(rToken.totalSupply()); // {rTok} @@ -215,12 +218,8 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // - We're fully collateralized // - The BU exchange rate {BU/rTok} did not decrease - // Keep a small buffer of individual collateral; "excess" assets are beyond the buffer. - needed = needed.mul(FIX_ONE.plus(backingBuffer)); + // Handout surplus assets + newly minted RToken - // Calculate all balances above the backingBuffer: - // - rToken balances above the rTokenBuffer - // - non-RToken balances above the backingBuffer uint256 length = erc20s.length; RevenueTotals memory totals = distributor.totals(); uint256[] memory toRSR = new uint256[](length); @@ -229,11 +228,9 @@ contract BackingManagerP1 is TradingP1, IBackingManager { IAsset asset = assetRegistry.toAsset(erc20s[i]); // {tok} = {BU} * {tok/BU} - uint192 req = erc20s[i] != IERC20(address(rToken)) - ? needed.mul(basketHandler.quantity(erc20s[i]), CEIL) - : backingBuffer.mul(_safeWrap(rToken.totalSupply()), CEIL); - + uint192 req = needed.mul(basketHandler.quantity(erc20s[i]), CEIL); uint192 bal = asset.bal(address(this)); + if (bal.gt(req)) { // delta: {qTok}, the excess quantity of this asset that we hold uint256 delta = bal.minus(req).shiftl_toUint(int8(asset.erc20Decimals())); From e241f42ce3b7c6d24aceade1f71ac79f2e2b0878 Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Fri, 28 Apr 2023 15:53:25 -0400 Subject: [PATCH 121/462] add more tests. --- contracts/p0/BasketHandler.sol | 14 +- contracts/p1/BasketHandler.sol | 2 +- contracts/p1/RToken.sol | 5 +- test/Main.test.ts | 254 ++++++++++++++++++++++++++++++--- 4 files changed, 249 insertions(+), 26 deletions(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index be4c573986..2285dbb1d4 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -142,8 +142,8 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // Nonce of the first reference basket from the current history // A new historical record begins whenever the prime basket is changed - // There can be 0 to any number of reference baskets from the current history - uint48 private historicalNonce; // {nonce} + // There can be 0 to any number of reference baskets from the current prime basket history + uint48 private primeNonce; // {nonce} // The historical baskets by basket nonce; includes current basket mapping(uint48 => Basket) private historicalBaskets; @@ -266,7 +266,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { config.targetNames[erc20s[i]] = names[i]; } - historicalNonce = nonce + 1; // set historicalNonce to the next nonce + primeNonce = nonce + 1; // set primeNonce to the next nonce emit PrimeBasketSet(erc20s, targetAmts, names); } @@ -452,8 +452,8 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { uint192[] memory portions, uint192 amount ) external view returns (address[] memory erc20s, uint256[] memory quantities) { - // directly after upgrade the historicalNonce will be 0, which is not a valid value - require(historicalNonce > 0, "historicalNonce uninitialized"); + // directly after upgrade the primeNonce will be 0, which is not a valid value + require(primeNonce > 0, "primeNonce uninitialized"); require(basketNonces.length == portions.length, "portions does not mirror basketNonces"); // Confirm portions sum to FIX_ONE @@ -471,10 +471,10 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // Calculate the linear combination basket for (uint48 i = 0; i < basketNonces.length; ++i) { require( - basketNonces[i] >= historicalNonce && basketNonces[i] <= nonce, + basketNonces[i] >= primeNonce && basketNonces[i] <= nonce, "invalid basketNonce" ); // will always revert directly after setPrimeBasket() - Basket storage b = historicalBaskets[i]; + Basket storage b = historicalBaskets[basketNonces[i]]; // Add-in refAmts contribution from historical basket for (uint256 j = 0; j < b.erc20s.length; ++j) { diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 0461b5552d..fab38c7897 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -11,7 +11,7 @@ import "../interfaces/IMain.sol"; import "../libraries/Array.sol"; import "../libraries/Fixed.sol"; import "./mixins/Component.sol"; - +import "hardhat/console.sol"; // A "valid collateral array" is a an IERC20[] value without rtoken, rsr, or any duplicate values // A BackupConfig value is valid if erc20s is a valid collateral array diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index cc0870daa9..5455cbf3e8 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -9,7 +9,7 @@ import "../libraries/Fixed.sol"; import "../libraries/Throttle.sol"; import "../vendor/ERC20PermitUpgradeable.sol"; import "./mixins/Component.sol"; - +import "hardhat/console.sol"; /** * @title RTokenP1 * An ERC20 with an elastic supply and governable exchange rate to basket units. @@ -339,8 +339,9 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // Check post-balances for (uint256 i = 0; i < minERC20s.length; ++i) { + uint256 bal = minERC20s[i].balanceOf(recipient); require( - minERC20s[i].balanceOf(recipient) - initialMinERC20Balances[i] >= minAmounts[i], + bal - initialMinERC20Balances[i] >= minAmounts[i], "redemption below minimum" ); } diff --git a/test/Main.test.ts b/test/Main.test.ts index a25573b178..2318c82548 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -109,11 +109,13 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { let token2: StaticATokenMock let token3: CTokenMock let backupToken1: ERC20Mock + let backupToken2: ERC20Mock let collateral0: FiatCollateral let collateral1: FiatCollateral let collateral2: ATokenFiatCollateral let collateral3: CTokenFiatCollateral let backupCollateral1: FiatCollateral + let backupCollateral2: FiatCollateral let erc20s: ERC20Mock[] let basketsNeededAmts: BigNumber[] @@ -175,6 +177,9 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { backupToken1 = erc20s[2] // USDT backupCollateral1 = collateral[2] + backupToken2 = erc20s[3] // BUSD + backupCollateral2 = collateral[3] + collateral0 = basket[0] collateral1 = basket[1] collateral2 = basket[2] @@ -1727,18 +1732,21 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { .withArgs([token0.address], [fp('1')], [ethers.utils.formatBytes32String('USD')]) }) - describe('Historical Redemptions', () => { + describe.only('Historical Redemptions', () => { const issueAmount = fp('10000') let usdcChainlink: MockV3Aggregator + let daiChainlink: MockV3Aggregator beforeEach(async () => { usdcChainlink = await ethers.getContractAt('MockV3Aggregator', await collateral1.chainlinkFeed()) + daiChainlink = await ethers.getContractAt('MockV3Aggregator', await collateral0.chainlinkFeed()) // register backups await assetRegistry.connect(owner).register(backupCollateral1.address) await basketHandler .connect(owner) .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [backupToken1.address]) + await assetRegistry.connect(owner).register(backupCollateral2.address) // issue rTokens await token0.connect(addr1).approve(rToken.address, issueAmount) @@ -1748,17 +1756,90 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await rToken.connect(addr1).issue(issueAmount) }) - it('Should correctly quote a historical redemption (simple)', async () => { + const expectEqualArrays = (arr1: Array, arr2: Array) => { + expect(arr1.length).equal(arr2.length) + for (let i = 0; i < arr1.length; i++) { + expect(arr1[i]).equal(arr2[i]) + } + } + + const getBalances = async (account: string, tokens: Array) => { + const bals: Array = [] + for (const token of tokens) { + bals.push(await token.balanceOf(account)) + } + return bals + } + + const expectDelta = (x: Array, y: Array, z: Array) => { + for (let i = 0; i < x.length; i++) { + expect(z[i]).equal(x[i].add(y[i])) + } + } + + it('Should correctly quote a historical redemption (current)', async () => { + /* + Test Quote + */ + const basketNonces = [1] + const portions = [fp('1')] + const amount = fp('10000') + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, amount) + + expect(quote.erc20s.length).equal(4) + expect(quote.quantities.length).equal(4) + + const expectedTokens = [token0, token1, token2, token3] + const expectedAddresses = expectedTokens.map((t) => t.address) + const expectedQuantities = [ + fp('0.25').mul(issueAmount).div(await collateral0.refPerTok()).div(bn(`1e${18 - (await token0.decimals())}`)), + fp('0.25').mul(issueAmount).div(await collateral1.refPerTok()).div(bn(`1e${18 - (await token1.decimals())}`)), + fp('0.25').mul(issueAmount).div(await collateral2.refPerTok()).div(bn(`1e${18 - (await token2.decimals())}`)), + fp('0.25').mul(issueAmount).div(await collateral3.refPerTok()).div(bn(`1e${18 - (await token3.decimals())}`)) + ] + expectEqualArrays(quote.erc20s, expectedAddresses) + expectEqualArrays(quote.quantities, expectedQuantities) + + /* + Test Historical Redemption + */ + const balsBefore = await getBalances(addr1.address, expectedTokens) + await rToken.connect(addr1).customRedemption( + addr1.address, + amount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + const balsAfter = await getBalances(addr1.address, expectedTokens) + expectDelta(balsBefore, quote.quantities, balsAfter) + }) + + it('Should correctly quote a historical redemption [single-asset default & replacement]', async () => { + /* + Setup + */ // default usdc & refresh basket to use backup collateral - await usdcChainlink.updateAnswer(bn('0.8e8')) + await usdcChainlink.updateAnswer(bn('0.8e8')) // default token1 await basketHandler.refreshBasket() await advanceTime(Number(config.warmupPeriod) + 1) expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(false) - const quote = await basketHandler.quoteHistoricalRedemption([1, 2], [fp('0.5'), fp('0.5')], fp('10000')) - - const expectedTokens = [token0.address, token1.address, token2.address, token3.address, backupToken1.address] + /* + Test Quote + */ + const basketNonces = [1, 2] + const portions = [fp('0.5'), fp('0.5')] + const amount = fp('10000') + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, amount) + + expect(quote.erc20s.length).equal(5) + expect(quote.quantities.length).equal(5) + + const expectedTokens = [token0, token1, token2, token3, backupToken1] + const expectedAddresses = expectedTokens.map((t) => t.address) const expectedQuantities = [ fp('0.25').mul(issueAmount).div(await collateral0.refPerTok()).div(bn(`1e${18 - (await token0.decimals())}`)), fp('0.125').mul(issueAmount).div(await collateral1.refPerTok()).div(bn(`1e${18 - (await token1.decimals())}`)), @@ -1766,16 +1847,157 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { fp('0.25').mul(issueAmount).div(await collateral3.refPerTok()).div(bn(`1e${18 - (await token3.decimals())}`)), fp('0.125').mul(issueAmount).div(await backupCollateral1.refPerTok()).div(bn(`1e${18 - (await backupToken1.decimals())}`)), ] - expect(quote.erc20s[0]).equal(expectedTokens[0]) - expect(quote.erc20s[1]).equal(expectedTokens[1]) - expect(quote.erc20s[2]).equal(expectedTokens[2]) - expect(quote.erc20s[3]).equal(expectedTokens[3]) - expect(quote.erc20s[4]).equal(expectedTokens[4]) - expect(quote.quantities[0]).equal(expectedQuantities[0]) - expect(quote.quantities[1]).equal(expectedQuantities[1]) - expect(quote.quantities[2]).equal(expectedQuantities[2]) - expect(quote.quantities[3]).equal(expectedQuantities[3]) - expect(quote.quantities[4]).equal(expectedQuantities[4]) + expectEqualArrays(quote.erc20s, expectedAddresses) + expectEqualArrays(quote.quantities, expectedQuantities) + + /* + Test Historical Redemption + */ + const balsBefore = await getBalances(addr1.address, expectedTokens) + + // rToken is undercollateralized, no backupToken1. should fail + await expect(rToken.connect(addr1).customRedemption( + addr1.address, + amount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + )).revertedWith("redemption below minimum") + + // send enough backupToken1 to BackingManager to recollateralize and process redemption correctly + await backupToken1.mint(backingManager.address, issueAmount) + + await rToken.connect(addr1).customRedemption( + addr1.address, + amount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + const balsAfter = await getBalances(addr1.address, expectedTokens) + expectDelta(balsBefore, quote.quantities, balsAfter) + }) + + it('Should correctly quote a historical redemption [single-asset refPerTok appreciation]', async () => { + /* + Setup + */ + // appreciate aDai and refresh basket + await token2.setExchangeRate(fp('2')) + await basketHandler.refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + + /* + Test Quote + */ + const basketNonces = [1, 2] + const portions = [fp('0.5'), fp('0.5')] + const amount = fp('10000') + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, amount) + + expect(quote.erc20s.length).equal(4) + expect(quote.quantities.length).equal(4) + + const expectedTokens = [token0, token1, token2, token3] + const expectedAddresses = expectedTokens.map((t) => t.address) + const expectedQuantities = [ + fp('0.25').mul(issueAmount).div(await collateral0.refPerTok()).div(bn(`1e${18 - (await token0.decimals())}`)), + fp('0.25').mul(issueAmount).div(await collateral1.refPerTok()).div(bn(`1e${18 - (await token1.decimals())}`)), + fp('0.25').mul(issueAmount).div(await collateral2.refPerTok()).div(bn(`1e${18 - (await token2.decimals())}`)), + fp('0.25').mul(issueAmount).div(await collateral3.refPerTok()).div(bn(`1e${18 - (await token3.decimals())}`)) + ] + expectEqualArrays(quote.erc20s, expectedAddresses) + expectEqualArrays(quote.quantities, expectedQuantities) + + /* + Test Historical Redemption + */ + const balsBefore = await getBalances(addr1.address, expectedTokens) + await rToken.connect(addr1).customRedemption( + addr1.address, + amount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + const balsAfter = await getBalances(addr1.address, expectedTokens) + expectDelta(balsBefore, quote.quantities, balsAfter) + }) + + it('Should correctly quote a historical redemption [full basket default, multi-token-backup]', async () => { + /* + Setup + */ + // add 2nd token to backup config + await basketHandler + .connect(owner) + .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(2), [backupToken1.address, backupToken2.address]) + // default usdc & refresh basket to use backup collateral + await usdcChainlink.updateAnswer(bn('0.8e8')) // default token1 + await daiChainlink.updateAnswer(bn('0.8e8')) // default token0, token2, token3 + await basketHandler.refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + + /* + Test Quote + */ + const basketNonces = [1, 2] + const portions = [fp('0.2'), fp('0.8')] + const amount = fp('10000') + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, amount) + + expect(quote.erc20s.length).equal(6) + expect(quote.quantities.length).equal(6) + + const expectedTokens = [token0, token1, token2, token3, backupToken1, backupToken2] + const expectedAddresses = expectedTokens.map((t) => t.address) + const expectedQuantities = [ + fp('0.05').mul(issueAmount).div(await collateral0.refPerTok()).div(bn(`1e${18 - (await token0.decimals())}`)), + fp('0.05').mul(issueAmount).div(await collateral1.refPerTok()).div(bn(`1e${18 - (await token1.decimals())}`)), + fp('0.05').mul(issueAmount).div(await collateral2.refPerTok()).div(bn(`1e${18 - (await token2.decimals())}`)), + fp('0.05').mul(issueAmount).div(await collateral3.refPerTok()).div(bn(`1e${18 - (await token3.decimals())}`)), + fp('0.40').mul(issueAmount).div(await backupCollateral1.refPerTok()).div(bn(`1e${18 - (await backupToken1.decimals())}`)), + fp('0.40').mul(issueAmount).div(await backupCollateral2.refPerTok()).div(bn(`1e${18 - (await backupToken2.decimals())}`)), + ] + expectEqualArrays(quote.erc20s, expectedAddresses) + expectEqualArrays(quote.quantities, expectedQuantities) + + /* + Test Historical Redemption + */ + const balsBefore = await getBalances(addr1.address, expectedTokens) + await backupToken1.mint(backingManager.address, issueAmount) + + // rToken is undercollateralized, no backupToken2. should fail + await expect(rToken.connect(addr1).customRedemption( + addr1.address, + amount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + )).revertedWith("redemption below minimum") + + // send enough backupToken2 to BackingManager to recollateralize and process redemption correctly + await backupToken2.mint(backingManager.address, issueAmount) + + await rToken.connect(addr1).customRedemption( + addr1.address, + amount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + const balsAfter = await getBalances(addr1.address, expectedTokens) + expectDelta(balsBefore, quote.quantities, balsAfter) }) }) From da7115f54b40f10ec7f5d826e2a89eb2801434ae Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Fri, 28 Apr 2023 16:49:57 -0400 Subject: [PATCH 122/462] fix broken rtoken tests. --- test/RToken.test.ts | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/test/RToken.test.ts b/test/RToken.test.ts index bdf43c3d53..a54c8bbebd 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -1087,7 +1087,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Should fail if revertOnPartialRedemption is true await expect( rToken.connect(addr1).redeem(issueAmount.div(2), 1 + (await basketHandler.nonce())) - ).to.be.revertedWith('non-current basket nonce') + ).to.be.revertedWith('invalid basketNonce') }) it('Should prorate redemption if basket is DISABLED from fallen refPerTok() #fast', async function () { @@ -1112,18 +1112,13 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await token2.balanceOf(addr1.address)).to.equal(initialBal) }) - it('Should not interact with unregistered collateral while DISABLED #fast', async function () { + it('Should not redeem() unregistered collateral #fast', async function () { // Unregister collateral2 await assetRegistry.connect(owner).unregister(collateral2.address) await expect( rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) - ).to.emit(rToken, 'Redemption') - expect(await rToken.totalSupply()).to.equal(0) - expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) - expect(await token1.balanceOf(addr1.address)).to.equal(initialBal) - expect(await token2.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(4))) - expect(await token3.balanceOf(addr1.address)).to.equal(initialBal) + ).revertedWith('erc20 unregistered') }) it('Should redeem prorata when refPerTok() is 0 #fast', async function () { @@ -1141,22 +1136,6 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await token3.balanceOf(addr1.address)).to.be.equal(initialBal) }) - it('Should transfer full balance if de-valuation #fast', async function () { - // Unregister collateral3 - await assetRegistry.connect(owner).unregister(collateral3.address) - - await expect( - rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) - ).to.emit(rToken, 'Redemption') - expect(await rToken.totalSupply()).to.equal(0) - expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) - expect(await token1.balanceOf(addr1.address)).to.equal(initialBal) - expect(await token2.balanceOf(addr1.address)).to.equal(initialBal) - expect(await token3.balanceOf(addr1.address)).to.equal( - initialBal.sub(issueAmount.div(bn('1e10')).div(4).mul(50)) // decimal shift + quarter of basket + cToken - ) - }) - it('Should not overflow BU exchange rate above 1e9 on redeem', async function () { // Leave only 1 RToken issue await rToken.connect(addr1).redeem(issueAmount.sub(bn('1e18')), await basketHandler.nonce()) From e59f79c8640b2818d789b51b171624ebffca6738 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 29 Apr 2023 21:03:53 -0400 Subject: [PATCH 123/462] tests back to passing --- test/FacadeAct.test.ts | 8 ++------ test/Recollateralization.test.ts | 10 ++++++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts index 12b897ea14..2968e25160 100644 --- a/test/FacadeAct.test.ts +++ b/test/FacadeAct.test.ts @@ -933,12 +933,8 @@ describe('FacadeAct contract', () => { }) // RToken forwarded - expect(await rToken.balanceOf(backingManager.address)).to.equal( - config.backingBuffer.mul(await rToken.totalSupply()).div(fp('1')) - ) - expect(await rToken.balanceOf(rTokenTrader.address)).to.equal( - hndAmt.sub(issueAmount.mul(config.backingBuffer).div(fp('1'))) - ) + expect(await rToken.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.balanceOf(rTokenTrader.address)).to.equal(hndAmt) }) }) }) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 5ec934cc5c..89c11640d5 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -1096,7 +1096,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await token1.balanceOf(backingManager.address)).to.equal( toBNDecimals(issueAmount, 6).add(1) ) - expect(await rToken.totalSupply()).to.equal(totalValue) + expect(await rToken.totalSupply()).to.equal(issueAmount) // assets kept in backing buffer // Check price in USD of the current RToken await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) @@ -1310,7 +1310,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await rToken.totalSupply()).to.be.gt(issueAmount) // New RToken minting // Check price in USD of the current RToken - expect(await rToken.basketsNeeded()).to.equal(await rToken.totalSupply()) // no haircut + expect(await rToken.basketsNeeded()).to.be.gte(await rToken.totalSupply()) // no haircut await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) }) @@ -2325,7 +2325,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { issueAmount.add(1) ) expect(await backupToken1.balanceOf(backingManager.address)).to.equal(issueAmount.add(1)) - expect(await rToken.totalSupply()).to.equal(issueAmount.add(1)) + expect(await rToken.totalSupply()).to.equal(issueAmount) // Check price in USD of the current RToken - Remains the same await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) @@ -2510,8 +2510,10 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(bonus) expect(await backupToken1.balanceOf(backingManager.address)).to.equal(bonus) + const supply = bonus.sub(bonus.mul(config.backingBuffer).div(fp('1'))) // Should mint the excess in order to re-handout to RToken holders and stakers - expect(await rToken.totalSupply()).to.equal(bonus) + expect(await rToken.totalSupply()).to.be.closeTo(supply, supply.div(bn('1e6'))) + expect(await rToken.totalSupply()).to.be.gte(supply) // Check price in USD of the current RToken - overcollateralized and still targeting 1 await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) From 6f932f13a0ac1c53519e011d370005c52517a740 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 29 Apr 2023 21:12:16 -0400 Subject: [PATCH 124/462] fix typo --- contracts/plugins/trading/DutchTrade.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index ff5a9995d9..343604dba9 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -190,7 +190,7 @@ contract DutchTrade is ITrade { require(timestamp > startTime, "cannot bid block auction was created"); require(timestamp < endTime, "auction over"); - uint192 progression = divuu(uint48(block.timestamp) - startTime, endTime - startTime); + uint192 progression = divuu(timestamp - startTime, endTime - startTime); // assert(progression <= FIX_ONE); // {buyTok/sellTok} From 1ecbdc90e441f67b2f7d15c0d83a564468a55e38 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sun, 30 Apr 2023 00:43:23 -0400 Subject: [PATCH 125/462] move tradeEnd into BackingManager + bugfixes --- contracts/p0/BackingManager.sol | 13 ++++++++- contracts/p0/mixins/Trading.sol | 23 ++-------------- contracts/p1/BackingManager.sol | 14 ++++++++-- contracts/p1/RevenueTrader.sol | 2 +- contracts/p1/mixins/Trading.sol | 35 ++++++------------------ contracts/plugins/trading/DutchTrade.sol | 21 ++++++++------ 6 files changed, 48 insertions(+), 60 deletions(-) diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 1dd3527d4e..fa6dedd026 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -26,6 +26,8 @@ contract BackingManagerP0 is TradingP0, IBackingManager { uint48 public tradingDelay; // {s} how long to wait until resuming trading after switching uint192 public backingBuffer; // {%} how much extra backing collateral to keep + mapping(TradeKind => uint48) private tradeEnd; // {s} The last endTime() of an auction of each kind + function init( IMain main_, uint48 tradingDelay_, @@ -74,6 +76,13 @@ contract BackingManagerP0 is TradingP0, IBackingManager { main.assetRegistry().refresh(); main.furnace().melt(); + // DoS prevention: unless caller is self, require 1 empty block between like-kind auctions + // Assumption: chain has <= 12s blocktimes + require( + _msgSender() == address(this) || tradeEnd[kind] < block.timestamp + 12, + "wait 1 block" + ); + require(tradesOpen == 0, "trade open"); require(main.basketHandler().isReady(), "basket not ready"); require( @@ -109,7 +118,9 @@ contract BackingManagerP0 is TradingP0, IBackingManager { if (req.sellAmount > bal) main.stRSR().seizeRSR(req.sellAmount - bal); } - tryTrade(kind, req); + // Execute Trade + ITrade trade = tryTrade(kind, req); + tradeEnd[kind] = trade.endTime(); } else { // Haircut time compromiseBasketsNeeded(basketsHeld.bottom); diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index dd160150d2..af0938158d 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -26,8 +26,6 @@ abstract contract TradingP0 is RewardableP0, ITrading { uint192 public minTradeVolume; // {UoA} - mapping(TradeKind => uint48) public lastEndTime; // {s} block timestamp - // untestable: // `else` branch of `onlyInitializing` (ie. revert) is currently untestable. // This function is only called inside other `init` functions, each of which is wrapped @@ -63,7 +61,8 @@ abstract contract TradingP0 is RewardableP0, ITrading { /// Try to initiate a trade with a trading partner provided by the broker /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION - function tryTrade(TradeKind kind, TradeRequest memory req) internal { + /// @return trade The trade contract created + function tryTrade(TradeKind kind, TradeRequest memory req) internal returns (ITrade trade) { IBroker broker = main.broker(); assert(address(trades[req.sell.erc20()]) == address(0)); require(!broker.disabled(), "broker disabled"); @@ -71,23 +70,7 @@ abstract contract TradingP0 is RewardableP0, ITrading { req.sell.erc20().safeApprove(address(broker), 0); req.sell.erc20().safeApprove(address(broker), req.sellAmount); - // Only allow starting the next auction back-to-back if msgSender is self - // Only time this happens is BackingManager.settleTrade() -> BackingManager.rebalance() - // TODO is there a better way to do this? - if (_msgSender() != address(this)) { - // Warning, Assumption: blocktime <= 12s - // Require at least 1 block between auctions of the same kind - // This gives space for someone to start one of the opposite kinds of auctions - uint48 lastEnd = lastEndTime[ - kind == TradeKind.DUTCH_AUCTION ? TradeKind.BATCH_AUCTION : TradeKind.DUTCH_AUCTION - ]; - require(block.timestamp > lastEnd, "wait 1 block"); - } - - ITrade trade = broker.openTrade(kind, req); - uint48 endTime = trade.endTime(); - if (endTime > lastEndTime[kind]) lastEndTime[kind] = endTime; - + trade = broker.openTrade(kind, req); trades[req.sell.erc20()] = trade; tradesOpen++; emit TradeStarted( diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 76aa3166fc..d2c282b5eb 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -38,6 +38,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // === 3.0.0 === IFurnace private furnace; + mapping(TradeKind => uint48) private tradeEnd; // {s} The last endTime() of an auction of each kind // ==== Invariants ==== // tradingDelay <= MAX_TRADING_DELAY and backingBuffer <= MAX_BACKING_BUFFER @@ -106,6 +107,13 @@ contract BackingManagerP1 is TradingP1, IBackingManager { assetRegistry.refresh(); furnace.melt(); + // DoS prevention: unless caller is self, require 1 empty block between like-kind auctions + // Assumption: chain has <= 12s blocktimes + require( + _msgSender() == address(this) || tradeEnd[kind] < block.timestamp + 12, + "wait 1 block" + ); + require(tradesOpen == 0, "trade open"); require(basketHandler.isReady(), "basket not ready"); require(block.timestamp >= basketHandler.timestamp() + tradingDelay, "trading delayed"); @@ -138,7 +146,9 @@ contract BackingManagerP1 is TradingP1, IBackingManager { if (req.sellAmount > bal) stRSR.seizeRSR(req.sellAmount - bal); } - tryTrade(kind, req); + // Execute Trade + ITrade trade = tryTrade(kind, req); + tradeEnd[kind] = trade.endTime(); } else { // Haircut time compromiseBasketsNeeded(basketsHeld.bottom); @@ -285,5 +295,5 @@ contract BackingManagerP1 is TradingP1, IBackingManager { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[41] private __gap; + uint256[39] private __gap; } diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index d06959374f..6cb6b95c9d 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -83,7 +83,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { IAsset buy = assetRegistry.toAsset(tokenToBuy); // === Refresh === - // do not need to refresh when caller is BackingManager + // do not need to refresh when caller is BackingManager.forwardRevenue() if (_msgSender() != address(backingManager)) { if (erc20 == IERC20(address(rToken)) || tokenToBuy == IERC20(address(rToken))) { // if either token is the RToken, refresh everything diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index c1bb7f9ed3..2461bd15dc 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -32,9 +32,6 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl uint192 public minTradeVolume; // {UoA} - // === 3.0.0 === - mapping(TradeKind => uint48) public lastEndTime; // {s} block timestamp - // ==== Invariants ==== // tradesOpen = len(values(trades)) // trades[sell] != 0 iff trade[sell] has been opened and not yet settled @@ -104,6 +101,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl /// Try to initiate a trade with a trading partner provided by the broker /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION + /// @return trade The trade contract created /// @custom:interaction (only reads or writes `trades`, and is marked `nonReentrant`) // checks: // (not external, so we don't need auth or pause checks) @@ -120,7 +118,11 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // This is reentrancy-safe because we're using the `nonReentrant` modifier on every method of // this contract that changes state this function refers to. // slither-disable-next-line reentrancy-vulnerabilities-1 - function tryTrade(TradeKind kind, TradeRequest memory req) internal nonReentrant { + function tryTrade(TradeKind kind, TradeRequest memory req) + internal + nonReentrant + returns (ITrade trade) + { /* */ IERC20 sell = req.sell.erc20(); assert(address(trades[sell]) == address(0)); @@ -128,28 +130,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl IERC20Upgradeable(address(sell)).safeApprove(address(broker), 0); IERC20Upgradeable(address(sell)).safeApprove(address(broker), req.sellAmount); - // Only allow starting the next auction back-to-back if msgSender is self - // Only time this happens is BackingManager.settleTrade() -> BackingManager.rebalance() - // TODO is there a better way to do this? - if (_msgSender() != address(this)) { - // Warning, Assumption: blocktime <= 12s - // Require at least 1 block between auctions of the same kind - // This gives space for someone to start one of the opposite kinds of auctions - require( - block.timestamp > - lastEndTime[ - kind == TradeKind.DUTCH_AUCTION - ? TradeKind.BATCH_AUCTION - : TradeKind.DUTCH_AUCTION - ], - "wait 1 block" - ); - } - - ITrade trade = broker.openTrade(kind, req); - uint48 endTime = trade.endTime(); - if (endTime > lastEndTime[kind]) lastEndTime[kind] = endTime; - + trade = broker.openTrade(kind, req); trades[sell] = trade; tradesOpen++; emit TradeStarted(trade, sell, req.buy.erc20(), req.sellAmount, req.minBuyAmount); @@ -187,5 +168,5 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[45] private __gap; + uint256[46] private __gap; } diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 343604dba9..7efd5ceb8e 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -71,7 +71,7 @@ contract DutchTrade is ITrade { uint256 sellAmount_, uint48 auctionLength ) external stateTransition(TradeStatus.NOT_STARTED, TradeStatus.OPEN) { - require(address(sell) != address(0) || address(buy) != address(0), "zero address token"); + assert(address(sell_) != address(0) && address(buy_) != address(0)); // misuse of contract // Only start dutch auctions under well-defined prices // @@ -118,15 +118,16 @@ contract DutchTrade is ITrade { /// Bid for the auction lot at the current price; settling atomically via a callback /// @dev Caller must have provided approval - function bid() external { + /// @return amountIn {qBuyTok} The quantity of tokens the bidder paid + function bid() external returns (uint256 amountIn) { require(bidder == address(0), "bid received"); // {qBuyTok} - uint256 buyAmount = bidAmount(uint48(block.timestamp)); + amountIn = bidAmount(uint48(block.timestamp)); // Transfer in buy tokens bidder = msg.sender; - buy.safeTransferFrom(bidder, address(this), buyAmount); + buy.safeTransferFrom(bidder, address(this), amountIn); // TODO examine reentrancy - think it's probably ok // the other candidate design is ditch the bid() function entirely and have them transfer @@ -141,6 +142,8 @@ contract DutchTrade is ITrade { } /// Settle the auction, emptying the contract of balances + /// @return soldAmt {qSellTok} Token quantity sold by the protocol + /// @return boughtAmt {qBuyTok} Token quantity purchased by the protocol function settle() external stateTransition(TradeStatus.OPEN, TradeStatus.CLOSED) @@ -155,13 +158,13 @@ contract DutchTrade is ITrade { require(block.timestamp >= endTime, "auction not over"); } - // {qBuyTok} - uint256 boughtAmount = buy.balanceOf(address(this)); + uint256 sellBal = sell.balanceOf(address(this)); + soldAmt = sellAmount > sellBal ? sellAmount - sellBal : 0; + boughtAmt = buy.balanceOf(address(this)); // Transfer balances back to origin - buy.safeTransfer(address(origin), boughtAmount); - sell.safeTransfer(address(origin), sell.balanceOf(address(this))); - return (sellAmount, boughtAmount); + buy.safeTransfer(address(origin), boughtAmt); + sell.safeTransfer(address(origin), sellBal); } /// Anyone can transfer any ERC20 back to the origin after the trade has been closed From 443709120b351e20f9fc4c9ed0eb813e5f185d97 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sun, 30 Apr 2023 00:43:49 -0400 Subject: [PATCH 126/462] initial revenue dutch tests --- test/Revenues.test.ts | 120 +++++++++++++++++++++++++++++++++++++++++- test/utils/trades.ts | 55 ++++++++++++++++++- 2 files changed, 172 insertions(+), 3 deletions(-) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 4baec598e6..e306f198c3 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -43,7 +43,7 @@ import { } from '../typechain' import { whileImpersonating } from './utils/impersonation' import snapshotGasCost from './utils/snapshotGasCost' -import { advanceTime, getLatestBlockTimestamp } from './utils/time' +import { advanceTime, advanceToTimestamp, getLatestBlockTimestamp } from './utils/time' import { withinQuad } from './utils/matchers' import { Collateral, @@ -56,7 +56,7 @@ import { REVENUE_HIDING, } from './fixtures' import { expectRTokenPrice, setOraclePrice } from './utils/oracles' -import { expectTrade, getTrade } from './utils/trades' +import { dutchBuyAmount, expectTrade, getTrade } from './utils/trades' import { useEnv } from '#/utils/env' const describeGas = @@ -2336,6 +2336,122 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Check status - nothing claimed expect(await aaveToken.balanceOf(backingManager.address)).to.equal(0) }) + + context('DutchTrade', () => { + const auctionLength = 300 + beforeEach(async () => { + await broker.connect(owner).setDutchAuctionLength(auctionLength) + }) + + it('Should not trade when paused', async () => { + await main.connect(owner).pauseTrading() + await expect( + rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + ).to.be.revertedWith('frozen or trading paused') + }) + + it('Should not trade when frozen', async () => { + await main.connect(owner).freezeLong() + await expect( + rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + ).to.be.revertedWith('frozen or trading paused') + }) + + it('Should only run 1 trade per ERC20 at a time', async () => { + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) + await expect( + rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + ).to.be.revertedWith('trade open') + + // Other ERC20 should be able to open trade + await token1.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await rTokenTrader.manageToken(token1.address, TradeKind.BATCH_AUCTION) + }) + + it('Should quote piecewise-falling price correctly throughout entirety of auction', async () => { + issueAmount = issueAmount.div(2) + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + const trade = await ethers.getContractAt( + 'DutchTrade', + await rTokenTrader.trades(token0.address) + ) + await rToken.connect(addr1).approve(trade.address, initialBal) + + const start = await trade.startTime() + const end = await trade.endTime() + + // Simulate 5 minutes of blocks, should swap at right price each time + for (let now = await getLatestBlockTimestamp(); now < end; now += 12) { + const actual = await trade.connect(addr1).bidAmount(now) + const expected = await dutchBuyAmount( + fp(now - start).div(end - start), + rTokenAsset.address, + collateral0.address, + issueAmount, + config.minTradeVolume, + config.maxTradeSlippage + ) + expect(actual).to.equal(expected) + + const staticResult = await trade.connect(addr1).callStatic.bid() + expect(staticResult).to.equal(expected) + await advanceToTimestamp((await getLatestBlockTimestamp()) + 12) + } + }) + + it('Should handle no bid case correctly', async () => { + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + const trade = await ethers.getContractAt( + 'DutchTrade', + await rTokenTrader.trades(token0.address) + ) + await rToken.connect(addr1).approve(trade.address, initialBal) + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 1) + await expect( + trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + ).to.be.revertedWith('auction over') + await expect(trade.connect(addr1).bid()).be.revertedWith('auction over') + + // Should be able to settle + await expect(trade.settle()).to.be.revertedWith('only origin can settle') + await expect(rTokenTrader.settleTrade(token0.address)) + .to.emit(rTokenTrader, 'TradeSettled') + .withArgs(trade.address, token0.address, rToken.address, 0, 0) + }) + + it('Should bid in final second of auction and not launch another auction', async () => { + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + const trade = await ethers.getContractAt( + 'DutchTrade', + await rTokenTrader.trades(token0.address) + ) + await rToken.connect(addr1).approve(trade.address, initialBal) + + // Snipe auction at 1s left + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 3) + await trade.connect(addr1).bid() + expect(await trade.canSettle()).to.equal(false) + expect(await trade.status()).to.equal(2) // Status.CLOSED + expect(await trade.bidder()).to.equal(addr1.address) + expect(await token0.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(4))) + + const expected = await dutchBuyAmount( + fp('299').div(300), // after all txs in this test, will be left at 299/300s + rTokenAsset.address, + collateral0.address, + issueAmount, + config.minTradeVolume, + config.maxTradeSlippage + ) + expect(await rTokenTrader.tradesOpen()).to.equal(0) + expect(await rToken.balanceOf(rTokenTrader.address)).to.be.closeTo(0, 100) + expect(await rToken.balanceOf(furnace.address)).to.equal(expected) + }) + }) }) context('With simple basket of ATokens and CTokens', function () { diff --git a/test/utils/trades.ts b/test/utils/trades.ts index a05254f2c7..4211e80380 100644 --- a/test/utils/trades.ts +++ b/test/utils/trades.ts @@ -2,7 +2,7 @@ import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { expect } from 'chai' import { TestITrading, GnosisTrade } from '../../typechain' -import { fp, divCeil } from '../../common/numbers' +import { bn, fp, divCeil } from '../../common/numbers' export const expectTrade = async (trader: TestITrading, auctionInfo: Partial) => { if (!auctionInfo.sell) throw new Error('Must provide sell token to find trade') @@ -73,3 +73,56 @@ export const toMinBuyAmt = ( return divCeil(divCeil(product, highBuyPrice), fp('1')) // (c) } + +// Returns the buy amount in the auction for the given progression +export const dutchBuyAmount = async ( + progression: BigNumber, + assetInAddr: string, + assetOutAddr: string, + outAmount: BigNumber, + minTradeVolume: BigNumber, + maxTradeSlippage: BigNumber +): Promise => { + const assetIn = await ethers.getContractAt('IAsset', assetInAddr) + const assetOut = await ethers.getContractAt('IAsset', assetOutAddr) + const [sellLow, sellHigh] = await assetOut.price() // {UoA/sellTok} + const [buyLow, buyHigh] = await assetIn.price() // {UoA/buyTok} + + const inMaxTradeVolume = await assetIn.maxTradeVolume() + let maxTradeVolume = await assetOut.maxTradeVolume() + if (inMaxTradeVolume.lt(maxTradeVolume)) maxTradeVolume = inMaxTradeVolume + + const auctionVolume = outAmount.mul(sellHigh).div(fp('1')) + const slippage = maxTradeSlippage + .mul( + fp('1').sub( + auctionVolume.sub(minTradeVolume).mul(fp('1')).div(maxTradeVolume.sub(minTradeVolume)) + ) + ) + .div(fp('1')) + + // console.log('slippage: ', slippage) + + const lowPrice = sellLow.mul(fp('1').sub(slippage)).div(buyHigh) + const middlePrice = divCeil(sellHigh.mul(fp('1')), buyLow) + const highPrice = middlePrice.add(divCeil(middlePrice, bn('2'))) // 50% above middlePrice + + const price = progression.lt(fp('0.15')) + ? highPrice.sub(highPrice.sub(middlePrice).mul(progression).div(fp('0.15'))) + : middlePrice.sub( + middlePrice + .sub(lowPrice) + .mul(progression.sub(fp('0.15'))) + .div(fp('0.85')) + ) + // console.log( + // 'progression: ', + // progression, + // 'price: ', + // price, + // 'buyAmount: ', + // divCeil(outAmount.mul(price), fp('1')) + // ) + + return divCeil(outAmount.mul(price), fp('1')) +} From a4008fb12fab56883429711d580c7ec322e6bbb4 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sun, 30 Apr 2023 21:36:43 -0400 Subject: [PATCH 127/462] sync BackingManagerP0/P1 --- contracts/p0/BackingManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index fa6dedd026..e4adf89721 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -158,7 +158,6 @@ contract BackingManagerP0 is TradingP0, IBackingManager { uint192 needed = main.rToken().basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); // {BU} { IRToken rToken = main.rToken(); - needed = rToken.basketsNeeded(); // {BU} if (basketsHeld.bottom.gt(needed)) { int8 decimals = int8(rToken.decimals()); uint192 totalSupply = shiftl_toFix(rToken.totalSupply(), -decimals); // {rTok} @@ -171,6 +170,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { rToken.mint(address(this), rTok); rToken.setBasketsNeeded(basketsHeld.bottom); + needed = basketsHeld.bottom; } } From 46f7e1ea7c123d88ee900b89231c77f5b0673ea3 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sun, 30 Apr 2023 21:49:34 -0400 Subject: [PATCH 128/462] nit: add a comment back in --- contracts/mixins/Auth.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/mixins/Auth.sol b/contracts/mixins/Auth.sol index fa4735c285..ea0583b159 100644 --- a/contracts/mixins/Auth.sol +++ b/contracts/mixins/Auth.sol @@ -22,6 +22,7 @@ abstract contract Auth is AccessControlUpgradeable, IAuth { * - Issuance Paused: disallow issuance * * Typically freezing thaws on its own in a predetermined number of blocks. + * However, OWNER can freeze forever and unfreeze. */ /// The rest of the contract uses the shorthand; these declarations are here for getters From 449911b10aa8b473db361d7079b1c685cf31f2e2 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sun, 30 Apr 2023 21:51:20 -0400 Subject: [PATCH 129/462] lint clean --- contracts/p0/BackingManager.sol | 2 +- contracts/p1/BackingManager.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index e4adf89721..60012db23a 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -26,7 +26,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { uint48 public tradingDelay; // {s} how long to wait until resuming trading after switching uint192 public backingBuffer; // {%} how much extra backing collateral to keep - mapping(TradeKind => uint48) private tradeEnd; // {s} The last endTime() of an auction of each kind + mapping(TradeKind => uint48) private tradeEnd; // {s} last endTime() of an auction per kind function init( IMain main_, diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index d2c282b5eb..59874317ea 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -38,7 +38,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // === 3.0.0 === IFurnace private furnace; - mapping(TradeKind => uint48) private tradeEnd; // {s} The last endTime() of an auction of each kind + mapping(TradeKind => uint48) private tradeEnd; // {s} last endTime() of an auction per kind // ==== Invariants ==== // tradingDelay <= MAX_TRADING_DELAY and backingBuffer <= MAX_BACKING_BUFFER From fc9a3de819379d51aba96ab996cc06b21730587e Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sun, 30 Apr 2023 21:59:29 -0400 Subject: [PATCH 130/462] better error message + fix EasyAuction test --- contracts/p0/BackingManager.sol | 2 +- contracts/p1/BackingManager.sol | 2 +- test/integration/EasyAuction.test.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 60012db23a..f40fbd0a88 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -80,7 +80,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { // Assumption: chain has <= 12s blocktimes require( _msgSender() == address(this) || tradeEnd[kind] < block.timestamp + 12, - "wait 1 block" + "already rebalancing" ); require(tradesOpen == 0, "trade open"); diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 59874317ea..b18594f89f 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -111,7 +111,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // Assumption: chain has <= 12s blocktimes require( _msgSender() == address(this) || tradeEnd[kind] < block.timestamp + 12, - "wait 1 block" + "already rebalancing" ); require(tradesOpen == 0, "trade open"); diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 2eba6213bc..78e8783c99 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -176,7 +176,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function // Check Gnosis expect(await rsr.balanceOf(easyAuction.address)).to.equal(sellAmt) await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( - 'trade open' + 'already rebalancing' ) // Auction should not be able to be settled @@ -603,7 +603,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function // Check Gnosis expect(await token0.balanceOf(easyAuction.address)).to.equal(issueAmount) await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( - 'trade open' + 'already rebalancing' ) // Auction should not be able to be settled @@ -706,7 +706,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function // Check Gnosis expect(await token0.balanceOf(easyAuction.address)).to.be.closeTo(issueAmount, 1) await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( - 'trade open' + 'already rebalancing' ) // Auction should not be able to be settled From 11d881745b8e30da8d7459b16f8698c15a25bfa9 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 1 May 2023 12:13:22 -0400 Subject: [PATCH 131/462] BackingManager tests --- test/Recollateralization.test.ts | 169 +++++++++++++++++++++++++++++-- test/Revenues.test.ts | 13 ++- 2 files changed, 171 insertions(+), 11 deletions(-) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 89c11640d5..bd3ecba973 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -5,7 +5,13 @@ import { expect } from 'chai' import { BigNumber, ContractFactory } from 'ethers' import { ethers } from 'hardhat' import { IConfig } from '../common/configuration' -import { BN_SCALE_FACTOR, CollateralStatus, TradeKind, MAX_UINT256 } from '../common/constants' +import { + BN_SCALE_FACTOR, + CollateralStatus, + TradeKind, + MAX_UINT256, + ZERO_ADDRESS, +} from '../common/constants' import { expectEvents } from '../common/events' import { bn, fp, pow10, toBNDecimals, divCeil } from '../common/numbers' import { @@ -23,12 +29,13 @@ import { StaticATokenMock, TestIBackingManager, TestIBasketHandler, + TestIBroker, TestIMain, TestIRToken, TestIStRSR, USDCMock, } from '../typechain' -import { advanceTime, getLatestBlockTimestamp } from './utils/time' +import { advanceTime, advanceToTimestamp, getLatestBlockTimestamp } from './utils/time' import { Collateral, defaultFixture, @@ -39,7 +46,7 @@ import { PRICE_TIMEOUT, } from './fixtures' import snapshotGasCost from './utils/snapshotGasCost' -import { expectTrade, getTrade } from './utils/trades' +import { expectTrade, getTrade, dutchBuyAmount } from './utils/trades' import { withinQuad } from './utils/matchers' import { expectRTokenPrice, setOraclePrice } from './utils/oracles' import { useEnv } from '#/utils/env' @@ -93,6 +100,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { let backingManager: TestIBackingManager let basketHandler: TestIBasketHandler let main: TestIMain + let broker: TestIBroker interface IBackingInfo { tokens: string[] @@ -156,6 +164,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { basketHandler, main, rTokenAsset, + broker, } = await loadFixture(defaultFixture)) token0 = erc20s[collateral.indexOf(basket[0])] token1 = erc20s[collateral.indexOf(basket[1])] @@ -799,7 +808,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await stRSR.connect(addr1).stake(stakeAmount) }) - it('Should not trade if paused', async () => { + it('Should not trade if trading paused', async () => { await main.connect(owner).pauseTrading() await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'frozen or trading paused' @@ -813,6 +822,16 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ) }) + it('Should trade if issuance paused', async () => { + // Setup new prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + await main.connect(owner).pauseIssuance() + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) + }) + it('Should not trade if UNPRICED', async () => { await advanceTime(ORACLE_TIMEOUT.toString()) await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( @@ -3092,6 +3111,138 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmtRemToken)) expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) }) + + context('DutchTrade', () => { + const auctionLength = 300 + beforeEach(async () => { + await broker.connect(owner).setDutchAuctionLength(auctionLength) + + // Set up rebalancing scenario + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + await basketHandler.refreshBasket() + }) + + it('Should not trade when paused', async () => { + await main.connect(owner).pauseTrading() + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.be.revertedWith( + 'frozen or trading paused' + ) + }) + + it('Should not trade when frozen', async () => { + await main.connect(owner).freezeLong() + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.be.revertedWith( + 'frozen or trading paused' + ) + }) + + it('Should trade if issuance paused', async () => { + await main.connect(owner).pauseIssuance() + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) + }) + + it('Should only run 1 trade at a time', async () => { + await backingManager.rebalance(TradeKind.DUTCH_AUCTION) + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.be.revertedWith( + 'already rebalancing' + ) + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'trade open' + ) + }) + + it('Should quote piecewise-falling price correctly throughout entirety of auction', async () => { + await backingManager.rebalance(TradeKind.DUTCH_AUCTION) + const trade = await ethers.getContractAt( + 'DutchTrade', + await backingManager.trades(token0.address) + ) + await token1.connect(addr1).approve(trade.address, initialBal) + + const start = await trade.startTime() + const end = await trade.endTime() + + // Simulate 5 minutes of blocks, should swap at right price each time + for (let now = await getLatestBlockTimestamp(); now < end; now += 12) { + const actual = await trade.connect(addr1).bidAmount(now) + const expected = divCeil( + await dutchBuyAmount( + fp(now - start).div(end - start), + rTokenAsset.address, + collateral0.address, + issueAmount, + config.minTradeVolume, + config.maxTradeSlippage + ), + bn('1e12') // fix for decimals + ) + expect(actual).to.equal(expected) + + const staticResult = await trade.connect(addr1).callStatic.bid() + expect(staticResult).to.equal(expected) + await advanceToTimestamp((await getLatestBlockTimestamp()) + 12) + } + }) + + it('Should handle no bid case correctly', async () => { + await backingManager.rebalance(TradeKind.DUTCH_AUCTION) + const trade = await ethers.getContractAt( + 'DutchTrade', + await backingManager.trades(token0.address) + ) + await token1.connect(addr1).approve(trade.address, initialBal) + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 1) + await expect( + trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + ).to.be.revertedWith('auction over') + await expect(trade.connect(addr1).bid()).be.revertedWith('auction over') + + // Should be able to settle + await expect(trade.settle()).to.be.revertedWith('only origin can settle') + await expect(backingManager.settleTrade(token0.address)) + .to.emit(backingManager, 'TradeSettled') + .withArgs(trade.address, token0.address, token1.address, 0, 0) + }) + + it('Should bid in final second of auction and launch another auction', async () => { + await backingManager.rebalance(TradeKind.DUTCH_AUCTION) + const trade = await ethers.getContractAt( + 'DutchTrade', + await backingManager.trades(token0.address) + ) + await token1.connect(addr1).approve(trade.address, initialBal) + + // Snipe auction at 1s left + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 3) + await trade.connect(addr1).bid() + expect(await trade.canSettle()).to.equal(false) + expect(await trade.status()).to.equal(2) // Status.CLOSED + expect(await trade.bidder()).to.equal(addr1.address) + expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) + + const expected = divCeil( + await dutchBuyAmount( + fp('299').div(300), // after all txs in this test, will be left at 299/300s + collateral0.address, + collateral1.address, + issueAmount, + config.minTradeVolume, + config.maxTradeSlippage + ), + bn('1e12') // decimals + ) + expect(await backingManager.tradesOpen()).to.equal(1) + expect(await token1.balanceOf(backingManager.address)).to.equal(expected) + + // Should launch another auction: RSR -> token1 + expect(await backingManager.trades(token0.address)).to.equal(ZERO_ADDRESS) + expect(await backingManager.trades(rsr.address)).to.not.equal(ZERO_ADDRESS) + expect(await backingManager.tradesOpen()).to.equal(1) + }) + }) }) context('With issued Rtokens', function () { @@ -3119,7 +3270,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await backingManager.connect(owner).setBackingBuffer(0) // Provide approvals - await token0.connect(addr1).approve(rToken.address, initialBal) + await token1.connect(addr1).approve(rToken.address, initialBal) await token1.connect(addr1).approve(rToken.address, initialBal) await token2.connect(addr1).approve(rToken.address, initialBal) await token3.connect(addr1).approve(rToken.address, initialBal) @@ -3903,7 +4054,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Perform Mock Bids for the new Token (addr1 has balance) - await token0.connect(addr1).approve(gnosis.address, minBuyAmt2) + await token1.connect(addr1).approve(gnosis.address, minBuyAmt2) await gnosis.placeBid(0, { bidder: addr1.address, sellAmount: sellAmt2, @@ -3957,7 +4108,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(quotes).to.eql(newQuotes) // Perform Mock Bids (addr1 has balance) - await token0.connect(addr1).approve(gnosis.address, minBuyAmt1) + await token1.connect(addr1).approve(gnosis.address, minBuyAmt1) await gnosis.placeBid(1, { bidder: addr1.address, sellAmount: toBNDecimals(sellAmt1, 6), @@ -4017,7 +4168,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ;[, quotes] = await facade.connect(addr1).callStatic.issue(rToken.address, bn('1e18')) expect(quotes).to.eql(newQuotes) - await token0.connect(addr1).approve(gnosis.address, minBuyAmt3) + await token1.connect(addr1).approve(gnosis.address, minBuyAmt3) await gnosis.placeBid(2, { bidder: addr1.address, sellAmount: toBNDecimals(sellAmt3, 8), @@ -4661,7 +4812,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await backingManager.connect(owner).setBackingBuffer(0) // Provide approvals - await token0.connect(addr1).approve(rToken.address, initialBal) + await token1.connect(addr1).approve(rToken.address, initialBal) await token1.connect(addr1).approve(rToken.address, initialBal) await token2.connect(addr1).approve(rToken.address, initialBal) await token3.connect(addr1).approve(rToken.address, initialBal) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index e306f198c3..ef71f2c7e3 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2357,16 +2357,25 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ).to.be.revertedWith('frozen or trading paused') }) + it('Should trade if issuance paused', async () => { + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await main.connect(owner).pauseIssuance() + await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + }) + it('Should only run 1 trade per ERC20 at a time', async () => { await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) + await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) await expect( rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) ).to.be.revertedWith('trade open') + await expect( + rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('trade open') // Other ERC20 should be able to open trade await token1.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await rTokenTrader.manageToken(token1.address, TradeKind.BATCH_AUCTION) + await rTokenTrader.manageToken(token1.address, TradeKind.DUTCH_AUCTION) }) it('Should quote piecewise-falling price correctly throughout entirety of auction', async () => { From fccddf6544051a2d193f3948bfd4789cb0b49a25 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 1 May 2023 15:22:48 -0400 Subject: [PATCH 132/462] fix tests --- test/Recollateralization.test.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index bd3ecba973..5c8092e916 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -825,6 +825,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { it('Should trade if issuance paused', async () => { // Setup new prime basket await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + await basketHandler.refreshBasket() + await main.connect(owner).pauseIssuance() await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( backingManager, @@ -3270,7 +3272,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await backingManager.connect(owner).setBackingBuffer(0) // Provide approvals - await token1.connect(addr1).approve(rToken.address, initialBal) + await token0.connect(addr1).approve(rToken.address, initialBal) await token1.connect(addr1).approve(rToken.address, initialBal) await token2.connect(addr1).approve(rToken.address, initialBal) await token3.connect(addr1).approve(rToken.address, initialBal) @@ -4054,7 +4056,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Perform Mock Bids for the new Token (addr1 has balance) - await token1.connect(addr1).approve(gnosis.address, minBuyAmt2) + await token0.connect(addr1).approve(gnosis.address, minBuyAmt2) await gnosis.placeBid(0, { bidder: addr1.address, sellAmount: sellAmt2, @@ -4108,7 +4110,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(quotes).to.eql(newQuotes) // Perform Mock Bids (addr1 has balance) - await token1.connect(addr1).approve(gnosis.address, minBuyAmt1) + await token0.connect(addr1).approve(gnosis.address, minBuyAmt1) await gnosis.placeBid(1, { bidder: addr1.address, sellAmount: toBNDecimals(sellAmt1, 6), @@ -4168,7 +4170,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ;[, quotes] = await facade.connect(addr1).callStatic.issue(rToken.address, bn('1e18')) expect(quotes).to.eql(newQuotes) - await token1.connect(addr1).approve(gnosis.address, minBuyAmt3) + await token0.connect(addr1).approve(gnosis.address, minBuyAmt3) await gnosis.placeBid(2, { bidder: addr1.address, sellAmount: toBNDecimals(sellAmt3, 8), From 0d838db8c1cdafac0ea3cb414f0e728e9e5fe1f5 Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Mon, 1 May 2023 15:42:07 -0400 Subject: [PATCH 133/462] add historical redemption tests to mirror redeeem tests. --- test/RToken.test.ts | 696 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 696 insertions(+) diff --git a/test/RToken.test.ts b/test/RToken.test.ts index a54c8bbebd..bb38cf75de 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -1414,6 +1414,702 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) }) }) + + context.only('Historical redemptions with issued RTokens', function () { + let issueAmount: BigNumber + + beforeEach(async function () { + // Issue some RTokens to user + issueAmount = bn('100e18') + // Provide approvals + await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, initialBal))) + + // Issue rTokens + await rToken.connect(addr1).issue(issueAmount) + }) + + it('Should redeem RTokens correctly', async function () { + const redeemAmount = bn('100e18') + + // Check balances + expect(await rToken.balanceOf(addr1.address)).to.equal(redeemAmount) + expect(await rToken.totalSupply()).to.equal(redeemAmount) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + + // Redeem rTokens + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, redeemAmount) + await rToken.connect(addr1).customRedemption( + addr1.address, + redeemAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + + // Check funds were transferred + expect(await rToken.balanceOf(addr1.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(0) + + await Promise.all( + tokens.map(async (t) => { + expect(await t.balanceOf(addr1.address)).to.equal(initialBal) + }) + ) + + // Check asset value + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + issueAmount.sub(redeemAmount) + ) + }) + + it('Should redeem to a different account', async function () { + // Provide approvals + await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, initialBal))) + + // Redeem rTokens to another account + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount) + await expect( + rToken.connect(addr1).customRedemption( + addr2.address, + issueAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + ) + .to.emit(rToken, 'Redemption') + .withArgs(addr1.address, addr2.address, issueAmount, issueAmount) + expect(await rToken.balanceOf(addr1.address)).to.equal(0) + expect(await rToken.balanceOf(addr2.address)).to.equal(0) + expect(await token0.balanceOf(addr2.address)).to.equal(initialBal.add(issueAmount.div(4))) + }) + + it('Should redeem RTokens correctly for multiple users', async function () { + const issueAmount = bn('100e18') + const redeemAmount = bn('100e18') + + // Issue new RTokens + await Promise.all(tokens.map((t) => t.connect(addr2).approve(rToken.address, initialBal))) + + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + + // Issue rTokens + await rToken.connect(addr2).issue(issueAmount) + + // Check asset value + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + issueAmount.mul(2) + ) + + // Redeem rTokens + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, redeemAmount) + await rToken.connect(addr1).customRedemption( + addr1.address, + redeemAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + + // Check asset value + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + issueAmount.mul(2).sub(redeemAmount) + ) + + // Redeem rTokens with another user + await rToken.connect(addr2).customRedemption( + addr2.address, + redeemAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + + // Check funds were transferred + expect(await rToken.balanceOf(addr1.address)).to.equal(0) + expect(await rToken.balanceOf(addr2.address)).to.equal(0) + + expect(await rToken.totalSupply()).to.equal(0) + + await Promise.all( + tokens.map(async (t) => { + expect(await t.balanceOf(addr1.address)).to.equal(initialBal) + expect(await t.balanceOf(addr2.address)).to.equal(initialBal) + }) + ) + + // Check asset value + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + issueAmount.mul(2).sub(redeemAmount.mul(2)) + ) + }) + + it('Should redeem if basket is IFFY #fast', async function () { + // Default one of the tokens - 50% price reduction and mark default as probable + await setOraclePrice(collateral3.address, bn('0.5e8')) + + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount) + await rToken.connect(addr1).customRedemption( + addr1.address, + issueAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + expect(await rToken.totalSupply()).to.equal(0) + }) + + it('Should redeem if basket is UNPRICED #fast', async function () { + await advanceTime(ORACLE_TIMEOUT.toString()) + + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount) + await rToken.connect(addr1).customRedemption( + addr1.address, + issueAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + expect(await rToken.totalSupply()).to.equal(0) + }) + + it('Should redeem if paused #fast', async function () { + await main.connect(owner).pauseTrading() + await main.connect(owner).pauseIssuance() + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount) + await rToken.connect(addr1).customRedemption( + addr1.address, + issueAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + expect(await rToken.totalSupply()).to.equal(0) + }) + + it('Should revert if frozen #fast', async function () { + await main.connect(owner).freezeShort() + + // Try to redeem + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount) + await expect( + rToken.connect(addr1).customRedemption( + addr1.address, + issueAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + ).to.be.revertedWith('frozen') + + // Check values + expect(await rToken.totalSupply()).to.equal(issueAmount) + }) + + it('Should revert if empty redemption #fast', async function () { + // Eliminate most token balances + const bal = issueAmount.div(4) + await token0.connect(owner).burn(backingManager.address, bal) + await token1.connect(owner).burn(backingManager.address, toBNDecimals(bal, 6)) + await token2.connect(owner).burn(backingManager.address, bal) + + // Should not revert with empty redemption yet + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount.div(2)) + await rToken.connect(addr1).customRedemption( + addr1.address, + issueAmount.div(2), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) + + // Burn the rest + await token3 + .connect(owner) + .burn(backingManager.address, await token3.balanceOf(backingManager.address)) + + // Now it should revert + await expect( + rToken.connect(addr1).customRedemption( + addr1.address, + issueAmount.div(2), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + ).to.be.revertedWith('empty redemption') + + // Check values + expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) + }) + + it('Should revert if different basketNonce #fast', async function () { + // Should fail if revertOnPartialRedemption is true + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount.div(2)) + await expect( + rToken.connect(addr1).customRedemption( + addr1.address, + issueAmount.div(2), + [2], + portions, + quote.erc20s, + quote.quantities + ) + ).to.be.revertedWith('invalid basketNonce') + }) + + it('Should prorate redemption if basket is DISABLED from fallen refPerTok() #fast', async function () { + // Default immediately + await token2.setExchangeRate(fp('0.1')) // 90% decrease + + // Even though a single BU requires 10x token2 as before, it should still hand out evenly + + // 1st redemption + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount.div(2)) + await expect( + rToken.connect(addr1).customRedemption( + addr1.address, + issueAmount.div(2), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + ).to.emit(rToken, 'Redemption') + expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) + expect(await token0.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(8))) + expect(await token2.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(8))) + + // 2nd redemption + await expect( + rToken.connect(addr1).customRedemption( + addr1.address, + issueAmount.div(2), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + ).to.emit(rToken, 'Redemption') + expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) + expect(await token2.balanceOf(addr1.address)).to.equal(initialBal) + }) + + it('Should not redeem() unregistered collateral #fast', async function () { + // Unregister collateral2 + + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount) + await assetRegistry.connect(owner).unregister(collateral2.address) + await expect( + rToken.connect(addr1).customRedemption( + addr1.address, + issueAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + ).revertedWith('erc20 unregistered') + }) + + it('Should redeem prorata when refPerTok() is 0 #fast', async function () { + // Set refPerTok to 0 + await token2.setExchangeRate(fp('0')) + + // Redemption + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount) + await expect( + rToken.connect(addr1).customRedemption( + addr1.address, + issueAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + ).to.emit(rToken, 'Redemption') + expect(await rToken.totalSupply()).to.equal(0) + expect(await token0.balanceOf(addr1.address)).to.be.equal(initialBal) + expect(await token1.balanceOf(addr1.address)).to.be.equal(initialBal) + expect(await token2.balanceOf(addr1.address)).to.be.equal(initialBal) + expect(await token3.balanceOf(addr1.address)).to.be.equal(initialBal) + }) + + it('Should not overflow BU exchange rate above 1e9 on redeem', async function () { + // Leave only 1 RToken issue + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount.sub(bn('1e18'))) + await rToken.connect(addr1).customRedemption( + addr1.address, + issueAmount.sub(bn('1e18')), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + + expect(await rToken.totalSupply()).to.equal(fp('1')) + + // setBasketsNeeded() + await whileImpersonating(backingManager.address, async (signer) => { + await rToken.connect(signer).setBasketsNeeded(fp('1e9')) + }) + + const redeemAmount: BigNumber = bn('1.5e9') + + // Redeem rTokens successfully + const quote2 = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, bn(redeemAmount)) + await expect( + rToken.connect(addr1).customRedemption( + addr1.address, + bn(redeemAmount), + basketNonces, + portions, + quote2.erc20s, + quote2.quantities + ) + ).to.not.be.reverted + }) + + context('And redemption throttling', function () { + // the fixture-configured redemption throttle uses 5% + let redemptionThrottleParams: ThrottleParams + let redeemAmount: BigNumber + + beforeEach(async function () { + redemptionThrottleParams = { + amtRate: fp('2'), // 2 RToken, + pctRate: fp('0.1'), // 10% + } + await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + const params = await rToken.redemptionThrottleParams() + expect(params[0]).to.equal(redemptionThrottleParams.amtRate) + expect(params[1]).to.equal(redemptionThrottleParams.pctRate) + + // Charge throttle + await advanceTime(3600) + }) + + it('Should be able to do geometric redemptions to scale down supply', async function () { + // Should complete is just under 30 iterations at rates: 10% + 1e18 + const numIterations = 30 + for (let i = 0; i < numIterations; i++) { + const totalSupply = await rToken.totalSupply() + if (totalSupply.eq(0)) break + + // Charge + redeem + await advanceTime(3600) + redeemAmount = await rToken.redemptionAvailable() + + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, redeemAmount) + await rToken.connect(addr1).customRedemption( + addr1.address, + redeemAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + issueAmount = issueAmount.sub(redeemAmount) + expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Should reach dust supply before exhausting loop iterations + expect(i < numIterations - 1).to.equal(true) + } + + expect(await rToken.totalSupply()).to.equal(0) + }) + + it('Should revert on overly-large redemption #fast', async function () { + redeemAmount = issueAmount.mul(redemptionThrottleParams.pctRate).div(fp('1')) + expect(await rToken.redemptionAvailable()).to.equal(redeemAmount) + + // Check issuance throttle - full + expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) + + redeemAmount = issueAmount.mul(redemptionThrottleParams.pctRate).div(fp('1')) + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, redeemAmount.add(1)) + await expect( + rToken.connect(addr1).customRedemption( + addr1.address, + redeemAmount.add(1), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + ).to.be.revertedWith('supply change throttled') + const quote2 = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, redeemAmount) + await rToken.connect(addr1).customRedemption( + addr1.address, + redeemAmount.add(1), + basketNonces, + portions, + quote2.erc20s, + quote2.quantities + ) + + // Check updated redemption throttle + expect(await rToken.redemptionAvailable()).to.equal(bn(0)) + + // Check issuance throttle - remains + expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) + }) + + it('Should support 1e48 amtRate throttles', async function () { + const throttles = JSON.parse(JSON.stringify(config.redemptionThrottle)) + throttles.amtRate = bn('1e48') + await rToken.connect(owner).setIssuanceThrottleParams(throttles) + await rToken.connect(owner).setRedemptionThrottleParams(throttles) + + // Mint collateral + await token0.mint(addr1.address, throttles.amtRate) + await token1.mint(addr1.address, throttles.amtRate) + await token2.mint(addr1.address, throttles.amtRate) + await token3.mint(addr1.address, throttles.amtRate) + + // Provide approvals + await Promise.all( + tokens.map((t) => t.connect(addr1).approve(rToken.address, MAX_UINT256)) + ) + + // Charge throttle + await advanceTime(3600) + expect(await rToken.issuanceAvailable()).to.equal(throttles.amtRate) + + // Issue + await rToken.connect(addr1).issue(throttles.amtRate) + expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount.add(throttles.amtRate)) + + // Redeem + expect(await rToken.redemptionAvailable()).to.equal(throttles.amtRate) + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, throttles.amtRate) + await rToken.connect(addr1).customRedemption( + addr1.address, + throttles.amtRate, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) + }) + + it('Should use amtRate if pctRate is zero', async function () { + redeemAmount = redemptionThrottleParams.amtRate + redemptionThrottleParams.pctRate = bn(0) + await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + + // Large redemption should fail + const basketNonces = [1] + const portions = [fp('1')] + const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, redeemAmount.add(1)) + await rToken.connect(addr1).customRedemption( + addr1.address, + redeemAmount.add(1), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + await expect( + rToken.connect(addr1).redeem(redeemAmount.add(1), await basketHandler.nonce()) + ).to.be.revertedWith('supply change throttled') + + // amtRate redemption should succeed + await expect( + rToken.connect(addr1).redeem(redeemAmount, await basketHandler.nonce()) + ).to.emit(rToken, 'Redemption') + + // Check redemption throttle is 0 + expect(await rToken.redemptionAvailable()).to.equal(bn(0)) + }) + + it('Should throttle after allowing two redemptions of half value #fast', async function () { + redeemAmount = issueAmount.mul(redemptionThrottleParams.pctRate).div(fp('1')) + // Check redemption throttle + expect(await rToken.redemptionAvailable()).to.equal(redeemAmount) + + // Issuance throttle is fully charged + expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) + + // Redeem #1 + await rToken.connect(addr1).redeem(redeemAmount.div(2), await basketHandler.nonce()) + + // Check redemption throttle updated + expect(await rToken.redemptionAvailable()).to.equal(redeemAmount.div(2)) + + // Issuance throttle remains equal + expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) + + // Redeem #2 + await rToken.connect(addr1).redeem(redeemAmount.div(2), await basketHandler.nonce()) + + // Check redemption throttle updated - very small + expect(await rToken.redemptionAvailable()).to.be.closeTo(fp('0.002638'), fp('0.000001')) + + // Issuance throttle remains equal + expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) + + // Redeem #3 - should not be processed + await expect( + rToken.connect(addr1).redeem(redeemAmount.div(100), await basketHandler.nonce()) + ).to.be.revertedWith('supply change throttled') + + // Advance time significantly + await advanceTime(10000000000) + + // Check redemption throttle recharged + const balance = issueAmount.sub(redeemAmount) + const redeemAmountUpd = balance.mul(redemptionThrottleParams.pctRate).div(fp('1')) + expect(await rToken.redemptionAvailable()).to.equal(redeemAmountUpd) + + // Issuance throttle remains equal + expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) + }) + + it('Should handle redemption throttle correctly, using only amount', async function () { + // Check initial balance + expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) + + // set fixed amount + redemptionThrottleParams.amtRate = fp('25') + redemptionThrottleParams.pctRate = bn(0) + await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + + // Check redemption throttle + expect(await rToken.redemptionAvailable()).to.equal(redemptionThrottleParams.amtRate) + + // Issuance throttle is fully charged + expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) + + // Redeem #1 - Will be processed + redeemAmount = fp('12.5') + await rToken.connect(addr1).redeem(redeemAmount, await basketHandler.nonce()) + + // Check redemption throttle updated + expect(await rToken.redemptionAvailable()).to.equal(redeemAmount) + + // Issuance throttle remains equal + expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) + + // Attempt to redeem max amt, should not be processed + await expect( + rToken + .connect(addr1) + .redeem(redemptionThrottleParams.amtRate, await basketHandler.nonce()) + ).to.be.revertedWith('supply change throttled') + + // Advance one hour. Redemption should be fully rechardged + await advanceTime(3600) + + // Check redemption throttle updated + expect(await rToken.redemptionAvailable()).to.equal(redemptionThrottleParams.amtRate) + + // Issuance throttle remains equal + expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) + + // Redeem #2 - will be processed + await rToken + .connect(addr1) + .redeem(redemptionThrottleParams.amtRate, await basketHandler.nonce()) + + // Check redemption throttle emptied + expect(await rToken.redemptionAvailable()).to.equal(bn(0)) + + // Issuance throttle remains equal + expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) + + // Check redemptions processed successfully + expect(await rToken.balanceOf(addr1.address)).to.equal( + issueAmount.sub(redeemAmount).sub(redemptionThrottleParams.amtRate) + ) + }) + + it('Should update issuance throttle correctly on redemption', async function () { + const rechargePerBlock = config.issuanceThrottle.amtRate.div(BLOCKS_PER_HOUR) + + redeemAmount = issueAmount.mul(redemptionThrottleParams.pctRate).div(fp('1')) + // Check redemption throttle + expect(await rToken.redemptionAvailable()).to.equal(redeemAmount) + + // Issuance throttle is fully charged + expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) + + // Issue all available + await rToken.connect(addr1).issue(config.issuanceThrottle.amtRate) + + // Issuance throttle empty + expect(await rToken.issuanceAvailable()).to.equal(bn(0)) + + // Redemption allowed increase + const redeemAmountUpd = issueAmount + .add(config.issuanceThrottle.amtRate) + .mul(redemptionThrottleParams.pctRate) + .div(fp('1')) + expect(await rToken.redemptionAvailable()).to.equal(redeemAmountUpd) + + // Redeem #1 - Will be processed + redeemAmount = fp('10000') + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) + await rToken.connect(addr1).redeem(redeemAmount, await basketHandler.nonce()) + + // Check redemption throttle updated + expect(await rToken.redemptionAvailable()).to.equal(redeemAmountUpd.sub(redeemAmount)) + + // Issuance throttle recharged, impacted mostly by redemption - 10K + period recharge + expect(await rToken.issuanceAvailable()).to.equal(redeemAmount.add(rechargePerBlock)) + + // Check issuance and redemption processed successfully + expect(await rToken.balanceOf(addr1.address)).to.equal( + issueAmount.add(config.issuanceThrottle.amtRate).sub(redeemAmount) + ) + }) + }) + }) }) describe('Melt/Mint #fast', () => { From 26ec739a82d146e2bd071cad432e06a9d3b87064 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 1 May 2023 15:56:19 -0400 Subject: [PATCH 134/462] guard against reentrancy + eliminate 12s magic number throughout codebase --- contracts/interfaces/IMain.sol | 3 +++ contracts/p0/BackingManager.sol | 5 +++-- contracts/p0/Broker.sol | 8 ++++---- contracts/p0/Furnace.sol | 2 +- contracts/p0/StRSR.sol | 2 +- contracts/p1/BackingManager.sol | 8 +++++--- contracts/p1/Broker.sol | 7 ++++--- contracts/p1/Furnace.sol | 2 +- contracts/p1/StRSR.sol | 2 +- 9 files changed, 23 insertions(+), 16 deletions(-) diff --git a/contracts/interfaces/IMain.sol b/contracts/interfaces/IMain.sol index 06ff3639b3..9b08fd6c7d 100644 --- a/contracts/interfaces/IMain.sol +++ b/contracts/interfaces/IMain.sol @@ -16,6 +16,9 @@ import "./IStRSR.sol"; import "./ITrading.sol"; import "./IVersioned.sol"; +// Warning, assumption: Chain must have blocktimes >= 12s +uint48 constant ONE_BLOCK = 12; //{s} + // === Auth roles === bytes32 constant OWNER = bytes32(bytes("OWNER")); diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index f40fbd0a88..51c78f33b7 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -79,7 +79,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { // DoS prevention: unless caller is self, require 1 empty block between like-kind auctions // Assumption: chain has <= 12s blocktimes require( - _msgSender() == address(this) || tradeEnd[kind] < block.timestamp + 12, + _msgSender() == address(this) || tradeEnd[kind] < block.timestamp + ONE_BLOCK, "already rebalancing" ); @@ -119,8 +119,9 @@ contract BackingManagerP0 is TradingP0, IBackingManager { } // Execute Trade + tradeEnd[kind] = uint48(block.timestamp) + ONE_BLOCK; ITrade trade = tryTrade(kind, req); - tradeEnd[kind] = trade.endTime(); + if (trade.endTime() > tradeEnd[kind]) tradeEnd[kind] = trade.endTime(); } else { // Haircut time compromiseBasketsNeeded(basketsHeld.bottom); diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index fadb582231..f89beb626a 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -21,9 +21,9 @@ contract BrokerP0 is ComponentP0, IBroker { using EnumerableSet for EnumerableSet.AddressSet; using SafeERC20 for IERC20Metadata; - // The fraction of the supply of the bidding token that is the min bid size in case of default - uint192 public constant MIN_BID_SHARE_OF_TOTAL_SUPPLY = 1e9; // (1} = 1e-7% uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration -1 week + uint48 public constant MIN_AUCTION_LENGTH = ONE_BLOCK; // {s} min auction length - 1 block + // warning: blocktime <= 12s assumption // Added for interface compatibility with P1 ITrade public batchTradeImplementation; @@ -135,7 +135,7 @@ contract BrokerP0 is ComponentP0, IBroker { /// @custom:governance function setBatchAuctionLength(uint48 newAuctionLength) public governance { require( - newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, + newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH, "invalid batchAuctionLength" ); emit BatchAuctionLengthSet(batchAuctionLength, newAuctionLength); @@ -156,7 +156,7 @@ contract BrokerP0 is ComponentP0, IBroker { /// @custom:governance function setDutchAuctionLength(uint48 newAuctionLength) public governance { require( - newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, + newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH, "invalid dutchAuctionLength" ); emit DutchAuctionLengthSet(dutchAuctionLength, newAuctionLength); diff --git a/contracts/p0/Furnace.sol b/contracts/p0/Furnace.sol index c4e0cb994a..a8e436b663 100644 --- a/contracts/p0/Furnace.sol +++ b/contracts/p0/Furnace.sol @@ -13,7 +13,7 @@ contract FurnaceP0 is ComponentP0, IFurnace { using FixLib for uint192; uint192 public constant MAX_RATIO = FIX_ONE; // {1} 100% - uint48 public constant PERIOD = 12; // {s} 12 seconds; 1 block on PoS Ethereum + uint48 public constant PERIOD = ONE_BLOCK; // {s} 12 seconds; 1 block on PoS Ethereum uint192 public ratio; // {1} What fraction of balance to melt each PERIOD diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index c347ffcfce..83db4146a6 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -31,7 +31,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { using EnumerableSet for EnumerableSet.AddressSet; using FixLib for uint192; - uint48 public constant PERIOD = 12; // {s} 12 seconds; 1 block on PoS Ethereum + uint48 public constant PERIOD = ONE_BLOCK; // {s} 12 seconds; 1 block on PoS Ethereum uint48 public constant MIN_UNSTAKING_DELAY = PERIOD * 2; // {s} uint48 public constant MAX_UNSTAKING_DELAY = 31536000; // {s} 1 year uint192 public constant MAX_REWARD_RATIO = 1e18; diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index b18594f89f..a58b7a7e0d 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -110,7 +110,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // DoS prevention: unless caller is self, require 1 empty block between like-kind auctions // Assumption: chain has <= 12s blocktimes require( - _msgSender() == address(this) || tradeEnd[kind] < block.timestamp + 12, + _msgSender() == address(this) || tradeEnd[kind] < block.timestamp + ONE_BLOCK, "already rebalancing" ); @@ -146,9 +146,11 @@ contract BackingManagerP1 is TradingP1, IBackingManager { if (req.sellAmount > bal) stRSR.seizeRSR(req.sellAmount - bal); } - // Execute Trade + // Execute Trade -- prevent reentrancy + tradeEnd[kind] = uint48(block.timestamp) + ONE_BLOCK; ITrade trade = tryTrade(kind, req); - tradeEnd[kind] = trade.endTime(); + uint48 endTime = trade.endTime(); + if (endTime > tradeEnd[kind]) tradeEnd[kind] = endTime; } else { // Haircut time compromiseBasketsNeeded(basketsHeld.bottom); diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 78380189d1..a8a34c121b 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -23,6 +23,8 @@ contract BrokerP1 is ComponentP1, IBroker { using Clones for address; uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration - 1 week + uint48 public constant MIN_AUCTION_LENGTH = ONE_BLOCK; // {s} min auction length - 1 block + // warning: blocktime <= 12s assumption IBackingManager private backingManager; IRevenueTrader private rsrTrader; @@ -148,7 +150,7 @@ contract BrokerP1 is ComponentP1, IBroker { /// @custom:governance function setBatchAuctionLength(uint48 newAuctionLength) public governance { require( - newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, + newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH, "invalid batchAuctionLength" ); emit BatchAuctionLengthSet(batchAuctionLength, newAuctionLength); @@ -169,7 +171,7 @@ contract BrokerP1 is ComponentP1, IBroker { /// @custom:governance function setDutchAuctionLength(uint48 newAuctionLength) public governance { require( - newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, + newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH, "invalid dutchAuctionLength" ); emit DutchAuctionLengthSet(dutchAuctionLength, newAuctionLength); @@ -204,7 +206,6 @@ contract BrokerP1 is ComponentP1, IBroker { address(trade), req.sellAmount ); - trade.init(this, caller, gnosis, batchAuctionLength, req); return trade; } diff --git a/contracts/p1/Furnace.sol b/contracts/p1/Furnace.sol index 14edfacbd1..1596942cf5 100644 --- a/contracts/p1/Furnace.sol +++ b/contracts/p1/Furnace.sol @@ -13,7 +13,7 @@ contract FurnaceP1 is ComponentP1, IFurnace { using FixLib for uint192; uint192 public constant MAX_RATIO = FIX_ONE; // {1} 100% - uint48 public constant PERIOD = 12; // {s} 12 seconds; 1 block on PoS Ethereum + uint48 public constant PERIOD = ONE_BLOCK; // {s} 12 seconds; 1 block on PoS Ethereum IRToken private rToken; diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 829a2c1ea1..c6e7249c59 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -34,7 +34,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab using CountersUpgradeable for CountersUpgradeable.Counter; using SafeERC20Upgradeable for IERC20Upgradeable; - uint48 public constant PERIOD = 12; // {s} 12 seconds; 1 block on PoS Ethereum + uint48 public constant PERIOD = ONE_BLOCK; // {s} 12 seconds; 1 block on PoS Ethereum uint48 public constant MIN_UNSTAKING_DELAY = PERIOD * 2; // {s} uint48 public constant MAX_UNSTAKING_DELAY = 31536000; // {s} 1 year uint192 public constant MAX_REWARD_RATIO = FIX_ONE; // {1} 100% From b8bc9c053043f4b00b063a96565dab0f6ad2d8c7 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 1 May 2023 16:02:41 -0400 Subject: [PATCH 135/462] reorder FacadeAct tests --- test/FacadeAct.test.ts | 104 ++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts index 2968e25160..dceaacda8f 100644 --- a/test/FacadeAct.test.ts +++ b/test/FacadeAct.test.ts @@ -591,58 +591,6 @@ describe('FacadeAct contract', () => { ) }) - it('Revenues - Should handle assets with invalid claim logic', async () => { - // Redeem all RTokens - await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) - - // Setup a new aToken with invalid claim data - const ATokenCollateralFactory = await ethers.getContractFactory( - 'InvalidATokenFiatCollateralMock' - ) - const chainlinkFeed = ( - await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) - ) - - const invalidATokenCollateral: InvalidATokenFiatCollateralMock = < - InvalidATokenFiatCollateralMock - >await ATokenCollateralFactory.deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: aToken.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: await aTokenAsset.oracleTimeout(), - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: await aTokenAsset.delayUntilDefault(), - }, - REVENUE_HIDING - ) - - // Perform asset swap - await assetRegistry.connect(owner).swapRegistered(invalidATokenCollateral.address) - - // Setup new basket with the invalid AToken - await basketHandler.connect(owner).setPrimeBasket([aToken.address], [fp('1')]) - - // Switch basket - await basketHandler.connect(owner).refreshBasket() - - const rewardAmountAAVE = bn('0.5e18') - - // AAVE Rewards - await aToken.setRewards(backingManager.address, rewardAmountAAVE) - - // Via Facade get next call - will not attempt to claim - No action taken - const [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(ZERO_ADDRESS) - expect(data).to.equal('0x') - - // Check status - nothing claimed - expect(await aaveToken.balanceOf(backingManager.address)).to.equal(0) - }) - it('Revenues - Should handle minTradeVolume = 0', async () => { // Set minTradeVolume = 0 await rsrTrader.connect(owner).setMinTradeVolume(bn(0)) @@ -770,6 +718,58 @@ describe('FacadeAct contract', () => { .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, minBuyAmt) }) + it('Revenues - Should handle assets with invalid claim logic', async () => { + // Redeem all RTokens + await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) + + // Setup a new aToken with invalid claim data + const ATokenCollateralFactory = await ethers.getContractFactory( + 'InvalidATokenFiatCollateralMock' + ) + const chainlinkFeed = ( + await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) + ) + + const invalidATokenCollateral: InvalidATokenFiatCollateralMock = < + InvalidATokenFiatCollateralMock + >await ATokenCollateralFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: chainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: aToken.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: await aTokenAsset.oracleTimeout(), + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: await aTokenAsset.delayUntilDefault(), + }, + REVENUE_HIDING + ) + + // Perform asset swap + await assetRegistry.connect(owner).swapRegistered(invalidATokenCollateral.address) + + // Setup new basket with the invalid AToken + await basketHandler.connect(owner).setPrimeBasket([aToken.address], [fp('1')]) + + // Switch basket + await basketHandler.connect(owner).refreshBasket() + + const rewardAmountAAVE = bn('0.5e18') + + // AAVE Rewards + await aToken.setRewards(backingManager.address, rewardAmountAAVE) + + // Via Facade get next call - will not attempt to claim - No action taken + const [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) + expect(addr).to.equal(ZERO_ADDRESS) + expect(data).to.equal('0x') + + // Check status - nothing claimed + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(0) + }) + it('Revenues - Should handle multiple assets with same reward token', async () => { // Update Reward token for AToken to use same as CToken const ATokenCollateralFactory = await ethers.getContractFactory('ATokenFiatCollateral') From 7ec3441b48edd8e53da91504334bebb578c10ec2 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 1 May 2023 16:27:44 -0400 Subject: [PATCH 136/462] confirm DutchTrade has starting balance --- contracts/plugins/trading/DutchTrade.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 7efd5ceb8e..eb53e73b65 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -39,8 +39,8 @@ contract DutchTrade is ITrade { IERC20Metadata public buy; uint192 public sellAmount; // {sellTok} - uint48 public startTime; // timestamp at which the dutch auction began - uint48 public endTime; // timestamp the dutch auction ends, if no bids have been received + uint48 public startTime; // {s} when the dutch auction began + uint48 public endTime; // {s} when the dutch auction ends, if no bids are received uint192 public middlePrice; // {buyTok/sellTok} The price at which the function is piecewise uint192 public lowPrice; // {buyTok/sellTok} The price the auction ends at @@ -84,6 +84,8 @@ contract DutchTrade is ITrade { origin = origin_; sell = sell_.erc20(); buy = buy_.erc20(); + + require(sellAmount_ <= sell.balanceOf(address(this)), "unfunded trade"); sellAmount = shiftl_toFix(sellAmount_, -int8(sell.decimals())); // {sellTok} startTime = uint48(block.timestamp); endTime = uint48(block.timestamp) + auctionLength; From 358e72d4b52d0b5a45ef2403196452b4805d904e Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 1 May 2023 16:33:19 -0400 Subject: [PATCH 137/462] wrap up DutchTrade testing --- test/Broker.test.ts | 1006 ++++++++++++++++++------------ test/Recollateralization.test.ts | 3 + test/Revenues.test.ts | 3 + 3 files changed, 598 insertions(+), 414 deletions(-) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index d013f2d6dd..c3192aa3a7 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -6,7 +6,7 @@ import { BigNumber, ContractFactory } from 'ethers' import { ethers, upgrades } from 'hardhat' import { IConfig, MAX_AUCTION_LENGTH } from '../common/configuration' import { MAX_UINT96, TradeKind, TradeStatus, ZERO_ADDRESS, ONE_ADDRESS } from '../common/constants' -import { bn, toBNDecimals } from '../common/numbers' +import { bn, fp, divCeil, toBNDecimals } from '../common/numbers' import { DutchTrade, ERC20Mock, @@ -362,6 +362,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await expect( broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) ).to.be.revertedWith('broker disabled') + await expect( + broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + ).to.be.revertedWith('broker disabled') }) }) @@ -380,6 +383,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await expect( broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) ).to.be.revertedWith('frozen or trading paused') + await expect( + broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + ).to.be.revertedWith('frozen or trading paused') }) }) @@ -398,10 +404,13 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await expect( broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) ).to.be.revertedWith('frozen or trading paused') + await expect( + broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + ).to.be.revertedWith('frozen or trading paused') }) }) - it('Should not allow to open trade if a trader', async () => { + it('Should only allow to open trade if a trader', async () => { const amount: BigNumber = bn('100e18') const tradeRequest: ITradeRequest = { @@ -490,501 +499,670 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) describe('Trades', () => { - it('Should initialize trade correctly - only once', async () => { - const amount: BigNumber = bn('100e18') + context('GnosisTrade', () => { + it('Should initialize GnosisTrade correctly - only once', async () => { + const amount: BigNumber = bn('100e18') + + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const trade: GnosisTrade = await TradeFactory.deploy() + + // Check state + expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } + + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.not.be.reverted + + // Check trade values + expect(await trade.KIND()).to.equal(TradeKind.BATCH_AUCTION) + expect(await trade.gnosis()).to.equal(gnosis.address) + expect(await trade.auctionId()).to.equal(0) + expect(await trade.status()).to.equal(TradeStatus.OPEN) + expect(await trade.broker()).to.equal(broker.address) + expect(await trade.origin()).to.equal(backingManager.address) + expect(await trade.sell()).to.equal(token0.address) + expect(await trade.buy()).to.equal(token1.address) + expect(await trade.initBal()).to.equal(amount) + expect(await trade.endTime()).to.equal( + (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength) + ) + expect(await trade.worstCasePrice()).to.equal(bn('0')) + expect(await trade.canSettle()).to.equal(false) - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + // Attempt to initialize again + await expect( + trade.init( + await trade.broker(), + await trade.origin(), + await trade.gnosis(), + await broker.batchAuctionLength(), + tradeRequest + ) + ).to.be.revertedWith('Invalid trade state') + }) - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + it('Should initialize GnosisTrade with minimum buy amount of at least 1', async () => { + const amount: BigNumber = bn('100e18') - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const trade: GnosisTrade = await TradeFactory.deploy() - // Fund trade and initialize - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.batchAuctionLength, - tradeRequest + // Check state + expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateralZ.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } + + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.not.be.reverted + + // Check trade values + expect(await trade.gnosis()).to.equal(gnosis.address) + expect(await trade.auctionId()).to.equal(0) + expect(await trade.status()).to.equal(TradeStatus.OPEN) + expect(await trade.broker()).to.equal(broker.address) + expect(await trade.origin()).to.equal(backingManager.address) + expect(await trade.sell()).to.equal(token0.address) + expect(await trade.buy()).to.equal(tokenZ.address) + expect(await trade.initBal()).to.equal(amount) + expect(await trade.endTime()).to.equal( + (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength) ) - ).to.not.be.reverted - - // Check trade values - expect(await trade.gnosis()).to.equal(gnosis.address) - expect(await trade.auctionId()).to.equal(0) - expect(await trade.status()).to.equal(TradeStatus.OPEN) - expect(await trade.broker()).to.equal(broker.address) - expect(await trade.origin()).to.equal(backingManager.address) - expect(await trade.sell()).to.equal(token0.address) - expect(await trade.buy()).to.equal(token1.address) - expect(await trade.initBal()).to.equal(amount) - expect(await trade.endTime()).to.equal( - (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength) - ) - expect(await trade.worstCasePrice()).to.equal(bn('0')) - expect(await trade.canSettle()).to.equal(false) + expect(await trade.worstCasePrice()).to.equal(bn('0')) + expect(await trade.canSettle()).to.equal(false) + }) - // Attempt to initialize again - await expect( - trade.init( - await trade.broker(), - await trade.origin(), - await trade.gnosis(), - await broker.batchAuctionLength(), - tradeRequest + it('Should protect against reentrancy when initializing GnosisTrade', async () => { + const amount: BigNumber = bn('100e18') + + // Create a Reetrant Gnosis + const GnosisReentrantFactory: ContractFactory = await ethers.getContractFactory( + 'GnosisMockReentrant' ) - ).to.be.revertedWith('Invalid trade state') - }) + const reentrantGnosis: GnosisMockReentrant = ( + await GnosisReentrantFactory.deploy() + ) + await reentrantGnosis.setReenterOnInit(true) - it('Should initialize trade with minimum buy amount of at least 1', async () => { - const amount: BigNumber = bn('100e18') + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const trade: GnosisTrade = await TradeFactory.deploy() - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + // Check state + expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateralZ.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } + // Fund trade and initialize with reentrant Gnosis + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + broker.address, + backingManager.address, + reentrantGnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.be.revertedWith('Invalid trade state') + }) - // Fund trade and initialize - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.batchAuctionLength, - tradeRequest - ) - ).to.not.be.reverted - - // Check trade values - expect(await trade.gnosis()).to.equal(gnosis.address) - expect(await trade.auctionId()).to.equal(0) - expect(await trade.status()).to.equal(TradeStatus.OPEN) - expect(await trade.broker()).to.equal(broker.address) - expect(await trade.origin()).to.equal(backingManager.address) - expect(await trade.sell()).to.equal(token0.address) - expect(await trade.buy()).to.equal(tokenZ.address) - expect(await trade.initBal()).to.equal(amount) - expect(await trade.endTime()).to.equal( - (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength) - ) - expect(await trade.worstCasePrice()).to.equal(bn('0')) - expect(await trade.canSettle()).to.equal(false) - }) + it('Should perform balance and amounts validations on init', async () => { + const amount: BigNumber = bn('100e18') + const invalidAmount: BigNumber = MAX_UINT96.add(1) - it('Should protect against reentrancy when initializing trade', async () => { - const amount: BigNumber = bn('100e18') + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const trade: GnosisTrade = await TradeFactory.deploy() - // Create a Reetrant Gnosis - const GnosisReentrantFactory: ContractFactory = await ethers.getContractFactory( - 'GnosisMockReentrant' - ) - const reentrantGnosis: GnosisMockReentrant = ( - await GnosisReentrantFactory.deploy() - ) - await reentrantGnosis.setReenterOnInit(true) + // Check state + expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + // Initialize trade - Sell Amount too large + // Fund trade + await token0.connect(owner).mint(trade.address, invalidAmount) + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: invalidAmount, + minBuyAmount: bn('0'), + } + + // Attempt to initialize + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.be.revertedWith('sellAmount too large') + + // Initialize trade - MinBuyAmount too large + tradeRequest.sellAmount = amount + tradeRequest.minBuyAmount = invalidAmount + + // Attempt to initialize + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.be.revertedWith('minBuyAmount too large') + + // Restore value + tradeRequest.minBuyAmount = bn('0') + + // Fund trade with large balance + await token0.connect(owner).mint(trade.address, invalidAmount) + + // Attempt to initialize + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.be.revertedWith('initBal too large') + }) - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + it('Should not allow to initialize an unfunded trade', async () => { + const amount: BigNumber = bn('100e18') - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const trade: GnosisTrade = await TradeFactory.deploy() - // Fund trade and initialize with reentrant Gnosis - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - broker.address, - backingManager.address, - reentrantGnosis.address, - config.batchAuctionLength, - tradeRequest - ) - ).to.be.revertedWith('Invalid trade state') - }) + // Check state + expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - it('Should perform balance and amounts validations on init', async () => { - const amount: BigNumber = bn('100e18') - const invalidAmount: BigNumber = MAX_UINT96.add(1) + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + // Attempt to initialize without funding + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.be.revertedWith('unfunded trade') + }) - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + it('Should be able to settle a trade - performing validations', async () => { + const amount: BigNumber = bn('100e18') - // Initialize trade - Sell Amount too large - // Fund trade - await token0.connect(owner).mint(trade.address, invalidAmount) - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: invalidAmount, - minBuyAmount: bn('0'), - } + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const trade: GnosisTrade = await TradeFactory.deploy() - // Attempt to initialize - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.batchAuctionLength, - tradeRequest - ) - ).to.be.revertedWith('sellAmount too large') + // Check state - cannot be settled + expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + expect(await trade.canSettle()).to.equal(false) - // Initialize trade - MinBuyAmount too large - tradeRequest.sellAmount = amount - tradeRequest.minBuyAmount = invalidAmount + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } - // Attempt to initialize - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.batchAuctionLength, - tradeRequest - ) - ).to.be.revertedWith('minBuyAmount too large') + // Attempt to settle (will fail) + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.be.revertedWith('Invalid trade state') + }) - // Restore value - tradeRequest.minBuyAmount = bn('0') + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.not.be.reverted + + // Check trade is initialized but still cannot be settled + expect(await trade.status()).to.equal(TradeStatus.OPEN) + expect(await trade.canSettle()).to.equal(false) + + // Advance time till trade can be settled + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Check status - can be settled now + expect(await trade.status()).to.equal(TradeStatus.OPEN) + expect(await trade.canSettle()).to.equal(true) + + // Attempt to settle from other address (not origin) + await expect(trade.connect(addr1).settle()).to.be.revertedWith('only origin can settle') + + // Settle trade + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.not.be.reverted + }) - // Fund trade with large balance - await token0.connect(owner).mint(trade.address, invalidAmount) + // Check status + expect(await trade.status()).to.equal(TradeStatus.CLOSED) + expect(await trade.canSettle()).to.equal(false) + }) - // Attempt to initialize - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.batchAuctionLength, - tradeRequest + it('Should protect against reentrancy when settling GnosisTrade', async () => { + const amount: BigNumber = bn('100e18') + + // Create a Reetrant Gnosis + const GnosisReentrantFactory: ContractFactory = await ethers.getContractFactory( + 'GnosisMockReentrant' ) - ).to.be.revertedWith('initBal too large') - }) + const reentrantGnosis: GnosisMockReentrant = ( + await GnosisReentrantFactory.deploy() + ) + await reentrantGnosis.setReenterOnInit(false) + await reentrantGnosis.setReenterOnSettle(true) + + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const trade: GnosisTrade = await TradeFactory.deploy() + + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } + + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + broker.address, + backingManager.address, + reentrantGnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.not.be.reverted + + // Advance time till trade can be settled + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Attempt Settle trade + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.be.revertedWith('Invalid trade state') + }) + }) - it('Should not allow to initialize an unfunded trade', async () => { - const amount: BigNumber = bn('100e18') + it('Should be able to settle a GnosisTrade - handles arbitrary funds being sent to trade', async () => { + const amount: BigNumber = bn('100e18') - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const trade: GnosisTrade = await TradeFactory.deploy() - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + // Check state - cannot be settled + expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + expect(await trade.canSettle()).to.equal(false) - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } - // Attempt to initialize without funding - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.batchAuctionLength, - tradeRequest - ) - ).to.be.revertedWith('unfunded trade') - }) + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.not.be.reverted + + // Check trade is initialized but still cannot be settled + expect(await trade.status()).to.equal(TradeStatus.OPEN) + expect(await trade.canSettle()).to.equal(false) + + // Advance time till trade can be settled + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Check status - can be settled now + expect(await trade.status()).to.equal(TradeStatus.OPEN) + expect(await trade.canSettle()).to.equal(true) + + // Perform mock bid - do not cover full amount + const bidAmount: BigNumber = amount.sub(bn('1e18')) + const minBuyAmt: BigNumber = toBNDecimals(bidAmount, 6) + await token1.connect(owner).mint(addr1.address, minBuyAmt) + await token1.connect(addr1).approve(gnosis.address, minBuyAmt) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: bidAmount, + buyAmount: minBuyAmt, + }) - it('Should be able to settle a trade - performing validations', async () => { - const amount: BigNumber = bn('100e18') + // Settle auction directly in Gnosis + await gnosis.settleAuction(0) - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + // Send tokens to the trade to try to disable it (Potential attack) + const additionalFundsSell: BigNumber = amount + const additionalFundsBuy: BigNumber = toBNDecimals(amount.div(2), 6) - // Check state - cannot be settled - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - expect(await trade.canSettle()).to.equal(false) + await token0.connect(owner).mint(trade.address, amount) + await token1.connect(owner).mint(trade.address, toBNDecimals(amount.div(2), 6)) - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } + // Settle trade + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.not.be.reverted + }) - // Attempt to settle (will fail) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(trade.connect(bmSigner).settle()).to.be.revertedWith('Invalid trade state') - }) + // Check status + expect(await trade.status()).to.equal(TradeStatus.CLOSED) + expect(await trade.canSettle()).to.equal(false) - // Fund trade and initialize - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.batchAuctionLength, - tradeRequest + // Additional funds transfered to origin + expect(await token0.balanceOf(backingManager.address)).to.equal( + bn('1e18').add(additionalFundsSell) + ) + expect(await token1.balanceOf(backingManager.address)).to.equal( + minBuyAmt.add(additionalFundsBuy) ) - ).to.not.be.reverted - // Check trade is initialized but still cannot be settled - expect(await trade.status()).to.equal(TradeStatus.OPEN) - expect(await trade.canSettle()).to.equal(false) + // Funds sent to bidder + expect(await token0.balanceOf(addr1.address)).to.equal(bidAmount) + }) - // Advance time till trade can be settled - await advanceTime(config.batchAuctionLength.add(100).toString()) + it('Should allow anyone to transfer to origin after a GnosisTrade is complete', async () => { + const amount: BigNumber = bn('100e18') - // Check status - can be settled now - expect(await trade.status()).to.equal(TradeStatus.OPEN) - expect(await trade.canSettle()).to.equal(true) + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const trade: GnosisTrade = await TradeFactory.deploy() - // Attempt to settle from other address (not origin) - await expect(trade.connect(addr1).settle()).to.be.revertedWith('only origin can settle') + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } - // Settle trade - await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(trade.connect(bmSigner).settle()).to.not.be.reverted - }) + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.not.be.reverted + + // Check balances on Trade and Origin + expect(await token0.balanceOf(trade.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + + // Attempt to transfer the new funds to origin while the Trade is still open + await expect(trade.transferToOriginAfterTradeComplete(token0.address)).to.be.revertedWith( + 'only after trade is closed' + ) - // Check status - expect(await trade.status()).to.equal(TradeStatus.CLOSED) - expect(await trade.canSettle()).to.equal(false) - }) + // Advance time till trade can be settled + await advanceTime(config.batchAuctionLength.add(100).toString()) - it('Should protect against reentrancy when settling trade', async () => { - const amount: BigNumber = bn('100e18') + // Settle trade + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.not.be.reverted + }) - // Create a Reetrant Gnosis - const GnosisReentrantFactory: ContractFactory = await ethers.getContractFactory( - 'GnosisMockReentrant' - ) - const reentrantGnosis: GnosisMockReentrant = ( - await GnosisReentrantFactory.deploy() - ) - await reentrantGnosis.setReenterOnInit(false) - await reentrantGnosis.setReenterOnSettle(true) + // Check status + expect(await trade.status()).to.equal(TradeStatus.CLOSED) - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + // Check balances on Trade and Origin - Funds sent back to origin (no bids) + expect(await token0.balanceOf(trade.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(amount) - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } + // Send arbitrary funds to Trade + const newFunds: BigNumber = amount.div(2) + await token0.connect(owner).mint(trade.address, newFunds) - // Fund trade and initialize - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - broker.address, - backingManager.address, - reentrantGnosis.address, - config.batchAuctionLength, - tradeRequest - ) - ).to.not.be.reverted + // Check balances again + expect(await token0.balanceOf(trade.address)).to.equal(newFunds) + expect(await token0.balanceOf(backingManager.address)).to.equal(amount) - // Advance time till trade can be settled - await advanceTime(config.batchAuctionLength.add(100).toString()) + // Transfer to origin + await expect(trade.transferToOriginAfterTradeComplete(token0.address)) + .to.emit(token0, 'Transfer') + .withArgs(trade.address, backingManager.address, newFunds) - // Attempt Settle trade - await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(trade.connect(bmSigner).settle()).to.be.revertedWith('Invalid trade state') + // Check balances again - funds sent to origin + expect(await token0.balanceOf(trade.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(amount.add(newFunds)) }) }) - it('Should be able to settle a trade - handles arbitrary funds being sent to trade', async () => { - const amount: BigNumber = bn('100e18') - - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + context('DutchTrade', () => { + it('Should initialize DutchTrade correctly - only once', async () => { + const amount: BigNumber = config.rTokenMaxTradeVolume - // Check state - cannot be settled - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - expect(await trade.canSettle()).to.equal(false) + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + const trade: DutchTrade = await TradeFactory.deploy() - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } + // Check state + expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - // Fund trade and initialize - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.batchAuctionLength, - tradeRequest + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ).to.not.be.reverted + + // Check trade values + expect(await trade.KIND()).to.equal(TradeKind.DUTCH_AUCTION) + expect(await trade.status()).to.equal(TradeStatus.OPEN) + expect(await trade.origin()).to.equal(backingManager.address) + expect(await trade.sell()).to.equal(token0.address) + expect(await trade.buy()).to.equal(token1.address) + expect(await trade.sellAmount()).to.equal(amount) + expect(await trade.startTime()).to.equal(await getLatestBlockTimestamp()) + expect(await trade.endTime()).to.equal( + (await getLatestBlockTimestamp()) + Number(config.dutchAuctionLength) ) - ).to.not.be.reverted - - // Check trade is initialized but still cannot be settled - expect(await trade.status()).to.equal(TradeStatus.OPEN) - expect(await trade.canSettle()).to.equal(false) - - // Advance time till trade can be settled - await advanceTime(config.batchAuctionLength.add(100).toString()) + const [sellLow, sellHigh] = await collateral0.price() + const [buyLow, buyHigh] = await collateral1.price() + expect(await trade.middlePrice()).to.equal(divCeil(sellHigh.mul(fp('1')), buyLow)) + expect(await trade.lowPrice()).to.equal(sellLow.mul(fp('1')).div(buyHigh)) + expect(await trade.canSettle()).to.equal(false) - // Check status - can be settled now - expect(await trade.status()).to.equal(TradeStatus.OPEN) - expect(await trade.canSettle()).to.equal(true) - - // Perform mock bid - do not cover full amount - const bidAmount: BigNumber = amount.sub(bn('1e18')) - const minBuyAmt: BigNumber = toBNDecimals(bidAmount, 6) - await token1.connect(owner).mint(addr1.address, minBuyAmt) - await token1.connect(addr1).approve(gnosis.address, minBuyAmt) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: bidAmount, - buyAmount: minBuyAmt, + // Attempt to initialize again + await expect( + trade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ).to.be.revertedWith('Invalid trade state') }) - // Settle auction directly in Gnosis - await gnosis.settleAuction(0) - - // Send tokens to the trade to try to disable it (Potential attack) - const additionalFundsSell: BigNumber = amount - const additionalFundsBuy: BigNumber = toBNDecimals(amount.div(2), 6) + it('Should apply full maxTradeSlippage to lowPrice at minTradeVolume', async () => { + const amount: BigNumber = config.minTradeVolume - await token0.connect(owner).mint(trade.address, amount) - await token1.connect(owner).mint(trade.address, toBNDecimals(amount.div(2), 6)) + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + const trade: DutchTrade = await TradeFactory.deploy() - // Settle trade - await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(trade.connect(bmSigner).settle()).to.not.be.reverted + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ).to.not.be.reverted + + // Check trade values + const [sellLow, sellHigh] = await collateral0.price() + const [buyLow, buyHigh] = await collateral1.price() + expect(await trade.middlePrice()).to.equal(divCeil(sellHigh.mul(fp('1')), buyLow)) + const withoutSlippage = sellLow.mul(fp('1')).div(buyHigh) + const withSlippage = withoutSlippage.sub( + withoutSlippage.mul(config.maxTradeSlippage).div(fp('1')) + ) + expect(await trade.lowPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) }) - // Check status - expect(await trade.status()).to.equal(TradeStatus.CLOSED) - expect(await trade.canSettle()).to.equal(false) + it('Should not allow to initialize an unfunded trade', async () => { + const amount: BigNumber = bn('100e18') - // Additional funds transfered to origin - expect(await token0.balanceOf(backingManager.address)).to.equal( - bn('1e18').add(additionalFundsSell) - ) - expect(await token1.balanceOf(backingManager.address)).to.equal( - minBuyAmt.add(additionalFundsBuy) - ) + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + const trade: DutchTrade = await TradeFactory.deploy() - // Funds sent to bidder - expect(await token0.balanceOf(addr1.address)).to.equal(bidAmount) - }) + // Check state + expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - it('Should allow anyone to transfer to origin after a trade is complete', async () => { - const amount: BigNumber = bn('100e18') + // Attempt to initialize without funding + await expect( + trade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ).to.be.revertedWith('unfunded trade') + }) - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + it('Should allow anyone to transfer to origin after a DutchTrade is complete', async () => { + const amount: BigNumber = bn('100e18') - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + const trade: DutchTrade = await TradeFactory.deploy() - // Fund trade and initialize - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.batchAuctionLength, - tradeRequest + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ).to.not.be.reverted + + // Check balances on Trade and Origin + expect(await token0.balanceOf(trade.address)).to.equal(amount) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + + // Attempt to transfer the new funds to origin while the Trade is still open + await expect(trade.transferToOriginAfterTradeComplete(token0.address)).to.be.revertedWith( + 'only after trade is closed' ) - ).to.not.be.reverted - // Check balances on Trade and Origin - expect(await token0.balanceOf(trade.address)).to.equal(0) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) + // Advance time till trade can be settled + await advanceTime(config.batchAuctionLength.add(100).toString()) - // Attempt to transfer the new funds to origin while the Trade is still open - await expect(trade.transferToOriginAfterTradeComplete(token0.address)).to.be.revertedWith( - 'only after trade is closed' - ) - - // Advance time till trade can be settled - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // Settle trade - await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(trade.connect(bmSigner).settle()).to.not.be.reverted - }) + // Settle trade + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.not.be.reverted + }) - // Check status - expect(await trade.status()).to.equal(TradeStatus.CLOSED) + // Check status + expect(await trade.status()).to.equal(TradeStatus.CLOSED) - // Check balances on Trade and Origin - Funds sent back to origin (no bids) - expect(await token0.balanceOf(trade.address)).to.equal(0) - expect(await token0.balanceOf(backingManager.address)).to.equal(amount) + // Check balances on Trade and Origin - Funds sent back to origin (no bids) + expect(await token0.balanceOf(trade.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(amount) - // Send arbitrary funds to Trade - const newFunds: BigNumber = amount.div(2) - await token0.connect(owner).mint(trade.address, newFunds) + // Send arbitrary funds to Trade + const newFunds: BigNumber = amount.div(2) + await token0.connect(owner).mint(trade.address, newFunds) - // Check balances again - expect(await token0.balanceOf(trade.address)).to.equal(newFunds) - expect(await token0.balanceOf(backingManager.address)).to.equal(amount) + // Check balances again + expect(await token0.balanceOf(trade.address)).to.equal(newFunds) + expect(await token0.balanceOf(backingManager.address)).to.equal(amount) - // Transfer to origin - await expect(trade.transferToOriginAfterTradeComplete(token0.address)) - .to.emit(token0, 'Transfer') - .withArgs(trade.address, backingManager.address, newFunds) + // Transfer to origin + await expect(trade.transferToOriginAfterTradeComplete(token0.address)) + .to.emit(token0, 'Transfer') + .withArgs(trade.address, backingManager.address, newFunds) - // Check balances again - funds sent to origin - expect(await token0.balanceOf(trade.address)).to.equal(0) - expect(await token0.balanceOf(backingManager.address)).to.equal(amount.add(newFunds)) + // Check balances again - funds sent to origin + expect(await token0.balanceOf(trade.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(amount.add(newFunds)) + }) }) }) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 5c8092e916..5d5499164f 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -3207,6 +3207,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expect(backingManager.settleTrade(token0.address)) .to.emit(backingManager, 'TradeSettled') .withArgs(trade.address, token0.address, token1.address, 0, 0) + + // Should NOT start another auction, since caller was not DutchTrade + expect(await backingManager.tradesOpen()).to.equal(0) }) it('Should bid in final second of auction and launch another auction', async () => { diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index ef71f2c7e3..3481fbbd7a 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2429,6 +2429,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expect(rTokenTrader.settleTrade(token0.address)) .to.emit(rTokenTrader, 'TradeSettled') .withArgs(trade.address, token0.address, rToken.address, 0, 0) + + // Should NOT start another auction, since caller was not DutchTrade + expect(await backingManager.tradesOpen()).to.equal(0) }) it('Should bid in final second of auction and not launch another auction', async () => { From ebc206751b9351ae1cc524d78c9da9baf76be46c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 1 May 2023 17:05:53 -0400 Subject: [PATCH 138/462] gas tests for Broker / BackingManager --- test/Broker.test.ts | 225 +++++++++++++----- test/Recollateralization.test.ts | 59 ++++- test/__snapshots__/Broker.test.ts.snap | 20 +- .../Recollateralization.test.ts.snap | 16 +- 4 files changed, 238 insertions(+), 82 deletions(-) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index c3192aa3a7..60487996b2 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -1167,95 +1167,190 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) describeGas('Gas Reporting', () => { - let amount: BigNumber - let tradeRequest: ITradeRequest - let TradeFactory: ContractFactory - let newTrade: GnosisTrade - - beforeEach(async () => { - amount = bn('100e18') + context('GnosisTrade', () => { + let amount: BigNumber + let tradeRequest: ITradeRequest + let TradeFactory: ContractFactory + let newTrade: GnosisTrade - tradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: bn('100e18'), - minBuyAmount: bn('0'), - } + beforeEach(async () => { + amount = bn('100e18') - // Mint required tokens - await token0.connect(owner).mint(backingManager.address, amount) - await token0.connect(owner).mint(rsrTrader.address, amount) - await token0.connect(owner).mint(rTokenTrader.address, amount) - await token0.connect(owner).mint(addr1.address, amount) + tradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: bn('100e18'), + minBuyAmount: bn('0'), + } - // Create a new trade - TradeFactory = await ethers.getContractFactory('GnosisTrade') - newTrade = await TradeFactory.deploy() - }) + // Mint required tokens + await token0.connect(owner).mint(backingManager.address, amount) + await token0.connect(owner).mint(rsrTrader.address, amount) + await token0.connect(owner).mint(rTokenTrader.address, amount) + await token0.connect(owner).mint(addr1.address, amount) - it('Open Trade ', async () => { - // Open from traders - // Backing Manager - await whileImpersonating(backingManager.address, async (bmSigner) => { - await token0.connect(bmSigner).approve(broker.address, amount) - await snapshotGasCost( - broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) - ) + // Create a new trade + TradeFactory = await ethers.getContractFactory('GnosisTrade') + newTrade = await TradeFactory.deploy() }) - // RSR Trader - await whileImpersonating(rsrTrader.address, async (rsrSigner) => { - await token0.connect(rsrSigner).approve(broker.address, amount) - await snapshotGasCost( - broker.connect(rsrSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) - ) + it('Open Trade ', async () => { + // Open from traders + // Backing Manager + await whileImpersonating(backingManager.address, async (bmSigner) => { + await token0.connect(bmSigner).approve(broker.address, amount) + await snapshotGasCost( + broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ) + }) + + // RSR Trader + await whileImpersonating(rsrTrader.address, async (rsrSigner) => { + await token0.connect(rsrSigner).approve(broker.address, amount) + await snapshotGasCost( + broker.connect(rsrSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ) + }) + + // RToken Trader + await whileImpersonating(rTokenTrader.address, async (rtokSigner) => { + await token0.connect(rtokSigner).approve(broker.address, amount) + await snapshotGasCost( + broker.connect(rtokSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ) + }) }) - // RToken Trader - await whileImpersonating(rTokenTrader.address, async (rtokSigner) => { - await token0.connect(rtokSigner).approve(broker.address, amount) + it('Initialize Trade ', async () => { + // Fund trade and initialize + await token0.connect(owner).mint(newTrade.address, amount) await snapshotGasCost( - broker.connect(rtokSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + newTrade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) ) }) - }) - it('Initialize Trade ', async () => { - // Fund trade and initialize - await token0.connect(owner).mint(newTrade.address, amount) - await snapshotGasCost( - newTrade.init( + it('Settle Trade ', async () => { + // Fund trade and initialize + await token0.connect(owner).mint(newTrade.address, amount) + await newTrade.init( broker.address, backingManager.address, gnosis.address, config.batchAuctionLength, tradeRequest ) - ) + + // Advance time till trade can be settled + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Settle trade + await whileImpersonating(backingManager.address, async (bmSigner) => { + await snapshotGasCost(newTrade.connect(bmSigner).settle()) + }) + + // Check status + expect(await newTrade.status()).to.equal(TradeStatus.CLOSED) + expect(await newTrade.canSettle()).to.equal(false) + }) }) - it('Settle Trade ', async () => { - // Fund trade and initialize - await token0.connect(owner).mint(newTrade.address, amount) - await newTrade.init( - broker.address, - backingManager.address, - gnosis.address, - config.batchAuctionLength, - tradeRequest - ) + context('DutchTrade', () => { + let amount: BigNumber + let tradeRequest: ITradeRequest + let TradeFactory: ContractFactory + let newTrade: DutchTrade - // Advance time till trade can be settled - await advanceTime(config.batchAuctionLength.add(100).toString()) + beforeEach(async () => { + amount = bn('100e18') - // Settle trade - await whileImpersonating(backingManager.address, async (bmSigner) => { - await snapshotGasCost(newTrade.connect(bmSigner).settle()) + tradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: bn('100e18'), + minBuyAmount: bn('0'), + } + + // Mint required tokens + await token0.connect(owner).mint(backingManager.address, amount) + await token0.connect(owner).mint(rsrTrader.address, amount) + await token0.connect(owner).mint(rTokenTrader.address, amount) + await token0.connect(owner).mint(addr1.address, amount) + + // Create a new trade + TradeFactory = await ethers.getContractFactory('DutchTrade') + newTrade = await TradeFactory.deploy() }) - // Check status - expect(await newTrade.status()).to.equal(TradeStatus.CLOSED) - expect(await newTrade.canSettle()).to.equal(false) + it('Open Trade ', async () => { + // Open from traders + // Backing Manager + await whileImpersonating(backingManager.address, async (bmSigner) => { + await token0.connect(bmSigner).approve(broker.address, amount) + await snapshotGasCost( + broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + ) + }) + + // RSR Trader + await whileImpersonating(rsrTrader.address, async (rsrSigner) => { + await token0.connect(rsrSigner).approve(broker.address, amount) + await snapshotGasCost( + broker.connect(rsrSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + ) + }) + + // RToken Trader + await whileImpersonating(rTokenTrader.address, async (rtokSigner) => { + await token0.connect(rtokSigner).approve(broker.address, amount) + await snapshotGasCost( + broker.connect(rtokSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + ) + }) + }) + + it('Initialize Trade ', async () => { + // Fund trade and initialize + await token0.connect(owner).mint(newTrade.address, amount) + await snapshotGasCost( + newTrade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ) + }) + + it('Settle Trade ', async () => { + // Fund trade and initialize + await token0.connect(owner).mint(newTrade.address, amount) + await newTrade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + + // Advance time till trade can be settled + await advanceTime(config.dutchAuctionLength.add(100).toString()) + + // Settle trade + await whileImpersonating(backingManager.address, async (bmSigner) => { + await snapshotGasCost(newTrade.connect(bmSigner).settle()) + }) + + // Check status + expect(await newTrade.status()).to.equal(TradeStatus.CLOSED) + expect(await newTrade.canSettle()).to.equal(false) + }) }) }) }) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 5d5499164f..c98cf52e2c 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -4817,7 +4817,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await backingManager.connect(owner).setBackingBuffer(0) // Provide approvals - await token1.connect(addr1).approve(rToken.address, initialBal) + await token0.connect(addr1).approve(rToken.address, initialBal) await token1.connect(addr1).approve(rToken.address, initialBal) await token2.connect(addr1).approve(rToken.address, initialBal) await token3.connect(addr1).approve(rToken.address, initialBal) @@ -4834,7 +4834,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await rsr.connect(owner).mint(addr1.address, initialBal) }) - it('Settle Trades / Manage Funds', async () => { + it('rebalance() - GnosisTrade ', async () => { // Register Collateral await assetRegistry.connect(owner).register(backupCollateral1.address) await assetRegistry.connect(owner).register(backupCollateral2.address) @@ -4860,18 +4860,20 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Mark Default - Perform basket switch await assetRegistry.refresh() await expect(basketHandler.refreshBasket()).to.emit(basketHandler, 'BasketSet') + await advanceTime(config.tradingDelay.toNumber()) + await advanceTime(config.warmupPeriod.toNumber()) + await advanceTime(config.batchAuctionLength.toNumber()) // Run auctions - First Settle trades then Manage Funds // Will sell all balance of token2 const sellAmt2 = await token2.balanceOf(backingManager.address) - await snapshotGasCost(backingManager.settleTrade(token2.address)) + await expect(backingManager.settleTrade(token2.address)).to.be.revertedWith('no trade open') await snapshotGasCost(backingManager.rebalance(TradeKind.BATCH_AUCTION)) // Another call should not create any new auctions if still ongoing await expect(backingManager.settleTrade(token2.address)).to.be.revertedWith( 'cannot settle yet' ) - await snapshotGasCost(backingManager.rebalance(TradeKind.BATCH_AUCTION)) // Perform Mock Bids for the new Token (addr1 has balance) // Get minBuyAmt, we will have now surplus of backupToken1 @@ -4893,6 +4895,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Run auctions - First Settle trades then Manage Funds await snapshotGasCost(backingManager.settleTrade(token2.address)) + await advanceTime(12) await snapshotGasCost(backingManager.rebalance(TradeKind.BATCH_AUCTION)) // Perform Mock Bids for the new Token (addr1 has balance) @@ -4913,6 +4916,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Run auctions - First Settle trades then Manage Funds await snapshotGasCost(backingManager.settleTrade(backupToken1.address)) + await advanceTime(12) await snapshotGasCost(backingManager.rebalance(TradeKind.BATCH_AUCTION)) // Perform Mock Bids for RSR (addr1 has balance) @@ -4932,5 +4936,52 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await snapshotGasCost(backingManager.settleTrade(rsr.address)) expect(await backingManager.tradesOpen()).to.equal(0) }) + + it('rebalance() - DutchTrade ', async () => { + // Register Collateral + await assetRegistry.connect(owner).register(backupCollateral1.address) + + // Set backup configuration - USDT and aUSDT as backup + await basketHandler + .connect(owner) + .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [backupToken1.address]) + + // Perform stake + const stakeAmount: BigNumber = bn('10000e18') + await rsr.connect(addr1).approve(stRSR.address, stakeAmount) + await stRSR.connect(addr1).stake(stakeAmount) + + // Set Token2 to hard default - Reducing rate + await token2.setExchangeRate(fp('0.25')) + + // Mark Default - Perform basket switch + await assetRegistry.refresh() + await expect(basketHandler.refreshBasket()).to.emit(basketHandler, 'BasketSet') + await advanceTime(config.tradingDelay.toNumber()) + await advanceTime(config.warmupPeriod.toNumber()) + await advanceTime(config.dutchAuctionLength.toNumber()) + + // Run auctions - First Settle trades then Manage Funds + // Will sell all balance of token2 + await expect(backingManager.settleTrade(token2.address)).to.be.revertedWith('no trade open') + await snapshotGasCost(backingManager.rebalance(TradeKind.DUTCH_AUCTION)) + + // Another call should not create any new auctions if still ongoing + await expect(backingManager.settleTrade(token2.address)).to.be.revertedWith( + 'cannot settle yet' + ) + + // Bid + settle DutchTrade + const tradeAddr = await backingManager.trades(token2.address) + const trade = await ethers.getContractAt('DutchTrade', tradeAddr) + await backupToken1.connect(addr1).approve(trade.address, initialBal) + await snapshotGasCost(trade.connect(addr1).bid()) + + // Expect new trade started + expect(await backingManager.tradesOpen()).to.equal(1) + expect(await backingManager.trades(token2.address)).to.equal(ZERO_ADDRESS) + expect(await backingManager.trades(rsr.address)).to.not.equal(ZERO_ADDRESS) + await expect(backingManager.settleTrade(rsr.address)).to.be.revertedWith('cannot settle yet') + }) }) }) diff --git a/test/__snapshots__/Broker.test.ts.snap b/test/__snapshots__/Broker.test.ts.snap index 56bcf28e86..561e9b8d22 100644 --- a/test/__snapshots__/Broker.test.ts.snap +++ b/test/__snapshots__/Broker.test.ts.snap @@ -1,11 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`BrokerP1 contract #fast Gas Reporting Initialize Trade 1`] = `451349`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `233033`; -exports[`BrokerP1 contract #fast Gas Reporting Open Trade 1`] = `551875`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `350111`; -exports[`BrokerP1 contract #fast Gas Reporting Open Trade 2`] = `539713`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `352247`; -exports[`BrokerP1 contract #fast Gas Reporting Open Trade 3`] = `541851`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `354385`; -exports[`BrokerP1 contract #fast Gas Reporting Settle Trade 1`] = `113056`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63429`; + +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Initialize Trade 1`] = `451337`; + +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 1`] = `552332`; + +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 2`] = `540170`; + +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 3`] = `542308`; + +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Settle Trade 1`] = `113056`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index bf151f3b37..16319f1692 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `63713`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1386334`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1667295`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1499583`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `386392`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1754914`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `184737`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `1648298`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1699249`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `184745`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184833`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 7`] = `1737343`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1789003`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 8`] = `212855`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `212933`; From 42c3dca530604e8aeb051fbbfe3658f21d010461 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 2 May 2023 13:28:09 -0400 Subject: [PATCH 139/462] 80% -> 85% --- contracts/plugins/trading/DutchTrade.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index eb53e73b65..fef096c77a 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -188,7 +188,7 @@ contract DutchTrade is ITrade { /// Calculates how much buy token is needed to purchase the lot, at a particular timestamp /// Price Curve: /// - 1.5 * middlePrice down to the middlePrice for first 15% of auction - /// - middlePrice down to lowPrice for the last 80% of auction + /// - middlePrice down to lowPrice for the last 85% of auction /// @param timestamp {s} The block timestamp to get price for /// @return {qBuyTok} The amount of buy tokens required to purchase the lot function bidAmount(uint48 timestamp) public view returns (uint256) { From e399914a32c015bd0c4385f16917e6eef99c0e90 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 2 May 2023 13:29:33 -0400 Subject: [PATCH 140/462] phase3-rtoken/rTokenConfig.ts --- scripts/deployment/phase3-rtoken/rTokenConfig.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/deployment/phase3-rtoken/rTokenConfig.ts b/scripts/deployment/phase3-rtoken/rTokenConfig.ts index bac340d02a..c9f6967537 100644 --- a/scripts/deployment/phase3-rtoken/rTokenConfig.ts +++ b/scripts/deployment/phase3-rtoken/rTokenConfig.ts @@ -20,8 +20,10 @@ export const rTokenConfig: { [key: string]: IRToken } = { }, rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks + warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('14400'), // (the delay _after_ default has been confirmed) 4 hours batchAuctionLength: bn('900'), // 15 minutes + dutchAuctionLength: bn('600'), // 10 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days @@ -57,8 +59,10 @@ export const rTokenConfig: { [key: string]: IRToken } = { }, rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks + warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('14400'), // (the delay _after_ default has been confirmed) 4 hours batchAuctionLength: bn('900'), // 15 minutes + dutchAuctionLength: bn('600'), // 10 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days @@ -93,8 +97,10 @@ export const rTokenConfig: { [key: string]: IRToken } = { }, rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks + warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes + dutchAuctionLength: bn('600'), // 10 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days From 4a29a86f4a20dfdfef861bf101ee6ebd8ac42922 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 2 May 2023 13:34:58 -0400 Subject: [PATCH 141/462] set backing buffer to 0 in recollat test --- test/Recollateralization.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index c98cf52e2c..c0cf052f60 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -1212,6 +1212,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) it('Should recollateralize correctly when switching basket - Taking no Haircut - No RSR', async () => { + await backingManager.connect(owner).setBackingBuffer(0) + // Empty out the staking pool await stRSR.connect(addr1).unstake(stakeAmount) await advanceTime(config.unstakingDelay.toString()) @@ -1331,7 +1333,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await rToken.totalSupply()).to.be.gt(issueAmount) // New RToken minting // Check price in USD of the current RToken - expect(await rToken.basketsNeeded()).to.be.gte(await rToken.totalSupply()) // no haircut + expect(await rToken.basketsNeeded()).to.equal(await rToken.totalSupply()) // no haircut await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) }) From adf439daa126c6fb071693b325d2e51326d7a543 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 2 May 2023 18:28:12 -0400 Subject: [PATCH 142/462] clarify seemingly obsolete test --- test/Broker.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 60487996b2..2271eda222 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -558,7 +558,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ).to.be.revertedWith('Invalid trade state') }) - it('Should initialize GnosisTrade with minimum buy amount of at least 1', async () => { + // This test is only here for coverage + it('Should initialize GnosisTrade - zero decimal token', async () => { const amount: BigNumber = bn('100e18') // Create a Trade From 416bc32499366cc34c9789ce42d7bacc7b83a5e8 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 2 May 2023 18:35:45 -0400 Subject: [PATCH 143/462] pull out as much as possible into beforeEach from Broker GnosisTrade/DutchTrade tests --- test/Broker.test.ts | 107 +++++++------------------------------------- 1 file changed, 17 insertions(+), 90 deletions(-) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 2271eda222..3e69b65e02 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -500,16 +500,19 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { describe('Trades', () => { context('GnosisTrade', () => { - it('Should initialize GnosisTrade correctly - only once', async () => { - const amount: BigNumber = bn('100e18') + const amount = bn('100e18') + let trade: GnosisTrade + beforeEach(async () => { // Create a Trade const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + trade = await TradeFactory.deploy() // Check state expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - + expect(await trade.canSettle()).to.equal(false) + }) + it('Should initialize GnosisTrade correctly - only once', async () => { // Initialize trade - simulate from backingManager const tradeRequest: ITradeRequest = { sell: collateral0.address, @@ -560,15 +563,6 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // This test is only here for coverage it('Should initialize GnosisTrade - zero decimal token', async () => { - const amount: BigNumber = bn('100e18') - - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() - - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - // Initialize trade - simulate from backingManager const tradeRequest: ITradeRequest = { sell: collateral0.address, @@ -606,8 +600,6 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) it('Should protect against reentrancy when initializing GnosisTrade', async () => { - const amount: BigNumber = bn('100e18') - // Create a Reetrant Gnosis const GnosisReentrantFactory: ContractFactory = await ethers.getContractFactory( 'GnosisMockReentrant' @@ -617,13 +609,6 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ) await reentrantGnosis.setReenterOnInit(true) - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() - - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - // Initialize trade - simulate from backingManager const tradeRequest: ITradeRequest = { sell: collateral0.address, @@ -646,16 +631,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) it('Should perform balance and amounts validations on init', async () => { - const amount: BigNumber = bn('100e18') const invalidAmount: BigNumber = MAX_UINT96.add(1) - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() - - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - // Initialize trade - Sell Amount too large // Fund trade await token0.connect(owner).mint(trade.address, invalidAmount) @@ -711,15 +688,6 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) it('Should not allow to initialize an unfunded trade', async () => { - const amount: BigNumber = bn('100e18') - - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() - - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - // Initialize trade - simulate from backingManager const tradeRequest: ITradeRequest = { sell: collateral0.address, @@ -741,12 +709,6 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) it('Should be able to settle a trade - performing validations', async () => { - const amount: BigNumber = bn('100e18') - - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() - // Check state - cannot be settled expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) expect(await trade.canSettle()).to.equal(false) @@ -801,8 +763,6 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) it('Should protect against reentrancy when settling GnosisTrade', async () => { - const amount: BigNumber = bn('100e18') - // Create a Reetrant Gnosis const GnosisReentrantFactory: ContractFactory = await ethers.getContractFactory( 'GnosisMockReentrant' @@ -813,10 +773,6 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await reentrantGnosis.setReenterOnInit(false) await reentrantGnosis.setReenterOnSettle(true) - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() - // Initialize trade - simulate from backingManager const tradeRequest: ITradeRequest = { sell: collateral0.address, @@ -847,16 +803,6 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) it('Should be able to settle a GnosisTrade - handles arbitrary funds being sent to trade', async () => { - const amount: BigNumber = bn('100e18') - - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() - - // Check state - cannot be settled - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - expect(await trade.canSettle()).to.equal(false) - // Initialize trade - simulate from backingManager const tradeRequest: ITradeRequest = { sell: collateral0.address, @@ -931,12 +877,6 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) it('Should allow anyone to transfer to origin after a GnosisTrade is complete', async () => { - const amount: BigNumber = bn('100e18') - - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() - // Initialize trade - simulate from backingManager const tradeRequest: ITradeRequest = { sell: collateral0.address, @@ -1001,16 +941,22 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) context('DutchTrade', () => { - it('Should initialize DutchTrade correctly - only once', async () => { - const amount: BigNumber = config.rTokenMaxTradeVolume + let amount: BigNumber + let trade: DutchTrade + + beforeEach(async () => { + amount = config.rTokenMaxTradeVolume // Create a Trade const TradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') - const trade: DutchTrade = await TradeFactory.deploy() + trade = await TradeFactory.deploy() // Check state expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + expect(await trade.canSettle()).to.equal(false) + }) + it('Should initialize DutchTrade correctly - only once', async () => { // Fund trade and initialize await token0.connect(owner).mint(trade.address, amount) await expect( @@ -1053,11 +999,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) it('Should apply full maxTradeSlippage to lowPrice at minTradeVolume', async () => { - const amount: BigNumber = config.minTradeVolume - - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') - const trade: DutchTrade = await TradeFactory.deploy() + amount = config.minTradeVolume // Fund trade and initialize await token0.connect(owner).mint(trade.address, amount) @@ -1083,15 +1025,6 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) it('Should not allow to initialize an unfunded trade', async () => { - const amount: BigNumber = bn('100e18') - - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') - const trade: DutchTrade = await TradeFactory.deploy() - - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - // Attempt to initialize without funding await expect( trade.init( @@ -1105,12 +1038,6 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) it('Should allow anyone to transfer to origin after a DutchTrade is complete', async () => { - const amount: BigNumber = bn('100e18') - - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') - const trade: DutchTrade = await TradeFactory.deploy() - // Fund trade and initialize await token0.connect(owner).mint(trade.address, amount) await expect( From ab97a679634b5ce886503f610a97cb2ee9504e18 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 2 May 2023 18:41:59 -0400 Subject: [PATCH 144/462] nits --- test/Recollateralization.test.ts | 1 - test/utils/trades.ts | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index c0cf052f60..09b88f1356 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -3247,7 +3247,6 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Should launch another auction: RSR -> token1 expect(await backingManager.trades(token0.address)).to.equal(ZERO_ADDRESS) expect(await backingManager.trades(rsr.address)).to.not.equal(ZERO_ADDRESS) - expect(await backingManager.tradesOpen()).to.equal(1) }) }) }) diff --git a/test/utils/trades.ts b/test/utils/trades.ts index 4211e80380..a6ce40ab23 100644 --- a/test/utils/trades.ts +++ b/test/utils/trades.ts @@ -101,8 +101,6 @@ export const dutchBuyAmount = async ( ) .div(fp('1')) - // console.log('slippage: ', slippage) - const lowPrice = sellLow.mul(fp('1').sub(slippage)).div(buyHigh) const middlePrice = divCeil(sellHigh.mul(fp('1')), buyLow) const highPrice = middlePrice.add(divCeil(middlePrice, bn('2'))) // 50% above middlePrice @@ -115,14 +113,6 @@ export const dutchBuyAmount = async ( .mul(progression.sub(fp('0.15'))) .div(fp('0.85')) ) - // console.log( - // 'progression: ', - // progression, - // 'price: ', - // price, - // 'buyAmount: ', - // divCeil(outAmount.mul(price), fp('1')) - // ) return divCeil(outAmount.mul(price), fp('1')) } From bf2770b6aa5bf6606b82819968e7eb7cf96ecbc5 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 2 May 2023 18:53:35 -0400 Subject: [PATCH 145/462] empty buffer block --- contracts/p0/BackingManager.sol | 2 +- contracts/p1/BackingManager.sol | 2 +- test/Recollateralization.test.ts | 11 ++++++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 51c78f33b7..1473d43b68 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -79,7 +79,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { // DoS prevention: unless caller is self, require 1 empty block between like-kind auctions // Assumption: chain has <= 12s blocktimes require( - _msgSender() == address(this) || tradeEnd[kind] < block.timestamp + ONE_BLOCK, + _msgSender() == address(this) || tradeEnd[kind] + ONE_BLOCK < block.timestamp, "already rebalancing" ); diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index a58b7a7e0d..3d9d872a97 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -110,7 +110,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // DoS prevention: unless caller is self, require 1 empty block between like-kind auctions // Assumption: chain has <= 12s blocktimes require( - _msgSender() == address(this) || tradeEnd[kind] < block.timestamp + ONE_BLOCK, + _msgSender() == address(this) || tradeEnd[kind] + ONE_BLOCK < block.timestamp, "already rebalancing" ); diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 09b88f1356..845149744f 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -3148,7 +3148,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ) }) - it('Should only run 1 trade at a time', async () => { + it('Should only run 1 trade at a time, including into the empty buffer block', async () => { await backingManager.rebalance(TradeKind.DUTCH_AUCTION) await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.be.revertedWith( 'already rebalancing' @@ -3156,6 +3156,15 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'trade open' ) + + // Check the empty buffer block as well + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength) + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.be.revertedWith( + 'already rebalancing' + ) + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'trade open' + ) }) it('Should quote piecewise-falling price correctly throughout entirety of auction', async () => { From 42b30edf513c935db9e3a2db73fa86e8636d2992 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 2 May 2023 19:33:33 -0400 Subject: [PATCH 146/462] add full recollateralization tests --- test/Recollateralization.test.ts | 127 +++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 31 deletions(-) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 845149744f..9269e91120 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -18,6 +18,7 @@ import { Asset, ATokenFiatCollateral, CTokenMock, + DutchTrade, ERC20Mock, FacadeRead, FacadeTest, @@ -3223,39 +3224,103 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await backingManager.tradesOpen()).to.equal(0) }) - it('Should bid in final second of auction and launch another auction', async () => { - await backingManager.rebalance(TradeKind.DUTCH_AUCTION) - const trade = await ethers.getContractAt( - 'DutchTrade', - await backingManager.trades(token0.address) - ) - await token1.connect(addr1).approve(trade.address, initialBal) + context('Should successfully recollateralize after default', () => { + let trade1: DutchTrade // token0 -> token1 + let trade2: DutchTrade // RSR -> token1 - // Snipe auction at 1s left - await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 3) - await trade.connect(addr1).bid() - expect(await trade.canSettle()).to.equal(false) - expect(await trade.status()).to.equal(2) // Status.CLOSED - expect(await trade.bidder()).to.equal(addr1.address) - expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) - - const expected = divCeil( - await dutchBuyAmount( - fp('299').div(300), // after all txs in this test, will be left at 299/300s - collateral0.address, - collateral1.address, - issueAmount, - config.minTradeVolume, - config.maxTradeSlippage - ), - bn('1e12') // decimals - ) - expect(await backingManager.tradesOpen()).to.equal(1) - expect(await token1.balanceOf(backingManager.address)).to.equal(expected) + beforeEach(async () => { + await backingManager.rebalance(TradeKind.DUTCH_AUCTION) + trade1 = await ethers.getContractAt( + 'DutchTrade', + await backingManager.trades(token0.address) + ) + await token1.connect(addr1).approve(trade1.address, initialBal) + + // Snipe auction at 1s left + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 3) + await trade1.connect(addr1).bid() + expect(await trade1.canSettle()).to.equal(false) + expect(await trade1.status()).to.equal(2) // Status.CLOSED + expect(await trade1.bidder()).to.equal(addr1.address) + expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) + + const expected = divCeil( + await dutchBuyAmount( + fp('299').div(300), // after all txs so far, at 299/300s + collateral0.address, + collateral1.address, + issueAmount, + config.minTradeVolume, + config.maxTradeSlippage + ), + bn('1e12') // decimals + ) + expect(await backingManager.tradesOpen()).to.equal(1) + expect(await token1.balanceOf(backingManager.address)).to.equal(expected) + + // Should launch RSR recapitalization auction to fill ~3% + expect(await backingManager.trades(token0.address)).to.equal(ZERO_ADDRESS) + trade2 = await ethers.getContractAt( + 'DutchTrade', + await backingManager.trades(rsr.address) + ) + expect(trade2.address).to.not.equal(ZERO_ADDRESS) + }) + + afterEach(async () => { + // Should be fully capitalized again + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await backingManager.tradesOpen()).to.equal(0) + }) + + it('even under worst-possible bids', async () => { + // Advance to final second of auction + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 3) + expect(await trade2.status()).to.equal(1) // TradeStatus.OPEN + expect(await trade2.canSettle()).to.equal(false) + + // Bid + settle RSR auction + await token1.connect(addr1).approve(trade2.address, initialBal) + await expect(trade2.connect(addr1).bid()).to.emit(backingManager, 'TradeSettled') + }) - // Should launch another auction: RSR -> token1 - expect(await backingManager.trades(token0.address)).to.equal(ZERO_ADDRESS) - expect(await backingManager.trades(rsr.address)).to.not.equal(ZERO_ADDRESS) + it('via fallback to Batch Auction', async () => { + // Advance past auction timeout + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength) + expect(await trade2.status()).to.equal(1) // TradeStatus.OPEN + expect(await trade2.canSettle()).to.equal(true) + + // Settle trade + await backingManager.settleTrade(rsr.address) + expect(await backingManager.tradesOpen()).to.equal(0) + expect(await rsr.balanceOf(trade2.address)).to.equal(0) + expect(await token0.balanceOf(trade2.address)).to.equal(0) + expect(await token1.balanceOf(trade2.address)).to.equal(0) + + // Only BATCH_AUCTION can be launched + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.be.revertedWith( + 'already rebalancing' + ) + await backingManager.rebalance(TradeKind.BATCH_AUCTION) + expect(await backingManager.tradesOpen()).to.equal(1) + + // Bid in Gnosis + const t = await getTrade(backingManager, rsr.address) + const sellAmt = await t.initBal() + const minBuyAmt = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + expect(await t.KIND()).to.equal(TradeKind.BATCH_AUCTION) + await token1.connect(addr1).approve(gnosis.address, minBuyAmt) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: minBuyAmt, + }) + await advanceTime(config.batchAuctionLength.toNumber()) + await expect(backingManager.settleTrade(rsr.address)).not.to.emit( + backingManager, + 'TradeStarted' + ) + }) }) }) }) From 24304fb4f307e6a418dcaa1e1290f7f89cfeefc7 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 2 May 2023 19:41:06 -0400 Subject: [PATCH 147/462] RevenueTraderP1.manageToken() reentrancy nitpick --- contracts/p1/RevenueTrader.sol | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 6cb6b95c9d..ea14ef1562 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -79,8 +79,8 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { return; } - IAsset sell = assetRegistry.toAsset(erc20); - IAsset buy = assetRegistry.toAsset(tokenToBuy); + IAsset sell; + IAsset buy; // === Refresh === // do not need to refresh when caller is BackingManager.forwardRevenue() @@ -89,8 +89,13 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { // if either token is the RToken, refresh everything assetRegistry.refresh(); furnace.melt(); + + sell = assetRegistry.toAsset(erc20); + buy = assetRegistry.toAsset(tokenToBuy); } else { // otherwise, refresh just buy + sell + sell = assetRegistry.toAsset(erc20); + buy = assetRegistry.toAsset(tokenToBuy); sell.refresh(); buy.refresh(); } From 46ef4456352a3f4546c37c08d4a08bbbb9d3a09c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 2 May 2023 19:42:57 -0400 Subject: [PATCH 148/462] comment RCEI --- contracts/p1/RevenueTrader.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index ea14ef1562..f7190b05f3 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -101,6 +101,8 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { } } + // === Checks/Effects === + require(address(trades[erc20]) == address(0), "trade open"); require(erc20.balanceOf(address(this)) > 0, "0 balance"); @@ -125,8 +127,9 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { minTradeVolume, maxTradeSlippage ); - require(launch, "trade not worth launching"); + + // === Interactions === tryTrade(kind, req); } From 3cb60b713b976f29fcd4deeae483920373ecb698 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 2 May 2023 19:48:40 -0400 Subject: [PATCH 149/462] label checks/effects/interactions --- contracts/p1/BackingManager.sol | 9 +++++++-- contracts/p1/RevenueTrader.sol | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 3d9d872a97..872d8202af 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -107,6 +107,8 @@ contract BackingManagerP1 is TradingP1, IBackingManager { assetRegistry.refresh(); furnace.melt(); + // == Checks/Effects == + // DoS prevention: unless caller is self, require 1 empty block between like-kind auctions // Assumption: chain has <= 12s blocktimes require( @@ -139,15 +141,18 @@ contract BackingManagerP1 is TradingP1, IBackingManager { (bool doTrade, TradeRequest memory req) = RecollateralizationLibP1 .prepareRecollateralizationTrade(this, basketsHeld); + // == Interactions == + if (doTrade) { + tradeEnd[kind] = uint48(block.timestamp) + ONE_BLOCK; // reentrancy protection + // Seize RSR if needed if (req.sell.erc20() == rsr) { uint256 bal = req.sell.erc20().balanceOf(address(this)); if (req.sellAmount > bal) stRSR.seizeRSR(req.sellAmount - bal); } - // Execute Trade -- prevent reentrancy - tradeEnd[kind] = uint48(block.timestamp) + ONE_BLOCK; + // Execute Trade ITrade trade = tryTrade(kind, req); uint48 endTime = trade.endTime(); if (endTime > tradeEnd[kind]) tradeEnd[kind] = endTime; diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index f7190b05f3..a7af82f26b 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -82,7 +82,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { IAsset sell; IAsset buy; - // === Refresh === + // == Refresh == // do not need to refresh when caller is BackingManager.forwardRevenue() if (_msgSender() != address(backingManager)) { if (erc20 == IERC20(address(rToken)) || tokenToBuy == IERC20(address(rToken))) { @@ -101,7 +101,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { } } - // === Checks/Effects === + // == Checks/Effects == require(address(trades[erc20]) == address(0), "trade open"); require(erc20.balanceOf(address(this)) > 0, "0 balance"); @@ -129,7 +129,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { ); require(launch, "trade not worth launching"); - // === Interactions === + // == Interactions == tryTrade(kind, req); } From e234148ddebd7915bc6e6099186d853d0d424ff7 Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Wed, 3 May 2023 18:29:47 -0400 Subject: [PATCH 150/462] Feature/remove delegate call (#782) * refactor to use special modified ERC4626 * add test suite for RewardableERC20Vault. * add test coverage for rewardable vault. * cleanup linting. * add normal erc20 vault for tokens that don't get rewards. * remove abstract. * remove delegate calls. * get gas snapshots. * edit RewardableLib, create CTokenVault and switch over tests * remove ERC4626, fix linting. * add claimRewards to ATokenFiatCollateral. * create tests for atoken fiat collateral. can't claim rewards for some reason. * linting. * refactor asset tests to work. * fix collateral tests. * fix strsr tests. * dry test minting. * fixing tests. * refactor to use special modified ERC4626 * add test suite for RewardableERC20Vault. * add test coverage for rewardable vault. * cleanup linting. * add normal erc20 vault for tokens that don't get rewards. * remove abstract. * remove delegate calls. * get gas snapshots. * edit RewardableLib, create CTokenVault and switch over tests * remove ERC4626, fix linting. * add claimRewards to ATokenFiatCollateral. * create tests for atoken fiat collateral. can't claim rewards for some reason. * linting. * refactor asset tests to work. * fix collateral tests. * fix strsr tests. * dry test minting. * fixing tests. * fix rtoken tests. * fix moar tests. * walking back some test changes...idk what i was thinking. * fixing more tests. * revenue tests done. * fixing tests. * fixing scenarios. * fixing linting. * add fix for weird solidity bug. * fix ftoken tests. * fixing tests. * fix mocking, fix comet tests. * fix asset plugin tests. * fix max basket test. * add claimRewards function to convex wrapper. * add test for claimRewards() on wrapper. * add tests for all plugin/wrappers. * fix linting. * remove deprecated facadeAct test. * fix linting. * fix deployment scripts. * remove comp from constructor args in ctokenvault. * fix atoken collateral tests. * fix linting, remove old ctokenvaultmock. * remove comptroller from collateral plugin. * fix lint. * fix tests. * fix tests and lint. * switch to OZ implementation of 4626. * fixed most tests. * fix integration tests. * fix ctoken fiat collat tests. * fix linting. * add salt to fixture to fix dumb hardhat bug. * fix linting. * address comments, simplify erc4626 and rewardable vault. * fix linting, use oz code directly. * fix compilation. * address comments, fix tests. * fix revenue tests. * add version to assets. * add deprecation note. * fix compilation. * fix tests and linting. * fix atoken tests. * remove kruft TODO * RewardableERC20Vault nits + documentation --------- Co-authored-by: Taylor Brent --- contracts/interfaces/IRewardable.sol | 2 - contracts/p0/mixins/Rewardable.sol | 20 +- contracts/p1/mixins/RewardableLib.sol | 24 +- contracts/plugins/assets/Asset.sol | 5 +- contracts/plugins/assets/RTokenAsset.sol | 5 +- contracts/plugins/assets/VersionedAsset.sol | 17 + .../assets/aave/ATokenFiatCollateral.sol | 7 +- .../plugins/assets/aave/StaticATokenLM.sol | 14 +- .../compoundv2/CTokenFiatCollateral.sol | 30 +- .../compoundv2/CTokenNonFiatCollateral.sol | 6 +- .../CTokenSelfReferentialCollateral.sol | 15 +- .../plugins/assets/compoundv2/CTokenVault.sol | 33 + .../assets/compoundv3/CTokenV3Collateral.sol | 5 +- .../assets/compoundv3/CometExtMock.sol | 10 + .../assets/compoundv3/CusdcV3Wrapper.sol | 10 +- .../assets/compoundv3/ICusdcV3Wrapper.sol | 10 +- .../assets/compoundv3/vendor/CometCore.sol | 56 ++ .../assets/compoundv3/vendor/CometStorage.sol | 61 ++ .../assets/convex/CvxStableCollateral.sol | 11 +- .../convex/vendor/ConvexStakingWrapper.sol | 10 +- .../assets/vaults/RewardableERC20Vault.sol | 93 ++ contracts/plugins/mocks/ATokenMock.sol | 12 + contracts/plugins/mocks/CTokenVaultMock2.sol | 51 ++ .../plugins/mocks/ERC20MockRewarding.sol | 29 + .../mocks/InvalidATokenFiatCollateralMock.sol | 1 + .../mocks/RewardableERC20VaultTest.sol | 20 + contracts/vendor/oz/ERC4626.sol | 262 ++++++ contracts/vendor/oz/IERC4626.sol | 232 +++++ docs/collateral.md | 7 +- docs/solidity-style.md | 8 - .../phase2-assets/2_deploy_collateral.ts | 77 +- .../deploy_flux_finance_collateral.ts | 54 +- .../deploy-ctoken-fiat-collateral.ts | 4 +- .../deploy-ctoken-nonfiat-collateral.ts | 4 +- ...eploy-ctoken-selfreferential-collateral.ts | 4 +- test/FacadeAct.test.ts | 73 +- test/FacadeRead.test.ts | 35 +- test/FacadeWrite.test.ts | 10 +- test/Furnace.test.ts | 19 +- test/Main.test.ts | 17 +- test/RToken.test.ts | 24 +- test/Recollateralization.test.ts | 66 +- test/Revenues.test.ts | 440 ++++----- test/ZTradingExtremes.test.ts | 22 +- test/ZZStRSR.test.ts | 8 +- test/fixtures.ts | 40 +- test/integration/AssetPlugins.test.ts | 247 +++-- test/integration/fixtures.ts | 41 +- test/integration/fork-block-numbers.ts | 1 + test/plugins/Asset.test.ts | 10 +- test/plugins/Collateral.test.ts | 237 ++--- test/plugins/RewardableERC20Vault.test.ts | 574 ++++++++++++ .../RewardableERC20Vault.test.ts.snap | 13 + .../aave/ATokenFiatCollateral.test.ts | 844 ++++++++++++++++++ .../aave}/StaticATokenLM.test.ts | 58 +- .../individual-collateral/collateralTests.ts | 32 +- .../compoundv2/CTokenFiatCollateral.test.ts | 123 +-- .../compoundv3/CometTestSuite.test.ts | 48 +- .../compoundv3/CusdcV3Wrapper.test.ts | 16 +- .../compoundv3/constants.ts | 1 + .../convex/CvxStableMetapoolSuite.test.ts | 20 +- .../CvxStableRTokenMetapoolTestSuite.test.ts | 20 +- .../convex/CvxStableTestSuite.test.ts | 20 +- .../convex/CvxVolatileTestSuite.test.ts | 20 +- .../plugins/individual-collateral/fixtures.ts | 279 +++--- .../flux-finance/FTokenFiatCollateral.test.ts | 80 +- .../flux-finance/helpers.ts | 6 +- test/scenario/ComplexBasket.test.ts | 327 +++---- test/scenario/MaxBasketSize.test.ts | 39 +- test/scenario/RevenueHiding.test.ts | 14 +- test/scenario/cETH.test.ts | 12 +- test/scenario/cWBTC.test.ts | 17 +- test/utils/tokens.ts | 34 + 73 files changed, 3786 insertions(+), 1310 deletions(-) create mode 100644 contracts/plugins/assets/VersionedAsset.sol create mode 100644 contracts/plugins/assets/compoundv2/CTokenVault.sol create mode 100644 contracts/plugins/assets/compoundv3/CometExtMock.sol create mode 100644 contracts/plugins/assets/compoundv3/vendor/CometCore.sol create mode 100644 contracts/plugins/assets/compoundv3/vendor/CometStorage.sol create mode 100644 contracts/plugins/assets/vaults/RewardableERC20Vault.sol create mode 100644 contracts/plugins/mocks/CTokenVaultMock2.sol create mode 100644 contracts/plugins/mocks/ERC20MockRewarding.sol create mode 100644 contracts/plugins/mocks/RewardableERC20VaultTest.sol create mode 100644 contracts/vendor/oz/ERC4626.sol create mode 100644 contracts/vendor/oz/IERC4626.sol create mode 100644 test/plugins/RewardableERC20Vault.test.ts create mode 100644 test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap create mode 100644 test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts rename test/{integration => plugins/individual-collateral/aave}/StaticATokenLM.test.ts (98%) create mode 100644 test/utils/tokens.ts diff --git a/contracts/interfaces/IRewardable.sol b/contracts/interfaces/IRewardable.sol index 28fd68ef4a..3749ed0f6f 100644 --- a/contracts/interfaces/IRewardable.sol +++ b/contracts/interfaces/IRewardable.sol @@ -15,7 +15,6 @@ interface IRewardable { /// Claim rewards earned by holding a balance of the ERC20 token /// Must emit `RewardsClaimed` for each token rewards are claimed for - /// @dev delegatecall: there be dragons here! /// @custom:interaction function claimRewards() external; } @@ -27,7 +26,6 @@ interface IRewardable { interface IRewardableComponent is IRewardable { /// Claim rewards for a single ERC20 /// Must emit `RewardsClaimed` for each token rewards are claimed for - /// @dev delegatecall: there be dragons here! /// @custom:interaction function claimRewardsSingle(IERC20 erc20) external; } diff --git a/contracts/p0/mixins/Rewardable.sol b/contracts/p0/mixins/Rewardable.sol index ade8b07a4b..8d22f54d5f 100644 --- a/contracts/p0/mixins/Rewardable.sol +++ b/contracts/p0/mixins/Rewardable.sol @@ -19,13 +19,9 @@ abstract contract RewardableP0 is ComponentP0, IRewardableComponent { IERC20[] memory erc20s = reg.erc20s(); for (uint256 i = 0; i < erc20s.length; i++) { - IAsset asset = reg.toAsset(erc20s[i]); - - // Claim rewards via delegatecall - address(asset).functionDelegateCall( - abi.encodeWithSignature("claimRewards()"), - "rewards claim failed" - ); + // empty try/catch because not every erc20 will be wrapped & have a claimRewards func + // solhint-disable-next-line + try IRewardable(address(erc20s[i])).claimRewards() {} catch {} } } @@ -34,12 +30,8 @@ abstract contract RewardableP0 is ComponentP0, IRewardableComponent { /// @param erc20 The ERC20 to claimRewards on /// @custom:interaction CEI function claimRewardsSingle(IERC20 erc20) external notTradingPausedOrFrozen { - IAsset asset = main.assetRegistry().toAsset(erc20); - - // Claim rewards via delegatecall - address(asset).functionDelegateCall( - abi.encodeWithSignature("claimRewards()"), - "rewards claim failed" - ); + // empty try/catch because not every erc20 will be wrapped & have a claimRewards func + // solhint-disable-next-line + try IRewardable(address(erc20)).claimRewards() {} catch {} } } diff --git a/contracts/p1/mixins/RewardableLib.sol b/contracts/p1/mixins/RewardableLib.sol index 33566044e9..6cdcd42008 100644 --- a/contracts/p1/mixins/RewardableLib.sol +++ b/contracts/p1/mixins/RewardableLib.sol @@ -16,32 +16,30 @@ library RewardableLibP1 { using Address for address; using SafeERC20 for IERC20; + event Complete(); + // === Used by Traders + RToken === /// Claim all rewards /// @custom:interaction mostly CEI but see comments // actions: - // do asset.delegatecall(abi.encodeWithSignature("claimRewards()")) for asset in assets + // do asset.claimRewards() for asset in assets function claimRewards(IAssetRegistry reg) internal { Registry memory registry = reg.getRegistry(); - for (uint256 i = 0; i < registry.assets.length; ++i) { - // Claim rewards via delegatecall - address(registry.assets[i]).functionDelegateCall( - abi.encodeWithSignature("claimRewards()"), - "rewards claim failed" - ); + for (uint256 i = 0; i < registry.erc20s.length; ++i) { + // empty try/catch because not every erc20 will be wrapped & have a claimRewards func + // solhint-disable-next-line + try IRewardable(address(registry.erc20s[i])).claimRewards() {} catch {} } } /// Claim rewards for a single ERC20 /// @custom:interaction mostly CEI but see comments // actions: - // do asset.delegatecall(abi.encodeWithSignature("claimRewards()")) + // do asset.claimRewards() function claimRewardsSingle(IAsset asset) internal { - // Claim rewards via delegatecall - address(asset).functionDelegateCall( - abi.encodeWithSignature("claimRewards()"), - "rewards claim failed" - ); + // empty try/catch because not every erc20 will be wrapped & have a claimRewards func + // solhint-disable-next-line + try IRewardable(address(asset.erc20())).claimRewards() {} catch {} } } diff --git a/contracts/plugins/assets/Asset.sol b/contracts/plugins/assets/Asset.sol index 2304559918..64d2073d7d 100644 --- a/contracts/plugins/assets/Asset.sol +++ b/contracts/plugins/assets/Asset.sol @@ -5,8 +5,9 @@ import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../../interfaces/IAsset.sol"; import "./OracleLib.sol"; +import "./VersionedAsset.sol"; -contract Asset is IAsset { +contract Asset is IAsset, VersionedAsset { using FixLib for uint192; using OracleLib for AggregatorV3Interface; @@ -161,7 +162,7 @@ contract Asset is IAsset { // solhint-disable no-empty-blocks /// Claim rewards earned by holding a balance of the ERC20 token - /// @dev Use delegatecall + /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins function claimRewards() external virtual {} // solhint-enable no-empty-blocks diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index 1ad1a28bce..b1c461878a 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -5,10 +5,11 @@ import "../../p1/mixins/RecollateralizationLib.sol"; import "../../interfaces/IMain.sol"; import "../../interfaces/IRToken.sol"; import "./Asset.sol"; +import "./VersionedAsset.sol"; /// Once an RToken gets large enough to get a price feed, replacing this asset with /// a simpler one will do wonders for gas usage -contract RTokenAsset is IAsset { +contract RTokenAsset is IAsset, VersionedAsset { using FixLib for uint192; using OracleLib for AggregatorV3Interface; @@ -144,7 +145,7 @@ contract RTokenAsset is IAsset { // solhint-disable no-empty-blocks /// Claim rewards earned by holding a balance of the ERC20 token - /// @dev Use delegatecall + /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins function claimRewards() external virtual {} // solhint-enable no-empty-blocks diff --git a/contracts/plugins/assets/VersionedAsset.sol b/contracts/plugins/assets/VersionedAsset.sol new file mode 100644 index 0000000000..1b92bac328 --- /dev/null +++ b/contracts/plugins/assets/VersionedAsset.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.17; + +import "../../interfaces/IVersioned.sol"; + +// This value should be updated on each release +string constant ASSET_VERSION = "3.0.0"; + +/** + * @title VersionedAsset + * @notice A mix-in to track semantic versioning uniformly across asset plugin contracts. + */ +abstract contract VersionedAsset is IVersioned { + function version() public pure virtual override returns (string memory) { + return ASSET_VERSION; + } +} diff --git a/contracts/plugins/assets/aave/ATokenFiatCollateral.sol b/contracts/plugins/assets/aave/ATokenFiatCollateral.sol index 819b32bb1f..e9ed7267f9 100644 --- a/contracts/plugins/assets/aave/ATokenFiatCollateral.sol +++ b/contracts/plugins/assets/aave/ATokenFiatCollateral.sol @@ -52,11 +52,8 @@ contract ATokenFiatCollateral is AppreciatingFiatCollateral { } /// Claim rewards earned by holding a balance of the ERC20 token - /// @dev Use delegatecall + /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins function claimRewards() external virtual override(Asset, IRewardable) { - IERC20 stkAAVE = IStaticAToken(address(erc20)).REWARD_TOKEN(); - uint256 oldBal = stkAAVE.balanceOf(address(this)); - IStaticAToken(address(erc20)).claimRewardsToSelf(true); - emit RewardsClaimed(stkAAVE, stkAAVE.balanceOf(address(this)) - oldBal); + IRewardable(address(erc20)).claimRewards(); } } diff --git a/contracts/plugins/assets/aave/StaticATokenLM.sol b/contracts/plugins/assets/aave/StaticATokenLM.sol index c95a95ce7f..d22344d075 100644 --- a/contracts/plugins/assets/aave/StaticATokenLM.sol +++ b/contracts/plugins/assets/aave/StaticATokenLM.sol @@ -38,6 +38,9 @@ contract StaticATokenLM is using WadRayMath for uint256; using RayMathNoRounding for uint256; + /// Emitted whenever a reward token balance is claimed + event RewardsClaimed(IERC20 indexed erc20, uint256 indexed amount); + bytes public constant EIP712_REVISION = bytes("1"); bytes32 internal constant EIP712_DOMAIN = keccak256( @@ -419,7 +422,6 @@ contract StaticATokenLM is ); uint256 lifetimeRewards = _lifetimeRewardsClaimed.add(freshlyClaimed); uint256 rewardsAccrued = lifetimeRewards.sub(_lifetimeRewards).wadToRay(); - if (supply > 0 && rewardsAccrued > 0) { _accRewardsPerToken = _accRewardsPerToken.add( (rewardsAccrued).rayDivNoRounding(supply.wadToRay()) @@ -456,6 +458,7 @@ contract StaticATokenLM is uint256 balance = balanceOf(onBehalfOf); uint256 reward = _getClaimableRewards(onBehalfOf, balance, false); uint256 totBal = REWARD_TOKEN.balanceOf(address(this)); + if (reward > totBal) { reward = totBal; } @@ -496,6 +499,15 @@ contract StaticATokenLM is _claimRewardsOnBehalf(msg.sender, msg.sender, forceUpdate); } + function claimRewards() external virtual nonReentrant { + if (address(INCENTIVES_CONTROLLER) == address(0)) { + return; + } + uint256 oldBal = REWARD_TOKEN.balanceOf(msg.sender); + _claimRewardsOnBehalf(msg.sender, msg.sender, true); + emit RewardsClaimed(REWARD_TOKEN, REWARD_TOKEN.balanceOf(msg.sender) - oldBal); + } + /** * @notice Update the rewardDebt for a user with balance as his balance * @param user The user to update diff --git a/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol index 8cc3273186..2ce5b6a23a 100644 --- a/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol @@ -4,7 +4,9 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../../../libraries/Fixed.sol"; import "../AppreciatingFiatCollateral.sol"; +import "../../../interfaces/IRewardable.sol"; import "./ICToken.sol"; +import "../../../vendor/oz/IERC4626.sol"; /** * @title CTokenFiatCollateral @@ -20,19 +22,14 @@ contract CTokenFiatCollateral is AppreciatingFiatCollateral { uint8 public immutable referenceERC20Decimals; - IComptroller public immutable comptroller; + ICToken public immutable cToken; /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide - /// @param comptroller_ The CompoundFinance Comptroller - constructor( - CollateralConfig memory config, - uint192 revenueHiding, - IComptroller comptroller_ - ) AppreciatingFiatCollateral(config, revenueHiding) { - require(address(comptroller_) != address(0), "comptroller missing"); - ICToken erc20 = ICToken(address(config.erc20)); - referenceERC20Decimals = IERC20Metadata(erc20.underlying()).decimals(); - comptroller = comptroller_; + constructor(CollateralConfig memory config, uint192 revenueHiding) + AppreciatingFiatCollateral(config, revenueHiding) + { + cToken = ICToken(address(IERC4626(address(config.erc20)).asset())); + referenceERC20Decimals = IERC20Metadata(cToken.underlying()).decimals(); } /// Refresh exchange rates and update default status. @@ -40,7 +37,7 @@ contract CTokenFiatCollateral is AppreciatingFiatCollateral { function refresh() public virtual override { // == Refresh == // Update the Compound Protocol - ICToken(address(erc20)).exchangeRateCurrent(); + cToken.exchangeRateCurrent(); // Intentional and correct for the super call to be last! super.refresh(); // already handles all necessary default checks @@ -48,17 +45,14 @@ contract CTokenFiatCollateral is AppreciatingFiatCollateral { /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens function _underlyingRefPerTok() internal view override returns (uint192) { - uint256 rate = ICToken(address(erc20)).exchangeRateStored(); + uint256 rate = cToken.exchangeRateStored(); int8 shiftLeft = 8 - int8(referenceERC20Decimals) - 18; return shiftl_toFix(rate, shiftLeft); } /// Claim rewards earned by holding a balance of the ERC20 token - /// @dev delegatecall + /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins function claimRewards() external virtual override(Asset, IRewardable) { - IERC20 comp = IERC20(comptroller.getCompAddress()); - uint256 oldBal = comp.balanceOf(address(this)); - comptroller.claimComp(address(this)); - emit RewardsClaimed(comp, comp.balanceOf(address(this)) - oldBal); + IRewardable(address(erc20)).claimRewards(); } } diff --git a/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol index e6bdcdd5d9..54b381355f 100644 --- a/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol @@ -22,14 +22,12 @@ contract CTokenNonFiatCollateral is CTokenFiatCollateral { /// @param targetUnitChainlinkFeed_ Feed units: {UoA/target} /// @param targetUnitOracleTimeout_ {s} oracle timeout to use for targetUnitChainlinkFeed /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide - /// @param comptroller_ The CompoundFinance Comptroller constructor( CollateralConfig memory config, AggregatorV3Interface targetUnitChainlinkFeed_, uint48 targetUnitOracleTimeout_, - uint192 revenueHiding, - IComptroller comptroller_ - ) CTokenFiatCollateral(config, revenueHiding, comptroller_) { + uint192 revenueHiding + ) CTokenFiatCollateral(config, revenueHiding) { require(address(targetUnitChainlinkFeed_) != address(0), "missing targetUnit feed"); require(targetUnitOracleTimeout_ > 0, "targetUnitOracleTimeout zero"); targetUnitChainlinkFeed = targetUnitChainlinkFeed_; diff --git a/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol index c2b45b57ce..d3329f4ad2 100644 --- a/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol @@ -17,23 +17,17 @@ contract CTokenSelfReferentialCollateral is AppreciatingFiatCollateral { uint8 public immutable referenceERC20Decimals; - IComptroller public immutable comptroller; - /// @param config.chainlinkFeed Feed units: {UoA/ref} /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide /// @param referenceERC20Decimals_ The number of decimals in the reference token - /// @param comptroller_ The CompoundFinance Comptroller constructor( CollateralConfig memory config, uint192 revenueHiding, - uint8 referenceERC20Decimals_, - IComptroller comptroller_ + uint8 referenceERC20Decimals_ ) AppreciatingFiatCollateral(config, revenueHiding) { require(config.defaultThreshold == 0, "default threshold not supported"); require(referenceERC20Decimals_ > 0, "referenceERC20Decimals missing"); - require(address(comptroller_) != address(0), "comptroller missing"); referenceERC20Decimals = referenceERC20Decimals_; - comptroller = comptroller_; } /// Can revert, used by other contract functions in order to catch errors @@ -80,11 +74,8 @@ contract CTokenSelfReferentialCollateral is AppreciatingFiatCollateral { } /// Claim rewards earned by holding a balance of the ERC20 token - /// @dev delegatecall + /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins function claimRewards() external virtual override(Asset, IRewardable) { - IERC20 comp = IERC20(comptroller.getCompAddress()); - uint256 oldBal = comp.balanceOf(address(this)); - comptroller.claimComp(address(this)); - emit RewardsClaimed(comp, comp.balanceOf(address(this)) - oldBal); + IRewardable(address(erc20)).claimRewards(); } } diff --git a/contracts/plugins/assets/compoundv2/CTokenVault.sol b/contracts/plugins/assets/compoundv2/CTokenVault.sol new file mode 100644 index 0000000000..8d8c8501aa --- /dev/null +++ b/contracts/plugins/assets/compoundv2/CTokenVault.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity ^0.8.17; + +import "../vaults/RewardableERC20Vault.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "./ICToken.sol"; + +contract CTokenVault is RewardableERC20Vault { + using SafeERC20 for ERC20; + + IComptroller public immutable comptroller; + + constructor( + ERC20 _asset, + string memory _name, + string memory _symbol, + IComptroller _comptroller + ) RewardableERC20Vault(_asset, _name, _symbol, ERC20(_comptroller.getCompAddress())) { + comptroller = _comptroller; + } + + function _claimAssetRewards() internal virtual override { + comptroller.claimComp(address(this)); + } + + function exchangeRateCurrent() external returns (uint256) { + return ICToken(asset()).exchangeRateCurrent(); + } + + function exchangeRateStored() external view returns (uint256) { + return ICToken(asset()).exchangeRateStored(); + } +} diff --git a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol index 5b9ae48022..ea923e9dce 100644 --- a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol +++ b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol @@ -47,10 +47,9 @@ contract CTokenV3Collateral is AppreciatingFiatCollateral { return shiftl_toFix(erc20.balanceOf(account), -int8(erc20Decimals)); } + /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins function claimRewards() external override(Asset, IRewardable) { - uint256 oldBal = rewardERC20.balanceOf(address(this)); - ICusdcV3Wrapper(address(erc20)).claimTo(address(this), address(this)); - emit RewardsClaimed(rewardERC20, rewardERC20.balanceOf(address(this)) - oldBal); + IRewardable(address(erc20)).claimRewards(); } function _underlyingRefPerTok() internal view virtual override returns (uint192) { diff --git a/contracts/plugins/assets/compoundv3/CometExtMock.sol b/contracts/plugins/assets/compoundv3/CometExtMock.sol new file mode 100644 index 0000000000..535edc4ee8 --- /dev/null +++ b/contracts/plugins/assets/compoundv3/CometExtMock.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.17; + +import "./vendor/CometCore.sol"; + +contract CometExtMock is CometCore { + function setBaseSupplyIndex(uint64 newIndex) external { + baseSupplyIndex = newIndex; + } +} diff --git a/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol b/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol index ab7a15e722..bb6930c331 100644 --- a/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol +++ b/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol @@ -182,6 +182,10 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers { accrueAccountRewards(dst); } + function claimRewards() external { + claimTo(msg.sender, msg.sender); + } + /// @param src The account to claim from /// @param dst The address to send claimed rewards to function claimTo(address src, address dst) public { @@ -191,15 +195,15 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers { accrueAccount(src); uint256 claimed = rewardsClaimed[src]; uint256 accrued = baseTrackingAccrued[src] * RESCALE_FACTOR; - + uint256 owed; if (accrued > claimed) { - uint256 owed = accrued - claimed; + owed = accrued - claimed; rewardsClaimed[src] = accrued; rewardsAddr.claimTo(address(underlyingComet), address(this), address(this), true); IERC20(rewardERC20).safeTransfer(dst, owed); - emit RewardClaimed(src, dst, address(rewardERC20), owed); } + emit RewardsClaimed(rewardERC20, owed); } /// Accure the cUSDCv3 account of the wrapper diff --git a/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol b/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol index 452cfaec79..67a676b3be 100644 --- a/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol +++ b/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol @@ -5,15 +5,9 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "./vendor/CometInterface.sol"; import "./IWrappedERC20.sol"; +import "../../../interfaces/IRewardable.sol"; -interface ICusdcV3Wrapper is IWrappedERC20 { - event RewardClaimed( - address indexed src, - address indexed recipient, - address indexed token, - uint256 amount - ); - +interface ICusdcV3Wrapper is IWrappedERC20, IRewardable { struct UserBasic { uint104 principal; uint64 baseTrackingAccrued; diff --git a/contracts/plugins/assets/compoundv3/vendor/CometCore.sol b/contracts/plugins/assets/compoundv3/vendor/CometCore.sol new file mode 100644 index 0000000000..ad5d8bff3a --- /dev/null +++ b/contracts/plugins/assets/compoundv3/vendor/CometCore.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.17; + +import "./CometStorage.sol"; + +abstract contract CometCore is CometStorage { + struct AssetInfo { + uint8 offset; + address asset; + address priceFeed; + uint64 scale; + uint64 borrowCollateralFactor; + uint64 liquidateCollateralFactor; + uint64 liquidationFactor; + uint128 supplyCap; + } + + /** Internal constants **/ + + /// @dev The max number of assets this contract is hardcoded to support + /// Do not change this variable without updating all the fields throughout the contract, + // including the size of UserBasic.assetsIn and corresponding integer conversions. + uint8 internal constant MAX_ASSETS = 15; + + /// @dev The max number of decimals base token can have + /// Note this cannot just be increased arbitrarily. + uint8 internal constant MAX_BASE_DECIMALS = 18; + + /// @dev The max value for a collateral factor (1) + uint64 internal constant MAX_COLLATERAL_FACTOR = FACTOR_SCALE; + + /// @dev Offsets for specific actions in the pause flag bit array + uint8 internal constant PAUSE_SUPPLY_OFFSET = 0; + uint8 internal constant PAUSE_TRANSFER_OFFSET = 1; + uint8 internal constant PAUSE_WITHDRAW_OFFSET = 2; + uint8 internal constant PAUSE_ABSORB_OFFSET = 3; + uint8 internal constant PAUSE_BUY_OFFSET = 4; + + /// @dev The decimals required for a price feed + uint8 internal constant PRICE_FEED_DECIMALS = 8; + + /// @dev 365 days * 24 hours * 60 minutes * 60 seconds + uint64 internal constant SECONDS_PER_YEAR = 31_536_000; + + /// @dev The scale for base tracking accrual + uint64 internal constant BASE_ACCRUAL_SCALE = 1e6; + + /// @dev The scale for base index (depends on time/rate scales, not base token) + uint64 internal constant BASE_INDEX_SCALE = 1e15; + + /// @dev The scale for prices (in USD) + uint64 internal constant PRICE_SCALE = uint64(10**PRICE_FEED_DECIMALS); + + /// @dev The scale for factors + uint64 internal constant FACTOR_SCALE = 1e18; +} diff --git a/contracts/plugins/assets/compoundv3/vendor/CometStorage.sol b/contracts/plugins/assets/compoundv3/vendor/CometStorage.sol new file mode 100644 index 0000000000..9564ca89b1 --- /dev/null +++ b/contracts/plugins/assets/compoundv3/vendor/CometStorage.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.17; + +/** + * @title Compound's Comet Configuration Interface + * @author Compound + */ +contract CometStorage { + struct TotalsCollateral { + uint128 totalSupplyAsset; + uint128 _reserved; + } + + struct UserBasic { + int104 principal; + uint64 baseTrackingIndex; + uint64 baseTrackingAccrued; + uint16 assetsIn; + uint8 _reserved; + } + + struct UserCollateral { + uint128 balance; + uint128 _reserved; + } + + struct LiquidatorPoints { + uint32 numAbsorbs; + uint64 numAbsorbed; + uint128 approxSpend; + uint32 _reserved; + } + + /// @dev Aggregate variables tracked for the entire market + uint64 internal baseSupplyIndex; + uint64 internal baseBorrowIndex; + uint64 internal trackingSupplyIndex; + uint64 internal trackingBorrowIndex; + uint104 internal totalSupplyBase; + uint104 internal totalBorrowBase; + uint40 internal lastAccrualTime; + uint8 internal pauseFlags; + + /// @notice Aggregate variables tracked for each collateral asset + mapping(address => TotalsCollateral) public totalsCollateral; + + /// @notice Mapping of users to accounts which may be permitted to manage the user account + mapping(address => mapping(address => bool)) public isAllowed; + + /// @notice The next expected nonce for an address, for validating authorizations via signature + mapping(address => uint256) public userNonce; + + /// @notice Mapping of users to base principal and other basic data + mapping(address => UserBasic) public userBasic; + + /// @notice Mapping of users to collateral data per collateral asset + mapping(address => mapping(address => UserCollateral)) public userCollateral; + + /// @notice Mapping of magic liquidator points + mapping(address => LiquidatorPoints) public liquidatorPoints; +} diff --git a/contracts/plugins/assets/convex/CvxStableCollateral.sol b/contracts/plugins/assets/convex/CvxStableCollateral.sol index 4f26ef9e30..77123e2745 100644 --- a/contracts/plugins/assets/convex/CvxStableCollateral.sol +++ b/contracts/plugins/assets/convex/CvxStableCollateral.sol @@ -131,16 +131,9 @@ contract CvxStableCollateral is AppreciatingFiatCollateral, PoolTokens { } /// Claim rewards earned by holding a balance of the ERC20 token - /// @dev Use delegatecall + /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins function claimRewards() external override(Asset, IRewardable) { - IConvexStakingWrapper wrapper = IConvexStakingWrapper(address(erc20)); - IERC20 cvx = IERC20(wrapper.cvx()); - IERC20 crv = IERC20(wrapper.crv()); - uint256 cvxOldBal = cvx.balanceOf(address(this)); - uint256 crvOldBal = crv.balanceOf(address(this)); - wrapper.getReward(address(this)); - emit RewardsClaimed(cvx, cvx.balanceOf(address(this)) - cvxOldBal); - emit RewardsClaimed(crv, crv.balanceOf(address(this)) - crvOldBal); + IRewardable(address(erc20)).claimRewards(); } // === Internal === diff --git a/contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol b/contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol index c2984c5861..aa83bacdf8 100644 --- a/contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol +++ b/contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol @@ -41,7 +41,6 @@ interface IConvexDeposits { // if used as collateral some modifications will be needed to fit the specific platform // Based on audited contracts: https://github.com/convex-eth/platform/blob/main/contracts/contracts/wrappers/CvxCrvStakingWrapper.sol -// TODO check on contract size to see if blocker contract ConvexStakingWrapper is ERC20, ReentrancyGuard { using SafeERC20 for IERC20; using SafeMath for uint256; @@ -92,6 +91,7 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { ); event Withdrawn(address indexed _user, uint256 _amount, bool _unwrapped); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + event RewardsClaimed(IERC20 indexed erc20, uint256 indexed amount); constructor() public ERC20("StakedConvexToken", "stkCvx") {} @@ -404,6 +404,14 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { return claimable; } + function claimRewards() external { + uint256 cvxOldBal = IERC20(cvx).balanceOf(msg.sender); + uint256 crvOldBal = IERC20(crv).balanceOf(msg.sender); + _checkpointAndClaim([address(msg.sender), address(msg.sender)]); + emit RewardsClaimed(IERC20(cvx), IERC20(cvx).balanceOf(msg.sender) - cvxOldBal); + emit RewardsClaimed(IERC20(crv), IERC20(crv).balanceOf(msg.sender) - crvOldBal); + } + function getReward(address _account) external { //claim directly in checkpoint logic to save a bit of gas _checkpointAndClaim([_account, _account]); diff --git a/contracts/plugins/assets/vaults/RewardableERC20Vault.sol b/contracts/plugins/assets/vaults/RewardableERC20Vault.sol new file mode 100644 index 0000000000..c9140be702 --- /dev/null +++ b/contracts/plugins/assets/vaults/RewardableERC20Vault.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity ^0.8.17; + +import "../../../interfaces/IRewardable.sol"; +import "../../../vendor/oz/ERC4626.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title RewardableERC20Vault + * @notice A transferrable vault token wrapping an inner ERC20 that earns rewards. + * Holding the vault token for a period of time earns the holder the right to + * their prorata share of the global rewards earned during that time. + * @dev To inherit, override _claimAssetRewards() + */ +abstract contract RewardableERC20Vault is IRewardable, ERC4626 { + using SafeERC20 for ERC20; + + uint256 public immutable one; // {qShare/share} + ERC20 public immutable rewardToken; + + uint256 public rewardsPerShare; // {qRewards/share} + mapping(address => uint256) public lastRewardsPerShare; // {qRewards/share} + mapping(address => uint256) public accumulatedRewards; // {qRewards} + mapping(address => uint256) public claimedRewards; // {qRewards} + + constructor( + ERC20 _asset, + string memory _name, + string memory _symbol, + ERC20 _rewardToken + ) ERC4626(_asset, _name, _symbol) { + rewardToken = _rewardToken; + one = 10**decimals(); + } + + function claimRewards() external { + _claimAndSyncRewards(); + _syncAccount(msg.sender); + _claimAccountRewards(msg.sender); + } + + function _syncAccount(address account) internal { + if (account == address(0)) return; + uint256 shares = balanceOf(account); + uint256 accountRewardsPerShare = lastRewardsPerShare[account]; + if (rewardsPerShare == accountRewardsPerShare) return; + uint256 delta = rewardsPerShare - accountRewardsPerShare; + + // {qRewards} = {qRewards/share} * {qShare} / {qShare/share} + uint256 newRewards = (delta * shares) / one; + lastRewardsPerShare[account] = rewardsPerShare; + accumulatedRewards[account] += newRewards; + } + + function _claimAndSyncRewards() internal { + uint256 delta; + uint256 _totalSupply = totalSupply(); + if (_totalSupply > 0) { + uint256 initialBal = rewardToken.balanceOf(address(this)); + _claimAssetRewards(); + uint256 endingBal = rewardToken.balanceOf(address(this)); + delta = endingBal - initialBal; + + // {qRewards/share} += {qRewards} * {qShare/share} / {qShare} + rewardsPerShare += ((delta) * one) / _totalSupply; + } + } + + function _claimAssetRewards() internal virtual; + + function _claimAccountRewards(address account) internal { + uint256 claimableRewards = accumulatedRewards[account] - claimedRewards[account]; + emit RewardsClaimed(IERC20(address(rewardToken)), claimableRewards); + if (claimableRewards == 0) return; + claimedRewards[account] = accumulatedRewards[account]; + rewardToken.safeTransfer(account, claimableRewards); + } + + function _beforeTokenTransfer( + address from, + address to, + uint256 + ) internal virtual override { + _claimAndSyncRewards(); + _syncAccount(from); + _syncAccount(to); + } + + function _decimalsOffset() internal view virtual override returns (uint8) { + return 9; + } +} diff --git a/contracts/plugins/mocks/ATokenMock.sol b/contracts/plugins/mocks/ATokenMock.sol index 850a1f39eb..c852d1e3a3 100644 --- a/contracts/plugins/mocks/ATokenMock.sol +++ b/contracts/plugins/mocks/ATokenMock.sol @@ -27,6 +27,9 @@ contract ATokenMock is ERC20Mock { contract StaticATokenMock is ERC20Mock { using FixLib for uint192; + /// Emitted whenever a reward token balance is claimed + event RewardsClaimed(IERC20 indexed erc20, uint256 indexed amount); + ATokenMock internal aToken; uint256 internal _exchangeRate; @@ -91,4 +94,13 @@ contract StaticATokenMock is ERC20Mock { function _toExchangeRate(uint192 fiatcoinRedemptionRate) internal pure returns (uint256) { return fiatcoinRedemptionRate.mulu_toUint(1e27, ROUND); } + + function claimRewards() external { + uint256 oldBal = aaveToken.balanceOf(msg.sender); + if (address(aaveToken) != address(0) && aaveBalances[msg.sender] > 0) { + aaveToken.mint(msg.sender, aaveBalances[msg.sender]); + aaveBalances[msg.sender] = 0; + } + emit RewardsClaimed(IERC20(address(aaveToken)), aaveToken.balanceOf(msg.sender) - oldBal); + } } diff --git a/contracts/plugins/mocks/CTokenVaultMock2.sol b/contracts/plugins/mocks/CTokenVaultMock2.sol new file mode 100644 index 0000000000..8704c674dc --- /dev/null +++ b/contracts/plugins/mocks/CTokenVaultMock2.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity ^0.8.17; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "../assets/compoundv2/CTokenVault.sol"; +import "../assets/compoundv2/ICToken.sol"; +import "./CTokenMock.sol"; + +contract CTokenVaultMock is ERC20Mock, IRewardable { + ERC20Mock public comp; + CTokenMock public asset; + IComptroller public comptroller; + + constructor( + string memory _name, + string memory _symbol, + address _underlyingToken, + ERC20Mock _comp, + IComptroller _comptroller + ) ERC20Mock(_name, _symbol) { + asset = new CTokenMock("cToken Mock", "cMOCK", _underlyingToken); + comp = _comp; + comptroller = _comptroller; + } + + // function mint(uint256 amount, address recipient) external { + // _mint(recipient, amount); + // } + + function decimals() public pure override returns (uint8) { + return 8; + } + + function exchangeRateCurrent() external returns (uint256) { + return asset.exchangeRateCurrent(); + } + + function exchangeRateStored() external view returns (uint256) { + return asset.exchangeRateStored(); + } + + function claimRewards() external { + uint256 oldBal = comp.balanceOf(msg.sender); + comptroller.claimComp(msg.sender); + emit RewardsClaimed(IERC20(address(comp)), comp.balanceOf(msg.sender) - oldBal); + } + + function setExchangeRate(uint192 fiatcoinRedemptionRate) external { + asset.setExchangeRate(fiatcoinRedemptionRate); + } +} \ No newline at end of file diff --git a/contracts/plugins/mocks/ERC20MockRewarding.sol b/contracts/plugins/mocks/ERC20MockRewarding.sol new file mode 100644 index 0000000000..7a2fc54593 --- /dev/null +++ b/contracts/plugins/mocks/ERC20MockRewarding.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.17; + +import "./ERC20MockDecimals.sol"; + +contract ERC20MockRewarding is ERC20MockDecimals { + ERC20MockDecimals public rewardToken; + mapping(address => uint256) public accruedRewards; + + // solhint-disable-next-line no-empty-blocks + constructor( + string memory name, + string memory symbol, + uint8 decimals_, + ERC20MockDecimals rewardToken_ + ) ERC20MockDecimals(name, symbol, decimals_) { + rewardToken = rewardToken_; + } + + function accrueRewards(uint256 amount, address recipient) external { + rewardToken.mint(address(this), amount); + accruedRewards[recipient] += amount; + } + + function claim() external { + rewardToken.transfer(msg.sender, accruedRewards[msg.sender]); + accruedRewards[msg.sender] = 0; + } +} diff --git a/contracts/plugins/mocks/InvalidATokenFiatCollateralMock.sol b/contracts/plugins/mocks/InvalidATokenFiatCollateralMock.sol index 5a814f71d7..e349804c35 100644 --- a/contracts/plugins/mocks/InvalidATokenFiatCollateralMock.sol +++ b/contracts/plugins/mocks/InvalidATokenFiatCollateralMock.sol @@ -9,6 +9,7 @@ contract InvalidATokenFiatCollateralMock is ATokenFiatCollateral { {} /// Reverting claimRewards function + /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins function claimRewards() external pure override { revert("claimRewards() error"); } diff --git a/contracts/plugins/mocks/RewardableERC20VaultTest.sol b/contracts/plugins/mocks/RewardableERC20VaultTest.sol new file mode 100644 index 0000000000..bc368ece29 --- /dev/null +++ b/contracts/plugins/mocks/RewardableERC20VaultTest.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.17; + +import "../assets/vaults/RewardableERC20Vault.sol"; +import "./ERC20MockRewarding.sol"; + +contract RewardableERC20VaultTest is RewardableERC20Vault { + + constructor( + ERC20 _asset, + string memory _name, + string memory _symbol, + ERC20 _rewardToken + ) RewardableERC20Vault(_asset, _name, _symbol, _rewardToken) { + } + + function _claimAssetRewards() internal virtual override { + ERC20MockRewarding(asset()).claim(); + } +} \ No newline at end of file diff --git a/contracts/vendor/oz/ERC4626.sol b/contracts/vendor/oz/ERC4626.sol new file mode 100644 index 0000000000..087503c716 --- /dev/null +++ b/contracts/vendor/oz/ERC4626.sol @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.8.1) (token/ERC20/extensions/ERC4626.sol) + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "./IERC4626.sol"; +import "@openzeppelin/contracts/utils/math/Math.sol"; + +/** + * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in + * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626]. + * + * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for + * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends + * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this + * contract and not the "assets" token which is an independent contract. + * + * [CAUTION] + * ==== + * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning + * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation + * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial + * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may + * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by + * verifying the amount received is as expected, using a wrapper that performs these checks such as + * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. + * + * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` + * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault + * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself + * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset + * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's + * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more + * expensive than it is profitable. More details about the underlying math can be found + * xref:erc4626.adoc#inflation-attack[here]. + * + * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued + * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets + * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience + * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the + * `_convertToShares` and `_convertToAssets` functions. + * + * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. + * ==== + * + * _Available since v4.7._ + * + * code taken from openzeppelin-contracts, commit hash: eedca5d + */ +abstract contract ERC4626 is ERC20, IERC4626 { + using Math for uint256; + + IERC20 private immutable _asset; + uint8 private immutable _underlyingDecimals; + + /** + * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). + */ + constructor( + IERC20 asset_, + string memory _name, + string memory _symbol + ) ERC20(_name, _symbol) { + (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); + _underlyingDecimals = success ? assetDecimals : 18; + _asset = asset_; + } + + /** + * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. + */ + function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { + (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( + abi.encodeWithSelector(IERC20Metadata.decimals.selector) + ); + if (success && encodedDecimals.length >= 32) { + uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); + if (returnedDecimals <= type(uint8).max) { + return (true, uint8(returnedDecimals)); + } + } + return (false, 0); + } + + /** + * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This + * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the + * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. + * + * See {IERC20Metadata-decimals}. + */ + function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { + return _underlyingDecimals + _decimalsOffset(); + } + + /** @dev See {IERC4626-asset}. */ + function asset() public view virtual override returns (address) { + return address(_asset); + } + + /** @dev See {IERC4626-totalAssets}. */ + function totalAssets() public view virtual override returns (uint256) { + return _asset.balanceOf(address(this)); + } + + /** @dev See {IERC4626-convertToShares}. */ + function convertToShares(uint256 assets) public view virtual override returns (uint256) { + return _convertToShares(assets, Math.Rounding.Down); + } + + /** @dev See {IERC4626-convertToAssets}. */ + function convertToAssets(uint256 shares) public view virtual override returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Down); + } + + /** @dev See {IERC4626-maxDeposit}. */ + function maxDeposit(address) public view virtual override returns (uint256) { + return type(uint256).max; + } + + /** @dev See {IERC4626-maxMint}. */ + function maxMint(address) public view virtual override returns (uint256) { + return type(uint256).max; + } + + /** @dev See {IERC4626-maxWithdraw}. */ + function maxWithdraw(address owner) public view virtual override returns (uint256) { + return _convertToAssets(balanceOf(owner), Math.Rounding.Down); + } + + /** @dev See {IERC4626-maxRedeem}. */ + function maxRedeem(address owner) public view virtual override returns (uint256) { + return balanceOf(owner); + } + + /** @dev See {IERC4626-previewDeposit}. */ + function previewDeposit(uint256 assets) public view virtual override returns (uint256) { + return _convertToShares(assets, Math.Rounding.Down); + } + + /** @dev See {IERC4626-previewMint}. */ + function previewMint(uint256 shares) public view virtual override returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Up); + } + + /** @dev See {IERC4626-previewWithdraw}. */ + function previewWithdraw(uint256 assets) public view virtual override returns (uint256) { + return _convertToShares(assets, Math.Rounding.Up); + } + + /** @dev See {IERC4626-previewRedeem}. */ + function previewRedeem(uint256 shares) public view virtual override returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Down); + } + + /** @dev See {IERC4626-deposit}. */ + function deposit(uint256 assets, address receiver) public virtual override returns (uint256) { + require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max"); + + uint256 shares = previewDeposit(assets); + _deposit(_msgSender(), receiver, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-mint}. + * + * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero. + * In this case, the shares will be minted without requiring any assets to be deposited. + */ + function mint(uint256 shares, address receiver) public virtual override returns (uint256) { + require(shares <= maxMint(receiver), "ERC4626: mint more than max"); + + uint256 assets = previewMint(shares); + _deposit(_msgSender(), receiver, assets, shares); + + return assets; + } + + /** @dev See {IERC4626-withdraw}. */ + function withdraw(uint256 assets, address receiver, address owner) public virtual override returns (uint256) { + require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max"); + + uint256 shares = previewWithdraw(assets); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-redeem}. */ + function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256) { + require(shares <= maxRedeem(owner), "ERC4626: redeem more than max"); + + uint256 assets = previewRedeem(shares); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return assets; + } + + /** + * @dev Internal conversion function (from assets to shares) with support for rounding direction. + */ + function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { + return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding); + } + + /** + * @dev Internal conversion function (from shares to assets) with support for rounding direction. + */ + function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { + return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); + } + + /** + * @dev Deposit/mint common workflow. + */ + function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { + // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the + // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the + // assets are transferred and before the shares are minted, which is a valid state. + // slither-disable-next-line reentrancy-no-eth + SafeERC20.safeTransferFrom(_asset, caller, address(this), assets); + _mint(receiver, shares); + + emit Deposit(caller, receiver, assets, shares); + } + + /** + * @dev Withdraw/redeem common workflow. + */ + function _withdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal virtual { + if (caller != owner) { + _spendAllowance(owner, caller, shares); + } + + // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the + // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the + // shares are burned and after the assets are transferred, which is a valid state. + _burn(owner, shares); + SafeERC20.safeTransfer(_asset, receiver, assets); + + emit Withdraw(caller, receiver, owner, assets, shares); + } + + function _decimalsOffset() internal view virtual returns (uint8) { + return 0; + } +} \ No newline at end of file diff --git a/contracts/vendor/oz/IERC4626.sol b/contracts/vendor/oz/IERC4626.sol new file mode 100644 index 0000000000..bace5aaf01 --- /dev/null +++ b/contracts/vendor/oz/IERC4626.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.8.0) (interfaces/IERC4626.sol) + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in + * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. + * + * _Available since v4.7._ + */ +interface IERC4626 is IERC20, IERC20Metadata { + event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + + event Withdraw( + address indexed sender, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares + ); + + /** + * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + * + * - MUST be an ERC-20 token contract. + * - MUST NOT revert. + */ + function asset() external view returns (address assetTokenAddress); + + /** + * @dev Returns the total amount of the underlying asset that is “managed” by Vault. + * + * - SHOULD include any compounding that occurs from yield. + * - MUST be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT revert. + */ + function totalAssets() external view returns (uint256 totalManagedAssets); + + /** + * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, + * through a deposit call. + * + * - MUST return a limited value if receiver is subject to some deposit limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. + * - MUST NOT revert. + */ + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + * in the same transaction. + * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + * deposit would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * deposit execution, and are accounted for during deposit. + * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + * - MUST return a limited value if receiver is subject to some mint limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. + * - MUST NOT revert. + */ + function maxMint(address receiver) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + * same transaction. + * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + * would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by minting. + */ + function previewMint(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + * execution, and are accounted for during mint. + * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the + * Vault, through a withdraw call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + * called + * in the same transaction. + * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + * the withdrawal would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * withdraw execution, and are accounted for during withdraw. + * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, + * through a redeem call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxRedeem(address owner) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + * same transaction. + * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + * redemption would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by redeeming. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * redeem execution, and are accounted for during redeem. + * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); +} \ No newline at end of file diff --git a/docs/collateral.md b/docs/collateral.md index 09d88d9621..aa4fe491cd 100644 --- a/docs/collateral.md +++ b/docs/collateral.md @@ -37,7 +37,6 @@ interface IRewardable { /// Claim rewards earned by holding a balance of the ERC20 token /// Must emit `RewardsClaimed` for each token rewards are claimed for - /// @dev delegatecall: there be dragons here! /// @custom:interaction function claimRewards() external; } @@ -344,11 +343,11 @@ If a collateral implementor extends [Fiat Collateral](../contracts/plugins/asset If `status()` ever returns `CollateralStatus.DISABLED`, then it must always return `CollateralStatus.DISABLED` in the future. -### Token rewards should be claimable via delegatecall. +### Token rewards should be claimable. -Protocol contracts that hold an asset for any significant amount of time are all able to call `claimRewards()` via delegatecall. The plugin contract should include whatever logic is necessary to claim rewards from all relevant defi protocols. These rewards are often emissions from other protocols, but may also be something like trading fees in the case of UNIV3 collateral. To take advantage of this: +Protocol contracts that hold an asset for any significant amount of time are all able to call `claimRewards()` on the ERC20 itself (previously on the asset/collateral plugin via delegatecall). The erc20 or its wrapper contract should include whatever logic is necessary to claim rewards from all relevant defi protocols. These rewards are often emissions from other protocols, but may also be something like trading fees in the case of UNIV3 collateral. To take advantage of this: -- `claimRewards()` should expected to be executed via delegatecall. It must claim all rewards that may be earned by holding the asset ERC20. +- `claimRewards()` must claim all rewards that may be earned by holding the asset ERC20 and send them to the holder. - The `RewardsClaimed` event should be emitted for each token type claimed. ### Smaller Constraints diff --git a/docs/solidity-style.md b/docs/solidity-style.md index ac2568566d..23991fc423 100644 --- a/docs/solidity-style.md +++ b/docs/solidity-style.md @@ -303,14 +303,6 @@ For additional information on how to use the plugins and how to perform upgrades [upgrades-docs]: https://docs.openzeppelin.com/upgrades [forceimport]: https://docs.openzeppelin/upgrades-plugins/1.x/api-hardhat-upgrades#force-import -### Why it's safe to use multicall on our upgradable contracts - -In our P1 implementation both our RevenueTrader and BackingManager components contain `delegatecall`, even though they are themselves implementations that sit behind an ERC1967Proxy (UUPSUpgradeable). This is disallowed by default by OZ's upgradable plugin. - -In this case, we think it is acceptable. The special danger of containing a `delegatecall` in a proxy implementation contract is that the `delegatecall` can self-destruct the proxy if the executed code contains `selfdestruct`. In this case `Multicall` executes `delegatecall` on `address(this)`, which resolves to the address of its caller, the proxy. This executes the `fallback` function, which results in another `delegatecall` to the implementation contract. So the only way for a `selfdestruct` to happen is if the implementation contract itself contains a `selfdestruct`, which it does not. - -Note that `delegatecall` can also be dangerous for other reasons, such as transferring tokens out of the address in an unintended way. The same argument applies to any such case; only the code from the implementation contract can be called. - ### Developer discipline Here, "contract state" refers to the normal storage variables of a smart contract. diff --git a/scripts/deployment/phase2-assets/2_deploy_collateral.ts b/scripts/deployment/phase2-assets/2_deploy_collateral.ts index 4a76eb3b94..d6265e788c 100644 --- a/scripts/deployment/phase2-assets/2_deploy_collateral.ts +++ b/scripts/deployment/phase2-assets/2_deploy_collateral.ts @@ -379,19 +379,27 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy CToken Fiat Collateral - cDAI **************************/ + const CTokenFactory = await ethers.getContractFactory('CTokenVault') + const cDai = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cDAI!) + + const cDaiVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cDAI!, + `${await cDai.name()} Vault`, + `${await cDai.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) const { collateral: cDaiCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.DAI, oracleError: fp('0.0025').toString(), // 0.25% - cToken: networkConfig[chainId].tokens.cDAI, + cToken: cDaiVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - comptroller: networkConfig[chainId].COMPTROLLER, + revenueHiding: revenueHiding.toString() }) collateral = await ethers.getContractAt('ICollateral', cDaiCollateral) await (await collateral.refresh()).wait() @@ -403,19 +411,26 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy CToken Fiat Collateral - cUSDC **************************/ + const cUsdc = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDC!) + + const cUsdcVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cUSDC!, + `${await cUsdc.name()} Vault`, + `${await cUsdc.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) const { collateral: cUsdcCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, oracleError: fp('0.0025').toString(), // 0.25% - cToken: networkConfig[chainId].tokens.cUSDC, + cToken: cUsdcVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - comptroller: networkConfig[chainId].COMPTROLLER, + revenueHiding: revenueHiding.toString() }) collateral = await ethers.getContractAt('ICollateral', cUsdcCollateral) await (await collateral.refresh()).wait() @@ -427,19 +442,26 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy CToken Fiat Collateral - cUSDT **************************/ + const cUsdt = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDT!) + + const cUsdtVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cUSDT!, + `${await cUsdt.name()} Vault`, + `${await cUsdt.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) const { collateral: cUsdtCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDT, oracleError: fp('0.0025').toString(), // 0.25% - cToken: networkConfig[chainId].tokens.cUSDT, + cToken: cUsdtVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - comptroller: networkConfig[chainId].COMPTROLLER, + revenueHiding: revenueHiding.toString() }) collateral = await ethers.getContractAt('ICollateral', cUsdtCollateral) await (await collateral.refresh()).wait() @@ -451,19 +473,26 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy CToken Fiat Collateral - cUSDP **************************/ + const cUsdp = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDP!) + + const cUsdpVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cUSDP!, + `${await cUsdp.name()} Vault`, + `${await cUsdp.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) const { collateral: cUsdpCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDP, oracleError: fp('0.01').toString(), // 1% - cToken: networkConfig[chainId].tokens.cUSDP, + cToken: cUsdpVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.02').toString(), // 2% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - comptroller: networkConfig[chainId].COMPTROLLER, + revenueHiding: revenueHiding.toString() }) collateral = await ethers.getContractAt('ICollateral', cUsdpCollateral) await (await collateral.refresh()).wait() @@ -475,6 +504,14 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy CToken Non-Fiat Collateral - cWBTC **************************/ + const cWBTC = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cWBTC!) + + const cWBTCVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cWBTC!, + `${await cWBTC.name()} Vault`, + `${await cWBTC.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) const wbtcOracleError = fp('0.02') // 2% const btcOracleError = fp('0.005') // 0.5% @@ -485,15 +522,14 @@ async function main() { referenceUnitFeed: networkConfig[chainId].chainlinkFeeds.WBTC, targetUnitFeed: networkConfig[chainId].chainlinkFeeds.BTC, combinedOracleError: combinedBTCWBTCError.toString(), - cToken: networkConfig[chainId].tokens.cWBTC, + cToken: cWBTCVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetUnitOracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('BTC'), defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - comptroller: networkConfig[chainId].COMPTROLLER, + revenueHiding: revenueHiding.toString() }) collateral = await ethers.getContractAt('ICollateral', cWBTCCollateral) await (await collateral.refresh()).wait() @@ -505,17 +541,24 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy CToken Self-Referential Collateral - cETH **************************/ + const cETH = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cETH!) + + const cETHVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cETH!, + `${await cETH.name()} Vault`, + `${await cETH.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) const { collateral: cETHCollateral } = await hre.run('deploy-ctoken-selfreferential-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.ETH, oracleError: fp('0.005').toString(), // 0.5% - cToken: networkConfig[chainId].tokens.cETH, + cToken: cETHVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('ETH'), revenueHiding: revenueHiding.toString(), - comptroller: networkConfig[chainId].COMPTROLLER, referenceERC20Decimals: '18', }) collateral = await ethers.getContractAt('ICollateral', cETHCollateral) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts index 64c4212130..ae52a67fe6 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts @@ -14,6 +14,7 @@ import { } from '../../common' import { priceTimeout, oracleTimeout, revenueHiding } from '../../utils' import { ICollateral } from '../../../../typechain' +import { ZERO_ADDRESS } from '#/tasks/deployment/create-deployer-registry' async function main() { // ==== Read Configuration ==== @@ -41,19 +42,27 @@ async function main() { const deployedCollateral: string[] = [] /******** Deploy FToken Fiat Collateral - fUSDC **************************/ + const FTokenFactory = await ethers.getContractFactory('CTokenVault') + const fUsdc = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.fUSDC!) + + const fUsdcVault = await FTokenFactory.deploy( + networkConfig[chainId].tokens.fUSDC!, + `${await fUsdc.name()} Vault`, + `${await fUsdc.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) const { collateral: fUsdcCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, oracleError: fp('0.0025').toString(), // 0.25% - cToken: networkConfig[chainId].tokens.fUSDC, + cToken: fUsdcVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - comptroller: networkConfig[chainId].FLUX_FINANCE_COMPTROLLER, + revenueHiding: revenueHiding.toString() }) let collateral = await ethers.getContractAt('ICollateral', fUsdcCollateral) await (await collateral.refresh()).wait() @@ -65,19 +74,26 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy FToken Fiat Collateral - fUSDT **************************/ + const fUsdt = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.fUSDT!) + + const fUsdtVault = await FTokenFactory.deploy( + networkConfig[chainId].tokens.fUSDT!, + `${await fUsdt.name()} Vault`, + `${await fUsdt.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) const { collateral: fUsdtCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDT, oracleError: fp('0.0025').toString(), // 0.25% - cToken: networkConfig[chainId].tokens.fUSDT, + cToken: fUsdtVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - comptroller: networkConfig[chainId].FLUX_FINANCE_COMPTROLLER, + revenueHiding: revenueHiding.toString() }) collateral = await ethers.getContractAt('ICollateral', fUsdtCollateral) await (await collateral.refresh()).wait() @@ -89,19 +105,26 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy FToken Fiat Collateral - fDAI **************************/ + const fDai = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.fDAI!) + + const fDaiVault = await FTokenFactory.deploy( + networkConfig[chainId].tokens.fDAI!, + `${await fDai.name()} Vault`, + `${await fDai.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) const { collateral: fDaiCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.DAI, oracleError: fp('0.0025').toString(), // 0.25% - cToken: networkConfig[chainId].tokens.fDAI, + cToken: fDaiVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - comptroller: networkConfig[chainId].FLUX_FINANCE_COMPTROLLER, + revenueHiding: revenueHiding.toString() }) collateral = await ethers.getContractAt('ICollateral', fDaiCollateral) await collateral.refresh() @@ -113,19 +136,26 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy FToken Fiat Collateral - fFRAX **************************/ + const fFrax = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.fFRAX!) + + const fFraxVault = await FTokenFactory.deploy( + networkConfig[chainId].tokens.fFRAX!, + `${await fFrax.name()} Vault`, + `${await fFrax.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) const { collateral: fFRAXCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.FRAX, oracleError: fp('0.01').toString(), // 1% - cToken: networkConfig[chainId].tokens.fFRAX, + cToken: fFraxVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.02').toString(), // 2% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - comptroller: networkConfig[chainId].FLUX_FINANCE_COMPTROLLER, + revenueHiding: revenueHiding.toString() }) collateral = await ethers.getContractAt('ICollateral', fFRAXCollateral) await collateral.refresh() diff --git a/tasks/deployment/collateral/deploy-ctoken-fiat-collateral.ts b/tasks/deployment/collateral/deploy-ctoken-fiat-collateral.ts index 30e9cff768..92dd550aa6 100644 --- a/tasks/deployment/collateral/deploy-ctoken-fiat-collateral.ts +++ b/tasks/deployment/collateral/deploy-ctoken-fiat-collateral.ts @@ -13,7 +13,6 @@ task('deploy-ctoken-fiat-collateral', 'Deploys a CToken Fiat Collateral') .addParam('defaultThreshold', 'Default Threshold') .addParam('delayUntilDefault', 'Delay until default') .addParam('revenueHiding', 'Revenue Hiding') - .addParam('comptroller', 'Comptroller address') .setAction(async (params, hre) => { const [deployer] = await hre.ethers.getSigners() @@ -33,8 +32,7 @@ task('deploy-ctoken-fiat-collateral', 'Deploys a CToken Fiat Collateral') defaultThreshold: params.defaultThreshold, delayUntilDefault: params.delayUntilDefault, }, - params.revenueHiding, - params.comptroller + params.revenueHiding ) await collateral.deployed() diff --git a/tasks/deployment/collateral/deploy-ctoken-nonfiat-collateral.ts b/tasks/deployment/collateral/deploy-ctoken-nonfiat-collateral.ts index ebb8fc1476..fc3002df53 100644 --- a/tasks/deployment/collateral/deploy-ctoken-nonfiat-collateral.ts +++ b/tasks/deployment/collateral/deploy-ctoken-nonfiat-collateral.ts @@ -15,7 +15,6 @@ task('deploy-ctoken-nonfiat-collateral', 'Deploys a CToken Non-Fiat Collateral') .addParam('defaultThreshold', 'Default Threshold') .addParam('delayUntilDefault', 'Delay until default') .addParam('revenueHiding', 'Revenue Hiding') - .addParam('comptroller', 'Comptroller address') .setAction(async (params, hre) => { const [deployer] = await hre.ethers.getSigners() @@ -41,8 +40,7 @@ task('deploy-ctoken-nonfiat-collateral', 'Deploys a CToken Non-Fiat Collateral') }, params.targetUnitFeed, params.targetUnitOracleTimeout, - params.revenueHiding, - params.comptroller + params.revenueHiding ) await collateral.deployed() diff --git a/tasks/deployment/collateral/deploy-ctoken-selfreferential-collateral.ts b/tasks/deployment/collateral/deploy-ctoken-selfreferential-collateral.ts index 6280aee48e..97e401b69c 100644 --- a/tasks/deployment/collateral/deploy-ctoken-selfreferential-collateral.ts +++ b/tasks/deployment/collateral/deploy-ctoken-selfreferential-collateral.ts @@ -12,7 +12,6 @@ task('deploy-ctoken-selfreferential-collateral', 'Deploys a CToken Self-referent .addParam('targetName', 'Target Name') .addParam('revenueHiding', 'Revenue Hiding') .addParam('referenceERC20Decimals', 'Decimals in the reference token') - .addParam('comptroller', 'Comptroller address') .setAction(async (params, hre) => { const [deployer] = await hre.ethers.getSigners() @@ -36,8 +35,7 @@ task('deploy-ctoken-selfreferential-collateral', 'Deploys a CToken Self-referent delayUntilDefault: 0, }, params.revenueHiding, - params.referenceERC20Decimals, - params.comptroller + params.referenceERC20Decimals ) ) await collateral.deployed() diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts index e1ce54fa8c..429d733bd9 100644 --- a/test/FacadeAct.test.ts +++ b/test/FacadeAct.test.ts @@ -19,14 +19,12 @@ import { ATokenFiatCollateral, ComptrollerMock, CTokenFiatCollateral, - CTokenMock, ERC20Mock, FacadeAct, IFacadeRead, FiatCollateral, GnosisMock, IAssetRegistry, - InvalidATokenFiatCollateralMock, MockV3Aggregator, StaticATokenMock, TestIBackingManager, @@ -36,6 +34,7 @@ import { TestIRToken, TestIStRSR, USDCMock, + CTokenVaultMock, } from '../typechain' import { Collateral, @@ -48,6 +47,7 @@ import { } from './fixtures' import snapshotGasCost from './utils/snapshotGasCost' import { useEnv } from '#/utils/env' +import { mintCollaterals } from './utils/tokens' const DEFAULT_THRESHOLD = fp('0.01') // 1% @@ -66,7 +66,7 @@ describe('FacadeAct contract', () => { let token: ERC20Mock let usdc: USDCMock let aToken: StaticATokenMock - let cToken: CTokenMock + let cToken: CTokenVaultMock let aaveToken: ERC20Mock let compToken: ERC20Mock let compoundMock: ComptrollerMock @@ -161,7 +161,9 @@ describe('FacadeAct contract', () => { aToken = ( await ethers.getContractAt('StaticATokenMock', await aTokenAsset.erc20()) ) - cToken = await ethers.getContractAt('CTokenMock', await cTokenAsset.erc20()) + cToken = ( + await ethers.getContractAt('CTokenVaultMock', await cTokenAsset.erc20()) + ) // Backup tokens and collaterals - USDT - aUSDT - aUSDC - aBUSD backupToken1 = erc20s[2] // USDT @@ -177,15 +179,7 @@ describe('FacadeAct contract', () => { beforeEach(async () => { // Mint Tokens initialBal = bn('10000000000e18') - await token.connect(owner).mint(addr1.address, initialBal) - await usdc.connect(owner).mint(addr1.address, initialBal) - await aToken.connect(owner).mint(addr1.address, initialBal) - await cToken.connect(owner).mint(addr1.address, initialBal) - - await token.connect(owner).mint(addr2.address, initialBal) - await usdc.connect(owner).mint(addr2.address, initialBal) - await aToken.connect(owner).mint(addr2.address, initialBal) - await cToken.connect(owner).mint(addr2.address, initialBal) + await mintCollaterals(owner, [addr1, addr2], initialBal, basket) // Mint RSR await rsr.connect(owner).mint(addr1.address, initialBal) @@ -546,58 +540,6 @@ describe('FacadeAct contract', () => { ) }) - it('Revenues - Should handle assets with invalid claim logic', async () => { - // Redeem all RTokens - await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) - - // Setup a new aToken with invalid claim data - const ATokenCollateralFactory = await ethers.getContractFactory( - 'InvalidATokenFiatCollateralMock' - ) - const chainlinkFeed = ( - await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) - ) - - const invalidATokenCollateral: InvalidATokenFiatCollateralMock = < - InvalidATokenFiatCollateralMock - >await ATokenCollateralFactory.deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: aToken.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: await aTokenAsset.oracleTimeout(), - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: await aTokenAsset.delayUntilDefault(), - }, - REVENUE_HIDING - ) - - // Perform asset swap - await assetRegistry.connect(owner).swapRegistered(invalidATokenCollateral.address) - - // Setup new basket with the invalid AToken - await basketHandler.connect(owner).setPrimeBasket([aToken.address], [fp('1')]) - - // Switch basket - await basketHandler.connect(owner).refreshBasket() - - const rewardAmountAAVE = bn('0.5e18') - - // AAVE Rewards - await aToken.setRewards(backingManager.address, rewardAmountAAVE) - - // Via Facade get next call - will not attempt to claim - No action taken - const [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(ZERO_ADDRESS) - expect(data).to.equal('0x') - - // Check status - nothing claimed - expect(await aaveToken.balanceOf(backingManager.address)).to.equal(0) - }) - it('Revenues - Should handle minTradeVolume = 0', async () => { // Set minTradeVolume = 0 await rsrTrader.connect(owner).setMinTradeVolume(bn(0)) @@ -769,6 +711,7 @@ describe('FacadeAct contract', () => { await owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), // weird hardhat-ethers bug }) // Check status - rewards claimed for both collaterals diff --git a/test/FacadeRead.test.ts b/test/FacadeRead.test.ts index 0b2ed0c76a..cbf5b2f842 100644 --- a/test/FacadeRead.test.ts +++ b/test/FacadeRead.test.ts @@ -7,7 +7,7 @@ import { bn, fp } from '../common/numbers' import { setOraclePrice } from './utils/oracles' import { Asset, - CTokenMock, + CTokenVaultMock, ERC20Mock, FacadeRead, FacadeTest, @@ -29,6 +29,7 @@ import { } from './fixtures' import { getLatestBlockTimestamp, setNextBlockTimestamp } from './utils/time' import { CollateralStatus, MAX_UINT256 } from '#/common/constants' +import { mintCollaterals } from './utils/tokens' describe('FacadeRead contract', () => { let owner: SignerWithAddress @@ -41,7 +42,7 @@ describe('FacadeRead contract', () => { let token: ERC20Mock let usdc: USDCMock let aToken: StaticATokenMock - let cToken: CTokenMock + let cTokenVault: CTokenVaultMock let rsr: ERC20Mock let basket: Collateral[] @@ -79,7 +80,9 @@ describe('FacadeRead contract', () => { aToken = ( await ethers.getContractAt('StaticATokenMock', await aTokenAsset.erc20()) ) - cToken = await ethers.getContractAt('CTokenMock', await cTokenAsset.erc20()) + cTokenVault = ( + await ethers.getContractAt('CTokenVaultMock', await cTokenAsset.erc20()) + ) }) describe('Views', () => { @@ -93,7 +96,7 @@ describe('FacadeRead contract', () => { expect(erc20s[0]).to.equal(token.address) expect(erc20s[1]).to.equal(usdc.address) expect(erc20s[2]).to.equal(aToken.address) - expect(erc20s[3]).to.equal(cToken.address) + expect(erc20s[3]).to.equal(cTokenVault.address) expect(breakdown[0]).to.be.closeTo(fp('0.25'), 10) expect(breakdown[1]).to.be.closeTo(fp('0.25'), 10) expect(breakdown[2]).to.be.closeTo(fp('0.25'), 10) @@ -107,15 +110,7 @@ describe('FacadeRead contract', () => { beforeEach(async () => { // Mint Tokens initialBal = bn('10000000000e18') - await token.connect(owner).mint(addr1.address, initialBal) - await usdc.connect(owner).mint(addr1.address, initialBal) - await aToken.connect(owner).mint(addr1.address, initialBal) - await cToken.connect(owner).mint(addr1.address, initialBal) - - await token.connect(owner).mint(addr2.address, initialBal) - await usdc.connect(owner).mint(addr2.address, initialBal) - await aToken.connect(owner).mint(addr2.address, initialBal) - await cToken.connect(owner).mint(addr2.address, initialBal) + await mintCollaterals(owner, [addr1, addr2], initialBal, basket) // Issue some RTokens issueAmount = bn('100e18') @@ -124,7 +119,7 @@ describe('FacadeRead contract', () => { await token.connect(addr1).approve(rToken.address, initialBal) await usdc.connect(addr1).approve(rToken.address, initialBal) await aToken.connect(addr1).approve(rToken.address, initialBal) - await cToken.connect(addr1).approve(rToken.address, initialBal) + await cTokenVault.connect(addr1).approve(rToken.address, initialBal) // Issue rTokens await rToken.connect(addr1).issue(issueAmount) @@ -159,7 +154,7 @@ describe('FacadeRead contract', () => { expect(toks[0]).to.equal(token.address) expect(toks[1]).to.equal(usdc.address) expect(toks[2]).to.equal(aToken.address) - expect(toks[3]).to.equal(cToken.address) + expect(toks[3]).to.equal(cTokenVault.address) expect(quantities.length).to.equal(4) expect(quantities[0]).to.equal(issueAmount.div(4)) expect(quantities[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) @@ -183,7 +178,7 @@ describe('FacadeRead contract', () => { expect(toks[0]).to.equal(token.address) expect(toks[1]).to.equal(usdc.address) expect(toks[2]).to.equal(aToken.address) - expect(toks[3]).to.equal(cToken.address) + expect(toks[3]).to.equal(cTokenVault.address) expect(quantities[0]).to.equal(issueAmount.div(4)) expect(quantities[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) expect(quantities[2]).to.equal(issueAmount.div(4)) @@ -365,7 +360,7 @@ describe('FacadeRead contract', () => { if (erc20s[i] == token.address) bal = issueAmount.div(4) if (erc20s[i] == usdc.address) bal = issueAmount.div(4).div(bn('1e12')) if (erc20s[i] == aToken.address) bal = issueAmount.div(4) - if (erc20s[i] == cToken.address) bal = issueAmount.div(4).mul(50).div(bn('1e10')) + if (erc20s[i] == cTokenVault.address) bal = issueAmount.div(4).mul(50).div(bn('1e10')) expect(balances[i]).to.equal(bal) const balNeeded = bal.add(bal.mul(backingBuffer).div(fp('1'))) @@ -427,7 +422,7 @@ describe('FacadeRead contract', () => { expect(erc20s[0]).to.equal(token.address) expect(erc20s[1]).to.equal(usdc.address) expect(erc20s[2]).to.equal(aToken.address) - expect(erc20s[3]).to.equal(cToken.address) + expect(erc20s[3]).to.equal(cTokenVault.address) expect(breakdown[0]).to.equal(fp('0')) // dai expect(breakdown[1]).to.equal(fp('1')) // usdc expect(breakdown[2]).to.equal(fp('0')) // adai @@ -484,7 +479,7 @@ describe('FacadeRead contract', () => { expect(erc20s.length).to.equal(4) expect(targetNames.length).to.equal(4) expect(targetAmts.length).to.equal(4) - const expectedERC20s = [token.address, usdc.address, aToken.address, cToken.address] + const expectedERC20s = [token.address, usdc.address, aToken.address, cTokenVault.address] for (let i = 0; i < 4; i++) { expect(erc20s[i]).to.equal(expectedERC20s[i]) expect(targetNames[i]).to.equal(ethers.utils.formatBytes32String('USD')) @@ -516,7 +511,7 @@ describe('FacadeRead contract', () => { expect(erc20s.length).to.equal(4) expect(targetNames.length).to.equal(4) expect(targetAmts.length).to.equal(4) - const expectedERC20s = [token.address, usdc.address, aToken.address, cToken.address] + const expectedERC20s = [token.address, usdc.address, aToken.address, cTokenVault.address] for (let i = 0; i < 4; i++) { expect(erc20s[i]).to.equal(expectedERC20s[i]) expect(targetNames[i]).to.equal(ethers.utils.formatBytes32String('USD')) diff --git a/test/FacadeWrite.test.ts b/test/FacadeWrite.test.ts index e5f0e5fb05..ab843462f9 100644 --- a/test/FacadeWrite.test.ts +++ b/test/FacadeWrite.test.ts @@ -29,7 +29,6 @@ import snapshotGasCost from './utils/snapshotGasCost' import { Asset, CTokenFiatCollateral, - CTokenMock, ERC20Mock, FacadeRead, FacadeTest, @@ -50,6 +49,7 @@ import { TestIRToken, TimelockController, USDCMock, + CTokenVaultMock, } from '../typechain' import { Collateral, @@ -79,7 +79,7 @@ describe('FacadeWrite contract', () => { // Tokens let token: ERC20Mock let usdc: USDCMock - let cToken: CTokenMock + let cTokenVault: CTokenVaultMock let basket: Collateral[] // Aave / Comp @@ -144,7 +144,9 @@ describe('FacadeWrite contract', () => { token = await ethers.getContractAt('ERC20Mock', await tokenAsset.erc20()) usdc = await ethers.getContractAt('USDCMock', await usdcAsset.erc20()) - cToken = await ethers.getContractAt('CTokenMock', await cTokenAsset.erc20()) + cTokenVault = ( + await ethers.getContractAt('CTokenVaultMock', await cTokenAsset.erc20()) + ) // Deploy DFacadeWriteLib lib const facadeWriteLib = await (await ethers.getContractFactory('FacadeWriteLib')).deploy() @@ -565,7 +567,7 @@ describe('FacadeWrite contract', () => { // Check new state - backing updated expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) tokens = await facade.basketTokens(rToken.address) - expect(tokens).to.eql([token.address, cToken.address]) + expect(tokens).to.eql([token.address, cTokenVault.address]) }) it('Should setup roles correctly', async () => { diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index 5ae81c0b07..4b4b0bf01a 100644 --- a/test/Furnace.test.ts +++ b/test/Furnace.test.ts @@ -6,7 +6,7 @@ import hre, { ethers, upgrades } from 'hardhat' import { IConfig, MAX_RATIO } from '../common/configuration' import { bn, fp } from '../common/numbers' import { - CTokenMock, + CTokenVaultMock, ERC20Mock, StaticATokenMock, TestIFurnace, @@ -26,6 +26,7 @@ import snapshotGasCost from './utils/snapshotGasCost' import { cartesianProduct } from './utils/cases' import { ONE_PERIOD, ZERO_ADDRESS } from '../common/constants' import { useEnv } from '#/utils/env' +import { mintCollaterals } from './utils/tokens' const describeGas = IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip @@ -51,7 +52,7 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { let token0: ERC20Mock let token1: ERC20Mock let token2: StaticATokenMock - let token3: CTokenMock + let token3: CTokenVaultMock let collateral0: Collateral let collateral1: Collateral @@ -84,18 +85,12 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { token2 = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - token3 = await ethers.getContractAt('CTokenMock', await collateral3.erc20()) + token3 = ( + await ethers.getContractAt('CTokenVaultMock', await collateral3.erc20()) + ) // Mint Tokens - await token0.connect(owner).mint(addr1.address, initialBal) - await token1.connect(owner).mint(addr1.address, initialBal) - await token2.connect(owner).mint(addr1.address, initialBal) - await token3.connect(owner).mint(addr1.address, initialBal) - - await token0.connect(owner).mint(addr2.address, initialBal) - await token1.connect(owner).mint(addr2.address, initialBal) - await token2.connect(owner).mint(addr2.address, initialBal) - await token3.connect(owner).mint(addr2.address, initialBal) + await mintCollaterals(owner, [addr1, addr2], initialBal, basket) }) describe('Deployment #fast', () => { diff --git a/test/Main.test.ts b/test/Main.test.ts index ed775a2bfb..7b02d54357 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -32,7 +32,7 @@ import { Asset, ATokenFiatCollateral, CTokenFiatCollateral, - CTokenMock, + CTokenVaultMock, ERC20Mock, FacadeRead, FacadeTest, @@ -70,6 +70,7 @@ import { import snapshotGasCost from './utils/snapshotGasCost' import { advanceTime } from './utils/time' import { useEnv } from '#/utils/env' +import { mintCollaterals } from './utils/tokens' const DEFAULT_THRESHOLD = fp('0.01') // 1% @@ -107,7 +108,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { let token0: ERC20Mock let token1: USDCMock let token2: StaticATokenMock - let token3: CTokenMock + let token3: CTokenVaultMock let collateral0: FiatCollateral let collateral1: FiatCollateral let collateral2: ATokenFiatCollateral @@ -168,7 +169,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { token0 = erc20s[collateral.indexOf(basket[0])] token1 = erc20s[collateral.indexOf(basket[1])] token2 = erc20s[collateral.indexOf(basket[2])] - token3 = erc20s[collateral.indexOf(basket[3])] + token3 = erc20s[collateral.indexOf(basket[3])] collateral0 = basket[0] collateral1 = basket[1] @@ -177,15 +178,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { // Mint initial balances initialBal = bn('1000000e18') - await token0.connect(owner).mint(addr1.address, initialBal) - await token1.connect(owner).mint(addr1.address, initialBal) - await token2.connect(owner).mint(addr1.address, initialBal) - await token3.connect(owner).mint(addr1.address, initialBal) - - await token0.connect(owner).mint(addr2.address, initialBal) - await token1.connect(owner).mint(addr2.address, initialBal) - await token2.connect(owner).mint(addr2.address, initialBal) - await token3.connect(owner).mint(addr2.address, initialBal) + await mintCollaterals(owner, [addr1, addr2], initialBal, basket) }) describe('Deployment #fast', () => { diff --git a/test/RToken.test.ts b/test/RToken.test.ts index bdf43c3d53..8ac81bac71 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -18,7 +18,6 @@ import { bn, fp, shortString, toBNDecimals } from '../common/numbers' import { ATokenFiatCollateral, CTokenFiatCollateral, - CTokenMock, ERC20Mock, ERC1271Mock, FacadeTest, @@ -33,6 +32,7 @@ import { TestIMain, TestIRToken, USDCMock, + CTokenVaultMock, } from '../typechain' import { whileImpersonating } from './utils/impersonation' import snapshotGasCost from './utils/snapshotGasCost' @@ -55,6 +55,7 @@ import { } from './fixtures' import { cartesianProduct } from './utils/cases' import { useEnv } from '#/utils/env' +import { mintCollaterals } from './utils/tokens' const BLOCKS_PER_HOUR = bn(300) @@ -75,7 +76,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { let token0: ERC20Mock let token1: USDCMock let token2: StaticATokenMock - let token3: CTokenMock + let token3: CTokenVaultMock let tokens: ERC20Mock[] let collateral0: Collateral @@ -124,19 +125,14 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { token2 = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - token3 = await ethers.getContractAt('CTokenMock', await collateral3.erc20()) + token3 = ( + await ethers.getContractAt('CTokenVaultMock', await collateral3.erc20()) + ) tokens = [token0, token1, token2, token3] // Mint initial balances initialBal = fp('1e7') // 10x the issuance throttle amount - await Promise.all( - tokens.map((t) => - Promise.all([ - t.connect(owner).mint(addr1.address, initialBal), - t.connect(owner).mint(addr2.address, initialBal), - ]) - ) - ) + await mintCollaterals(owner, [addr1, addr2], initialBal, basket) }) describe('Deployment #fast', () => { @@ -380,7 +376,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.basketsNeeded()).to.equal(0) // Issue under limit, ensure correct number of baskets is set and we do not overflow - await Promise.all(tokens.map((t) => t.mint(addr1.address, MAX_THROTTLE_AMT_RATE))) + await mintCollaterals(owner, [addr1], MAX_THROTTLE_AMT_RATE, basket) await Promise.all( tokens.map((t) => t.connect(addr1).approve(rToken.address, MAX_THROTTLE_AMT_RATE)) ) @@ -1442,7 +1438,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { beforeEach(async () => { // Issue some RTokens - await Promise.all(tokens.map((t) => t.connect(owner).mint(addr1.address, initialBal))) + await mintCollaterals(owner, [addr1], issueAmount, basket) // Approvals await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, initialBal))) @@ -1874,7 +1870,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { describe('monetizeDonations #fast', () => { const donationAmt = fp('100') beforeEach(async () => { - await token3.mint(rToken.address, donationAmt) + await token3.connect(owner).mint(rToken.address, donationAmt) expect(await token3.balanceOf(rToken.address)).to.equal(donationAmt) }) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 081e158845..182545cb9a 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -12,6 +12,7 @@ import { Asset, ATokenFiatCollateral, CTokenMock, + CTokenVaultMock, ERC20Mock, FacadeRead, FacadeTest, @@ -43,6 +44,7 @@ import { expectTrade, getTrade } from './utils/trades' import { withinQuad } from './utils/matchers' import { expectRTokenPrice, setOraclePrice } from './utils/oracles' import { useEnv } from '#/utils/env' +import { mintCollaterals } from './utils/tokens' const DEFAULT_THRESHOLD = fp('0.01') // 1% @@ -70,6 +72,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { let token0: ERC20Mock let token1: USDCMock let token2: StaticATokenMock + let token3Vault: CTokenVaultMock let token3: CTokenMock let backupToken1: ERC20Mock let backupToken2: ERC20Mock @@ -160,7 +163,10 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { token0 = erc20s[collateral.indexOf(basket[0])] token1 = erc20s[collateral.indexOf(basket[1])] token2 = erc20s[collateral.indexOf(basket[2])] - token3 = erc20s[collateral.indexOf(basket[3])] + token3Vault = ( + await ethers.getContractAt('CTokenVaultMock', await basket[3].erc20()) + ) + token3 = await ethers.getContractAt('CTokenMock', await token3Vault.asset()) // Set Aave revenue token await token2.setAaveToken(aaveToken.address) @@ -178,19 +184,13 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Mint initial balances initialBal = bn('1000000e18') - await token0.connect(owner).mint(addr1.address, initialBal) - await token1.connect(owner).mint(addr1.address, initialBal) - await token2.connect(owner).mint(addr1.address, initialBal) - await token3.connect(owner).mint(addr1.address, initialBal) - await backupToken1.connect(owner).mint(addr1.address, initialBal) - await backupToken2.connect(owner).mint(addr1.address, initialBal) + await mintCollaterals(owner, [addr1, addr2], initialBal, basket) - await token0.connect(owner).mint(addr2.address, initialBal) - await token1.connect(owner).mint(addr2.address, initialBal) - await token2.connect(owner).mint(addr2.address, initialBal) - await token3.connect(owner).mint(addr2.address, initialBal) await backupToken1.connect(owner).mint(addr1.address, initialBal) await backupToken2.connect(owner).mint(addr1.address, initialBal) + + await backupToken1.connect(owner).mint(addr2.address, initialBal) + await backupToken2.connect(owner).mint(addr2.address, initialBal) }) describe('Default Handling - Basket Selection', function () { @@ -218,7 +218,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await token0.connect(addr1).approve(rToken.address, initialBal) await token1.connect(addr1).approve(rToken.address, initialBal) await token2.connect(addr1).approve(rToken.address, initialBal) - await token3.connect(addr1).approve(rToken.address, initialBal) + await token3Vault.connect(addr1).approve(rToken.address, initialBal) await backupToken1.connect(addr1).approve(rToken.address, initialBal) await backupToken2.connect(addr1).approve(rToken.address, initialBal) @@ -444,7 +444,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { .connect(owner) .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(2), [ token0.address, - token3.address, + token3Vault.address, ]) // Check initial state @@ -1069,7 +1069,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ) // Nothing occurs if we attempt to settle for a token that is not being traded - await expect(backingManager.settleTrade(token3.address)).to.not.emit + await expect(backingManager.settleTrade(token3Vault.address)).to.not.emit // Advance time till auction ended await advanceTime(config.auctionLength.add(100).toString()) @@ -3118,7 +3118,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await token0.connect(addr1).approve(rToken.address, initialBal) await token1.connect(addr1).approve(rToken.address, initialBal) await token2.connect(addr1).approve(rToken.address, initialBal) - await token3.connect(addr1).approve(rToken.address, initialBal) + await token3Vault.connect(addr1).approve(rToken.address, initialBal) await backupToken1.connect(addr1).approve(rToken.address, initialBal) await backupToken2.connect(addr1).approve(rToken.address, initialBal) @@ -3467,7 +3467,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Running auctions will trigger recollateralization - All balance will be redeemed const sellAmt0: BigNumber = await token0.balanceOf(backingManager.address) const sellAmt2: BigNumber = await token2.balanceOf(backingManager.address) - const sellAmt3: BigNumber = (await token3.balanceOf(backingManager.address)).mul(pow10(10)) // convert to 18 decimals for simplification + const sellAmt3: BigNumber = (await token3Vault.balanceOf(backingManager.address)).mul( + pow10(10) + ) // convert to 18 decimals for simplification const minBuyAmt0 = await toMinBuyAmt(sellAmt0, fp('0.8'), fp('1')) // Run auctions - Will start with token0 @@ -3577,7 +3579,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { name: 'TradeStarted', args: [ anyValue, - token3.address, + token3Vault.address, backupToken1.address, toBNDecimals(sellAmt3, 8), minBuyAmt3, @@ -3591,7 +3593,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Check new auction // Token3 -> Backup Token 1 Auction await expectTrade(backingManager, { - sell: token3.address, + sell: token3Vault.address, buy: backupToken1.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('2'), @@ -3633,7 +3635,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { name: 'TradeSettled', args: [ anyValue, - token3.address, + token3Vault.address, backupToken1.address, toBNDecimals(sellAmt3, 8), minBuyAmt3, @@ -3966,7 +3968,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Advance time till auction ended await advanceTime(config.auctionLength.add(100).toString()) - // Run auctions - will end current and open a new auction for token3 + // Run auctions - will end current and open a new auction for token3Vault // We only need now about 11.8 tokens for Token0 to be fully collateralized // Will check values later in the test to ensure they are in this range await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -3988,14 +3990,14 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Check new auction // Token3 -> Token0 Auction await expectTrade(backingManager, { - sell: token3.address, + sell: token3Vault.address, buy: token0.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('2'), }) // Get Trade - const t3 = await getTrade(backingManager, token3.address) + const t3 = await getTrade(backingManager, token3Vault.address) const sellAmt3 = (await t3.initBal()).mul(pow10(10)) // convert to 18 decimals let minBuyAmt3 = await toMinBuyAmt(sellAmt3, fp('1').div(50), fp('1')) expect(minBuyAmt3).to.be.closeTo(fp('11.28'), fp('0.01')) @@ -4031,7 +4033,13 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { { contract: backingManager, name: 'TradeSettled', - args: [anyValue, token3.address, token0.address, toBNDecimals(sellAmt3, 8), minBuyAmt3], + args: [ + anyValue, + token3Vault.address, + token0.address, + toBNDecimals(sellAmt3, 8), + minBuyAmt3, + ], emitted: true, }, { @@ -4147,7 +4155,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Running auctions will trigger recollateralization - All balance will be redeemed const sellAmt0: BigNumber = await token0.balanceOf(backingManager.address) const sellAmt2: BigNumber = await token2.balanceOf(backingManager.address) - const sellAmt3: BigNumber = (await token3.balanceOf(backingManager.address)).mul(pow10(10)) // convert to 18 decimals for simplification + const sellAmt3: BigNumber = (await token3Vault.balanceOf(backingManager.address)).mul( + pow10(10) + ) // convert to 18 decimals for simplification // Run auctions - will start with token0 and backuptoken1 const minBuyAmt = await toMinBuyAmt(sellAmt0, fp('0.5'), fp('1')) @@ -4264,7 +4274,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { name: 'TradeStarted', args: [ anyValue, - token3.address, + token3Vault.address, backupToken1.address, toBNDecimals(sellAmt3, 8), minBuyAmt3, @@ -4278,7 +4288,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Check new auction // Token3 -> Backup Token 1 Auction await expectTrade(backingManager, { - sell: token3.address, + sell: token3Vault.address, buy: backupToken1.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('2'), @@ -4325,7 +4335,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { name: 'TradeSettled', args: [ anyValue, - token3.address, + token3Vault.address, backupToken1.address, toBNDecimals(sellAmt3, 8), sellAmt3.div(50).div(2), @@ -4664,7 +4674,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await token0.connect(addr1).approve(rToken.address, initialBal) await token1.connect(addr1).approve(rToken.address, initialBal) await token2.connect(addr1).approve(rToken.address, initialBal) - await token3.connect(addr1).approve(rToken.address, initialBal) + await token3Vault.connect(addr1).approve(rToken.address, initialBal) await backupToken1.connect(addr1).approve(rToken.address, initialBal) await backupToken2.connect(addr1).approve(rToken.address, initialBal) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 24156f4b30..580dee5686 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -19,12 +19,11 @@ import { ATokenFiatCollateral, ComptrollerMock, CTokenFiatCollateral, - CTokenMock, + CTokenVaultMock, ERC20Mock, FacadeTest, GnosisMock, IAssetRegistry, - InvalidATokenFiatCollateralMock, MockV3Aggregator, RTokenAsset, StaticATokenMock, @@ -52,17 +51,15 @@ import { ORACLE_ERROR, ORACLE_TIMEOUT, PRICE_TIMEOUT, - REVENUE_HIDING, } from './fixtures' import { expectRTokenPrice, setOraclePrice } from './utils/oracles' import { expectTrade, getTrade } from './utils/trades' import { useEnv } from '#/utils/env' +import { mintCollaterals } from './utils/tokens' const describeGas = IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip -const DEFAULT_THRESHOLD = fp('0.01') // 1% - describe(`Revenues - P${IMPLEMENTATION}`, () => { let owner: SignerWithAddress let addr1: SignerWithAddress @@ -73,9 +70,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { let rsr: ERC20Mock let rsrAsset: Asset let compToken: ERC20Mock - let compAsset: Asset let compoundMock: ComptrollerMock let aaveToken: ERC20Mock + let aaveAsset: Asset // Trading let gnosis: GnosisMock @@ -88,7 +85,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { let token0: ERC20Mock let token1: USDCMock let token2: StaticATokenMock - let token3: CTokenMock + let token3: CTokenVaultMock let collateral0: FiatCollateral let collateral1: FiatCollateral let collateral2: ATokenFiatCollateral @@ -144,7 +141,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { rsr, rsrAsset, compToken, - compAsset, + aaveAsset, aaveToken, compoundMock, erc20s, @@ -182,19 +179,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { token2 = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - token3 = await ethers.getContractAt('CTokenMock', await collateral3.erc20()) + token3 = ( + await ethers.getContractAt('CTokenVaultMock', await collateral3.erc20()) + ) // Mint initial balances initialBal = bn('1000000e18') - await token0.connect(owner).mint(addr1.address, initialBal) - await token1.connect(owner).mint(addr1.address, initialBal) - await token2.connect(owner).mint(addr1.address, initialBal) - await token3.connect(owner).mint(addr1.address, initialBal) - - await token0.connect(owner).mint(addr2.address, initialBal) - await token1.connect(owner).mint(addr2.address, initialBal) - await token2.connect(owner).mint(addr2.address, initialBal) - await token3.connect(owner).mint(addr2.address, initialBal) + await mintCollaterals(owner, [addr1, addr2], initialBal, basket) }) describe('Deployment', () => { @@ -363,7 +354,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { let rewardAmountAAVE: BigNumber beforeEach(async function () { - issueAmount = bn('100e18') + issueAmount = bn('100000e18') // Provide approvals await token0.connect(addr1).approve(rToken.address, initialBal) @@ -418,9 +409,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('should claim a single reward', async () => { const rewardAmt = bn('100e18') - await token2.setRewards(rTokenTrader.address, rewardAmt) - await rTokenTrader.claimRewardsSingle(token2.address) - const balAfter = await aaveToken.balanceOf(rTokenTrader.address) + await token2.setRewards(backingManager.address, rewardAmt) + await backingManager.claimRewardsSingle(token2.address) + const balAfter = await aaveToken.balanceOf(backingManager.address) expect(balAfter).to.equal(rewardAmt) }) @@ -490,13 +481,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', args: [compToken.address, rewardAmountCOMP], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', args: [aaveToken.address, bn(0)], emitted: true, @@ -644,28 +635,27 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { .withArgs(STRSR_DEST, bn(0), bn(1)) // Set COMP tokens as reward -1 qtok - rewardAmountCOMP = bn(1) + rewardAmountAAVE = bn('1') // COMP Rewards - 1 qTok - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + await token2.setRewards(backingManager.address, rewardAmountAAVE) // Collect revenue await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', - args: [compToken.address, rewardAmountCOMP], + args: [compToken.address, bn(0)], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', - args: [aaveToken.address, bn(0)], + args: [aaveToken.address, rewardAmountAAVE], emitted: true, }, ]) - - expect(await compToken.balanceOf(backingManager.address)).to.equal(rewardAmountCOMP) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmountAAVE) // Check status of destinations at this point expect(await rsr.balanceOf(stRSR.address)).to.equal(0) @@ -685,9 +675,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ]) // Check no funds in Market, now in trader - expect(await compToken.balanceOf(gnosis.address)).to.equal(bn(0)) - expect(await compToken.balanceOf(backingManager.address)).to.equal(bn(0)) - expect(await compToken.balanceOf(rsrTrader.address)).to.equal(rewardAmountCOMP) + expect(await aaveToken.balanceOf(gnosis.address)).to.equal(bn(0)) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(bn(0)) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(rewardAmountAAVE) // Check destinations, nothing changed expect(await rsr.balanceOf(stRSR.address)).to.equal(0) @@ -714,34 +704,33 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { .withArgs(STRSR_DEST, bn(0), bn(1)) // Set COMP tokens as reward - rewardAmountCOMP = bn('1000e18') + rewardAmountAAVE = bn('1000e18') // COMP Rewards - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + await token2.setRewards(backingManager.address, rewardAmountAAVE) // Set COMP price to 0 - await setOraclePrice(compAsset.address, bn(0)) + await setOraclePrice(aaveAsset.address, bn(0)) // Refresh asset so lot price also = 0 - await compAsset.refresh() + await aaveAsset.refresh() // Collect revenue await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', - args: [compToken.address, rewardAmountCOMP], + args: [compToken.address, bn(0)], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', - args: [aaveToken.address, bn(0)], + args: [aaveToken.address, rewardAmountAAVE], emitted: true, }, ]) - - expect(await compToken.balanceOf(backingManager.address)).to.equal(rewardAmountCOMP) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmountAAVE) // Check status of destinations at this point expect(await rsr.balanceOf(stRSR.address)).to.equal(0) @@ -761,9 +750,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ]) // Check no funds in Market, now in trader - expect(await compToken.balanceOf(gnosis.address)).to.equal(bn(0)) - expect(await compToken.balanceOf(backingManager.address)).to.equal(bn(0)) - expect(await compToken.balanceOf(rsrTrader.address)).to.equal(rewardAmountCOMP) + expect(await aaveToken.balanceOf(gnosis.address)).to.equal(bn(0)) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(bn(0)) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(rewardAmountAAVE) // Check destinations, nothing changed expect(await rsr.balanceOf(stRSR.address)).to.equal(0) @@ -780,7 +769,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { .to.emit(distributor, 'DistributionSet') .withArgs(FURNACE_DEST, bn(0), bn(0)) - // Avoid dropping 20 qCOMP by making there be exactly 1 distribution share. + // Avoid dropping 20 qAAVE by making there be exactly 1 distribution share. await expect( distributor .connect(owner) @@ -789,49 +778,49 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { .to.emit(distributor, 'DistributionSet') .withArgs(STRSR_DEST, bn(0), bn(1)) - // Set COMP tokens as reward - rewardAmountCOMP = bn('1000e18') + // Set AAVE tokens as reward + rewardAmountAAVE = bn('1000e18') - // COMP Rewards - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + // AAVE Rewards + await token2.setRewards(backingManager.address, rewardAmountAAVE) - // Set new asset for COMP with low maxTradeVolume - const newCOMPAsset: Asset = await AssetFactory.deploy( + // Set new asset for AAVE with low maxTradeVolume + const newAAVEAsset: Asset = await AssetFactory.deploy( PRICE_TIMEOUT, - await compAsset.chainlinkFeed(), + await aaveAsset.chainlinkFeed(), ORACLE_ERROR, - compToken.address, + aaveToken.address, bn(1), // very low ORACLE_TIMEOUT ) // Set a very high price - const compPrice = bn('300e8') - await setOraclePrice(newCOMPAsset.address, compPrice) + const aavePrice = bn('300e8') + await setOraclePrice(newAAVEAsset.address, aavePrice) // Refresh asset - await newCOMPAsset.refresh() + await newAAVEAsset.refresh() // Swap asset - await assetRegistry.connect(owner).swapRegistered(newCOMPAsset.address) + await assetRegistry.connect(owner).swapRegistered(newAAVEAsset.address) // Collect revenue await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', - args: [compToken.address, rewardAmountCOMP], + args: [compToken.address, bn(0)], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', - args: [aaveToken.address, bn(0)], + args: [aaveToken.address, rewardAmountAAVE], emitted: true, }, ]) - expect(await compToken.balanceOf(backingManager.address)).to.equal(rewardAmountCOMP) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmountAAVE) // Check status of destinations at this point expect(await rsr.balanceOf(stRSR.address)).to.equal(0) @@ -845,7 +834,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { emitted: true, args: [ anyValue, - compToken.address, + aaveToken.address, rsr.address, bn(1), // the 1% increase here offsets the 1% decrease that would normally be applied to the sellAmt, but since 1 is the floor, isn't @@ -860,9 +849,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ]) // Check funds now in Market - expect(await compToken.balanceOf(gnosis.address)).to.equal(bn(1)) - expect(await compToken.balanceOf(backingManager.address)).to.equal(bn(0)) - expect(await compToken.balanceOf(rsrTrader.address)).to.equal(rewardAmountCOMP.sub(bn(1))) + expect(await aaveToken.balanceOf(gnosis.address)).to.equal(bn(1)) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(bn(0)) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(rewardAmountAAVE.sub(bn(1))) // Check destinations, nothing still - Auctions need to be completed expect(await rsr.balanceOf(stRSR.address)).to.equal(0) @@ -886,13 +875,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Can also claim through Facade await expectEvents(facadeTest.claimRewards(rToken.address), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -1011,7 +1000,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { PRICE_TIMEOUT, chainlinkFeed.address, ORACLE_ERROR, - compToken.address, + aaveToken.address, fp('1'), ORACLE_TIMEOUT ) @@ -1030,7 +1019,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { .to.emit(distributor, 'DistributionSet') .withArgs(FURNACE_DEST, bn(0), bn(0)) - // Avoid dropping 20 qCOMP by making there be exactly 1 distribution share. + // Avoid dropping 20 qAAVE by making there be exactly 1 distribution share. await expect( distributor .connect(owner) @@ -1039,22 +1028,22 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { .to.emit(distributor, 'DistributionSet') .withArgs(STRSR_DEST, bn(0), bn(1)) - // Set COMP tokens as reward - rewardAmountCOMP = fp('1.9') + // Set AAVE tokens as reward + rewardAmountAAVE = fp('1.9') - // COMP Rewards - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + // AAVE Rewards + await token2.setRewards(backingManager.address, rewardAmountAAVE) await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', - args: [compToken.address, rewardAmountCOMP], + args: [compToken.address, bn(0)], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', - args: [aaveToken.address, bn(0)], + args: [aaveToken.address, rewardAmountAAVE], emitted: true, }, ]) @@ -1063,7 +1052,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rsr.balanceOf(stRSR.address)).to.equal(0) expect(await rToken.balanceOf(furnace.address)).to.equal(0) - // Expected values based on Prices between COMP and RSR = 1 to 1 (for simplification) + // Expected values based on Prices between AAVE and RSR = 1 to 1 (for simplification) const sellAmt: BigNumber = fp('1').mul(100).div(101) // due to oracle error const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) @@ -1072,7 +1061,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { { contract: rsrTrader, name: 'TradeStarted', - args: [anyValue, compToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], + args: [anyValue, aaveToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], emitted: true, }, { @@ -1085,17 +1074,17 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const auctionTimestamp: number = await getLatestBlockTimestamp() // Check auction registered - // COMP -> RSR Auction + // AAVE -> RSR Auction await expectTrade(rsrTrader, { - sell: compToken.address, + sell: aaveToken.address, buy: rsr.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('0'), }) // Check funds in Market and Trader - expect(await compToken.balanceOf(gnosis.address)).to.equal(sellAmt) - expect(await compToken.balanceOf(rsrTrader.address)).to.equal(rewardAmountCOMP.sub(sellAmt)) + expect(await aaveToken.balanceOf(gnosis.address)).to.equal(sellAmt) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(rewardAmountAAVE.sub(sellAmt)) // Another call will not create a new auction (we only allow only one at a time per pair) await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1123,13 +1112,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await advanceTime(config.auctionLength.add(100).toString()) // Run auctions - const remainderSellAmt = rewardAmountCOMP.sub(sellAmt) + const remainderSellAmt = rewardAmountAAVE.sub(sellAmt) const remainderMinBuyAmt = await toMinBuyAmt(remainderSellAmt, fp('1'), fp('1')) await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ { contract: rsrTrader, name: 'TradeSettled', - args: [anyValue, compToken.address, rsr.address, sellAmt, minBuyAmt], + args: [anyValue, aaveToken.address, rsr.address, sellAmt, minBuyAmt], emitted: true, }, { @@ -1137,7 +1126,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { name: 'TradeStarted', args: [ anyValue, - compToken.address, + aaveToken.address, rsr.address, remainderSellAmt, withinQuad(remainderMinBuyAmt), @@ -1152,17 +1141,17 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ]) // Check new auction - // COMP -> RSR Auction + // AAVE -> RSR Auction await expectTrade(rsrTrader, { - sell: compToken.address, + sell: aaveToken.address, buy: rsr.address, endTime: (await getLatestBlockTimestamp()) + Number(config.auctionLength), externalId: bn('1'), }) // Check now all funds in Market - expect(await compToken.balanceOf(gnosis.address)).to.equal(remainderSellAmt) - expect(await compToken.balanceOf(rsrTrader.address)).to.equal(0) + expect(await aaveToken.balanceOf(gnosis.address)).to.equal(remainderSellAmt) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(0) // Perform Mock Bids for RSR (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, remainderMinBuyAmt) @@ -1180,7 +1169,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { { contract: rsrTrader, name: 'TradeSettled', - args: [anyValue, compToken.address, rsr.address, remainderSellAmt, remainderMinBuyAmt], + args: [anyValue, aaveToken.address, rsr.address, remainderSellAmt, remainderMinBuyAmt], emitted: true, }, { @@ -1251,13 +1240,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -1406,7 +1395,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { PRICE_TIMEOUT, chainlinkFeed.address, ORACLE_ERROR, - compToken.address, + aaveToken.address, fp('1'), ORACLE_TIMEOUT ) @@ -1432,32 +1421,32 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { .to.emit(distributor, 'DistributionSet') .withArgs(FURNACE_DEST, bn(1), bn(0)) - // Set COMP tokens as reward + // Set AAVE tokens as reward // Based on current f -> 1.6e18 to RSR and 0.4e18 to Rtoken - rewardAmountCOMP = bn('2e18') + rewardAmountAAVE = bn('2e18') - // COMP Rewards - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + // AAVE Rewards + await token2.setRewards(backingManager.address, rewardAmountAAVE) // Collect revenue - // Expected values based on Prices between COMP and RSR/RToken = 1 to 1 (for simplification) + // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) const sellAmt: BigNumber = fp('1').mul(100).div(101) // due to high price setting trade size const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) - const sellAmtRToken: BigNumber = rewardAmountCOMP.mul(20).div(100) // All Rtokens can be sold - 20% of total comp based on f + const sellAmtRToken: BigNumber = rewardAmountAAVE.mul(20).div(100) // All Rtokens can be sold - 20% of total comp based on f const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', - args: [compToken.address, rewardAmountCOMP], + args: [compToken.address, bn(0)], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', - args: [aaveToken.address, bn(0)], + args: [aaveToken.address, rewardAmountAAVE], emitted: true, }, ]) @@ -1471,7 +1460,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { { contract: rsrTrader, name: 'TradeStarted', - args: [anyValue, compToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], + args: [anyValue, aaveToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], emitted: true, }, { @@ -1479,7 +1468,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { name: 'TradeStarted', args: [ anyValue, - compToken.address, + aaveToken.address, rToken.address, sellAmtRToken, withinQuad(minBuyAmtRToken), @@ -1491,17 +1480,17 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const auctionTimestamp: number = await getLatestBlockTimestamp() // Check auctions registered - // COMP -> RSR Auction + // AAVE -> RSR Auction await expectTrade(rsrTrader, { - sell: compToken.address, + sell: aaveToken.address, buy: rsr.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('0'), }) - // COMP -> RToken Auction + // AAVE -> RToken Auction await expectTrade(rTokenTrader, { - sell: compToken.address, + sell: aaveToken.address, buy: rToken.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('1'), @@ -1526,26 +1515,26 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Close auctions // Calculate pending amount - const sellAmtRemainder: BigNumber = rewardAmountCOMP.sub(sellAmt).sub(sellAmtRToken) + const sellAmtRemainder: BigNumber = rewardAmountAAVE.sub(sellAmt).sub(sellAmtRToken) const minBuyAmtRemainder: BigNumber = await toMinBuyAmt(sellAmtRemainder, fp('1'), fp('1')) // Check funds in Market and Traders - expect(await compToken.balanceOf(gnosis.address)).to.equal(sellAmt.add(sellAmtRToken)) - expect(await compToken.balanceOf(rsrTrader.address)).to.equal(sellAmtRemainder) - expect(await compToken.balanceOf(rTokenTrader.address)).to.equal(0) + expect(await aaveToken.balanceOf(gnosis.address)).to.equal(sellAmt.add(sellAmtRToken)) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(sellAmtRemainder) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) // Run auctions await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ { contract: rsrTrader, name: 'TradeSettled', - args: [anyValue, compToken.address, rsr.address, sellAmt, minBuyAmt], + args: [anyValue, aaveToken.address, rsr.address, sellAmt, minBuyAmt], emitted: true, }, { contract: rTokenTrader, name: 'TradeSettled', - args: [anyValue, compToken.address, rToken.address, sellAmtRToken, minBuyAmtRToken], + args: [anyValue, aaveToken.address, rToken.address, sellAmtRToken, minBuyAmtRToken], emitted: true, }, { @@ -1553,7 +1542,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { name: 'TradeStarted', args: [ anyValue, - compToken.address, + aaveToken.address, rsr.address, sellAmtRemainder, withinQuad(minBuyAmtRemainder), @@ -1589,7 +1578,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { { contract: rsrTrader, name: 'TradeSettled', - args: [anyValue, compToken.address, rsr.address, sellAmtRemainder, minBuyAmtRemainder], + args: [anyValue, aaveToken.address, rsr.address, sellAmtRemainder, minBuyAmtRemainder], emitted: true, }, { @@ -1707,37 +1696,37 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) it('Should not trade dust when claiming rewards', async () => { - // Set COMP tokens as reward - both halves are < dust - rewardAmountCOMP = bn('0.01e18') + // Set AAVE tokens as reward - both halves are < dust + rewardAmountAAVE = bn('0.01e18') - // COMP Rewards - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + // AAVE Rewards + await token2.setRewards(backingManager.address, rewardAmountAAVE) // Collect revenue await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', - args: [compToken.address, rewardAmountCOMP], + args: [compToken.address, bn(0)], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', - args: [aaveToken.address, bn(0)], + args: [aaveToken.address, rewardAmountAAVE], emitted: true, }, ]) - expect(await compToken.balanceOf(backingManager.address)).to.equal(rewardAmountCOMP) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmountAAVE) // Set expected values, based on f = 0.6 - const expectedToTrader = rewardAmountCOMP.mul(60).div(100) - const expectedToFurnace = rewardAmountCOMP.sub(expectedToTrader) + const expectedToTrader = rewardAmountAAVE.mul(60).div(100) + const expectedToFurnace = rewardAmountAAVE.sub(expectedToTrader) // Check status of traders and destinations at this point - expect(await compToken.balanceOf(rsrTrader.address)).to.equal(0) - expect(await compToken.balanceOf(rTokenTrader.address)).to.equal(0) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(0) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) expect(await rsr.balanceOf(stRSR.address)).to.equal(0) expect(await rToken.balanceOf(furnace.address)).to.equal(0) @@ -1756,64 +1745,64 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ]) // Check funds sent to traders - expect(await compToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader) - expect(await compToken.balanceOf(rTokenTrader.address)).to.equal(expectedToFurnace) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(expectedToFurnace) expect(await rsr.balanceOf(stRSR.address)).to.equal(0) expect(await rToken.balanceOf(furnace.address)).to.equal(0) - expect(await compToken.balanceOf(backingManager.address)).to.equal(0) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(0) }) it('Should not trade if price for buy token = 0', async () => { - // Set COMP tokens as reward - rewardAmountCOMP = bn('1e18') + // Set AAVE tokens as reward + rewardAmountAAVE = bn('1e18') - // COMP Rewards - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + // AAVE Rewards + await token2.setRewards(backingManager.address, rewardAmountAAVE) // Collect revenue await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', - args: [compToken.address, rewardAmountCOMP], + args: [compToken.address, bn(0)], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', - args: [aaveToken.address, bn(0)], + args: [aaveToken.address, rewardAmountAAVE], emitted: true, }, ]) - expect(await compToken.balanceOf(backingManager.address)).to.equal(rewardAmountCOMP) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmountAAVE) // Set expected values, based on f = 0.6 - const expectedToTrader = rewardAmountCOMP.mul(60).div(100) - const expectedToFurnace = rewardAmountCOMP.sub(expectedToTrader) + const expectedToTrader = rewardAmountAAVE.mul(60).div(100) + const expectedToFurnace = rewardAmountAAVE.sub(expectedToTrader) // Check status of traders at this point - expect(await compToken.balanceOf(rsrTrader.address)).to.equal(0) - expect(await compToken.balanceOf(rTokenTrader.address)).to.equal(0) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(0) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) - // Handout COMP tokens to Traders - await backingManager.manageTokens([compToken.address]) + // Handout AAVE tokens to Traders + await backingManager.manageTokens([aaveToken.address]) // Check funds sent to traders - expect(await compToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader) - expect(await compToken.balanceOf(rTokenTrader.address)).to.equal(expectedToFurnace) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(expectedToFurnace) // Set RSR price to 0 await setOraclePrice(rsrAsset.address, bn('0')) // Should revert - await expect(rsrTrader.manageToken(compToken.address)).to.be.revertedWith( + await expect(rsrTrader.manageToken(aaveToken.address)).to.be.revertedWith( 'buy asset price unknown' ) // Funds still in Trader - expect(await compToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader) }) it('Should report violation when auction behaves incorrectly', async () => { @@ -1834,13 +1823,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectEvents(facadeTest.claimRewards(rToken.address), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -1963,13 +1952,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Claim rewards await expectEvents(facadeTest.claimRewards(rToken.address), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -2006,41 +1995,41 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) it('Should not distribute other tokens beyond RSR/RToken', async () => { - // Set COMP tokens as reward - rewardAmountCOMP = bn('1e18') + // Set AAVE tokens as reward + rewardAmountAAVE = bn('1e18') - // COMP Rewards - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + // AAVE Rewards + await token2.setRewards(backingManager.address, rewardAmountAAVE) // Collect revenue await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', - args: [compToken.address, rewardAmountCOMP], + args: [compToken.address, bn(0)], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', - args: [aaveToken.address, bn(0)], + args: [aaveToken.address, rewardAmountAAVE], emitted: true, }, ]) // Check funds in Backing Manager and destinations - expect(await compToken.balanceOf(backingManager.address)).to.equal(rewardAmountCOMP) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmountAAVE) expect(await rsr.balanceOf(stRSR.address)).to.equal(0) expect(await rToken.balanceOf(furnace.address)).to.equal(0) - // Attempt to distribute COMP token + // Attempt to distribute AAVE token await whileImpersonating(basketHandler.address, async (signer) => { await expect( - distributor.connect(signer).distribute(compToken.address, rewardAmountCOMP) + distributor.connect(signer).distribute(aaveToken.address, rewardAmountAAVE) ).to.be.revertedWith('RSR or RToken') }) // Check nothing changed - expect(await compToken.balanceOf(backingManager.address)).to.equal(rewardAmountCOMP) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmountAAVE) expect(await rsr.balanceOf(stRSR.address)).to.equal(0) expect(await rToken.balanceOf(furnace.address)).to.equal(0) }) @@ -2055,31 +2044,31 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { .to.emit(distributor, 'DistributionSet') .withArgs(other.address, bn(40), bn(60)) - // Set COMP tokens as reward - rewardAmountCOMP = bn('1e18') + // Set AAVE tokens as reward + rewardAmountAAVE = bn('1e18') - // COMP Rewards - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + // AAVE Rewards + await token2.setRewards(backingManager.address, rewardAmountAAVE) // Collect revenue - // Expected values based on Prices between COMP and RSR/RToken = 1 to 1 (for simplification) - const sellAmt: BigNumber = rewardAmountCOMP.mul(60).div(100) // due to f = 60% + // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) + const sellAmt: BigNumber = rewardAmountAAVE.mul(60).div(100) // due to f = 60% const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) - const sellAmtRToken: BigNumber = rewardAmountCOMP.sub(sellAmt) // Remainder + const sellAmtRToken: BigNumber = rewardAmountAAVE.sub(sellAmt) // Remainder const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', - args: [compToken.address, rewardAmountCOMP], + args: [compToken.address, bn(0)], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', - args: [aaveToken.address, bn(0)], + args: [aaveToken.address, rewardAmountAAVE], emitted: true, }, ]) @@ -2095,7 +2084,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { { contract: rsrTrader, name: 'TradeStarted', - args: [anyValue, compToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], + args: [anyValue, aaveToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], emitted: true, }, { @@ -2103,7 +2092,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { name: 'TradeStarted', args: [ anyValue, - compToken.address, + aaveToken.address, rToken.address, sellAmtRToken, withinQuad(minBuyAmtRToken), @@ -2115,24 +2104,24 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const auctionTimestamp: number = await getLatestBlockTimestamp() // Check auctions registered - // COMP -> RSR Auction + // AAVE -> RSR Auction await expectTrade(rsrTrader, { - sell: compToken.address, + sell: aaveToken.address, buy: rsr.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('0'), }) - // COMP -> RToken Auction + // AAVE -> RToken Auction await expectTrade(rTokenTrader, { - sell: compToken.address, + sell: aaveToken.address, buy: rToken.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('1'), }) // Check funds in Market - expect(await compToken.balanceOf(gnosis.address)).to.equal(rewardAmountCOMP) + expect(await aaveToken.balanceOf(gnosis.address)).to.equal(rewardAmountAAVE) // Advance time till auctions ended await advanceTime(config.auctionLength.add(100).toString()) @@ -2156,13 +2145,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { { contract: rsrTrader, name: 'TradeSettled', - args: [anyValue, compToken.address, rsr.address, sellAmt, minBuyAmt], + args: [anyValue, aaveToken.address, rsr.address, sellAmt, minBuyAmt], emitted: true, }, { contract: rTokenTrader, name: 'TradeSettled', - args: [anyValue, compToken.address, rToken.address, sellAmtRToken, minBuyAmtRToken], + args: [anyValue, aaveToken.address, rToken.address, sellAmtRToken, minBuyAmtRToken], emitted: true, }, { @@ -2212,13 +2201,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Collect revenue await expectEvents(rsrTrader.claimRewards(), [ { - contract: rsrTrader, + contract: token3, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: rsrTrader, + contract: token2, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, @@ -2253,19 +2242,19 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Claim and sweep rewards await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: token3, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, }, { - contract: backingManager, + contract: token2, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE], emitted: true, }, { - contract: backingManager, + contract: newToken, name: 'RewardsClaimed', args: [aaveToken.address, rewardAmountAAVE.add(1)], emitted: true, @@ -2277,53 +2266,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { rewardAmountAAVE.mul(2).add(1) ) }) - - it('Should revert on invalid claim logic', async () => { - // Setup a new aToken with invalid claim data - const ATokenCollateralFactory = await ethers.getContractFactory( - 'InvalidATokenFiatCollateralMock' - ) - const chainlinkFeed = ( - await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) - ) - - const invalidATokenCollateral: InvalidATokenFiatCollateralMock = < - InvalidATokenFiatCollateralMock - >((await ATokenCollateralFactory.deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: token2.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: await collateral2.delayUntilDefault(), - }, - REVENUE_HIDING - )) as unknown) - - // Perform asset swap - await assetRegistry.connect(owner).swapRegistered(invalidATokenCollateral.address) - - // Setup new basket with the invalid AToken - await basketHandler.connect(owner).setPrimeBasket([token2.address], [fp('1')]) - - // Switch basket - await basketHandler.connect(owner).refreshBasket() - - rewardAmountAAVE = bn('0.5e18') - - // AAVE Rewards - await token2.setRewards(backingManager.address, rewardAmountAAVE) - - // Claim and sweep rewards - should revert and bubble up msg - await expect(backingManager.claimRewards()).to.be.revertedWith('claimRewards() error') - - // Check status - nothing claimed - expect(await aaveToken.balanceOf(backingManager.address)).to.equal(0) - }) }) context('With simple basket of ATokens and CTokens', function () { @@ -3178,7 +3120,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { let rewardAmountAAVE: BigNumber beforeEach(async function () { - issueAmount = bn('100e18') + issueAmount = bn('100000e18') // Provide approvals await token0.connect(addr1).approve(rToken.address, initialBal) @@ -3207,7 +3149,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { rewardAmountAAVE = bn('0.6e18') // COMP Rewards - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + await compoundMock.setRewards(token3.address, rewardAmountCOMP) await compoundMock.setRewards(rsrTrader.address, rewardAmountCOMP) await compoundMock.setRewards(rTokenTrader.address, rewardAmountCOMP) await compoundMock.setRewards(rToken.address, rewardAmountCOMP) @@ -3257,7 +3199,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { rewardAmountCOMP = bn('2e18') // COMP Rewards - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + await compoundMock.setRewards(token3.address, rewardAmountCOMP) // Collect revenue // Expected values based on Prices between COMP and RSR/RToken = 1 to 1 (for simplification) diff --git a/test/ZTradingExtremes.test.ts b/test/ZTradingExtremes.test.ts index 2eff82ffa9..0f89cef3c1 100644 --- a/test/ZTradingExtremes.test.ts +++ b/test/ZTradingExtremes.test.ts @@ -11,7 +11,7 @@ import { ATokenFiatCollateral, ComptrollerMock, CTokenFiatCollateral, - CTokenMock, + CTokenVaultMock, ERC20Mock, FacadeTest, FiatCollateral, @@ -76,7 +76,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, let ERC20Mock: ContractFactory let ATokenMockFactory: ContractFactory - let CTokenMockFactory: ContractFactory + let CTokenVaultMockFactory: ContractFactory let ATokenCollateralFactory: ContractFactory let CTokenCollateralFactory: ContractFactory @@ -110,7 +110,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, ERC20Mock = await ethers.getContractFactory('ERC20Mock') ATokenMockFactory = await ethers.getContractFactory('StaticATokenMock') - CTokenMockFactory = await ethers.getContractFactory('CTokenMock') + CTokenVaultMockFactory = await ethers.getContractFactory('CTokenVaultMock') ATokenCollateralFactory = await ethers.getContractFactory('ATokenFiatCollateral') CTokenCollateralFactory = await ethers.getContractFactory('CTokenFiatCollateral') @@ -157,15 +157,17 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, return erc20 } - const prepCToken = async (index: number): Promise => { + const prepCToken = async (index: number): Promise => { const underlying: ERC20Mock = ( await ERC20Mock.deploy(`ERC20_NAME:${index}`, `ERC20_SYM:${index}`) ) - const erc20: CTokenMock = ( - await CTokenMockFactory.deploy( + const erc20: CTokenVaultMock = ( + await CTokenVaultMockFactory.deploy( `CToken_NAME:${index}`, `CToken_SYM:${index}`, - underlying.address + underlying.address, + compToken.address, + compoundMock.address ) ) await erc20.setExchangeRate(fp('1')) @@ -185,8 +187,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) await assetRegistry.connect(owner).register(collateral.address) return erc20 @@ -500,9 +501,6 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, } } - // Claim rewards - await expect(backingManager.claimRewards()).to.emit(backingManager, 'RewardsClaimed') - // Do auctions await runRevenueAuctionsUntilCompletion() } diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 0273cfd111..f725decf7c 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -9,7 +9,6 @@ import { setOraclePrice } from './utils/oracles' import { bn, fp, near, shortString } from '../common/numbers' import { expectEvents } from '../common/events' import { - CTokenMock, ERC20Mock, ERC1271Mock, FacadeRead, @@ -21,6 +20,7 @@ import { TestIMain, TestIRToken, TestIStRSR, + CTokenVaultMock, } from '../typechain' import { IConfig, MAX_RATIO, MAX_UNSTAKING_DELAY } from '../common/configuration' import { CollateralStatus, MAX_UINT256, ONE_PERIOD, ZERO_ADDRESS } from '../common/constants' @@ -77,7 +77,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { let token0: ERC20Mock let token1: ERC20Mock let token2: StaticATokenMock - let token3: CTokenMock + let token3: CTokenVaultMock let collateral0: Collateral let collateral1: Collateral let collateral2: Collateral @@ -175,7 +175,9 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { token2 = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - token3 = await ethers.getContractAt('CTokenMock', await collateral3.erc20()) + token3 = ( + await ethers.getContractAt('CTokenVaultMock', await collateral3.erc20()) + ) }) describe('Deployment #fast', () => { diff --git a/test/fixtures.ts b/test/fixtures.ts index bdfbc2d132..ac40a9a4cc 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -16,7 +16,7 @@ import { ComptrollerMock, CTokenFiatCollateral, CTokenSelfReferentialCollateral, - CTokenMock, + CTokenVaultMock, ERC20Mock, DeployerP0, DeployerP1, @@ -156,6 +156,7 @@ interface CollateralFixture { } async function collateralFixture( + compToken: ERC20Mock, comptroller: ComptrollerMock, aaveToken: ERC20Mock, config: IConfig @@ -163,7 +164,7 @@ async function collateralFixture( const ERC20: ContractFactory = await ethers.getContractFactory('ERC20Mock') const USDC: ContractFactory = await ethers.getContractFactory('USDCMock') const ATokenMockFactory: ContractFactory = await ethers.getContractFactory('StaticATokenMock') - const CTokenMockFactory: ContractFactory = await ethers.getContractFactory('CTokenMock') + const CTokenVaultMockFactory: ContractFactory = await ethers.getContractFactory('CTokenVaultMock') const FiatCollateralFactory: ContractFactory = await ethers.getContractFactory('FiatCollateral') const ATokenCollateralFactory = await ethers.getContractFactory('ATokenFiatCollateral') const CTokenCollateralFactory = await ethers.getContractFactory('CTokenFiatCollateral') @@ -236,10 +237,17 @@ async function collateralFixture( const makeCTokenCollateral = async ( symbol: string, referenceERC20: ERC20Mock, - chainlinkAddr: string - ): Promise<[CTokenMock, CTokenFiatCollateral]> => { - const erc20: CTokenMock = ( - await CTokenMockFactory.deploy(symbol + ' Token', symbol, referenceERC20.address) + chainlinkAddr: string, + compToken: ERC20Mock + ): Promise<[CTokenVaultMock, CTokenFiatCollateral]> => { + const erc20: CTokenVaultMock = ( + await CTokenVaultMockFactory.deploy( + symbol + ' Token', + symbol, + referenceERC20.address, + compToken.address, + comptroller.address + ) ) const coll = await CTokenCollateralFactory.deploy( { @@ -253,8 +261,7 @@ async function collateralFixture( defaultThreshold: defaultThreshold, delayUntilDefault: delayUntilDefault, }, - REVENUE_HIDING, - comptroller.address + REVENUE_HIDING ) await coll.refresh() return [erc20, coll] @@ -293,9 +300,19 @@ async function collateralFixture( const usdc = await makeSixDecimalCollateral('USDC') const usdt = await makeVanillaCollateral('USDT') const busd = await makeVanillaCollateral('BUSD') - const cdai = await makeCTokenCollateral('cDAI', dai[0], await dai[1].chainlinkFeed()) - const cusdc = await makeCTokenCollateral('cUSDC', usdc[0], await usdc[1].chainlinkFeed()) - const cusdt = await makeCTokenCollateral('cUSDT', usdt[0], await usdt[1].chainlinkFeed()) + const cdai = await makeCTokenCollateral('cDAI', dai[0], await dai[1].chainlinkFeed(), compToken) + const cusdc = await makeCTokenCollateral( + 'cUSDC', + usdc[0], + await usdc[1].chainlinkFeed(), + compToken + ) + const cusdt = await makeCTokenCollateral( + 'cUSDT', + usdt[0], + await usdt[1].chainlinkFeed(), + compToken + ) const adai = await makeATokenCollateral('aDAI', dai[0], await dai[1].chainlinkFeed(), aaveToken) const ausdc = await makeATokenCollateral( 'aUSDC', @@ -625,6 +642,7 @@ export const defaultFixture: Fixture = async function (): Promis // Deploy collateral for Main const { erc20s, collateral, basket, basketsNeededAmts } = await collateralFixture( + compToken, compoundMock, aaveToken, config diff --git a/test/integration/AssetPlugins.test.ts b/test/integration/AssetPlugins.test.ts index 9ccbaff904..71e1f99f1e 100644 --- a/test/integration/AssetPlugins.test.ts +++ b/test/integration/AssetPlugins.test.ts @@ -24,7 +24,6 @@ import forkBlockNumber from './fork-block-numbers' import { Asset, ATokenFiatCollateral, - ComptrollerMock, CTokenFiatCollateral, CTokenMock, CTokenNonFiatCollateral, @@ -47,6 +46,7 @@ import { TestIRToken, USDCMock, WETH9, + CTokenVault, } from '../../typechain' import { useEnv } from '#/utils/env' @@ -87,7 +87,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, let compToken: ERC20Mock let compAsset: Asset - let compoundMock: ComptrollerMock let aaveToken: ERC20Mock let aaveAsset: Asset @@ -111,14 +110,20 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, let stataUsdp: StaticATokenLM let cDai: CTokenMock + let cDaiVault: CTokenVault let cUsdc: CTokenMock + let cUsdcVault: CTokenVault let cUsdt: CTokenMock + let cUsdtVault: CTokenVault let cUsdp: CTokenMock + let cUsdpVault: CTokenVault let wbtc: ERC20Mock let cWBTC: CTokenMock + let cWBTCVault: CTokenVault let weth: ERC20Mock let cETH: CTokenMock + let cETHVault: CTokenVault let eurt: ERC20Mock let daiCollateral: FiatCollateral @@ -202,7 +207,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, aaveToken, compAsset, aaveAsset, - compoundMock, erc20s, collateral, basket, @@ -224,19 +228,25 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, busd = erc20s[3] // BUSD usdp = erc20s[4] // USDP tusd = erc20s[5] // TUSD - cDai = erc20s[6] // cDAI - cUsdc = erc20s[7] // cUSDC - cUsdt = erc20s[8] // cUSDT - cUsdp = erc20s[9] // cUSDT + cDaiVault = erc20s[6] // cDAI + cDai = await ethers.getContractAt('CTokenMock', await cDaiVault.asset()) // cDAI + cUsdcVault = erc20s[7] // cUSDC + cUsdc = await ethers.getContractAt('CTokenMock', await cUsdcVault.asset()) // cUSDC + cUsdtVault = erc20s[8] // cUSDT + cUsdt = await ethers.getContractAt('CTokenMock', await cUsdtVault.asset()) // cUSDT + cUsdpVault = erc20s[9] // cUSDT + cUsdp = await ethers.getContractAt('CTokenMock', await cUsdpVault.asset()) // cUSDT stataDai = erc20s[10] // static aDAI stataUsdc = erc20s[11] // static aUSDC stataUsdt = erc20s[12] // static aUSDT stataBusd = erc20s[13] // static aBUSD stataUsdp = erc20s[14] // static aUSDP wbtc = erc20s[15] // wBTC - cWBTC = erc20s[16] // cWBTC + cWBTCVault = erc20s[16] // cWBTC + cWBTC = await ethers.getContractAt('CTokenMock', await cWBTCVault.asset()) // cWBTC weth = erc20s[17] // wETH - cETH = erc20s[18] // cETH + cETHVault = erc20s[18] // cETH + cETH = await ethers.getContractAt('CTokenMock', await cETHVault.asset()) // cETH eurt = erc20s[19] // eurt // Get plain aTokens @@ -288,7 +298,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, stataDai = ( await ethers.getContractAt('StaticATokenLM', await aDaiCollateral.erc20()) ) - cDai = await ethers.getContractAt('CTokenMock', await cDaiCollateral.erc20()) // Get plain aToken aDai = ( @@ -311,6 +320,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // cDAI await whileImpersonating(holderCDAI, async (cdaiSigner) => { await cDai.connect(cdaiSigner).transfer(addr1.address, toBNDecimals(initialBal, 8).mul(100)) + await cDai.connect(addr1).approve(cDaiVault.address, toBNDecimals(initialBal, 8).mul(100)) + await cDaiVault.connect(addr1).mint(toBNDecimals(initialBal, 17).mul(100), addr1.address) }) // Setup balances for USDT @@ -330,6 +341,12 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await cWBTC .connect(cwbtcSigner) .transfer(addr1.address, toBNDecimals(initialBalBtcEth, 8).mul(1000)) + await cWBTC + .connect(addr1) + .approve(cWBTCVault.address, toBNDecimals(initialBalBtcEth, 8).mul(1000)) + await cWBTCVault + .connect(addr1) + .mint(toBNDecimals(initialBalBtcEth, 17).mul(1000), addr1.address) }) // WETH @@ -342,6 +359,12 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await cETH .connect(cethSigner) .transfer(addr1.address, toBNDecimals(initialBalBtcEth, 8).mul(1000)) + await cETH + .connect(addr1) + .approve(cETHVault.address, toBNDecimals(initialBalBtcEth, 8).mul(1000)) + await cETHVault + .connect(addr1) + .mint(toBNDecimals(initialBalBtcEth, 17).mul(1000), addr1.address) }) //EURT @@ -471,6 +494,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: ERC20Mock tokenAddress: string cToken: CTokenMock + cTokenVault: CTokenVault cTokenAddress: string cTokenCollateral: CTokenFiatCollateral pegPrice: BigNumber @@ -483,6 +507,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: dai, tokenAddress: networkConfig[chainId].tokens.DAI || '', cToken: cDai, + cTokenVault: cDaiVault, cTokenAddress: networkConfig[chainId].tokens.cDAI || '', cTokenCollateral: cDaiCollateral, pegPrice: fp('1'), @@ -492,6 +517,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: usdc, tokenAddress: networkConfig[chainId].tokens.USDC || '', cToken: cUsdc, + cTokenVault: cUsdcVault, cTokenAddress: networkConfig[chainId].tokens.cUSDC || '', cTokenCollateral: cUsdcCollateral, pegPrice: fp('1.0003994'), @@ -501,6 +527,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: usdt, tokenAddress: networkConfig[chainId].tokens.USDT || '', cToken: cUsdt, + cTokenVault: cUsdtVault, cTokenAddress: networkConfig[chainId].tokens.cUSDT || '', cTokenCollateral: cUsdtCollateral, pegPrice: fp('0.99934692'), @@ -510,6 +537,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: usdp, tokenAddress: networkConfig[chainId].tokens.USDP || '', cToken: cUsdp, + cTokenVault: cUsdpVault, cTokenAddress: networkConfig[chainId].tokens.cUSDP || '', cTokenCollateral: cUsdpCollateral, pegPrice: fp('0.99995491'), @@ -524,9 +552,10 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await ctkInf.cTokenCollateral.referenceERC20Decimals()).to.equal( await ctkInf.token.decimals() ) - expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cToken.address) - expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenAddress) + expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) + expect(await ctkInf.cTokenVault.asset()).to.equal(ctkInf.cTokenAddress) expect(await ctkInf.cToken.decimals()).to.equal(8) + expect(await ctkInf.cTokenVault.decimals()).to.equal(17) expect(await ctkInf.cTokenCollateral.targetName()).to.equal( ethers.utils.formatBytes32String('USD') ) @@ -547,8 +576,13 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, bn('1e4') ) + // TODO: deprecate await expect(ctkInf.cTokenCollateral.claimRewards()) - .to.emit(ctkInf.cTokenCollateral, 'RewardsClaimed') + .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') + .withArgs(compToken.address, 0) + + await expect(ctkInf.cTokenVault.claimRewards()) + .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') .withArgs(compToken.address, 0) expect(await ctkInf.cTokenCollateral.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) @@ -647,8 +681,13 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, bn('1e5') ) + // TODO: deprecate await expect(atkInf.aTokenCollateral.claimRewards()) - .to.emit(atkInf.aTokenCollateral, 'RewardsClaimed') + .to.emit(atkInf.stataToken, 'RewardsClaimed') + .withArgs(aaveToken.address, 0) + + await expect(atkInf.stataToken['claimRewards()']()) + .to.emit(atkInf.stataToken, 'RewardsClaimed') .withArgs(aaveToken.address, 0) // Check StaticAToken @@ -740,6 +779,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: ERC20Mock tokenAddress: string cToken: CTokenMock + cTokenVault: CTokenVault cTokenAddress: string cTokenCollateral: CTokenNonFiatCollateral targetPrice: BigNumber @@ -754,6 +794,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: wbtc, tokenAddress: networkConfig[chainId].tokens.WBTC || '', cToken: cWBTC, + cTokenVault: cWBTCVault, cTokenAddress: networkConfig[chainId].tokens.cWBTC || '', cTokenCollateral: cWBTCCollateral, targetPrice: fp('31311.5'), // approx price June 6, 2022 @@ -770,9 +811,10 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await ctkInf.cTokenCollateral.referenceERC20Decimals()).to.equal( await ctkInf.token.decimals() ) - expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cToken.address) - expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenAddress) + expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) + expect(await ctkInf.cTokenVault.asset()).to.equal(ctkInf.cTokenAddress) expect(await ctkInf.cToken.decimals()).to.equal(8) + expect(await ctkInf.cTokenVault.decimals()).to.equal(17) expect(await ctkInf.cTokenCollateral.targetName()).to.equal( ethers.utils.formatBytes32String(ctkInf.targetName) ) @@ -797,8 +839,13 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, true ) + // TODO: deprecate await expect(ctkInf.cTokenCollateral.claimRewards()) - .to.emit(ctkInf.cTokenCollateral, 'RewardsClaimed') + .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') + .withArgs(compToken.address, 0) + + await expect(ctkInf.cTokenVault.claimRewards()) + .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') .withArgs(compToken.address, 0) expect(await ctkInf.cTokenCollateral.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) @@ -861,6 +908,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: ERC20Mock tokenAddress: string cToken: CTokenMock + cTokenVault: CTokenVault cTokenAddress: string cTokenCollateral: CTokenSelfReferentialCollateral price: BigNumber @@ -874,6 +922,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: weth, tokenAddress: networkConfig[chainId].tokens.WETH || '', cToken: cETH, + cTokenVault: cETHVault, cTokenAddress: networkConfig[chainId].tokens.cETH || '', cTokenCollateral: cETHCollateral, price: fp('1859.17'), // approx price June 6, 2022 @@ -889,9 +938,10 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await ctkInf.cTokenCollateral.referenceERC20Decimals()).to.equal( await ctkInf.token.decimals() ) - expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cToken.address) - expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenAddress) + expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) + expect(await ctkInf.cTokenVault.asset()).to.equal(ctkInf.cTokenAddress) expect(await ctkInf.cToken.decimals()).to.equal(8) + expect(await ctkInf.cTokenVault.decimals()).to.equal(17) expect(await ctkInf.cTokenCollateral.targetName()).to.equal( ethers.utils.formatBytes32String(ctkInf.targetName) ) @@ -913,8 +963,13 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, bn('1e5') ) + // TODO: deprecate await expect(ctkInf.cTokenCollateral.claimRewards()) - .to.emit(ctkInf.cTokenCollateral, 'RewardsClaimed') + .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') + .withArgs(compToken.address, 0) + + await expect(ctkInf.cTokenVault.claimRewards()) + .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') .withArgs(compToken.address, 0) expect(await ctkInf.cTokenCollateral.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) @@ -1127,15 +1182,14 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: NO_PRICE_DATA_FEED, oracleError: ORACLE_ERROR, - erc20: cDai.address, + erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, }, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) // CTokens - Collateral with no price info should revert await expect(nonpriceCtokenCollateral.price()).to.be.reverted @@ -1152,15 +1206,14 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: mockChainlinkFeed.address, oracleError: ORACLE_ERROR, - erc20: cDai.address, + erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, }, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) await zeropriceCtokenCollateral.refresh() @@ -1346,7 +1399,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: NO_PRICE_DATA_FEED, oracleError: ORACLE_ERROR, - erc20: cWBTC.address, + erc20: cWBTCVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1355,8 +1408,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, }, NO_PRICE_DATA_FEED, MAX_ORACLE_TIMEOUT, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) ) @@ -1376,7 +1428,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: mockChainlinkFeed.address, oracleError: ORACLE_ERROR, - erc20: cWBTC.address, + erc20: cWBTCVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1385,8 +1437,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, }, mockChainlinkFeed.address, MAX_ORACLE_TIMEOUT, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) ) await zeropriceCtokenNonFiatCollateral.refresh() @@ -1488,7 +1539,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: NO_PRICE_DATA_FEED, oracleError: ORACLE_ERROR, - erc20: cETH.address, + erc20: cETHVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), @@ -1496,8 +1547,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, delayUntilDefault, }, REVENUE_HIDING, - await weth.decimals(), - compoundMock.address + await weth.decimals() ) // CTokens - Collateral with no price info should revert @@ -1519,7 +1569,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, priceTimeout: PRICE_TIMEOUT, chainlinkFeed: mockChainlinkFeed.address, oracleError: ORACLE_ERROR, - erc20: cETH.address, + erc20: cETHVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: MAX_ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), @@ -1527,8 +1577,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, delayUntilDefault, }, REVENUE_HIDING, - await weth.decimals(), - compoundMock.address + await weth.decimals() ) await zeroPriceCtokenSelfReferentialCollateral.refresh() @@ -1653,7 +1702,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, const backing = await facade.basketTokens(rToken.address) expect(backing[0]).to.equal(dai.address) expect(backing[1]).to.equal(stataDai.address) - expect(backing[2]).to.equal(cDai.address) + expect(backing[2]).to.equal(cDaiVault.address) expect(backing.length).to.equal(3) @@ -1667,7 +1716,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, const issueAmount: BigNumber = bn('10000e18') await dai.connect(addr1).approve(rToken.address, issueAmount) await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') await expectRTokenPrice( @@ -1686,18 +1735,20 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check balances before expect(await dai.balanceOf(backingManager.address)).to.equal(0) expect(await stataDai.balanceOf(backingManager.address)).to.equal(0) - expect(await cDai.balanceOf(backingManager.address)).to.equal(0) + expect(await cDaiVault.balanceOf(backingManager.address)).to.equal(0) expect(await dai.balanceOf(addr1.address)).to.equal(initialBal) // Balance for Static a Token is about 18641.55e18, about 93.21% of the provided amount (20K) const initialBalAToken = initialBal.mul(9321).div(10000) expect(await stataDai.balanceOf(addr1.address)).to.be.closeTo(initialBalAToken, fp('1.5')) - expect(await cDai.balanceOf(addr1.address)).to.equal(toBNDecimals(initialBal, 8).mul(100)) + expect(await cDaiVault.balanceOf(addr1.address)).to.equal( + toBNDecimals(initialBal, 17).mul(100) + ) // Provide approvals await dai.connect(addr1).approve(rToken.address, issueAmount) await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) // Check rToken balance expect(await rToken.balanceOf(addr1.address)).to.equal(0) @@ -1714,8 +1765,11 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, issueAmtAToken, fp('1') ) - const requiredCTokens: BigNumber = bn('227116e8') // approx 227K needed (~5K, 50% of basket) - Price: ~0.022 - expect(await cDai.balanceOf(backingManager.address)).to.be.closeTo(requiredCTokens, bn(1e8)) + const requiredCTokens: BigNumber = bn('227116e17') // approx 227K needed (~5K, 50% of basket) - Price: ~0.022 + expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo( + requiredCTokens, + bn('1e17') + ) // Balances for user expect(await dai.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(4))) @@ -1723,9 +1777,9 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, initialBalAToken.sub(issueAmtAToken), fp('1.5') ) - expect(await cDai.balanceOf(addr1.address)).to.be.closeTo( - toBNDecimals(initialBal, 8).mul(100).sub(requiredCTokens), - bn(1e8) + expect(await cDaiVault.balanceOf(addr1.address)).to.be.closeTo( + toBNDecimals(initialBal, 17).mul(100).sub(requiredCTokens), + bn('1e17') ) // Check RTokens issued to user expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) @@ -1751,14 +1805,14 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check balances after - Backing Manager is empty expect(await dai.balanceOf(backingManager.address)).to.equal(0) expect(await stataDai.balanceOf(backingManager.address)).to.be.closeTo(bn(0), fp('0.01')) - expect(await cDai.balanceOf(backingManager.address)).to.be.closeTo(bn(0), bn('1e6')) + expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo(bn(0), bn('1e15')) // Check funds returned to user expect(await dai.balanceOf(addr1.address)).to.equal(initialBal) expect(await stataDai.balanceOf(addr1.address)).to.be.closeTo(initialBalAToken, fp('1.5')) - expect(await cDai.balanceOf(addr1.address)).to.be.closeTo( - toBNDecimals(initialBal, 8).mul(100), - bn('1e7') + expect(await cDaiVault.balanceOf(addr1.address)).to.be.closeTo( + toBNDecimals(initialBal, 17).mul(100), + bn('1e16') ) // Check asset value left @@ -1775,7 +1829,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Provide approvals for issuances await dai.connect(addr1).approve(rToken.address, issueAmount) await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) // Issue rTokens await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') @@ -1786,7 +1840,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Store Balances after issuance const balanceAddr1Dai: BigNumber = await dai.balanceOf(addr1.address) const balanceAddr1aDai: BigNumber = await stataDai.balanceOf(addr1.address) - const balanceAddr1cDai: BigNumber = await cDai.balanceOf(addr1.address) + const balanceAddr1cDai: BigNumber = await cDaiVault.balanceOf(addr1.address) // Check rates and prices const [aDaiPriceLow1, aDaiPriceHigh1] = await aDaiCollateral.price() // ~1.07546 @@ -1921,12 +1975,12 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check balances - Fewer ATokens and cTokens should have been sent to the user const newBalanceAddr1Dai: BigNumber = await dai.balanceOf(addr1.address) const newBalanceAddr1aDai: BigNumber = await stataDai.balanceOf(addr1.address) - const newBalanceAddr1cDai: BigNumber = await cDai.balanceOf(addr1.address) + const newBalanceAddr1cDai: BigNumber = await cDaiVault.balanceOf(addr1.address) // Check received tokens represent ~10K in value at current prices expect(newBalanceAddr1Dai.sub(balanceAddr1Dai)).to.equal(issueAmount.div(4)) // = 2.5K (25% of basket) expect(newBalanceAddr1aDai.sub(balanceAddr1aDai)).to.be.closeTo(fp('2110.5'), fp('0.5')) // ~1.1873 * 2110.5 ~= 2.5K (25% of basket) - expect(newBalanceAddr1cDai.sub(balanceAddr1cDai)).to.be.closeTo(bn('151785e8'), bn('5e7')) // ~0.03294 * 151785.3 ~= 5K (50% of basket) + expect(newBalanceAddr1cDai.sub(balanceAddr1cDai)).to.be.closeTo(bn('151785e17'), bn('5e16')) // ~0.03294 * 151785.3 ~= 5K (50% of basket) // Check remainders in Backing Manager expect(await dai.balanceOf(backingManager.address)).to.equal(0) @@ -1934,7 +1988,10 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, fp('219.64'), // ~= 260 usd in value fp('0.01') ) - expect(await cDai.balanceOf(backingManager.address)).to.be.closeTo(bn(75331e8), bn('5e7')) // ~= 2481 usd in value + expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo( + bn('75331e17'), + bn('5e16') + ) // ~= 2481 usd in value // Check total asset value (remainder) expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( @@ -1967,7 +2024,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Provide approvals await dai.connect(addr1).approve(rToken.address, issueAmount) await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) // Check rToken balance expect(await rToken.balanceOf(addr1.address)).to.equal(0) @@ -2016,9 +2073,13 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Approve all balances for user await wbtc.connect(addr1).approve(rToken.address, await wbtc.balanceOf(addr1.address)) - await cWBTC.connect(addr1).approve(rToken.address, await cWBTC.balanceOf(addr1.address)) + await cWBTCVault + .connect(addr1) + .approve(rToken.address, await cWBTCVault.balanceOf(addr1.address)) await weth.connect(addr1).approve(rToken.address, await weth.balanceOf(addr1.address)) - await cETH.connect(addr1).approve(rToken.address, await cETH.balanceOf(addr1.address)) + await cETHVault + .connect(addr1) + .approve(rToken.address, await cETHVault.balanceOf(addr1.address)) await eurt.connect(addr1).approve(rToken.address, await eurt.balanceOf(addr1.address)) }) @@ -2035,7 +2096,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // cWBTC const cWBTCPrice = btcTargetPrice .mul(wbtcRefPrice) - .mul(fp('0.020065932066404677')) + .mul(fp('0.020065932166404677')) .div(BN_SCALE_FACTOR.pow(2)) await expectPrice(cWBTCCollateral.address, cWBTCPrice, ORACLE_ERROR, true, bn('1e8')) // close to $633 usd @@ -2058,9 +2119,9 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await basketHandler.fullyCollateralized()).to.equal(true) const backing = await facade.basketTokens(rToken.address) expect(backing[0]).to.equal(wbtc.address) - expect(backing[1]).to.equal(cWBTC.address) + expect(backing[1]).to.equal(cWBTCVault.address) expect(backing[2]).to.equal(weth.address) - expect(backing[3]).to.equal(cETH.address) + expect(backing[3]).to.equal(cETHVault.address) expect(backing[4]).to.equal(eurt.address) expect(backing.length).to.equal(5) @@ -2084,18 +2145,18 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check balances before expect(await wbtc.balanceOf(backingManager.address)).to.equal(0) - expect(await cWBTC.balanceOf(backingManager.address)).to.equal(0) + expect(await cWBTCVault.balanceOf(backingManager.address)).to.equal(0) expect(await weth.balanceOf(backingManager.address)).to.equal(0) - expect(await cETH.balanceOf(backingManager.address)).to.equal(0) + expect(await cETHVault.balanceOf(backingManager.address)).to.equal(0) expect(await eurt.balanceOf(backingManager.address)).to.equal(0) expect(await wbtc.balanceOf(addr1.address)).to.equal(toBNDecimals(initialBalBtcEth, 8)) - expect(await cWBTC.balanceOf(addr1.address)).to.equal( - toBNDecimals(initialBalBtcEth, 8).mul(1000) + expect(await cWBTCVault.balanceOf(addr1.address)).to.equal( + toBNDecimals(initialBalBtcEth, 17).mul(1000) ) expect(await weth.balanceOf(addr1.address)).to.equal(initialBalBtcEth) - expect(await cETH.balanceOf(addr1.address)).to.equal( - toBNDecimals(initialBalBtcEth, 8).mul(1000) + expect(await cETHVault.balanceOf(addr1.address)).to.equal( + toBNDecimals(initialBalBtcEth, 17).mul(1000) ) expect(await eurt.balanceOf(addr1.address)).to.equal( toBNDecimals(initialBalBtcEth, 6).mul(1000) @@ -2107,14 +2168,14 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check Balances after expect(await wbtc.balanceOf(backingManager.address)).to.equal(toBNDecimals(issueAmount, 8)) //1 full units - const requiredCWBTC: BigNumber = toBNDecimals(fp('49.85'), 8) // approx 49.5 cWBTC needed (~1 wbtc / 0.02006) - expect(await cWBTC.balanceOf(backingManager.address)).to.be.closeTo( + const requiredCWBTC: BigNumber = toBNDecimals(fp('49.85'), 17) // approx 49.5 cWBTC needed (~1 wbtc / 0.02006) + expect(await cWBTCVault.balanceOf(backingManager.address)).to.be.closeTo( requiredCWBTC, point1Pct(requiredCWBTC) ) expect(await weth.balanceOf(backingManager.address)).to.equal(issueAmount) //1 full units - const requiredCETH: BigNumber = toBNDecimals(fp('49.8'), 8) // approx 49.8 cETH needed (~1 weth / 0.02020) - expect(await cETH.balanceOf(backingManager.address)).to.be.closeTo( + const requiredCETH: BigNumber = toBNDecimals(fp('49.8'), 17) // approx 49.8 cETH needed (~1 weth / 0.02020) + expect(await cETHVault.balanceOf(backingManager.address)).to.be.closeTo( requiredCETH, point1Pct(requiredCETH) ) @@ -2124,14 +2185,14 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await wbtc.balanceOf(addr1.address)).to.equal( toBNDecimals(initialBalBtcEth.sub(issueAmount), 8) ) - const expectedcWBTCBalance = toBNDecimals(initialBalBtcEth, 8).mul(1000).sub(requiredCWBTC) - expect(await cWBTC.balanceOf(addr1.address)).to.be.closeTo( + const expectedcWBTCBalance = toBNDecimals(initialBalBtcEth, 17).mul(1000).sub(requiredCWBTC) + expect(await cWBTCVault.balanceOf(addr1.address)).to.be.closeTo( expectedcWBTCBalance, point1Pct(expectedcWBTCBalance) ) expect(await weth.balanceOf(addr1.address)).to.equal(initialBalBtcEth.sub(issueAmount)) - const expectedcETHBalance = toBNDecimals(initialBalBtcEth, 8).mul(1000).sub(requiredCETH) - expect(await cWBTC.balanceOf(addr1.address)).to.be.closeTo( + const expectedcETHBalance = toBNDecimals(initialBalBtcEth, 17).mul(1000).sub(requiredCETH) + expect(await cWBTCVault.balanceOf(addr1.address)).to.be.closeTo( expectedcETHBalance, point1Pct(expectedcETHBalance) ) @@ -2161,21 +2222,21 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check balances after - Backing Manager is empty expect(await wbtc.balanceOf(backingManager.address)).to.equal(0) - expect(await cWBTC.balanceOf(backingManager.address)).to.be.closeTo(bn(0), bn('10')) + expect(await cWBTCVault.balanceOf(backingManager.address)).to.be.closeTo(bn(0), bn('10e9')) expect(await weth.balanceOf(backingManager.address)).to.equal(0) - expect(await cETH.balanceOf(backingManager.address)).to.be.closeTo(bn(0), bn('10')) + expect(await cETHVault.balanceOf(backingManager.address)).to.be.closeTo(bn(0), bn('10e9')) expect(await eurt.balanceOf(backingManager.address)).to.equal(0) // Check funds returned to user expect(await wbtc.balanceOf(addr1.address)).to.equal(toBNDecimals(initialBalBtcEth, 8)) - expect(await cWBTC.balanceOf(addr1.address)).to.be.closeTo( - toBNDecimals(initialBalBtcEth, 8).mul(1000), - bn('10') + expect(await cWBTCVault.balanceOf(addr1.address)).to.be.closeTo( + toBNDecimals(initialBalBtcEth, 17).mul(1000), + bn('10e9') ) expect(await weth.balanceOf(addr1.address)).to.equal(initialBalBtcEth) - expect(await cETH.balanceOf(addr1.address)).to.be.closeTo( - toBNDecimals(initialBalBtcEth, 8).mul(1000), - bn('10') + expect(await cETHVault.balanceOf(addr1.address)).to.be.closeTo( + toBNDecimals(initialBalBtcEth, 17).mul(1000), + bn('10e9') ) expect(await eurt.balanceOf(addr1.address)).to.equal( toBNDecimals(initialBalBtcEth, 6).mul(1000) @@ -2194,7 +2255,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: cWBTCVault, name: 'RewardsClaimed', args: [compToken.address, bn(0)], emitted: true, @@ -2218,7 +2279,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await advanceTime(8000) // Claim rewards - await expect(backingManager.claimRewards()).to.emit(backingManager, 'RewardsClaimed') + await expect(backingManager.claimRewards()).to.emit(cWBTCVault, 'RewardsClaimed') // Check rewards both in COMP const rewardsCOMP1: BigNumber = await compToken.balanceOf(backingManager.address) @@ -2228,7 +2289,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await advanceTime(3600) // Get additional rewards - await expect(backingManager.claimRewards()).to.emit(backingManager, 'RewardsClaimed') + await expect(backingManager.claimRewards()).to.emit(cWBTCVault, 'RewardsClaimed') const rewardsCOMP2: BigNumber = await compToken.balanceOf(backingManager.address) expect(rewardsCOMP2.sub(rewardsCOMP1)).to.be.gt(0) @@ -2376,7 +2437,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, aaveToken, compAsset, aaveAsset, - compoundMock, erc20s, collateral, basket, @@ -2398,7 +2458,10 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, stataDai = ( await ethers.getContractAt('StaticATokenLM', await aDaiCollateral.erc20()) ) - cDai = await ethers.getContractAt('CTokenMock', await cDaiCollateral.erc20()) + cDaiVault = ( + await ethers.getContractAt('CTokenVault', await cDaiCollateral.erc20()) + ) + cDai = await ethers.getContractAt('CTokenMock', await cDaiVault.asset()) // Get plain aToken aDai = ( @@ -2421,6 +2484,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // cDAI await whileImpersonating(holderCDAI, async (cdaiSigner) => { await cDai.connect(cdaiSigner).transfer(addr1.address, toBNDecimals(initialBal, 8).mul(100)) + await cDai.connect(addr1).approve(cDaiVault.address, toBNDecimals(initialBal, 8).mul(100)) + await cDaiVault.connect(addr1).mint(toBNDecimals(initialBal, 17).mul(100), addr1.address) }) }) @@ -2454,7 +2519,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Provide approvals for issuances await dai.connect(addr1).approve(rToken.address, issueAmount) await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) // Issue rTokens await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index 86498f8e68..eb970a34aa 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -154,6 +154,8 @@ async function collateralFixture( throw new Error(`Missing network configuration for ${hre.network.name}`) } + const CTokenVaultFactory = await ethers.getContractFactory('CTokenVault') + const StaticATokenFactory: ContractFactory = await ethers.getContractFactory('StaticATokenLM') const FiatCollateralFactory: ContractFactory = await ethers.getContractFactory('FiatCollateral') const ATokenCollateralFactory = await ethers.getContractFactory('ATokenFiatCollateral') @@ -202,23 +204,28 @@ async function collateralFixture( const erc20: IERC20Metadata = ( await ethers.getContractAt('CTokenMock', tokenAddress) ) + const vault = await CTokenVaultFactory.deploy( + erc20.address, + `${await erc20.name()} Vault`, + `${await erc20.symbol()}-VAULT`, + comptroller.address + ) const coll = await CTokenCollateralFactory.deploy( { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: chainlinkAddr, oracleError: ORACLE_ERROR, - erc20: erc20.address, + erc20: vault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, }, - REVENUE_HIDING, - comptroller.address + REVENUE_HIDING ) await coll.refresh() - return [erc20, coll] + return [vault, coll] } const makeATokenCollateral = async ( @@ -292,12 +299,18 @@ async function collateralFixture( const erc20: IERC20Metadata = ( await ethers.getContractAt('CTokenMock', tokenAddress) ) + const vault = await CTokenVaultFactory.deploy( + erc20.address, + `${await erc20.name()} Vault`, + `${await erc20.symbol()}-VAULT`, + comptroller.address + ) const coll = await CTokenNonFiatCollateralFactory.deploy( { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracleAddr, oracleError: ORACLE_ERROR, - erc20: erc20.address, + erc20: vault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String(targetName), @@ -306,11 +319,10 @@ async function collateralFixture( }, targetUnitOracleAddr, ORACLE_TIMEOUT, - REVENUE_HIDING, - comptroller.address + REVENUE_HIDING ) await coll.refresh() - return [erc20, coll] + return [vault, coll] } const makeSelfReferentialCollateral = async ( @@ -343,13 +355,19 @@ async function collateralFixture( const erc20: IERC20Metadata = ( await ethers.getContractAt('CTokenMock', tokenAddress) ) + const vault = await CTokenVaultFactory.deploy( + erc20.address, + `${await erc20.name()} Vault`, + `${await erc20.symbol()}-VAULT`, + comptroller.address + ) const coll = ( await CTokenSelfReferentialCollateralFactory.deploy( { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: chainlinkAddr, oracleError: ORACLE_ERROR, - erc20: erc20.address, + erc20: vault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String(targetName), @@ -357,12 +375,11 @@ async function collateralFixture( delayUntilDefault, }, REVENUE_HIDING, - referenceERC20Decimals, - comptroller.address + referenceERC20Decimals ) ) await coll.refresh() - return [erc20, coll] + return [vault, coll] } const makeEURFiatCollateral = async ( diff --git a/test/integration/fork-block-numbers.ts b/test/integration/fork-block-numbers.ts index f757ebe749..f7b06dafc8 100644 --- a/test/integration/fork-block-numbers.ts +++ b/test/integration/fork-block-numbers.ts @@ -1,6 +1,7 @@ const forkBlockNumber = { 'aave-compound-rewards': 12521999, // Ethereum 'asset-plugins': 14916729, // Ethereum + 'adai-plugin': 14216729, // Ethereum 'mainnet-deployment': 15690042, // Ethereum 'flux-finance': 16836855, // Ethereum default: 16934828, // Ethereum diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index 42fec10fcb..b90f1be880 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -18,7 +18,7 @@ import { Asset, ATokenFiatCollateral, CTokenFiatCollateral, - CTokenMock, + CTokenVaultMock, ERC20Mock, FiatCollateral, IAssetRegistry, @@ -52,7 +52,7 @@ describe('Assets contracts #fast', () => { let token: ERC20Mock let usdc: USDCMock let aToken: StaticATokenMock - let cToken: CTokenMock + let cToken: CTokenVaultMock // Assets let collateral0: FiatCollateral @@ -114,18 +114,20 @@ describe('Assets contracts #fast', () => { aToken = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - cToken = await ethers.getContractAt('CTokenMock', await collateral3.erc20()) + cToken = ( + await ethers.getContractAt('CTokenVaultMock', await collateral3.erc20()) + ) await rsr.connect(wallet).mint(wallet.address, amt) await compToken.connect(wallet).mint(wallet.address, amt) await aaveToken.connect(wallet).mint(wallet.address, amt) - // Issue RToken to enable RToken.price for (let i = 0; i < basket.length; i++) { const tok = await ethers.getContractAt('ERC20Mock', await basket[i].erc20()) await tok.connect(wallet).mint(wallet.address, amt) await tok.connect(wallet).approve(rToken.address, amt) } + await rToken.connect(wallet).issue(amt) AssetFactory = await ethers.getContractFactory('Asset') diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index dbbb3d419c..d04693ec54 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -7,12 +7,11 @@ import { IConfig, MAX_DELAY_UNTIL_DEFAULT } from '../../common/configuration' import { CollateralStatus, MAX_UINT48, ZERO_ADDRESS } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { - AppreciatingFiatCollateral, ATokenFiatCollateral, ComptrollerMock, CTokenFiatCollateral, CTokenNonFiatCollateral, - CTokenMock, + CTokenVaultMock, CTokenSelfReferentialCollateral, ERC20Mock, EURFiatCollateral, @@ -29,6 +28,7 @@ import { TestIRToken, USDCMock, WETH9, + UnpricedAppreciatingFiatCollateralMock, } from '../../typechain' import { advanceTime, getLatestBlockTimestamp, setNextBlockTimestamp } from '../utils/time' import snapshotGasCost from '../utils/snapshotGasCost' @@ -63,7 +63,7 @@ describe('Collateral contracts', () => { let token: ERC20Mock let usdc: USDCMock let aToken: StaticATokenMock - let cToken: CTokenMock + let cToken: CTokenVaultMock let aaveToken: ERC20Mock let compToken: ERC20Mock @@ -124,7 +124,9 @@ describe('Collateral contracts', () => { aToken = ( await ethers.getContractAt('StaticATokenMock', await aTokenCollateral.erc20()) ) - cToken = await ethers.getContractAt('CTokenMock', await cTokenCollateral.erc20()) + cToken = ( + await ethers.getContractAt('CTokenVaultMock', await cTokenCollateral.erc20()) + ) await token.connect(owner).mint(owner.address, amt) await usdc.connect(owner).mint(owner.address, amt.div(bn('1e12'))) @@ -206,7 +208,7 @@ describe('Collateral contracts', () => { ) await expectPrice(aTokenCollateral.address, fp('1'), ORACLE_ERROR, true) await expect(aTokenCollateral.claimRewards()) - .to.emit(aTokenCollateral, 'RewardsClaimed') + .to.emit(aToken, 'RewardsClaimed') .withArgs(aaveToken.address, 0) // CToken @@ -230,7 +232,7 @@ describe('Collateral contracts', () => { ) await expectPrice(cTokenCollateral.address, fp('0.02'), ORACLE_ERROR, true) await expect(cTokenCollateral.claimRewards()) - .to.emit(cTokenCollateral, 'RewardsClaimed') + .to.emit(cToken, 'RewardsClaimed') .withArgs(compToken.address, 0) }) }) @@ -299,8 +301,7 @@ describe('Collateral contracts', () => { defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), }, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) ).to.be.revertedWith('delayUntilDefault zero') }) @@ -352,8 +353,7 @@ describe('Collateral contracts', () => { defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: MAX_DELAY_UNTIL_DEFAULT + 1, }, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) ).to.be.revertedWith('delayUntilDefault too long') }) @@ -406,8 +406,7 @@ describe('Collateral contracts', () => { defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), }, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) ).to.be.revertedWith('oracle error out of range') @@ -460,8 +459,7 @@ describe('Collateral contracts', () => { defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) ).to.be.revertedWith('oracle error out of range') }) @@ -500,32 +498,10 @@ describe('Collateral contracts', () => { defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, - fp('1'), - compoundMock.address + fp('1') ) ).to.be.revertedWith('revenueHiding out of range') }) - - it('Should not allow missing comptroller - CTokens', async () => { - // CTokenFiatCollateral - await expect( - CTokenFiatCollateralFactory.deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: await cTokenCollateral.chainlinkFeed(), - oracleError: ORACLE_ERROR, - erc20: cToken.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - }, - REVENUE_HIDING, - ZERO_ADDRESS - ) - ).to.be.revertedWith('comptroller missing') - }) }) describe('Prices #fast', () => { @@ -713,21 +689,21 @@ describe('Collateral contracts', () => { const UnpricedAppreciatingFactory = await ethers.getContractFactory( 'UnpricedAppreciatingFiatCollateralMock' ) - const unpricedAppFiatCollateral: AppreciatingFiatCollateral = ( - await UnpricedAppreciatingFactory.deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: await aTokenCollateral.chainlinkFeed(), // reuse - mock - oracleError: ORACLE_ERROR, - erc20: aToken.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - }, - REVENUE_HIDING - ) + const unpricedAppFiatCollateral: UnpricedAppreciatingFiatCollateralMock = < + UnpricedAppreciatingFiatCollateralMock + >await UnpricedAppreciatingFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await aTokenCollateral.chainlinkFeed(), // reuse - mock + oracleError: ORACLE_ERROR, + erc20: aToken.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + REVENUE_HIDING ) // Save prices @@ -1019,8 +995,7 @@ describe('Collateral contracts', () => { defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await cTokenCollateral.delayUntilDefault(), }, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) ) @@ -1041,7 +1016,7 @@ describe('Collateral contracts', () => { // Set COMP and AAVE rewards for Main const rewardAmountCOMP: BigNumber = bn('100e18') const rewardAmountAAVE: BigNumber = bn('20e18') - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + await compoundMock.setRewards(cToken.address, rewardAmountCOMP) await aToken.setRewards(backingManager.address, rewardAmountAAVE) // Check funds not yet swept @@ -1052,28 +1027,10 @@ describe('Collateral contracts', () => { await facadeTest.claimRewards(rToken.address) // Check rewards were transfered to BackingManager - expect(await compToken.balanceOf(backingManager.address)).to.equal(rewardAmountCOMP) + // only 1/4 of the minted ctoken was used to issue the rtoken + // expect(await compToken.balanceOf(backingManager.address)).to.equal(rewardAmountCOMP.div(4)) expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmountAAVE) }) - - it('Should handle failure in the Rewards call', async function () { - // Set COMP reward for Main - const rewardAmountCOMP: BigNumber = bn('100e18') - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) - - // Check funds not yet swept - expect(await compToken.balanceOf(backingManager.address)).to.equal(0) - - // Force call to fail, set an invalid COMP token in Comptroller - await compoundMock.connect(owner).setCompToken(cTokenCollateral.address) - await expect(facadeTest.claimRewards(rToken.address)).to.be.revertedWith( - 'rewards claim failed' - ) - - // Check funds not yet swept - expect(await compToken.balanceOf(backingManager.address)).to.equal(0) - expect(await aaveToken.balanceOf(backingManager.address)).to.equal(0) - }) }) // Tests specific to NonFiatCollateral.sol contract, not used by default in fixture @@ -1324,7 +1281,7 @@ describe('Collateral contracts', () => { let CTokenNonFiatFactory: ContractFactory let cTokenNonFiatCollateral: CTokenNonFiatCollateral let nonFiatToken: ERC20Mock - let cNonFiatToken: CTokenMock + let cNonFiatTokenVault: CTokenVaultMock let targetUnitOracle: MockV3Aggregator let referenceUnitOracle: MockV3Aggregator @@ -1340,9 +1297,15 @@ describe('Collateral contracts', () => { await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) // 1 WBTC/BTC ) // cToken - cNonFiatToken = await ( - await ethers.getContractFactory('CTokenMock') - ).deploy('cWBTC Token', 'cWBTC', nonFiatToken.address) + cNonFiatTokenVault = await ( + await ethers.getContractFactory('CTokenVaultMock') + ).deploy( + 'cWBTC Token', + 'cWBTC', + nonFiatToken.address, + compToken.address, + compoundMock.address + ) CTokenNonFiatFactory = await ethers.getContractFactory('CTokenNonFiatCollateral') @@ -1351,7 +1314,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracle.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatToken.address, + erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1360,13 +1323,12 @@ describe('Collateral contracts', () => { }, targetUnitOracle.address, ORACLE_TIMEOUT, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) await cTokenNonFiatCollateral.refresh() // Mint some tokens - await cNonFiatToken.connect(owner).mint(owner.address, amt.div(bn('1e10'))) + await cNonFiatTokenVault.connect(owner).mint(owner.address, amt.div(bn('1e10'))) }) it('Should not allow missing delayUntilDefault', async () => { @@ -1376,7 +1338,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracle.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatToken.address, + erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1385,8 +1347,7 @@ describe('Collateral contracts', () => { }, targetUnitOracle.address, ORACLE_TIMEOUT, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) ).to.be.revertedWith('delayUntilDefault zero') }) @@ -1398,7 +1359,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracle.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatToken.address, + erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1407,8 +1368,7 @@ describe('Collateral contracts', () => { }, targetUnitOracle.address, ORACLE_TIMEOUT, - fp('1'), - compoundMock.address + fp('1') ) ).to.be.revertedWith('revenueHiding out of range') }) @@ -1420,7 +1380,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ZERO_ADDRESS, oracleError: ORACLE_ERROR, - erc20: cNonFiatToken.address, + erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1429,8 +1389,7 @@ describe('Collateral contracts', () => { }, targetUnitOracle.address, ORACLE_TIMEOUT, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) ).to.be.revertedWith('missing chainlink feed') }) @@ -1442,7 +1401,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracle.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatToken.address, + erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1451,8 +1410,7 @@ describe('Collateral contracts', () => { }, ZERO_ADDRESS, ORACLE_TIMEOUT, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) ).to.be.revertedWith('missing targetUnit feed') }) @@ -1464,7 +1422,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracle.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatToken.address, + erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1473,34 +1431,11 @@ describe('Collateral contracts', () => { }, targetUnitOracle.address, bn('0'), - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) ).to.be.revertedWith('targetUnitOracleTimeout zero') }) - it('Should not allow missing comptroller', async () => { - await expect( - CTokenNonFiatFactory.deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: referenceUnitOracle.address, - oracleError: ORACLE_ERROR, - erc20: cNonFiatToken.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('BTC'), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - }, - targetUnitOracle.address, - ORACLE_TIMEOUT, - REVENUE_HIDING, - ZERO_ADDRESS - ) - ).to.be.revertedWith('comptroller missing') - }) - it('Should setup collateral correctly', async function () { // Non-Fiat Token expect(await cTokenNonFiatCollateral.isCollateral()).to.equal(true) @@ -1509,8 +1444,8 @@ describe('Collateral contracts', () => { ) expect(await cTokenNonFiatCollateral.chainlinkFeed()).to.equal(referenceUnitOracle.address) expect(await cTokenNonFiatCollateral.referenceERC20Decimals()).to.equal(18) - expect(await cTokenNonFiatCollateral.erc20()).to.equal(cNonFiatToken.address) - expect(await cNonFiatToken.decimals()).to.equal(8) + expect(await cTokenNonFiatCollateral.erc20()).to.equal(cNonFiatTokenVault.address) + expect(await cNonFiatTokenVault.decimals()).to.equal(8) expect(await cTokenNonFiatCollateral.targetName()).to.equal( ethers.utils.formatBytes32String('BTC') ) @@ -1534,7 +1469,7 @@ describe('Collateral contracts', () => { await expectPrice(cTokenNonFiatCollateral.address, fp('400'), ORACLE_ERROR, true) // 0.02 of 20k await expect(cTokenNonFiatCollateral.claimRewards()) - .to.emit(cTokenNonFiatCollateral, 'RewardsClaimed') + .to.emit(cNonFiatTokenVault, 'RewardsClaimed') .withArgs(compToken.address, 0) }) @@ -1545,7 +1480,7 @@ describe('Collateral contracts', () => { expect(await cTokenNonFiatCollateral.refPerTok()).to.equal(fp('0.02')) // Increase rate to double - await cNonFiatToken.setExchangeRate(fp(2)) + await cNonFiatTokenVault.setExchangeRate(fp(2)) await cTokenNonFiatCollateral.refresh() // Check price doubled @@ -1592,7 +1527,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: invalidChainlinkFeed.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatToken.address, + erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1601,8 +1536,7 @@ describe('Collateral contracts', () => { }, targetUnitOracle.address, ORACLE_TIMEOUT, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) ) @@ -1622,7 +1556,7 @@ describe('Collateral contracts', () => { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: referenceUnitOracle.address, oracleError: ORACLE_ERROR, - erc20: cNonFiatToken.address, + erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), @@ -1631,8 +1565,7 @@ describe('Collateral contracts', () => { }, invalidChainlinkFeed.address, ORACLE_TIMEOUT, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) // Reverting with no reason @@ -1782,7 +1715,7 @@ describe('Collateral contracts', () => { let CTokenSelfReferentialFactory: ContractFactory let cTokenSelfReferentialCollateral: CTokenSelfReferentialCollateral let selfRefToken: WETH9 - let cSelfRefToken: CTokenMock + let cSelfRefToken: CTokenVaultMock let chainlinkFeed: MockV3Aggregator beforeEach(async () => { @@ -1793,8 +1726,8 @@ describe('Collateral contracts', () => { // cToken Self Ref cSelfRefToken = await ( - await ethers.getContractFactory('CTokenMock') - ).deploy('cETH Token', 'cETH', selfRefToken.address) + await ethers.getContractFactory('CTokenVaultMock') + ).deploy('cETH Token', 'cETH', selfRefToken.address, compToken.address, compoundMock.address) CTokenSelfReferentialFactory = await ethers.getContractFactory( 'CTokenSelfReferentialCollateral' @@ -1814,8 +1747,7 @@ describe('Collateral contracts', () => { delayUntilDefault: DELAY_UNTIL_DEFAULT, }, REVENUE_HIDING, - await selfRefToken.decimals(), - compoundMock.address + await selfRefToken.decimals() ) ) await cTokenSelfReferentialCollateral.refresh() @@ -1839,8 +1771,7 @@ describe('Collateral contracts', () => { delayUntilDefault: DELAY_UNTIL_DEFAULT, }, fp('1'), - await selfRefToken.decimals(), - compoundMock.address + await selfRefToken.decimals() ) ).to.be.revertedWith('revenueHiding out of range') }) @@ -1860,33 +1791,11 @@ describe('Collateral contracts', () => { delayUntilDefault: DELAY_UNTIL_DEFAULT, }, REVENUE_HIDING, - 0, - compoundMock.address + 0 ) ).to.be.revertedWith('referenceERC20Decimals missing') }) - it('Should not allow missing comptroller', async () => { - await expect( - CTokenSelfReferentialFactory.deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: cSelfRefToken.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('ETH'), - defaultThreshold: bn(0), - delayUntilDefault: DELAY_UNTIL_DEFAULT, - }, - REVENUE_HIDING, - await selfRefToken.decimals(), - ZERO_ADDRESS - ) - ).to.be.revertedWith('comptroller missing') - }) - it('Should not allow invalid defaultThreshold', async () => { await expect( CTokenSelfReferentialFactory.deploy( @@ -1902,8 +1811,7 @@ describe('Collateral contracts', () => { delayUntilDefault: DELAY_UNTIL_DEFAULT, }, REVENUE_HIDING, - 0, - compoundMock.address + 0 ) ).to.be.revertedWith('default threshold not supported') }) @@ -1935,7 +1843,7 @@ describe('Collateral contracts', () => { await expectPrice(cTokenSelfReferentialCollateral.address, fp('0.02'), ORACLE_ERROR, true) await expect(cTokenSelfReferentialCollateral.claimRewards()) - .to.emit(cTokenSelfReferentialCollateral, 'RewardsClaimed') + .to.emit(cSelfRefToken, 'RewardsClaimed') .withArgs(compToken.address, 0) }) @@ -1989,8 +1897,7 @@ describe('Collateral contracts', () => { delayUntilDefault: DELAY_UNTIL_DEFAULT, }, REVENUE_HIDING, - await selfRefToken.decimals(), - compoundMock.address + await selfRefToken.decimals() ) ) diff --git a/test/plugins/RewardableERC20Vault.test.ts b/test/plugins/RewardableERC20Vault.test.ts new file mode 100644 index 0000000000..47f24678bd --- /dev/null +++ b/test/plugins/RewardableERC20Vault.test.ts @@ -0,0 +1,574 @@ +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { expect } from 'chai' +import { Wallet, ContractFactory, BigNumber } from 'ethers' +import { ethers } from 'hardhat' +import { bn, fp } from '../../common/numbers' +import { + ERC20MockDecimals, + ERC20MockRewarding, + RewardableERC20Vault, + RewardableERC20VaultTest, +} from '../../typechain' +import { cartesianProduct } from '../utils/cases' +import { useEnv } from '#/utils/env' +import { Implementation } from '../fixtures' +import snapshotGasCost from '../utils/snapshotGasCost' + +type Fixture = () => Promise + +interface RewardableERC20VaultFixture { + rewardableVault: RewardableERC20Vault + rewardableAsset: ERC20MockRewarding + rewardToken: ERC20MockDecimals +} + +const getFixture = ( + assetDecimals: number, + rewardDecimals: number +): Fixture => { + const fixture: Fixture = + async function (): Promise { + const rewardTokenFactory: ContractFactory = await ethers.getContractFactory( + 'ERC20MockDecimals' + ) + const rewardToken = ( + await rewardTokenFactory.deploy('Reward Token', 'REWARD', rewardDecimals) + ) + + const rewardableAssetFactory: ContractFactory = await ethers.getContractFactory( + 'ERC20MockRewarding' + ) + const rewardableAsset = ( + await rewardableAssetFactory.deploy( + 'Rewarding Test Asset', + 'rewardTEST', + assetDecimals, + rewardToken.address + ) + ) + + const rewardableVaultFactory: ContractFactory = await ethers.getContractFactory( + 'RewardableERC20VaultTest' + ) + const rewardableVault = ( + await rewardableVaultFactory.deploy( + rewardableAsset.address, + 'Rewarding Test Asset Vault', + 'vrewardTEST', + rewardToken.address + ) + ) + + return { + rewardableVault, + rewardableAsset, + rewardToken, + } + } + return fixture +} + +const toShares = (assets: BigNumber, assetDecimals: number, shareDecimals: number): BigNumber => { + return assets.mul(bn(10).pow(shareDecimals - assetDecimals)) +} + +const runTests = (assetDecimals: number, rewardDecimals: number) => { + describe('RewardableERC20Vault', () => { + // Decimals + let shareDecimals: number + + // Assets + let rewardableVault: RewardableERC20Vault + let rewardableAsset: ERC20MockRewarding + let rewardToken: ERC20MockDecimals + + // Main + let alice: Wallet + let bob: Wallet + + const initBalance = fp('10000').div(bn(10).pow(18 - assetDecimals)) + const rewardAmount = fp('200').div(bn(10).pow(18 - rewardDecimals)) + let oneShare: BigNumber + let initShares: BigNumber + + const fixture = getFixture(assetDecimals, rewardDecimals) + + before('load wallets', async () => { + ;[alice, bob] = (await ethers.getSigners()) as unknown as Wallet[] + }) + + beforeEach(async () => { + // Deploy fixture + ;({ rewardableVault, rewardableAsset, rewardToken } = await loadFixture(fixture)) + + await rewardableAsset.mint(alice.address, initBalance) + await rewardableAsset.connect(alice).approve(rewardableVault.address, initBalance) + await rewardableAsset.mint(bob.address, initBalance) + await rewardableAsset.connect(bob).approve(rewardableVault.address, initBalance) + + shareDecimals = await rewardableVault.decimals() + initShares = toShares(initBalance, assetDecimals, shareDecimals) + oneShare = bn('1').mul(bn(10).pow(shareDecimals)) + }) + + describe('Deployment', () => { + it('sets the rewardableVault rewardableAsset', async () => { + const seenAsset = await rewardableVault.asset() + expect(seenAsset).equal(rewardableAsset.address) + }) + + it('sets the rewardableVault reward token', async () => { + const seenRewardToken = await rewardableVault.rewardToken() + expect(seenRewardToken).equal(rewardToken.address) + }) + }) + + describe('alice deposit, accrue, alice deposit, bob deposit', () => { + let rewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.connect(alice).deposit(initBalance.div(8), alice.address) + + // bob deposit + await rewardableVault.connect(bob).deposit(initBalance.div(8), bob.address) + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct balance', async () => { + expect(initShares.mul(3).div(8)).equal(await rewardableVault.balanceOf(alice.address)) + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('bob shows correct balance', async () => { + expect(initShares.div(8)).equal(await rewardableVault.balanceOf(bob.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + + it('rewardsPerShare is correct', async () => { + // rewards / alice's deposit + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + }) + }) + + describe('alice deposit, accrue, alice deposit, accrue, bob deposit', () => { + let rewardsPerShare: BigNumber + let initRewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + + initRewardsPerShare = await rewardableVault.rewardsPerShare() + + // accrue + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + + // bob deposit + await rewardableVault.connect(alice).deposit(initBalance.div(8), bob.address) + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct lastRewardsPerShare', async () => { + // rewards / alice's deposit + expect(initRewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + expect(initRewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + const expectedRewardsPerShare = rewardAmount + .mul(oneShare) + .div(initShares.div(4)) + .add(rewardAmount.mul(oneShare).div(initShares.div(2))) + expect(rewardsPerShare).equal(expectedRewardsPerShare) + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + }) + + describe('alice deposit, accrue, alice withdraw', () => { + let rewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault + .connect(alice) + .withdraw(initBalance.div(8), alice.address, alice.address) + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('rewardsPerShare is correct', async () => { + // rewards / alice's deposit + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + }) + }) + + describe('alice deposit, accrue, bob deposit, alice withdraw', () => { + let rewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) + await rewardableVault + .connect(alice) + .withdraw(initBalance.div(8), alice.address, alice.address) + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + + it('rewardsPerShare is correct', async () => { + // rewards / alice's deposit + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + }) + }) + + describe('alice deposit, accrue, bob deposit, alice fully withdraw', () => { + let rewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) + await rewardableVault + .connect(alice) + .withdraw(initBalance.div(4), alice.address, alice.address) + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct balance', async () => { + expect(0).equal(await rewardableVault.balanceOf(alice.address)) + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('bob shows correct balance', async () => { + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + + it('rewardsPerShare is correct', async () => { + // rewards / alice's deposit + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + }) + }) + + describe('alice deposit, accrue, alice claim, bob deposit', () => { + let rewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.connect(alice).claimRewards() + + // bob deposit + await rewardableVault.connect(bob).deposit(initBalance.div(8), bob.address) + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct balance', async () => { + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) + }) + + it('alice has claimed rewards', async () => { + expect(rewardAmount).equal(await rewardToken.balanceOf(alice.address)) + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('bob shows correct balance', async () => { + expect(initShares.div(8)).equal(await rewardableVault.balanceOf(bob.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + + it('rewardsPerShare is correct', async () => { + // rewards / alice's deposit + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + }) + }) + + describe('alice deposit, accrue, bob deposit, accrue, bob claim, alice claim', () => { + let rewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + + // bob deposit + await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) + + // accrue + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + + // claims + await rewardableVault.connect(bob).claimRewards() + await rewardableVault.connect(alice).claimRewards() + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct balance', async () => { + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) + }) + + it('alice has claimed rewards', async () => { + expect(rewardAmount.add(rewardAmount.div(2))).equal( + await rewardToken.balanceOf(alice.address) + ) + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('bob shows correct balance', async () => { + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + + it('bob has claimed rewards', async () => { + expect(rewardAmount.div(2)).equal(await rewardToken.balanceOf(bob.address)) + }) + + it('rewardsPerShare is correct', async () => { + // (rewards / alice's deposit) + (rewards / (alice's deposit + bob's deposit)) + const expectedRewardsPerShare = rewardAmount + .mul(oneShare) + .div(initShares.div(4)) + .add(rewardAmount.mul(oneShare).div(initShares.div(2))) + expect(rewardsPerShare).equal(expectedRewardsPerShare) + }) + }) + + describe('does not accure rewards for an account while it has no deposits', () => { + // alice deposit, accrue, bob deposit, alice fully withdraw, accrue, alice deposit, alice claim, bob claim + let rewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + // accrue + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + // bob deposit + await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) + // alice withdraw all + await rewardableVault + .connect(alice) + .withdraw(initBalance.div(4), alice.address, alice.address) + // accrue + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + // alice re-deposit + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + // both claim + await rewardableVault.connect(bob).claimRewards() + await rewardableVault.connect(alice).claimRewards() + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct balance', async () => { + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('alice has claimed rewards', async () => { + expect(rewardAmount).equal(await rewardToken.balanceOf(alice.address)) + }) + + it('bob shows correct balance', async () => { + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + + it('bob has claimed rewards', async () => { + expect(rewardAmount).equal(await rewardToken.balanceOf(bob.address)) + }) + + it('rewardsPerShare is correct', async () => { + // (rewards / alice's deposit) + (rewards / bob's deposit) + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4)).mul(2)) + }) + }) + + describe('correctly updates rewards on transfer', () => { + let rewardsPerShare: BigNumber + + beforeEach(async () => { + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) + await rewardableVault.connect(alice).transfer(bob.address, initShares.div(4)) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableVault.connect(bob).claimRewards() + await rewardableVault.connect(alice).claimRewards() + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct balance', async () => { + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('alice has claimed rewards', async () => { + expect(rewardAmount).equal(await rewardToken.balanceOf(alice.address)) + }) + + it('bob shows correct balance', async () => { + expect(initShares.div(2)).equal(await rewardableVault.balanceOf(bob.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + + it('bob has claimed rewards', async () => { + expect(rewardAmount).equal(await rewardToken.balanceOf(bob.address)) + }) + + it('rewardsPerShare is correct', async () => { + // (rewards / alice's deposit) + (rewards / (alice's deposit + bob's deposit)) + expect(rewardsPerShare).equal( + rewardAmount + .mul(oneShare) + .div(initShares.div(4)) + .add(rewardAmount.mul(oneShare).div(initShares.div(2))) + ) + }) + }) + }) +} + +const decimalSeeds = [6, 8, 18] +const cases = cartesianProduct(decimalSeeds, decimalSeeds) +// const cases = [[6, 6]] +cases.forEach((params) => { + describe(`rewardableAsset assetDecimals: ${params[0]} / reward assetDecimals: ${params[1]}`, () => { + runTests(params[0], params[1]) + }) +}) + +export const IMPLEMENTATION: Implementation = + useEnv('PROTO_IMPL') == Implementation.P1.toString() ? Implementation.P1 : Implementation.P0 + +const describeGas = + IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip + +describeGas('Gas Reporting', () => { + // Assets + let rewardableVault: RewardableERC20Vault + let rewardableAsset: ERC20MockRewarding + + // Main + let alice: Wallet + + const initBalance = fp('10000') + const rewardAmount = fp('200') + + const fixture = getFixture(18, 18) + + before('load wallets', async () => { + ;[alice] = (await ethers.getSigners()) as unknown as Wallet[] + }) + + beforeEach(async () => { + // Deploy fixture + ;({ rewardableVault, rewardableAsset } = await loadFixture(fixture)) + + await rewardableAsset.mint(alice.address, initBalance) + await rewardableAsset.connect(alice).approve(rewardableVault.address, initBalance) + }) + + describe('RewardableERC20Vault', () => { + it('deposit', async function () { + // Deposit + await snapshotGasCost( + rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + ) + + // Deposit again + await snapshotGasCost( + rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + ) + }) + + it('withdraw', async function () { + await rewardableVault.connect(alice).deposit(initBalance, alice.address) + + await snapshotGasCost( + rewardableVault.connect(alice).withdraw(initBalance.div(2), alice.address, alice.address) + ) + + await snapshotGasCost( + rewardableVault.connect(alice).withdraw(initBalance.div(2), alice.address, alice.address) + ) + }) + + it('claimRewards', async function () { + await rewardableVault.connect(alice).deposit(initBalance, alice.address) + + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + + await snapshotGasCost(rewardableVault.connect(alice).claimRewards()) + + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + + await snapshotGasCost(rewardableVault.connect(alice).claimRewards()) + }) + }) +}) diff --git a/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap b/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap new file mode 100644 index 0000000000..85d7a7c8ad --- /dev/null +++ b/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Gas Reporting RewardableERC20Vault claimRewards 1`] = `159809`; + +exports[`Gas Reporting RewardableERC20Vault claimRewards 2`] = `83048`; + +exports[`Gas Reporting RewardableERC20Vault deposit 1`] = `118977`; + +exports[`Gas Reporting RewardableERC20Vault deposit 2`] = `85331`; + +exports[`Gas Reporting RewardableERC20Vault withdraw 1`] = `98012`; + +exports[`Gas Reporting RewardableERC20Vault withdraw 2`] = `66512`; diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts new file mode 100644 index 0000000000..d7c7701077 --- /dev/null +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -0,0 +1,844 @@ +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { expect } from 'chai' +import { BigNumber, ContractFactory } from 'ethers' +import hre, { ethers } from 'hardhat' +import { IMPLEMENTATION, ORACLE_ERROR, PRICE_TIMEOUT, REVENUE_HIDING } from '../../../fixtures' +import { DefaultFixture, Fixture, getDefaultFixture, ORACLE_TIMEOUT } from '../fixtures' +import { getChainId } from '../../../../common/blockchain-utils' +import forkBlockNumber from '../../../integration/fork-block-numbers' +import { + IConfig, + IGovParams, + IGovRoles, + IRevenueShare, + IRTokenConfig, + IRTokenSetup, + networkConfig, +} from '../../../../common/configuration' +import { CollateralStatus, MAX_UINT48, ZERO_ADDRESS } from '../../../../common/constants' +import { expectEvents, expectInIndirectReceipt } from '../../../../common/events' +import { bn, fp } from '../../../../common/numbers' +import { whileImpersonating } from '../../../utils/impersonation' +import { + expectPrice, + expectRTokenPrice, + expectUnpriced, + setOraclePrice, +} from '../../../utils/oracles' +import { advanceBlocks, advanceTime, getLatestBlockTimestamp } from '../../../utils/time' +import { + Asset, + ATokenFiatCollateral, + ERC20Mock, + FacadeRead, + FacadeTest, + FacadeWrite, + IAssetRegistry, + IBasketHandler, + InvalidMockV3Aggregator, + MockV3Aggregator, + RTokenAsset, + TestIBackingManager, + TestIDeployer, + TestIMain, + TestIRToken, + IAToken, + StaticATokenLM, + StaticATokenMock, + AggregatorInterface, +} from '../../../../typechain' +import { useEnv } from '#/utils/env' +import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' + +// Setup test environment +const setup = async (blockNumber: number) => { + // Use Mainnet fork + await hre.network.provider.request({ + method: 'hardhat_reset', + params: [ + { + forking: { + jsonRpcUrl: useEnv('MAINNET_RPC_URL'), + blockNumber: blockNumber, + }, + }, + ], + }) +} + +// Holder address in Mainnet +const holderDai = '0x16b34ce9a6a6f7fc2dd25ba59bf7308e7b38e186' + +const NO_PRICE_DATA_FEED = '0x51597f405303C4377E36123cBc172b13269EA163' + +const describeFork = useEnv('FORK') ? describe : describe.skip + +describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, function () { + let owner: SignerWithAddress + let addr1: SignerWithAddress + + // Tokens/Assets + let dai: ERC20Mock + let aDai: IAToken + let staticAToken: StaticATokenLM + let aDaiCollateral: ATokenFiatCollateral + let stkAave: ERC20Mock + let stkAaveAsset: Asset + let rsr: ERC20Mock + let rsrAsset: Asset + + // Core Contracts + let main: TestIMain + let rToken: TestIRToken + let rTokenAsset: RTokenAsset + let assetRegistry: IAssetRegistry + let backingManager: TestIBackingManager + let basketHandler: IBasketHandler + let chainlinkFeed: AggregatorInterface + + let deployer: TestIDeployer + let facade: FacadeRead + let facadeTest: FacadeTest + let facadeWrite: FacadeWrite + let govParams: IGovParams + let govRoles: IGovRoles + + // RToken Configuration + const dist: IRevenueShare = { + rTokenDist: bn(40), // 2/5 RToken + rsrDist: bn(60), // 3/5 RSR + } + const config: IConfig = { + dist: dist, + minTradeVolume: fp('1e4'), // $10k + rTokenMaxTradeVolume: fp('1e6'), // $1M + shortFreeze: bn('259200'), // 3 days + longFreeze: bn('2592000'), // 30 days + rewardRatio: bn('1069671574938'), // approx. half life of 90 days + unstakingDelay: bn('1209600'), // 2 weeks + tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) + auctionLength: bn('900'), // 15 minutes + backingBuffer: fp('0.0001'), // 0.01% + maxTradeSlippage: fp('0.01'), // 1% + issuanceThrottle: { + amtRate: fp('1e6'), // 1M RToken + pctRate: fp('0.05'), // 5% + }, + redemptionThrottle: { + amtRate: fp('1e6'), // 1M RToken + pctRate: fp('0.05'), // 5% + }, + warmupPeriod: bn('60'), + } + + const defaultThreshold = fp('0.01') // 1% + const delayUntilDefault = bn('86400') // 24h + + let initialBal: BigNumber + + let chainId: number + + let ATokenFiatCollateralFactory: ContractFactory + let MockV3AggregatorFactory: ContractFactory + let mockChainlinkFeed: MockV3Aggregator + + let defaultFixture: Fixture + + before(async () => { + await setup(forkBlockNumber['adai-plugin']) + defaultFixture = await getDefaultFixture('atoken') + chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + }) + + beforeEach(async () => { + ;[owner, addr1] = await ethers.getSigners() + ;({ rsr, rsrAsset, deployer, facade, facadeTest, facadeWrite, govParams } = await loadFixture( + defaultFixture + )) + + // DAI token + dai = ( + await ethers.getContractAt('ERC20Mock', networkConfig[chainId].tokens.DAI || '') + ) + // aDAI token + aDai = await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aDAI || '') + + // stkAAVE + stkAave = ( + await ethers.getContractAt('ERC20Mock', networkConfig[chainId].tokens.stkAAVE || '') + ) + + // Create stkAAVE asset + stkAaveAsset = ( + await ( + await ethers.getContractFactory('Asset') + ).deploy( + PRICE_TIMEOUT, + networkConfig[chainId].chainlinkFeeds.AAVE || '', + ORACLE_ERROR, + stkAave.address, + config.rTokenMaxTradeVolume, + ORACLE_TIMEOUT + ) + ) + + const StaticATokenFactory: ContractFactory = await ethers.getContractFactory('StaticATokenLM') + + staticAToken = ( + await StaticATokenFactory.connect(owner).deploy( + networkConfig[chainId].AAVE_LENDING_POOL, + networkConfig[chainId].tokens.aDAI, + 'Static Aave Interest Bearing DAI', + 'stataDAI' + ) + ) + + // Deploy aDai collateral plugin + ATokenFiatCollateralFactory = await ethers.getContractFactory('ATokenFiatCollateral') + aDaiCollateral = await ATokenFiatCollateralFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI as string, + oracleError: ORACLE_ERROR, + erc20: staticAToken.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold, + delayUntilDefault, + }, + REVENUE_HIDING + ) + + chainlinkFeed = await ethers.getContractAt( + 'AggregatorInterface', + networkConfig[chainId].chainlinkFeeds.DAI as string + ) + + // Setup balances for addr1 - Transfer from Mainnet holder + // aDAI + initialBal = bn('2000000e18') + await whileImpersonating(holderDai, async (daiSigner) => { + await dai.connect(daiSigner).transfer(addr1.address, initialBal) + }) + + const initialBalDai = await dai.balanceOf(addr1.address) + + await dai.connect(addr1).approve(staticAToken.address, initialBalDai) + await staticAToken.connect(addr1).deposit(addr1.address, initialBalDai, 0, true) + + // Set parameters + const rTokenConfig: IRTokenConfig = { + name: 'RTKN RToken', + symbol: 'RTKN', + mandate: 'mandate', + params: config, + } + + // Set primary basket + const rTokenSetup: IRTokenSetup = { + assets: [stkAaveAsset.address], + primaryBasket: [aDaiCollateral.address], + weights: [fp('1')], + backups: [], + beneficiaries: [], + } + + // Deploy RToken via FacadeWrite + const receipt = await ( + await facadeWrite.connect(owner).deployRToken(rTokenConfig, rTokenSetup) + ).wait() + + // Get Main + const mainAddr = expectInIndirectReceipt(receipt, deployer.interface, 'RTokenCreated').args.main + main = await ethers.getContractAt('TestIMain', mainAddr) + + // Get core contracts + assetRegistry = ( + await ethers.getContractAt('IAssetRegistry', await main.assetRegistry()) + ) + backingManager = ( + await ethers.getContractAt('TestIBackingManager', await main.backingManager()) + ) + basketHandler = ( + await ethers.getContractAt('IBasketHandler', await main.basketHandler()) + ) + rToken = await ethers.getContractAt('TestIRToken', await main.rToken()) + rTokenAsset = ( + await ethers.getContractAt('RTokenAsset', await assetRegistry.toAsset(rToken.address)) + ) + + // Set initial governance roles + govRoles = { + owner: owner.address, + guardian: ZERO_ADDRESS, + pausers: [], + shortFreezers: [], + longFreezers: [], + } + + // Setup owner and unpause + await facadeWrite.connect(owner).setupGovernance( + rToken.address, + false, // do not deploy governance + true, // unpaused + govParams, // mock values, not relevant + govRoles + ) + + // Setup mock chainlink feed for some of the tests (so we can change the value) + MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') + mockChainlinkFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + }) + + describe('Deployment', () => { + // Check the initial state + it('Should setup RToken, Assets, and Collateral correctly', async () => { + // Check Rewards assets (if applies) + // stkAAVE Asset + expect(await stkAaveAsset.isCollateral()).to.equal(false) + expect(await stkAaveAsset.erc20()).to.equal(stkAave.address) + expect(await stkAaveAsset.erc20()).to.equal(networkConfig[chainId].tokens.stkAAVE) + expect(await stkAave.decimals()).to.equal(18) + await expectPrice(stkAaveAsset.address, fp('169.05235423'), ORACLE_ERROR, true) + await expect(stkAaveAsset.claimRewards()).to.not.emit(stkAaveAsset, 'RewardsClaimed') + expect(await stkAaveAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) + + const refPerTok = await aDaiCollateral.refPerTok() + // Check Collateral plugin + // aDAI (ATokenFiatCollateral) + expect(await aDaiCollateral.isCollateral()).to.equal(true) + expect(await aDaiCollateral.erc20()).to.equal(staticAToken.address) + const aDaiErc20 = await ethers.getContractAt('ERC20Mock', aDai.address) + expect(await aDaiErc20.decimals()).to.equal(18) + expect(await staticAToken.decimals()).to.equal(18) + expect(await aDaiCollateral.targetName()).to.equal(ethers.utils.formatBytes32String('USD')) + expect(refPerTok).to.be.closeTo(fp('1.066'), fp('0.001')) + expect(await aDaiCollateral.targetPerRef()).to.equal(fp('1')) + expect(await aDaiCollateral.exposedReferencePrice()).to.equal(refPerTok) + + const answer = await chainlinkFeed.latestAnswer() + + await expectPrice( + aDaiCollateral.address, + answer + .mul(10 ** 10) + .mul(refPerTok) + .div(fp('1')), + ORACLE_ERROR, + true, + bn('1e5') + ) // close to $0.022 cents + + // Check claim data + await expect(staticAToken['claimRewards()']()) + .to.emit(staticAToken, 'RewardsClaimed') + .withArgs(stkAave.address, anyValue) + + await expect(aDaiCollateral.claimRewards()) + .to.emit(staticAToken, 'RewardsClaimed') + .withArgs(stkAave.address, anyValue) + expect(await aDaiCollateral.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) + + // Should setup contracts + expect(main.address).to.not.equal(ZERO_ADDRESS) + }) + + // Check assets/collaterals in the Asset Registry + it('Should register ERC20s and Assets/Collateral correctly', async () => { + // Check assets/collateral + const ERC20s = await assetRegistry.erc20s() + expect(ERC20s[0]).to.equal(rToken.address) + expect(ERC20s[1]).to.equal(rsr.address) + expect(ERC20s[2]).to.equal(stkAave.address) + expect(ERC20s[3]).to.equal(staticAToken.address) + expect(ERC20s.length).to.eql(4) + + // Assets + expect(await assetRegistry.toAsset(ERC20s[0])).to.equal(rTokenAsset.address) + expect(await assetRegistry.toAsset(ERC20s[1])).to.equal(rsrAsset.address) + expect(await assetRegistry.toAsset(ERC20s[2])).to.equal(stkAaveAsset.address) + expect(await assetRegistry.toAsset(ERC20s[3])).to.equal(aDaiCollateral.address) + + // Collaterals + expect(await assetRegistry.toColl(ERC20s[3])).to.equal(aDaiCollateral.address) + }) + + // Check RToken basket + it('Should register Basket correctly', async () => { + // Basket + expect(await basketHandler.fullyCollateralized()).to.equal(true) + const backing = await facade.basketTokens(rToken.address) + expect(backing[0]).to.equal(staticAToken.address) + expect(backing.length).to.equal(1) + + // Check other values + expect(await basketHandler.timestamp()).to.be.gt(bn(0)) + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) + + // TODO: confirm this is right + await expectPrice(basketHandler.address, fp('1'), ORACLE_ERROR, true, bn('1000')) + + // Check RToken price + const issueAmount: BigNumber = bn('10000e18') + await staticAToken.connect(addr1).approve(rToken.address, issueAmount.mul(100)) + await advanceTime(3600) + await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') + await expectRTokenPrice( + rTokenAsset.address, + fp('1'), + ORACLE_ERROR, + await backingManager.maxTradeSlippage(), + config.minTradeVolume.mul((await assetRegistry.erc20s()).length) + ) + }) + + // Validate constructor arguments + // Note: Adapt it to your plugin constructor validations + it('Should validate constructor arguments correctly', async () => { + // stkAAVEtroller + await expect( + ATokenFiatCollateralFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI as string, + oracleError: ORACLE_ERROR, + erc20: ZERO_ADDRESS, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold, + delayUntilDefault, + }, + REVENUE_HIDING + ) + ).to.be.revertedWith('missing erc20') + }) + }) + + describe('Issuance/Appreciation/Redemption', () => { + const MIN_ISSUANCE_PER_BLOCK = bn('10000e18') + + // Issuance and redemption, making the collateral appreciate over time + it('Should issue, redeem, and handle appreciation rates correctly', async () => { + const issueAmount: BigNumber = MIN_ISSUANCE_PER_BLOCK // instant issuance + + // Provide approvals for issuances + await staticAToken.connect(addr1).approve(rToken.address, issueAmount.mul(100)) + + await advanceTime(3600) + + // Issue rTokens + await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') + + // Check RTokens issued to user + expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) + + // Store Balances after issuance + const balanceAddr1aDai: BigNumber = await staticAToken.balanceOf(addr1.address) + + // Check rates and prices + const [aDaiPriceLow1, aDaiPriceHigh1] = await aDaiCollateral.price() + const aDaiRefPerTok1: BigNumber = await aDaiCollateral.refPerTok() + let answer = await chainlinkFeed.latestAnswer() + + await expectPrice( + aDaiCollateral.address, + answer + .mul(10 ** 10) + .mul(aDaiRefPerTok1) + .div(fp(1)), + ORACLE_ERROR, + true, + bn('1e5') + ) + expect(aDaiRefPerTok1).to.be.closeTo(fp('1.066'), fp('0.001')) + + // Check total asset value + const totalAssetValue1: BigNumber = await facadeTest.callStatic.totalAssetValue( + rToken.address + ) + expect(totalAssetValue1).to.be.closeTo(issueAmount, fp('150')) // approx 10K in value + + // Advance time and blocks slightly, causing refPerTok() to increase + await advanceTime(10000) + await advanceBlocks(10000) + + // Refresh cToken manually (required) + await aDaiCollateral.refresh() + expect(await aDaiCollateral.status()).to.equal(CollateralStatus.SOUND) + + // Check rates and prices - Have changed, slight inrease + const [aDaiPriceLow2, aDaiPriceHigh2] = await aDaiCollateral.price() // ~0.022016 + const aDaiRefPerTok2: BigNumber = await aDaiCollateral.refPerTok() // ~0.022016 + + // Check rates and price increase + expect(aDaiPriceLow2).to.be.gt(aDaiPriceLow1) + expect(aDaiPriceHigh2).to.be.gt(aDaiPriceHigh1) + expect(aDaiRefPerTok2).to.be.gt(aDaiRefPerTok1) + + answer = await chainlinkFeed.latestAnswer() + + // Still close to the original values + await expectPrice( + aDaiCollateral.address, + answer + .mul(10 ** 10) + .mul(aDaiRefPerTok2) + .div(fp(1)), + ORACLE_ERROR, + true, + bn('1e5') + ) + expect(aDaiRefPerTok2).to.be.closeTo(fp('1.066'), fp('0.001')) + + // Check total asset value increased + const totalAssetValue2: BigNumber = await facadeTest.callStatic.totalAssetValue( + rToken.address + ) + expect(totalAssetValue2).to.be.gt(totalAssetValue1) + + // Advance time and blocks slightly, causing refPerTok() to increase + await advanceTime(100000000) + await advanceBlocks(100000000) + + // Refresh cToken manually (required) + await aDaiCollateral.refresh() + expect(await aDaiCollateral.status()).to.equal(CollateralStatus.SOUND) + + // Check rates and prices - Have changed significantly + const [aDaiPriceLow3, aDaiPriceHigh3] = await aDaiCollateral.price() + const aDaiRefPerTok3: BigNumber = await aDaiCollateral.refPerTok() + + // Check rates and price increase + expect(aDaiPriceLow3).to.be.gt(aDaiPriceLow2) + expect(aDaiPriceHigh3).to.be.gt(aDaiPriceHigh2) + expect(aDaiRefPerTok3).to.be.gt(aDaiRefPerTok2) + + answer = await chainlinkFeed.latestAnswer() + + await expectPrice( + aDaiCollateral.address, + answer + .mul(10 ** 10) + .mul(aDaiRefPerTok3) + .div(fp(1)), + ORACLE_ERROR, + true, + bn('1e5') + ) + expect(aDaiRefPerTok3).to.be.closeTo(fp('1.217'), fp('0.001')) + + // Check total asset value increased + const totalAssetValue3: BigNumber = await facadeTest.callStatic.totalAssetValue( + rToken.address + ) + expect(totalAssetValue3).to.be.gt(totalAssetValue2) + + // Redeem Rtokens with the updated rates + await expect(rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce())).to.emit( + rToken, + 'Redemption' + ) + + // Check funds were transferred + expect(await rToken.balanceOf(addr1.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(0) + + // Check balances - Fewer aTokens should have been sent to the user + const newBalanceAddr1aDai: BigNumber = await staticAToken.balanceOf(addr1.address) + + // Check received tokens represent ~10K in value at current prices + expect(newBalanceAddr1aDai.sub(balanceAddr1aDai)).to.be.closeTo(bn('8212.4e18'), fp('0.1')) + + // Check remainders in Backing Manager + expect(await staticAToken.balanceOf(backingManager.address)).to.be.closeTo( + bn('1165.8e18'), + fp('0.1') + ) + + // Check total asset value (remainder) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( + bn('1420.0e18'), + fp('0.1') + ) + }) + }) + + // Note: Even if the collateral does not provide reward tokens, this test should be performed to check that + // claiming calls throughout the protocol are handled correctly and do not revert. + describe('Rewards', () => { + it('Should be able to claim rewards (if applicable)', async () => { + const MIN_ISSUANCE_PER_BLOCK = bn('10000e18') + const issueAmount: BigNumber = MIN_ISSUANCE_PER_BLOCK + + // Try to claim rewards at this point - Nothing for Backing Manager + expect(await stkAave.balanceOf(backingManager.address)).to.equal(0) + + await expectEvents(backingManager.claimRewards(), [ + { + contract: staticAToken, + name: 'RewardsClaimed', + args: [stkAave.address, anyValue], + emitted: true, + }, + ]) + + // No rewards so far + expect(await stkAave.balanceOf(backingManager.address)).to.equal(0) + + // Provide approvals for issuances + await staticAToken.connect(addr1).approve(rToken.address, issueAmount.mul(100)) + + await advanceTime(3600) + await advanceBlocks(300) + + // Issue rTokens + await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') + + // Check RTokens issued to user + expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) + + // Now we can claim rewards - check initial balance still 0 + expect(await stkAave.balanceOf(backingManager.address)).to.equal(0) + + // Advance Time + await advanceTime(12000) + await advanceBlocks(1000) + + // Claim rewards + await expect(backingManager.claimRewards()).to.emit(staticAToken, 'RewardsClaimed') + + // Check rewards both in stkAAVE and stkAAVE + const rewardsstkAAVE1: BigNumber = await stkAave.balanceOf(backingManager.address) + + expect(rewardsstkAAVE1).to.be.gt(0) + + // Keep moving time + await advanceTime(3600) + + // Get additional rewards + await expect(backingManager.claimRewards()).to.emit(staticAToken, 'RewardsClaimed') + + const rewardsstkAAVE2: BigNumber = await stkAave.balanceOf(backingManager.address) + + expect(rewardsstkAAVE2.sub(rewardsstkAAVE1)).to.be.gt(0) + }) + }) + + describe('Price Handling', () => { + it('Should handle invalid/stale Price', async () => { + // Does not revert with stale price + await advanceTime(ORACLE_TIMEOUT.toString()) + + // stkAAVEound + await expectUnpriced(aDaiCollateral.address) + + // Refresh should mark status IFFY + await aDaiCollateral.refresh() + expect(await aDaiCollateral.status()).to.equal(CollateralStatus.IFFY) + + // aTokens Collateral with no price + const nonpriceCtokenCollateral: ATokenFiatCollateral = await ( + await ethers.getContractFactory('ATokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: NO_PRICE_DATA_FEED, + oracleError: ORACLE_ERROR, + erc20: staticAToken.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold, + delayUntilDefault, + }, + REVENUE_HIDING + ) + + // aTokens - Collateral with no price info should revert + await expect(nonpriceCtokenCollateral.price()).to.be.reverted + + // Refresh should also revert - status is not modified + await expect(nonpriceCtokenCollateral.refresh()).to.be.reverted + expect(await nonpriceCtokenCollateral.status()).to.equal(CollateralStatus.SOUND) + + // Does not revert with zero price + const zeropriceCtokenCollateral: ATokenFiatCollateral = await ( + await ethers.getContractFactory('ATokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: staticAToken.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold, + delayUntilDefault, + }, + REVENUE_HIDING + ) + + await setOraclePrice(zeropriceCtokenCollateral.address, bn(0)) + + // Does not revert with zero price + await expectPrice(zeropriceCtokenCollateral.address, bn('0'), bn('0'), false) + + // Refresh should mark status IFFY + await zeropriceCtokenCollateral.refresh() + expect(await zeropriceCtokenCollateral.status()).to.equal(CollateralStatus.IFFY) + }) + }) + + // Note: Here the idea is to test all possible statuses and check all possible paths to default + // soft default = SOUND -> IFFY -> DISABLED due to sustained misbehavior + // hard default = SOUND -> DISABLED due to an invariant violation + // This may require to deploy some mocks to be able to force some of these situations + describe('Collateral Status', () => { + // Test for soft default + it('Updates status in case of soft default', async () => { + // Redeploy plugin using a Chainlink mock feed where we can change the price + const newCDaiCollateral: ATokenFiatCollateral = await ( + await ethers.getContractFactory('ATokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: await aDaiCollateral.erc20(), + maxTradeVolume: await aDaiCollateral.maxTradeVolume(), + oracleTimeout: await aDaiCollateral.oracleTimeout(), + targetName: await aDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await aDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + + // Check initial state + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newCDaiCollateral.whenDefault()).to.equal(MAX_UINT48) + + // Depeg one of the underlying tokens - Reducing price 20% + await setOraclePrice(newCDaiCollateral.address, bn('8e7')) // -20% + + // Force updates - Should update whenDefault and status + await expect(newCDaiCollateral.refresh()) + .to.emit(newCDaiCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.IFFY) + + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()).add( + delayUntilDefault + ) + expect(await newCDaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + + // Move time forward past delayUntilDefault + await advanceTime(Number(delayUntilDefault)) + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + + // Nothing changes if attempt to refresh after default + // CToken + const prevWhenDefault: BigNumber = await newCDaiCollateral.whenDefault() + await expect(newCDaiCollateral.refresh()).to.not.emit( + newCDaiCollateral, + 'CollateralStatusChanged' + ) + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await newCDaiCollateral.whenDefault()).to.equal(prevWhenDefault) + }) + + // Test for hard default + it('Updates status in case of hard default', async () => { + // Note: In this case requires to use a CToken mock to be able to change the rate + const sATokenMockFactory: ContractFactory = await ethers.getContractFactory( + 'StaticATokenMock' + ) + const aDaiErc20 = await ethers.getContractAt('ERC20Mock', aDai.address) + const symbol = await aDaiErc20.symbol() + const saDaiMock: StaticATokenMock = ( + await sATokenMockFactory.deploy(symbol + ' Token', symbol, dai.address) + ) + // Set initial exchange rate to the new aDai Mock + await saDaiMock.setExchangeRate(fp('0.02')) + + // Redeploy plugin using the new aDai mock + const newCDaiCollateral: ATokenFiatCollateral = await ( + await ethers.getContractFactory('ATokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await aDaiCollateral.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: saDaiMock.address, + maxTradeVolume: await aDaiCollateral.maxTradeVolume(), + oracleTimeout: await aDaiCollateral.oracleTimeout(), + targetName: await aDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await aDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + await newCDaiCollateral.refresh() + + // Check initial state + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newCDaiCollateral.whenDefault()).to.equal(MAX_UINT48) + + // Decrease rate for aDAI, will disable collateral immediately + await saDaiMock.setExchangeRate(fp('0.019')) + + // Force updates - Should update whenDefault and status for Atokens/aTokens + await expect(newCDaiCollateral.refresh()) + .to.emit(newCDaiCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) + + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) + expect(await newCDaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + }) + + it('Reverts if oracle reverts or runs out of gas, maintains status', async () => { + const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( + 'InvalidMockV3Aggregator' + ) + const invalidChainlinkFeed: InvalidMockV3Aggregator = ( + await InvalidMockV3AggregatorFactory.deploy(8, bn('1e8')) + ) + + const invalidCTokenCollateral: ATokenFiatCollateral = ( + await ATokenFiatCollateralFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: invalidChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: await aDaiCollateral.erc20(), + maxTradeVolume: await aDaiCollateral.maxTradeVolume(), + oracleTimeout: await aDaiCollateral.oracleTimeout(), + targetName: await aDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await aDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + ) + + // Reverting with no reason + await invalidChainlinkFeed.setSimplyRevert(true) + await expect(invalidCTokenCollateral.refresh()).to.be.reverted + expect(await invalidCTokenCollateral.status()).to.equal(CollateralStatus.SOUND) + + // Runnning out of gas (same error) + await invalidChainlinkFeed.setSimplyRevert(false) + await expect(invalidCTokenCollateral.refresh()).to.be.reverted + expect(await invalidCTokenCollateral.status()).to.equal(CollateralStatus.SOUND) + }) + }) +}) diff --git a/test/integration/StaticATokenLM.test.ts b/test/plugins/individual-collateral/aave/StaticATokenLM.test.ts similarity index 98% rename from test/integration/StaticATokenLM.test.ts rename to test/plugins/individual-collateral/aave/StaticATokenLM.test.ts index 39ffef66fa..eee4409004 100644 --- a/test/integration/StaticATokenLM.test.ts +++ b/test/plugins/individual-collateral/aave/StaticATokenLM.test.ts @@ -3,9 +3,9 @@ import { expect } from 'chai' import bnjs from 'bignumber.js' import { formatEther, parseEther, _TypedDataEncoder } from 'ethers/lib/utils' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import forkBlockNumber from './fork-block-numbers' -import { whileImpersonating } from '../utils/impersonation' -import { advanceTime } from '../utils/time' +import forkBlockNumber from '../../../integration/fork-block-numbers' +import { whileImpersonating } from '../../../utils/impersonation' +import { advanceTime } from '../../../utils/time' import { buildPermitParams, buildMetaDepositParams, @@ -13,12 +13,12 @@ import { evmRevert, evmSnapshot, waitForTx, -} from './utils' +} from '../../../integration/utils' import { BigNumber, ContractFactory, providers, Signer, utils } from 'ethers' -import { rayDiv, rayMul } from './ray-math' -import { getChainId } from '../../common/blockchain-utils' -import { networkConfig } from '../../common/configuration' -import { MAX_UINT256, ZERO_ADDRESS } from '../../common/constants' +import { rayDiv, rayMul } from '../../../integration/ray-math' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { MAX_UINT256, ZERO_ADDRESS } from '../../../../common/constants' import { ERC20Mock, IAaveIncentivesController, @@ -27,7 +27,7 @@ import { IWETH, SelfdestructTransfer, StaticATokenLM, -} from '../../typechain' +} from '../../../../typechain' import { useEnv } from '#/utils/env' let chainId: number @@ -297,11 +297,19 @@ describeFork('StaticATokenLM: aToken wrapper with static balances and liquidity const ctxtAfterWithdrawal = await getContext(ctxtParams) // Claiming the rewards - await waitForTx(await staticAToken.connect(userSigner).claimRewards(userSigner._address, false)) + await waitForTx( + await staticAToken + .connect(userSigner) + ['claimRewards(address,bool)'](userSigner._address, false) + ) const ctxtAfterClaimNoForce = await getContext(ctxtParams) - await waitForTx(await staticAToken.connect(userSigner).claimRewards(userSigner._address, true)) + await waitForTx( + await staticAToken + .connect(userSigner) + ['claimRewards(address,bool)'](userSigner._address, true) + ) const ctxtAfterClaimForce = await getContext(ctxtParams) @@ -389,13 +397,21 @@ describeFork('StaticATokenLM: aToken wrapper with static balances and liquidity const ctxtAfterWithdrawal = await getContext(ctxtParams) // Claim - await waitForTx(await staticAToken.connect(userSigner).claimRewards(userSigner._address, false)) + await waitForTx( + await staticAToken + .connect(userSigner) + ['claimRewards(address,bool)'](userSigner._address, false) + ) const ctxtAfterClaim = await getContext(ctxtParams) await waitForTx(await staticAToken.collectAndUpdateRewards()) const ctxtAfterUpdate = await getContext(ctxtParams) - await waitForTx(await staticAToken.connect(userSigner).claimRewards(userSigner._address, false)) + await waitForTx( + await staticAToken + .connect(userSigner) + ['claimRewards(address,bool)'](userSigner._address, false) + ) const ctxtAfterClaim2 = await getContext(ctxtParams) expect(ctxtInitial.userStaticATokenBalance).to.be.eq(0) @@ -1061,7 +1077,9 @@ describeFork('StaticATokenLM: aToken wrapper with static balances and liquidity // Claim await waitForTx( - await staticAToken.connect(user2Signer).claimRewards(user2Signer._address, true) + await staticAToken + .connect(user2Signer) + ['claimRewards(address,bool)'](user2Signer._address, true) ) const ctxtAfterClaim = await getContext(ctxtParams) @@ -1688,11 +1706,15 @@ describeFork('StaticATokenLM: aToken wrapper with static balances and liquidity const pendingRewards3 = await staticAToken.getClaimableRewards(userSigner._address) const userBalance3 = await stkAave.balanceOf(userSigner._address) - await staticAToken.connect(user2Signer).claimRewards(userSigner._address, true) + await staticAToken + .connect(user2Signer) + ['claimRewards(address,bool)'](userSigner._address, true) const userBalance4 = await stkAave.balanceOf(userSigner._address) await waitForTx( - await staticAToken.connect(userSigner).claimRewards(user2Signer._address, true) + await staticAToken + .connect(userSigner) + ['claimRewards(address,bool)'](user2Signer._address, true) ) const pendingRewards5 = await staticAToken.getClaimableRewards(userSigner._address) @@ -2145,7 +2167,9 @@ describeFork('StaticATokenLM: aToken wrapper with static balances and liquidity pendingRewards.push(pendingReward) } for (let i = 0; i < users.length; i++) { - await waitForTx(await staticAToken.connect(users[i]).claimRewards(receiverAddress, false)) + await waitForTx( + await staticAToken.connect(users[i])['claimRewards(address,bool)'](receiverAddress, false) + ) sum = sum.add(pendingRewards[i]) expect(await stkAave.balanceOf(await receiverAddress)).to.be.eq(sum) } diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index f3d910dd63..0d473115b6 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -134,6 +134,8 @@ export default function fn( }) describe('rewards', () => { + // TODO: these tests will be deprecated along with the claimRewards() + // function on collateral plugins (4/18/23) beforeEach(async () => { await beforeEachRewardsTest(ctx) }) @@ -142,18 +144,37 @@ export default function fn( await collateral.claimRewards() }) - itClaimsRewards('claims rewards', async () => { + itClaimsRewards('claims rewards (via collateral.claimRewards())', async () => { const amount = bn('20').mul(bn(10).pow(await ctx.tok.decimals())) await mintCollateralTo(ctx, amount, alice, collateral.address) - + console.log( + alice.address, + await ctx.tok.balanceOf(alice.address), + await ctx.tok.balanceOf(collateral.address) + ) await advanceBlocks(1000) await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) const balBefore = await (ctx.rewardToken as IERC20Metadata).balanceOf(collateral.address) - await expect(collateral.claimRewards()).to.emit(collateral, 'RewardsClaimed') + await expect(collateral.claimRewards()).to.emit(ctx.tok, 'RewardsClaimed') const balAfter = await (ctx.rewardToken as IERC20Metadata).balanceOf(collateral.address) expect(balAfter).gt(balBefore) }) + + itClaimsRewards('claims rewards (via erc20.claimRewards())', async () => { + const rewardable = await ethers.getContractAt('IRewardable', ctx.tok.address) + + const amount = bn('20').mul(bn(10).pow(await ctx.tok.decimals())) + await mintCollateralTo(ctx, amount, alice, alice.address) + + await advanceBlocks(1000) + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) + + const balBefore = await (ctx.rewardToken as IERC20Metadata).balanceOf(alice.address) + await expect(rewardable.connect(alice).claimRewards()).to.emit(ctx.tok, 'RewardsClaimed') + const balAfter = await (ctx.rewardToken as IERC20Metadata).balanceOf(alice.address) + expect(balAfter).gt(balBefore) + }) }) describe('prices', () => { @@ -281,7 +302,10 @@ export default function fn( }) itHasRevenueHiding('does revenue hiding correctly', async () => { - ctx.collateral = await deployCollateral({ revenueHiding: fp('0.01') }) + ctx.collateral = await deployCollateral({ + erc20: ctx.tok.address, + revenueHiding: fp('0.01'), + }) // Should remain SOUND after a 1% decrease await reduceRefPerTok(ctx, 1) // 1% decrease diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index d7f66cccfa..ecfdf11fcc 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -4,7 +4,7 @@ import { expect } from 'chai' import { BigNumber, ContractFactory } from 'ethers' import hre, { ethers } from 'hardhat' import { IMPLEMENTATION, ORACLE_ERROR, PRICE_TIMEOUT, REVENUE_HIDING } from '../../../fixtures' -import { defaultFixture, ORACLE_TIMEOUT } from '../fixtures' +import { DefaultFixture, Fixture, getDefaultFixture, ORACLE_TIMEOUT } from '../fixtures' import { getChainId } from '../../../../common/blockchain-utils' import forkBlockNumber from '../../../integration/fork-block-numbers' import { @@ -32,6 +32,7 @@ import { ComptrollerMock, CTokenFiatCollateral, CTokenMock, + CTokenVault, ERC20Mock, FacadeRead, FacadeTest, @@ -39,6 +40,7 @@ import { IAssetRegistry, InvalidMockV3Aggregator, MockV3Aggregator, + RewardableERC20Vault, RTokenAsset, TestIBackingManager, TestIBasketHandler, @@ -47,6 +49,7 @@ import { TestIRToken, } from '../../../../typechain' import { useEnv } from '#/utils/env' +import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' // Setup test environment const setup = async (blockNumber: number) => { @@ -78,6 +81,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Tokens/Assets let dai: ERC20Mock let cDai: CTokenMock + let cDaiVault: RewardableERC20Vault let cDaiCollateral: CTokenFiatCollateral let compToken: ERC20Mock let compAsset: Asset @@ -139,9 +143,11 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi let MockV3AggregatorFactory: ContractFactory let mockChainlinkFeed: MockV3Aggregator + let defaultFixture: Fixture + before(async () => { await setup(forkBlockNumber['asset-plugins']) - + defaultFixture = await getDefaultFixture('ctoken') chainId = await getChainId(hre) if (!networkConfig[chainId]) { throw new Error(`Missing network configuration for ${hre.network.name}`) @@ -187,6 +193,16 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi ) ) + const cDaiVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenVault') + cDaiVault = ( + await cDaiVaultFactory.deploy( + cDai.address, + 'cDAI RToken Vault', + 'rv_cDAI', + comptroller.address + ) + ) + // Deploy cDai collateral plugin CTokenCollateralFactory = await ethers.getContractFactory('CTokenFiatCollateral') cDaiCollateral = await CTokenCollateralFactory.deploy( @@ -194,15 +210,14 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi priceTimeout: PRICE_TIMEOUT, chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI as string, oracleError: ORACLE_ERROR, - erc20: cDai.address, + erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, }, - REVENUE_HIDING, - comptroller.address + REVENUE_HIDING ) // Setup balances for addr1 - Transfer from Mainnet holder @@ -212,6 +227,11 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await cDai.connect(cdaiSigner).transfer(addr1.address, toBNDecimals(initialBal, 8)) }) + const initialBalcDai = await cDai.balanceOf(addr1.address) + + await cDai.connect(addr1).approve(cDaiVault.address, initialBalcDai) + await cDaiVault.connect(addr1).deposit(initialBalcDai, addr1.address) + // Set parameters const rTokenConfig: IRTokenConfig = { name: 'RTKN RToken', @@ -293,8 +313,9 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // cDAI (CTokenFiatCollateral) expect(await cDaiCollateral.isCollateral()).to.equal(true) expect(await cDaiCollateral.referenceERC20Decimals()).to.equal(await dai.decimals()) - expect(await cDaiCollateral.erc20()).to.equal(cDai.address) + expect(await cDaiCollateral.erc20()).to.equal(cDaiVault.address) expect(await cDai.decimals()).to.equal(8) + expect(await cDaiVault.decimals()).to.equal(17) expect(await cDaiCollateral.targetName()).to.equal(ethers.utils.formatBytes32String('USD')) expect(await cDaiCollateral.refPerTok()).to.be.closeTo(fp('0.022'), fp('0.001')) expect(await cDaiCollateral.targetPerRef()).to.equal(fp('1')) @@ -310,9 +331,13 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi ) // close to $0.022 cents // Check claim data + await expect(cDaiVault.claimRewards()) + .to.emit(cDaiVault, 'RewardsClaimed') + .withArgs(compToken.address, anyValue) + await expect(cDaiCollateral.claimRewards()) - .to.emit(cDaiCollateral, 'RewardsClaimed') - .withArgs(compToken.address, 0) + .to.emit(cDaiVault, 'RewardsClaimed') + .withArgs(compToken.address, anyValue) expect(await cDaiCollateral.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) // Should setup contracts @@ -326,7 +351,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(ERC20s[0]).to.equal(rToken.address) expect(ERC20s[1]).to.equal(rsr.address) expect(ERC20s[2]).to.equal(compToken.address) - expect(ERC20s[3]).to.equal(cDai.address) + expect(ERC20s[3]).to.equal(cDaiVault.address) expect(ERC20s.length).to.eql(4) // Assets @@ -344,7 +369,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Basket expect(await basketHandler.fullyCollateralized()).to.equal(true) const backing = await facade.basketTokens(rToken.address) - expect(backing[0]).to.equal(cDai.address) + expect(backing[0]).to.equal(cDaiVault.address) expect(backing.length).to.equal(1) // Check other values @@ -355,7 +380,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Check RToken price const issueAmount: BigNumber = bn('10000e18') - await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) await advanceTime(3600) await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') await expectRTokenPrice( @@ -384,29 +409,9 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi defaultThreshold, delayUntilDefault, }, - REVENUE_HIDING, - ZERO_ADDRESS + REVENUE_HIDING ) ).to.be.revertedWith('missing erc20') - - // Comptroller - await expect( - CTokenCollateralFactory.deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI as string, - oracleError: ORACLE_ERROR, - erc20: cDai.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold, - delayUntilDefault, - }, - REVENUE_HIDING, - ZERO_ADDRESS - ) - ).to.be.revertedWith('comptroller missing') }) }) @@ -418,7 +423,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi const issueAmount: BigNumber = MIN_ISSUANCE_PER_BLOCK // instant issuance // Provide approvals for issuances - await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) await advanceTime(3600) @@ -429,7 +434,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) // Store Balances after issuance - const balanceAddr1cDai: BigNumber = await cDai.balanceOf(addr1.address) + const balanceAddr1cDai: BigNumber = await cDaiVault.balanceOf(addr1.address) // Check rates and prices const [cDaiPriceLow1, cDaiPriceHigh1] = await cDaiCollateral.price() // ~ 0.022015 cents @@ -526,13 +531,16 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(await rToken.totalSupply()).to.equal(0) // Check balances - Fewer cTokens should have been sent to the user - const newBalanceAddr1cDai: BigNumber = await cDai.balanceOf(addr1.address) + const newBalanceAddr1cDai: BigNumber = await cDaiVault.balanceOf(addr1.address) // Check received tokens represent ~10K in value at current prices - expect(newBalanceAddr1cDai.sub(balanceAddr1cDai)).to.be.closeTo(bn('303570e8'), bn('8e7')) // ~0.03294 * 303571 ~= 10K (100% of basket) + expect(newBalanceAddr1cDai.sub(balanceAddr1cDai)).to.be.closeTo(bn('303570e17'), bn('8e16')) // ~0.03294 * 303571 ~= 10K (100% of basket) // Check remainders in Backing Manager - expect(await cDai.balanceOf(backingManager.address)).to.be.closeTo(bn(150663e8), bn('5e7')) // ~= 4962.8 usd in value + expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo( + bn('150663e17'), + bn('5e16') + ) // ~= 4962.8 usd in value // Check total asset value (remainder) expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( @@ -554,9 +562,9 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: cDaiVault, name: 'RewardsClaimed', - args: [compToken.address, bn(0)], + args: [compToken.address, anyValue], emitted: true, }, ]) @@ -565,7 +573,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(await compToken.balanceOf(backingManager.address)).to.equal(0) // Provide approvals for issuances - await cDai.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) + await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) await advanceTime(3600) @@ -582,7 +590,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await advanceTime(8000) // Claim rewards - await expect(backingManager.claimRewards()).to.emit(backingManager, 'RewardsClaimed') + await expect(backingManager.claimRewards()).to.emit(cDaiVault, 'RewardsClaimed') // Check rewards both in COMP and stkAAVE const rewardsCOMP1: BigNumber = await compToken.balanceOf(backingManager.address) @@ -593,7 +601,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await advanceTime(3600) // Get additional rewards - await expect(backingManager.claimRewards()).to.emit(backingManager, 'RewardsClaimed') + await expect(backingManager.claimRewards()).to.emit(cDaiVault, 'RewardsClaimed') const rewardsCOMP2: BigNumber = await compToken.balanceOf(backingManager.address) @@ -621,15 +629,14 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi priceTimeout: PRICE_TIMEOUT, chainlinkFeed: NO_PRICE_DATA_FEED, oracleError: ORACLE_ERROR, - erc20: cDai.address, + erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, }, - REVENUE_HIDING, - comptroller.address + REVENUE_HIDING ) // CTokens - Collateral with no price info should revert @@ -647,15 +654,14 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi priceTimeout: PRICE_TIMEOUT, chainlinkFeed: mockChainlinkFeed.address, oracleError: ORACLE_ERROR, - erc20: cDai.address, + erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, }, - REVENUE_HIDING, - comptroller.address + REVENUE_HIDING ) await setOraclePrice(zeropriceCtokenCollateral.address, bn(0)) @@ -691,8 +697,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi defaultThreshold, delayUntilDefault: await cDaiCollateral.delayUntilDefault(), }, - REVENUE_HIDING, - comptroller.address + REVENUE_HIDING ) // Check initial state @@ -739,6 +744,16 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Set initial exchange rate to the new cDai Mock await cDaiMock.setExchangeRate(fp('0.02')) + const cDaiVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenVault') + const cDaiMockVault = ( + await cDaiVaultFactory.deploy( + cDaiMock.address, + 'cDAI Mock RToken Vault', + 'rv_mock_cDAI', + comptroller.address + ) + ) + // Redeploy plugin using the new cDai mock const newCDaiCollateral: CTokenFiatCollateral = await ( await ethers.getContractFactory('CTokenFiatCollateral') @@ -747,15 +762,14 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi priceTimeout: PRICE_TIMEOUT, chainlinkFeed: await cDaiCollateral.chainlinkFeed(), oracleError: ORACLE_ERROR, - erc20: cDaiMock.address, + erc20: cDaiMockVault.address, maxTradeVolume: await cDaiCollateral.maxTradeVolume(), oracleTimeout: await cDaiCollateral.oracleTimeout(), targetName: await cDaiCollateral.targetName(), defaultThreshold, delayUntilDefault: await cDaiCollateral.delayUntilDefault(), }, - REVENUE_HIDING, - comptroller.address + REVENUE_HIDING ) await newCDaiCollateral.refresh() @@ -797,8 +811,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi defaultThreshold, delayUntilDefault: await cDaiCollateral.delayUntilDefault(), }, - REVENUE_HIDING, - comptroller.address + REVENUE_HIDING ) ) diff --git a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts index f7caf424f7..3ea997da0d 100644 --- a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts @@ -6,7 +6,7 @@ import { CollateralStatus, } from '../pluginTestTypes' import { mintWcUSDC, makewCSUDC, resetFork, enableRewardsAccrual } from './helpers' -import { ethers } from 'hardhat' +import { ethers, network } from 'hardhat' import { ContractFactory, BigNumberish, BigNumber } from 'ethers' import { ERC20Mock, @@ -44,7 +44,9 @@ import { DELAY_UNTIL_DEFAULT, REWARDS, USDC, + COMET_EXT, } from './constants' +import { setCode } from '@nomicfoundation/hardhat-network-helpers' /* Define interfaces @@ -54,7 +56,6 @@ interface CometCollateralFixtureContext extends CollateralFixtureContext { cusdcV3: CometInterface wcusdcV3: ICusdcV3Wrapper usdc: ERC20Mock - wcusdcV3Mock: CusdcV3WrapperMock } interface CometCollateralFixtureContextMockComet extends CollateralFixtureContext { @@ -146,18 +147,7 @@ const makeCollateralFixtureContext = ( const cusdcV3 = fix.cusdcV3 const { wcusdcV3, usdc } = fix - const CusdcV3WrapperMockFactory = ( - await ethers.getContractFactory('CusdcV3WrapperMock') - ) - - const wcusdcV3Mock = ( - ((await CusdcV3WrapperMockFactory.deploy(wcusdcV3.address)) as ICusdcV3WrapperMock) - ) - const realMock = (await ethers.getContractAt( - 'ICusdcV3WrapperMock', - wcusdcV3Mock.address - )) as ICusdcV3WrapperMock - collateralOpts.erc20 = realMock.address + collateralOpts.erc20 = wcusdcV3.address const collateral = await deployCollateral(collateralOpts) const rewardToken = await ethers.getContractAt('ERC20Mock', COMP) @@ -166,8 +156,7 @@ const makeCollateralFixtureContext = ( collateral, chainlinkFeed, cusdcV3, - wcusdcV3: realMock, - wcusdcV3Mock, + wcusdcV3, usdc, tok: wcusdcV3, rewardToken, @@ -238,7 +227,7 @@ const mintCollateralTo: MintCollateralFunc = asyn user: SignerWithAddress, recipient: string ) => { - await mintWcUSDC(ctx.usdc, ctx.cusdcV3, ctx.wcusdcV3, user, amount, recipient) + await mintWcUSDC(ctx.usdc, ctx.cusdcV3, ctx.tok, user, amount, recipient) } const reduceTargetPerRef = async ( @@ -260,11 +249,26 @@ const increaseTargetPerRef = async ( } const reduceRefPerTok = async (ctx: CometCollateralFixtureContext, pctDecrease: BigNumberish) => { - const currentExchangeRate = await ctx.wcusdcV3.exchangeRate() - await ctx.wcusdcV3Mock.setMockExchangeRate( - true, - currentExchangeRate.sub(currentExchangeRate.mul(pctDecrease).div(100)) - ) + const totalsBasic = await ctx.cusdcV3.totalsBasic() + const bsi = totalsBasic.baseSupplyIndex + + // save old bytecode + const oldBytecode = await network.provider.send('eth_getCode', [COMET_EXT]) + + const mockFactory = await ethers.getContractFactory('CometExtMock') + const mock = await mockFactory.deploy() + const bytecode = await network.provider.send('eth_getCode', [mock.address]) + await setCode(COMET_EXT, bytecode) + + const cometAsMock = await ethers.getContractAt('CometExtMock', ctx.cusdcV3.address) + await cometAsMock.setBaseSupplyIndex(bsi.sub(bsi.mul(pctDecrease).div(100))) + + await setCode(COMET_EXT, oldBytecode) + // const currentExchangeRate = await ctx.wcusdcV3.exchangeRate() + // await ctx.wcusdcV3Mock.setMockExchangeRate( + // true, + // currentExchangeRate.sub(currentExchangeRate.mul(pctDecrease).div(100)) + // ) } const increaseRefPerTok = async () => { diff --git a/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts b/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts index c0c4ac9f9d..a09f88fbbc 100644 --- a/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts @@ -494,11 +494,11 @@ describeFork('Wrapped CUSDCv3', () => { expect(await wcusdcV3.isAllowed(bob.address, don.address)).to.eq(true) await expect(wcusdcV3.connect(don).claimTo(bob.address, bob.address)).to.emit( wcusdcV3, - 'RewardClaimed' + 'RewardsClaimed' ) }) - it('claims rewards and sends to claimer', async () => { + it('claims rewards and sends to claimer (claimTo)', async () => { const compToken = await ethers.getContractAt('ERC20Mock', COMP) expect(await compToken.balanceOf(wcusdcV3.address)).to.equal(0) await advanceTime(1000) @@ -506,11 +506,21 @@ describeFork('Wrapped CUSDCv3', () => { await expect(wcusdcV3.connect(bob).claimTo(bob.address, bob.address)).to.emit( wcusdcV3, - 'RewardClaimed' + 'RewardsClaimed' ) expect(await compToken.balanceOf(bob.address)).to.be.greaterThan(0) }) + it('claims rewards and sends to claimer (claimRewards)', async () => { + const compToken = await ethers.getContractAt('ERC20Mock', COMP) + expect(await compToken.balanceOf(wcusdcV3.address)).to.equal(0) + await advanceTime(1000) + await enableRewardsAccrual(cusdcV3) + + await expect(wcusdcV3.connect(bob).claimRewards()).to.emit(wcusdcV3, 'RewardsClaimed') + expect(await compToken.balanceOf(bob.address)).to.be.greaterThan(0) + }) + it('claims rewards by participation', async () => { const compToken = await ethers.getContractAt('ERC20Mock', COMP) diff --git a/test/plugins/individual-collateral/compoundv3/constants.ts b/test/plugins/individual-collateral/compoundv3/constants.ts index c7844802aa..0847fe2ae9 100644 --- a/test/plugins/individual-collateral/compoundv3/constants.ts +++ b/test/plugins/individual-collateral/compoundv3/constants.ts @@ -11,6 +11,7 @@ export const USDC = networkConfig['31337'].tokens.USDC as string export const USDC_HOLDER = '0x0a59649758aa4d66e25f08dd01271e891fe52199' export const COMET_CONFIGURATOR = '0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3' export const COMET_PROXY_ADMIN = '0x1EC63B5883C3481134FD50D5DAebc83Ecd2E8779' +export const COMET_EXT = '0x285617313887d43256F852cAE0Ee4de4b68D45B0' export const PRICE_TIMEOUT = bn(604800) // 1 week export const ORACLE_TIMEOUT = bn(86400) // 24 hours in seconds diff --git a/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts b/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts index dc16d43a2b..e73d306f90 100644 --- a/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts +++ b/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts @@ -450,7 +450,7 @@ describeFork(`Collateral: Convex - Stable Metapool (MIM+3Pool)`, () => { await expect(collateral.claimRewards()).to.not.be.reverted }) - it('claims rewards', async () => { + it('claims rewards (plugin)', async () => { const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) await mintCollateralTo(ctx, amount, alice, collateral.address) @@ -459,12 +459,28 @@ describeFork(`Collateral: Convex - Stable Metapool (MIM+3Pool)`, () => { const crvBefore = await crv.balanceOf(collateral.address) const cvxBefore = await cvx.balanceOf(collateral.address) - await expect(collateral.claimRewards()).to.emit(collateral, 'RewardsClaimed') + await expect(collateral.claimRewards()).to.emit(ctx.wPool, 'RewardsClaimed') const crvAfter = await crv.balanceOf(collateral.address) const cvxAfter = await cvx.balanceOf(collateral.address) expect(crvAfter).gt(crvBefore) expect(cvxAfter).gt(cvxBefore) }) + + it('claims rewards (wrapper)', async () => { + const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) + await mintCollateralTo(ctx, amount, alice, alice.address) + + await advanceBlocks(1000) + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) + + const crvBefore = await crv.balanceOf(alice.address) + const cvxBefore = await cvx.balanceOf(alice.address) + await expect(ctx.wPool.connect(alice).claimRewards()).to.emit(ctx.wPool, 'RewardsClaimed') + const crvAfter = await crv.balanceOf(alice.address) + const cvxAfter = await cvx.balanceOf(alice.address) + expect(crvAfter).gt(crvBefore) + expect(cvxAfter).gt(cvxBefore) + }) }) describe('prices', () => { diff --git a/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts index 44cac85b53..856c13bbc0 100644 --- a/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts @@ -435,7 +435,7 @@ describeFork(`Collateral: Convex - RToken Metapool (eUSD/fraxBP)`, () => { await expect(collateral.claimRewards()).to.not.be.reverted }) - it('claims rewards', async () => { + it('claims rewards (plugin)', async () => { const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) await mintCollateralTo(ctx, amount, alice, collateral.address) @@ -444,12 +444,28 @@ describeFork(`Collateral: Convex - RToken Metapool (eUSD/fraxBP)`, () => { const crvBefore = await crv.balanceOf(collateral.address) const cvxBefore = await cvx.balanceOf(collateral.address) - await expect(collateral.claimRewards()).to.emit(collateral, 'RewardsClaimed') + await expect(collateral.claimRewards()).to.emit(ctx.wPool, 'RewardsClaimed') const crvAfter = await crv.balanceOf(collateral.address) const cvxAfter = await cvx.balanceOf(collateral.address) expect(crvAfter).gt(crvBefore) expect(cvxAfter).gt(cvxBefore) }) + + it('claims rewards (wrapper)', async () => { + const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) + await mintCollateralTo(ctx, amount, alice, alice.address) + + await advanceBlocks(1000) + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) + + const crvBefore = await crv.balanceOf(alice.address) + const cvxBefore = await cvx.balanceOf(alice.address) + await expect(ctx.wPool.connect(alice).claimRewards()).to.emit(ctx.wPool, 'RewardsClaimed') + const crvAfter = await crv.balanceOf(alice.address) + const cvxAfter = await cvx.balanceOf(alice.address) + expect(crvAfter).gt(crvBefore) + expect(cvxAfter).gt(cvxBefore) + }) }) describe('prices', () => { diff --git a/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts b/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts index 35e331c292..9cb9f7acbb 100644 --- a/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts +++ b/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts @@ -442,7 +442,7 @@ describeFork(`Collateral: Convex - Stable (3Pool)`, () => { await expect(collateral.claimRewards()).to.not.be.reverted }) - it('claims rewards', async () => { + it('claims rewards (plugin)', async () => { const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) await mintCollateralTo(ctx, amount, alice, collateral.address) @@ -451,12 +451,28 @@ describeFork(`Collateral: Convex - Stable (3Pool)`, () => { const crvBefore = await crv.balanceOf(collateral.address) const cvxBefore = await cvx.balanceOf(collateral.address) - await expect(collateral.claimRewards()).to.emit(collateral, 'RewardsClaimed') + await expect(collateral.claimRewards()).to.emit(ctx.w3Pool, 'RewardsClaimed') const crvAfter = await crv.balanceOf(collateral.address) const cvxAfter = await cvx.balanceOf(collateral.address) expect(crvAfter).gt(crvBefore) expect(cvxAfter).gt(cvxBefore) }) + + it('claims rewards (wrapper)', async () => { + const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) + await mintCollateralTo(ctx, amount, alice, alice.address) + + await advanceBlocks(1000) + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) + + const crvBefore = await crv.balanceOf(alice.address) + const cvxBefore = await cvx.balanceOf(alice.address) + await expect(ctx.w3Pool.connect(alice).claimRewards()).to.emit(ctx.w3Pool, 'RewardsClaimed') + const crvAfter = await crv.balanceOf(alice.address) + const cvxAfter = await cvx.balanceOf(alice.address) + expect(crvAfter).gt(crvBefore) + expect(cvxAfter).gt(cvxBefore) + }) }) describe('prices', () => { diff --git a/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts b/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts index 6fea7aa79f..cbbd8780a0 100644 --- a/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts +++ b/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts @@ -472,7 +472,7 @@ describeFork(`Collateral: Convex - Volatile`, () => { await expect(collateral.claimRewards()).to.not.be.reverted }) - it('claims rewards', async () => { + it('claims rewards (plugin)', async () => { const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) await mintCollateralTo(ctx, amount, alice, collateral.address) @@ -481,12 +481,28 @@ describeFork(`Collateral: Convex - Volatile`, () => { const crvBefore = await crv.balanceOf(collateral.address) const cvxBefore = await cvx.balanceOf(collateral.address) - await expect(collateral.claimRewards()).to.emit(collateral, 'RewardsClaimed') + await expect(collateral.claimRewards()).to.emit(ctx.w3Pool, 'RewardsClaimed') const crvAfter = await crv.balanceOf(collateral.address) const cvxAfter = await cvx.balanceOf(collateral.address) expect(crvAfter).gt(crvBefore) expect(cvxAfter).gt(cvxBefore) }) + + it('claims rewards (wrapper)', async () => { + const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) + await mintCollateralTo(ctx, amount, alice, alice.address) + + await advanceBlocks(1000) + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) + + const crvBefore = await crv.balanceOf(alice.address) + const cvxBefore = await cvx.balanceOf(alice.address) + await expect(ctx.w3Pool.connect(alice).claimRewards()).to.emit(ctx.w3Pool, 'RewardsClaimed') + const crvAfter = await crv.balanceOf(alice.address) + const cvxAfter = await cvx.balanceOf(alice.address) + expect(crvAfter).gt(crvBefore) + expect(cvxAfter).gt(cvxBefore) + }) }) describe('prices', () => { diff --git a/test/plugins/individual-collateral/fixtures.ts b/test/plugins/individual-collateral/fixtures.ts index 3f790ad281..5e6e9675e4 100644 --- a/test/plugins/individual-collateral/fixtures.ts +++ b/test/plugins/individual-collateral/fixtures.ts @@ -31,7 +31,7 @@ import { export const ORACLE_TIMEOUT = bn('500000000') // 5700d - large for tests only -type Fixture = () => Promise +export type Fixture = () => Promise interface RSRFixture { rsr: ERC20Mock @@ -57,7 +57,8 @@ async function gnosisFixture(): Promise { type RSRAndModuleFixture = RSRFixture & ModuleFixture -interface DefaultFixture extends RSRAndModuleFixture { +export interface DefaultFixture extends RSRAndModuleFixture { + salt: string deployer: TestIDeployer rsrAsset: Asset facade: FacadeRead @@ -67,150 +68,160 @@ interface DefaultFixture extends RSRAndModuleFixture { govParams: IGovParams } -export const defaultFixture: Fixture = async function (): Promise { - const { rsr } = await rsrFixture() - const { gnosis } = await gnosisFixture() - const chainId = await getChainId(hre) - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - - // Deploy FacadeRead - const FacadeReadFactory: ContractFactory = await ethers.getContractFactory('FacadeRead') - const facade = await FacadeReadFactory.deploy() - - // Deploy FacadeAct - const FacadeActFactory: ContractFactory = await ethers.getContractFactory('FacadeAct') - const facadeAct = await FacadeActFactory.deploy() +export const getDefaultFixture = async function (salt: string) { + const defaultFixture: Fixture = async function (): Promise { + const { rsr } = await rsrFixture() + const { gnosis } = await gnosisFixture() + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } - // Deploy FacadeTest - const FacadeTestFactory: ContractFactory = await ethers.getContractFactory('FacadeTest') - const facadeTest = await FacadeTestFactory.deploy() + // Deploy FacadeRead + const FacadeReadFactory: ContractFactory = await ethers.getContractFactory('FacadeRead') + const facade = await FacadeReadFactory.deploy() - // Deploy TradingLib external library - const TradingLibFactory: ContractFactory = await ethers.getContractFactory( - 'RecollateralizationLibP1' - ) - const tradingLib: RecollateralizationLibP1 = ( - await TradingLibFactory.deploy() - ) + // Deploy FacadeAct + const FacadeActFactory: ContractFactory = await ethers.getContractFactory('FacadeAct') + const facadeAct = await FacadeActFactory.deploy() - // Deploy FacadeWriteLib external library - const facadeWriteLib = await (await ethers.getContractFactory('FacadeWriteLib')).deploy() - - // Deploy RSR Asset - const AssetFactory: ContractFactory = await ethers.getContractFactory('Asset') - const rsrAsset: Asset = await AssetFactory.deploy( - PRICE_TIMEOUT, - networkConfig[chainId].chainlinkFeeds.RSR || '', - ORACLE_ERROR, - rsr.address, - fp('1e6'), // max trade volume - ORACLE_TIMEOUT - ) + // Deploy FacadeTest + const FacadeTestFactory: ContractFactory = await ethers.getContractFactory('FacadeTest') + const facadeTest = await FacadeTestFactory.deploy() - // Create Deployer - const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP0', { - libraries: { TradingLibP0: tradingLib.address }, - }) - let deployer: TestIDeployer = ( - await DeployerFactory.deploy(rsr.address, gnosis.address, rsrAsset.address) - ) + // Deploy TradingLib external library + const TradingLibFactory: ContractFactory = await ethers.getContractFactory( + 'RecollateralizationLibP1' + ) + const tradingLib: RecollateralizationLibP1 = ( + await TradingLibFactory.deploy() + ) - if (IMPLEMENTATION == Implementation.P1) { - // Deploy implementations - const MainImplFactory: ContractFactory = await ethers.getContractFactory('MainP1') - const mainImpl: MainP1 = await MainImplFactory.deploy() + // Deploy FacadeWriteLib external library + const facadeWriteLib = await (await ethers.getContractFactory('FacadeWriteLib')).deploy() + + // Deploy RSR Asset + const AssetFactory: ContractFactory = await ethers.getContractFactory('Asset') + const rsrAsset: Asset = await AssetFactory.deploy( + PRICE_TIMEOUT, + networkConfig[chainId].chainlinkFeeds.RSR || '', + ORACLE_ERROR, + rsr.address, + fp('1e6'), // max trade volume + ORACLE_TIMEOUT + ) - const AssetRegImplFactory: ContractFactory = await ethers.getContractFactory('AssetRegistryP1') - const assetRegImpl: AssetRegistryP1 = await AssetRegImplFactory.deploy() + // Create Deployer + const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP0', { + libraries: { TradingLibP0: tradingLib.address }, + }) + let deployer: TestIDeployer = ( + await DeployerFactory.deploy(rsr.address, gnosis.address, rsrAsset.address) + ) - const BackingMgrImplFactory: ContractFactory = await ethers.getContractFactory( - 'BackingManagerP1', - { - libraries: { - RecollateralizationLibP1: tradingLib.address, + if (IMPLEMENTATION == Implementation.P1) { + // Deploy implementations + const MainImplFactory: ContractFactory = await ethers.getContractFactory('MainP1') + const mainImpl: MainP1 = await MainImplFactory.deploy() + + const AssetRegImplFactory: ContractFactory = await ethers.getContractFactory( + 'AssetRegistryP1' + ) + const assetRegImpl: AssetRegistryP1 = await AssetRegImplFactory.deploy() + + const BackingMgrImplFactory: ContractFactory = await ethers.getContractFactory( + 'BackingManagerP1', + { + libraries: { + RecollateralizationLibP1: tradingLib.address, + }, + } + ) + const backingMgrImpl: BackingManagerP1 = ( + await BackingMgrImplFactory.deploy() + ) + + const BskHandlerImplFactory: ContractFactory = await ethers.getContractFactory( + 'BasketHandlerP1' + ) + const bskHndlrImpl: BasketHandlerP1 = await BskHandlerImplFactory.deploy() + + const DistribImplFactory: ContractFactory = await ethers.getContractFactory('DistributorP1') + const distribImpl: DistributorP1 = await DistribImplFactory.deploy() + + const RevTraderImplFactory: ContractFactory = await ethers.getContractFactory( + 'RevenueTraderP1' + ) + const revTraderImpl: RevenueTraderP1 = await RevTraderImplFactory.deploy() + + const FurnaceImplFactory: ContractFactory = await ethers.getContractFactory('FurnaceP1') + const furnaceImpl: FurnaceP1 = await FurnaceImplFactory.deploy() + + const TradeImplFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const tradeImpl: GnosisTrade = await TradeImplFactory.deploy() + + const BrokerImplFactory: ContractFactory = await ethers.getContractFactory('BrokerP1') + const brokerImpl: BrokerP1 = await BrokerImplFactory.deploy() + + const RTokenImplFactory: ContractFactory = await ethers.getContractFactory('RTokenP1') + const rTokenImpl: RTokenP1 = await RTokenImplFactory.deploy() + + const StRSRImplFactory: ContractFactory = await ethers.getContractFactory('StRSRP1Votes') + const stRSRImpl: StRSRP1Votes = await StRSRImplFactory.deploy() + + // Setup Implementation addresses + const implementations: IImplementations = { + main: mainImpl.address, + trade: tradeImpl.address, + components: { + assetRegistry: assetRegImpl.address, + backingManager: backingMgrImpl.address, + basketHandler: bskHndlrImpl.address, + broker: brokerImpl.address, + distributor: distribImpl.address, + furnace: furnaceImpl.address, + rsrTrader: revTraderImpl.address, + rTokenTrader: revTraderImpl.address, + rToken: rTokenImpl.address, + stRSR: stRSRImpl.address, }, } - ) - const backingMgrImpl: BackingManagerP1 = await BackingMgrImplFactory.deploy() - const BskHandlerImplFactory: ContractFactory = await ethers.getContractFactory( - 'BasketHandlerP1' - ) - const bskHndlrImpl: BasketHandlerP1 = await BskHandlerImplFactory.deploy() - - const DistribImplFactory: ContractFactory = await ethers.getContractFactory('DistributorP1') - const distribImpl: DistributorP1 = await DistribImplFactory.deploy() - - const RevTraderImplFactory: ContractFactory = await ethers.getContractFactory('RevenueTraderP1') - const revTraderImpl: RevenueTraderP1 = await RevTraderImplFactory.deploy() - - const FurnaceImplFactory: ContractFactory = await ethers.getContractFactory('FurnaceP1') - const furnaceImpl: FurnaceP1 = await FurnaceImplFactory.deploy() - - const TradeImplFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const tradeImpl: GnosisTrade = await TradeImplFactory.deploy() - - const BrokerImplFactory: ContractFactory = await ethers.getContractFactory('BrokerP1') - const brokerImpl: BrokerP1 = await BrokerImplFactory.deploy() - - const RTokenImplFactory: ContractFactory = await ethers.getContractFactory('RTokenP1') - const rTokenImpl: RTokenP1 = await RTokenImplFactory.deploy() - - const StRSRImplFactory: ContractFactory = await ethers.getContractFactory('StRSRP1Votes') - const stRSRImpl: StRSRP1Votes = await StRSRImplFactory.deploy() - - // Setup Implementation addresses - const implementations: IImplementations = { - main: mainImpl.address, - trade: tradeImpl.address, - components: { - assetRegistry: assetRegImpl.address, - backingManager: backingMgrImpl.address, - basketHandler: bskHndlrImpl.address, - broker: brokerImpl.address, - distributor: distribImpl.address, - furnace: furnaceImpl.address, - rsrTrader: revTraderImpl.address, - rTokenTrader: revTraderImpl.address, - rToken: rTokenImpl.address, - stRSR: stRSRImpl.address, - }, + const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP1') + deployer = ( + await DeployerFactory.deploy(rsr.address, gnosis.address, rsrAsset.address, implementations) + ) } - const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP1') - deployer = ( - await DeployerFactory.deploy(rsr.address, gnosis.address, rsrAsset.address, implementations) - ) - } - - // Deploy Facade - const FacadeWriteFactory: ContractFactory = await ethers.getContractFactory('FacadeWrite', { - libraries: { - FacadeWriteLib: facadeWriteLib.address, - }, - }) - const facadeWrite = await FacadeWriteFactory.deploy(deployer.address) - - // Set default governance params - not used in tests - const govParams: IGovParams = { - votingDelay: bn(5), // 5 blocks - votingPeriod: bn(100), // 100 blocks - proposalThresholdAsMicroPercent: bn(1e6), // 1% - quorumPercent: bn(4), // 4% - timelockDelay: bn(60 * 60 * 24), // 1 day - } + // Deploy Facade + const FacadeWriteFactory: ContractFactory = await ethers.getContractFactory('FacadeWrite', { + libraries: { + FacadeWriteLib: facadeWriteLib.address, + }, + }) + const facadeWrite = await FacadeWriteFactory.deploy(deployer.address) + + // Set default governance params - not used in tests + const govParams: IGovParams = { + votingDelay: bn(5), // 5 blocks + votingPeriod: bn(100), // 100 blocks + proposalThresholdAsMicroPercent: bn(1e6), // 1% + quorumPercent: bn(4), // 4% + timelockDelay: bn(60 * 60 * 24), // 1 day + } - return { - rsr, - rsrAsset, - deployer, - gnosis, - facade, - facadeAct, - facadeTest, - facadeWrite, - govParams, + return { + salt, + rsr, + rsrAsset, + deployer, + gnosis, + facade, + facadeAct, + facadeTest, + facadeWrite, + govParams, + } } + return defaultFixture } diff --git a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts index c8fb9dd0d6..77c8d1f439 100644 --- a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts @@ -8,7 +8,8 @@ import { import { ethers } from 'hardhat' import { ContractFactory, BigNumberish } from 'ethers' import { - CTokenMock, + CTokenVault, + CTokenVaultMock, ICToken, MockV3Aggregator, MockV3Aggregator__factory, @@ -114,13 +115,29 @@ all.forEach((curr: FTokenEnumeration) => { const deployCollateral = async (opts: FTokenCollateralOpts = {}): Promise => { opts = { ...defaultCollateralOpts, ...opts } + let erc20Address = opts.erc20 + + if (erc20Address && erc20Address != ZERO_ADDRESS && erc20Address == curr.fToken) { + const erc20 = await ethers.getContractAt('ERC20Mock', opts.erc20!) + const CTokenVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenVault') + const fTokenVault = ( + await CTokenVaultFactory.deploy( + opts.erc20, + await erc20.name(), + await erc20.symbol(), + opts.comptroller! + ) + ) + erc20Address = fTokenVault.address + } + const FTokenCollateralFactory: ContractFactory = await ethers.getContractFactory( 'CTokenFiatCollateral' ) // fTokens are the same as cTokens modulo some extra stuff we don't care about const collateral = await FTokenCollateralFactory.deploy( { - erc20: opts.erc20, + erc20: erc20Address, targetName: opts.targetName, priceTimeout: opts.priceTimeout, chainlinkFeed: opts.chainlinkFeed, @@ -131,7 +148,6 @@ all.forEach((curr: FTokenEnumeration) => { delayUntilDefault: opts.delayUntilDefault, }, opts.revenueHiding, - opts.comptroller, { gasLimit: 2000000000 } ) await collateral.deployed() @@ -160,7 +176,7 @@ all.forEach((curr: FTokenEnumeration) => { collateralOpts.chainlinkFeed = chainlinkFeed.address const collateral = await deployCollateral(collateralOpts) - const erc20 = await ethers.getContractAt('ICToken', collateralOpts.erc20 as string) // the fToken + const erc20 = await ethers.getContractAt('CTokenVault', (await collateral.erc20()) as string) // the fToken return { alice, @@ -182,19 +198,47 @@ all.forEach((curr: FTokenEnumeration) => { await ethers.getContractFactory('MockV3Aggregator') ) + const comptroller = await ethers.getContractAt('ComptrollerMock', collateralOpts.comptroller!) + const chainlinkFeed = await MockV3AggregatorFactory.deploy(6, bn('1e6')) collateralOpts.chainlinkFeed = chainlinkFeed.address const FTokenMockFactory = await ethers.getContractFactory('CTokenMock') - const erc20 = await FTokenMockFactory.deploy('Mock FToken', 'Mock Ftk', curr.underlying) - collateralOpts.erc20 = erc20.address + + const underlyingFToken = await FTokenMockFactory.deploy( + 'Mock FToken', + 'Mock Ftk', + curr.underlying + ) + + const CTokenVaultMockFactory: ContractFactory = await ethers.getContractFactory( + 'CTokenVaultMock' + ) + + let compAddress = ZERO_ADDRESS + try { + compAddress = await comptroller.getCompAddress() + // eslint-disable-next-line no-empty + } catch {} + + const fTokenVault = ( + await CTokenVaultMockFactory.deploy( + await underlyingFToken.name(), + await underlyingFToken.symbol(), + underlyingFToken.address, + compAddress, + collateralOpts.comptroller! + ) + ) + + collateralOpts.erc20 = fTokenVault.address const collateral = await deployCollateral(collateralOpts) return { collateral, chainlinkFeed, - tok: erc20, + tok: fTokenVault, } } @@ -208,9 +252,10 @@ all.forEach((curr: FTokenEnumeration) => { user: SignerWithAddress, recipient: string ) => { - const tok = ctx.tok as ICToken - const underlying = await ethers.getContractAt('IERC20Metadata', await tok.underlying()) - await mintFToken(underlying, curr.holderUnderlying, tok, amount, recipient) + const tok = ctx.tok as CTokenVault + const fToken = await ethers.getContractAt('ICToken', await tok.asset()) + const underlying = await ethers.getContractAt('IERC20Metadata', await fToken.underlying()) + await mintFToken(underlying, curr.holderUnderlying, fToken, tok, amount, recipient) } const increaseRefPerTok = async (ctx: CollateralFixtureContext) => { @@ -218,13 +263,8 @@ all.forEach((curr: FTokenEnumeration) => { await (ctx.tok as ICToken).exchangeRateCurrent() } - const collateralSpecificConstructorTests = () => { - it('Should validate comptroller arg', async () => { - await expect(deployCollateral({ comptroller: ZERO_ADDRESS })).to.be.revertedWith( - 'comptroller missing' - ) - }) - } + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificConstructorTests = () => {} const collateralSpecificStatusTests = () => { it('does revenue hiding correctly', async () => { @@ -232,21 +272,21 @@ all.forEach((curr: FTokenEnumeration) => { const rate = fp('2') const rateAsRefPerTok = rate.div(50) - await (tok as CTokenMock).setExchangeRate(rate) // above current + await (tok as CTokenVaultMock).setExchangeRate(rate) // above current await collateral.refresh() const before = await collateral.refPerTok() expect(before).to.equal(rateAsRefPerTok.mul(fp('0.99')).div(fp('1'))) expect(await collateral.status()).to.equal(CollateralStatus.SOUND) // Should be SOUND if drops just under 1% - await (tok as CTokenMock).setExchangeRate(rate.mul(fp('0.99001')).div(fp('1'))) + await (tok as CTokenVaultMock).setExchangeRate(rate.mul(fp('0.99001')).div(fp('1'))) await collateral.refresh() let after = await collateral.refPerTok() expect(before).to.eq(after) expect(await collateral.status()).to.equal(CollateralStatus.SOUND) // Should be DISABLED if drops just over 1% - await (tok as CTokenMock).setExchangeRate(before.mul(fp('0.98999')).div(fp('1'))) + await (tok as CTokenVaultMock).setExchangeRate(before.mul(fp('0.98999')).div(fp('1'))) await collateral.refresh() after = await collateral.refPerTok() expect(before).to.be.gt(after) diff --git a/test/plugins/individual-collateral/flux-finance/helpers.ts b/test/plugins/individual-collateral/flux-finance/helpers.ts index 7f1838aea8..9327270bbb 100644 --- a/test/plugins/individual-collateral/flux-finance/helpers.ts +++ b/test/plugins/individual-collateral/flux-finance/helpers.ts @@ -1,4 +1,4 @@ -import { ICToken, IERC20Metadata } from '../../../../typechain' +import { CTokenVault, ICToken, IERC20Metadata } from '../../../../typechain' import { whileImpersonating } from '../../../utils/impersonation' import { BigNumberish } from 'ethers' import { getResetFork } from '../helpers' @@ -8,6 +8,7 @@ export const mintFToken = async ( underlying: IERC20Metadata, holderUnderlying: string, fToken: ICToken, + fTokenVault: CTokenVault, amount: BigNumberish, recipient: string ) => { @@ -15,7 +16,8 @@ export const mintFToken = async ( const balUnderlying = await underlying.balanceOf(signer.address) await underlying.connect(signer).approve(fToken.address, balUnderlying) await fToken.connect(signer).mint(balUnderlying) - await fToken.connect(signer).transfer(recipient, amount) + await fToken.connect(signer).approve(fTokenVault.address, amount) + await fTokenVault.connect(signer).mint(amount, recipient) }) } diff --git a/test/scenario/ComplexBasket.test.ts b/test/scenario/ComplexBasket.test.ts index 6aac8837ed..5484924838 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -9,7 +9,7 @@ import { bn, fp, pow10, toBNDecimals } from '../../common/numbers' import { Asset, ComptrollerMock, - CTokenMock, + CTokenVaultMock, ERC20Mock, FacadeRead, FacadeTest, @@ -83,11 +83,11 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { let usdToken: ERC20Mock let eurToken: ERC20Mock - let cUSDToken: CTokenMock + let cUSDTokenVault: CTokenVaultMock let aUSDToken: StaticATokenMock let wbtc: ERC20Mock - let cWBTC: CTokenMock - let cETH: CTokenMock + let cWBTCVault: CTokenVaultMock + let cETHVault: CTokenVaultMock let weth: WETH9 @@ -156,7 +156,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // Setup Factories const ERC20: ContractFactory = await ethers.getContractFactory('ERC20Mock') const WETH: ContractFactory = await ethers.getContractFactory('WETH9') - const CToken: ContractFactory = await ethers.getContractFactory('CTokenMock') + const CToken: ContractFactory = await ethers.getContractFactory('CTokenVaultMock') const MockV3AggregatorFactory: ContractFactory = await ethers.getContractFactory( 'MockV3Aggregator' ) @@ -184,9 +184,9 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // 2. CTokenFiatCollateral against USD // 3. ATokenFiatCollateral against USD // 4. NonFiatCollateral WBTC against BTC - // 5. CTokenNonFiatCollateral cWBTC against BTC + // 5. CTokenNonFiatCollateral cWBTCVault against BTC // 6. SelfReferentialCollateral WETH against ETH - // 7. CTokenSelfReferentialCollateral cETH against ETH + // 7. CTokenSelfReferentialCollateral cETHVault against ETH primeBasketERC20s = [] targetPricesInUoA = [] @@ -241,24 +241,23 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { collateral.push(await ethers.getContractAt('EURFiatCollateral', fiatEUR)) // 3. CTokenFiatCollateral against USD - cUSDToken = erc20s[4] // cDAI Token + cUSDTokenVault = erc20s[4] // cDAI Token const { collateral: cUSDCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: PRICE_TIMEOUT.toString(), priceFeed: usdFeed.address, oracleError: ORACLE_ERROR.toString(), - cToken: cUSDToken.address, + cToken: cUSDTokenVault.address, maxTradeVolume: MAX_TRADE_VOLUME.toString(), oracleTimeout: ORACLE_TIMEOUT.toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD.toString(), delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), revenueHiding: REVENUE_HIDING.toString(), - comptroller: compoundMock.address, noOutput: true, }) await assetRegistry.swapRegistered(cUSDCollateral) - primeBasketERC20s.push(cUSDToken.address) + primeBasketERC20s.push(cUSDTokenVault.address) targetPricesInUoA.push(fp('1')) // USD Target collateral.push(await ethers.getContractAt('CTokenFiatCollateral', cUSDCollateral)) @@ -307,14 +306,22 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { targetPricesInUoA.push(fp('20000')) // BTC Target collateral.push(await ethers.getContractAt('NonFiatCollateral', wBTCCollateral)) - // 6. CTokenNonFiatCollateral cWBTC against BTC - cWBTC = await CToken.deploy('cWBTC Token', 'cWBTC', wbtc.address) - const { collateral: cWBTCCollateral } = await hre.run('deploy-ctoken-nonfiat-collateral', { + // 6. CTokenNonFiatCollateral cWBTCVault against BTC + cWBTCVault = ( + await CToken.deploy( + 'cWBTCVault Token', + 'cWBTCVault', + wbtc.address, + compToken.address, + compoundMock.address + ) + ) + const { collateral: cWBTCVaultCollateral } = await hre.run('deploy-ctoken-nonfiat-collateral', { priceTimeout: PRICE_TIMEOUT.toString(), referenceUnitFeed: referenceUnitFeed.address, targetUnitFeed: targetUnitFeed.address, combinedOracleError: ORACLE_ERROR.toString(), - cToken: cWBTC.address, + cToken: cWBTCVault.address, maxTradeVolume: MAX_TRADE_VOLUME.toString(), oracleTimeout: ORACLE_TIMEOUT.toString(), targetUnitOracleTimeout: ORACLE_TIMEOUT.toString(), @@ -322,15 +329,14 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { defaultThreshold: DEFAULT_THRESHOLD.toString(), delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), revenueHiding: REVENUE_HIDING.toString(), - comptroller: compoundMock.address, noOutput: true, }) - await assetRegistry.register(cWBTCCollateral) - primeBasketERC20s.push(cWBTC.address) + await assetRegistry.register(cWBTCVaultCollateral) + primeBasketERC20s.push(cWBTCVault.address) targetPricesInUoA.push(fp('20000')) // BTC Target collateral.push( - await ethers.getContractAt('CTokenNonFiatCollateral', cWBTCCollateral) + await ethers.getContractAt('CTokenNonFiatCollateral', cWBTCVaultCollateral) ) // 7. SelfReferentialCollateral WETH against ETH @@ -355,30 +361,37 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await ethers.getContractAt('SelfReferentialCollateral', wETHCollateral) ) - // 8. CTokenSelfReferentialCollateral cETH against ETH + // 8. CTokenSelfReferentialCollateral cETHVault against ETH // Give higher maxTradeVolume: MAX_TRADE_VOLUME.toString(), - cETH = await CToken.deploy('cETH Token', 'cETH', weth.address) - const { collateral: cETHCollateral } = await hre.run( + cETHVault = ( + await CToken.deploy( + 'cETHVault Token', + 'cETHVault', + weth.address, + compToken.address, + compoundMock.address + ) + ) + const { collateral: cETHVaultCollateral } = await hre.run( 'deploy-ctoken-selfreferential-collateral', { priceTimeout: PRICE_TIMEOUT.toString(), priceFeed: ethFeed.address, oracleError: ORACLE_ERROR.toString(), - cToken: cETH.address, + cToken: cETHVault.address, maxTradeVolume: MAX_TRADE_VOLUME.toString(), oracleTimeout: ORACLE_TIMEOUT.toString(), targetName: hre.ethers.utils.formatBytes32String('ETH'), revenueHiding: REVENUE_HIDING.toString(), - comptroller: compoundMock.address, referenceERC20Decimals: bn(18).toString(), noOutput: true, } ) - await assetRegistry.register(cETHCollateral) - primeBasketERC20s.push(cETH.address) + await assetRegistry.register(cETHVaultCollateral) + primeBasketERC20s.push(cETHVault.address) targetPricesInUoA.push(fp('1200')) // ETH Target collateral.push( - await ethers.getContractAt('CTokenSelfReferentialCollateral', cETHCollateral) + await ethers.getContractAt('CTokenSelfReferentialCollateral', cETHVaultCollateral) ) targetAmts = [] @@ -462,8 +475,8 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await eurToken.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn1)) expect(expectedTkn1).to.equal(quotes[1]) - expect(await cUSDToken.balanceOf(backingManager.address)).to.equal(expectedTkn2) - expect(await cUSDToken.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn2)) + expect(await cUSDTokenVault.balanceOf(backingManager.address)).to.equal(expectedTkn2) + expect(await cUSDTokenVault.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn2)) expect(expectedTkn2).to.equal(quotes[2]) expect(await aUSDToken.balanceOf(backingManager.address)).to.equal(expectedTkn3) @@ -474,16 +487,16 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await wbtc.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn4)) expect(expectedTkn4).to.equal(quotes[4]) - expect(await cWBTC.balanceOf(backingManager.address)).to.equal(expectedTkn5) - expect(await cWBTC.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn5)) + expect(await cWBTCVault.balanceOf(backingManager.address)).to.equal(expectedTkn5) + expect(await cWBTCVault.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn5)) expect(expectedTkn5).to.equal(quotes[5]) expect(await weth.balanceOf(backingManager.address)).to.equal(expectedTkn6) expect(await weth.balanceOf(addr1.address)).to.equal(wethDepositAmt.sub(expectedTkn6)) expect(expectedTkn6).to.equal(quotes[6]) - expect(await cETH.balanceOf(backingManager.address)).to.equal(expectedTkn7) - expect(await cETH.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn7)) + expect(await cETHVault.balanceOf(backingManager.address)).to.equal(expectedTkn7) + expect(await cETHVault.balanceOf(addr1.address)).to.equal(initialBal.sub(expectedTkn7)) expect(expectedTkn7).to.equal(quotes[7]) // Redeem @@ -498,8 +511,8 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await eurToken.balanceOf(backingManager.address)).to.equal(0) expect(await eurToken.balanceOf(addr1.address)).to.equal(initialBal) - expect(await cUSDToken.balanceOf(backingManager.address)).to.equal(0) - expect(await cUSDToken.balanceOf(addr1.address)).to.equal(initialBal) + expect(await cUSDTokenVault.balanceOf(backingManager.address)).to.equal(0) + expect(await cUSDTokenVault.balanceOf(addr1.address)).to.equal(initialBal) expect(await aUSDToken.balanceOf(backingManager.address)).to.equal(0) expect(await aUSDToken.balanceOf(addr1.address)).to.equal(initialBal) @@ -507,14 +520,14 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await wbtc.balanceOf(backingManager.address)).to.equal(0) expect(await wbtc.balanceOf(addr1.address)).to.equal(initialBal) - expect(await cWBTC.balanceOf(backingManager.address)).to.equal(0) - expect(await cWBTC.balanceOf(addr1.address)).to.equal(initialBal) + expect(await cWBTCVault.balanceOf(backingManager.address)).to.equal(0) + expect(await cWBTCVault.balanceOf(addr1.address)).to.equal(initialBal) expect(await weth.balanceOf(backingManager.address)).to.equal(0) expect(await weth.balanceOf(addr1.address)).to.equal(wethDepositAmt) - expect(await cETH.balanceOf(backingManager.address)).to.equal(0) - expect(await cETH.balanceOf(addr1.address)).to.equal(initialBal) + expect(await cETHVault.balanceOf(backingManager.address)).to.equal(0) + expect(await cETHVault.balanceOf(addr1.address)).to.equal(initialBal) }) it('Should claim COMP rewards correctly - All RSR', async () => { @@ -564,13 +577,13 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectEvents(backingManager.claimRewards(), [ { - contract: backingManager, + contract: cUSDTokenVault, name: 'RewardsClaimed', args: [compToken.address, rewardAmount], emitted: true, }, { - contract: backingManager, + contract: aUSDToken, name: 'RewardsClaimed', args: [aaveToken.address, bn(0)], emitted: true, @@ -664,9 +677,9 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // Requires 200 cDAI (at $0.02 = $4 USD) expect(quotes[2]).to.equal(bn(200e8)) - // Requires 1600 cWBTC (at $400 = $640K USD) - matches 32 BTC @ 20K + // Requires 1600 cWBTCVault (at $400 = $640K USD) - matches 32 BTC @ 20K expect(quotes[5]).to.equal(bn(1600e8)) - // Requires 6400 cETH (at $24 = $153,600 K USD) - matches 128 ETH @ 1200 + // Requires 6400 cETHVault (at $24 = $153,600 K USD) - matches 128 ETH @ 1200 expect(quotes[7]).to.equal(bn(6400e8)) // Issue 1 RToken @@ -688,19 +701,19 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await rToken.totalSupply()).to.equal(issueAmount) // Increase redemption rate for cUSD to double - await cUSDToken.setExchangeRate(fp('2')) + await cUSDTokenVault.setExchangeRate(fp('2')) - // Increase redemption rate for cWBTC 25% - await cWBTC.setExchangeRate(fp('1.25')) + // Increase redemption rate for cWBTCVault 25% + await cWBTCVault.setExchangeRate(fp('1.25')) - // Increase redemption rate for cETH 5% - await cETH.setExchangeRate(fp('1.05')) + // Increase redemption rate for cETHVault 5% + await cETHVault.setExchangeRate(fp('1.05')) // Get updated quotes // Should now require: // Token2: 100 cDAI @ 0.004 = 4 USD - // Token5: 1280 cWBTC @ 500 = 640K USD - // Token7: 6095.23 cETH @ 25.2 = $153,600 USD + // Token5: 1280 cWBTCVault @ 500 = 640K USD + // Token7: 6095.23 cETHVault @ 25.2 = $153,600 USD const [, newQuotes] = await facade.connect(addr1).callStatic.issue(rToken.address, issueAmount) await assetRegistry.refresh() // refresh to update refPerTok() @@ -714,14 +727,14 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { const expectedTkn5: BigNumber = toBNDecimals( issueAmount.mul(targetAmts[5]).div(await collateral[5].refPerTok()), 8 - ) // cWBTC + ) // cWBTCVault expect(expectedTkn5).to.be.closeTo(newQuotes[5], point5Pct(newQuotes[5])) expect(newQuotes[5]).to.equal(bn(1280e8)) const expectedTkn7: BigNumber = toBNDecimals( issueAmount.mul(targetAmts[7]).div(await collateral[7].refPerTok()), 8 - ) // cETH + ) // cETHVault expect(expectedTkn7).to.be.closeTo(newQuotes[7], point5Pct(newQuotes[7])) expect(newQuotes[7]).to.be.closeTo(bn(6095e8), point5Pct(bn(6095e8))) @@ -738,7 +751,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(excessValueLow2).to.be.lt(fp('4')) expect(excessValueHigh2).to.be.gt(fp('4')) - // Excess cWBTC = 320 - valued at 320 * 500 = 160K usd (25%) + // Excess cWBTCVault = 320 - valued at 320 * 500 = 160K usd (25%) const excessQuantity5: BigNumber = quotes[5].sub(newQuotes[5]).mul(pow10(10)) // Convert to 18 decimals for simplification const [lowPrice5, highPrice5] = await collateral[5].price() const excessValueLow5: BigNumber = excessQuantity5.mul(lowPrice5).div(BN_SCALE_FACTOR) @@ -749,7 +762,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(excessValueLow5).to.be.lt(fp('160000')) expect(excessValueHigh5).to.be.gt(fp('160000')) - // Excess cETH = 304.7619- valued at 25.2 = 7679.999 usd (5%) + // Excess cETHVault = 304.7619- valued at 25.2 = 7679.999 usd (5%) const excessQuantity7: BigNumber = quotes[7].sub(newQuotes[7]).mul(pow10(10)) // Convert to 18 decimals for simplification const [lowPrice7, highPrice7] = await collateral[7].price() const excessValueLow7: BigNumber = excessQuantity7.mul(lowPrice7).div(BN_SCALE_FACTOR) @@ -791,14 +804,14 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(expectedToFurnace2).to.equal(bn(40e8)) // Token5- CWBTC - const expectedToTrader5 = toBNDecimals(excessQuantity5.mul(60).div(100), 8) // 60% of 320 tokens = 192 cWBTC - const expectedToFurnace5 = toBNDecimals(excessQuantity5, 8).sub(expectedToTrader5) // Remainder = 128 cWBTC + const expectedToTrader5 = toBNDecimals(excessQuantity5.mul(60).div(100), 8) // 60% of 320 tokens = 192 cWBTCVault + const expectedToFurnace5 = toBNDecimals(excessQuantity5, 8).sub(expectedToTrader5) // Remainder = 128 cWBTCVault expect(expectedToTrader5).to.equal(bn(192e8)) expect(expectedToFurnace5).to.equal(bn(128e8)) // Token7- CETH - const expectedToTrader7 = toBNDecimals(excessQuantity7.mul(60).div(100), 8) // 60% of 304.7619 = 182.85 cETH - const expectedToFurnace7 = toBNDecimals(excessQuantity7, 8).sub(expectedToTrader7) // Remainder = 121.9 cETH + const expectedToTrader7 = toBNDecimals(excessQuantity7.mul(60).div(100), 8) // 60% of 304.7619 = 182.85 cETHVault + const expectedToFurnace7 = toBNDecimals(excessQuantity7, 8).sub(expectedToTrader7) // Remainder = 121.9 cETHVault expect(expectedToTrader7).to.be.closeTo(bn('182.85e8'), point5Pct(bn('182.85e8'))) expect(expectedToFurnace7).to.be.closeTo(bn('121.9e8'), point5Pct(bn('121.9e8'))) @@ -853,14 +866,14 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // Check auctions registered // cUSD -> RSR Auction await expectTrade(rsrTrader, { - sell: cUSDToken.address, + sell: cUSDTokenVault.address, buy: rsr.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('0'), }) // Check trades - let trade = await getTrade(rsrTrader, cUSDToken.address) + let trade = await getTrade(rsrTrader, cUSDTokenVault.address) let auctionId = await trade.auctionId() const [, , , auctionSellAmt2, auctionbuyAmt2] = await gnosis.auctions(auctionId) expect(sellAmt2).to.be.closeTo(auctionSellAmt2, point5Pct(auctionSellAmt2)) @@ -868,13 +881,13 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // cUSD -> RToken Auction await expectTrade(rTokenTrader, { - sell: cUSDToken.address, + sell: cUSDTokenVault.address, buy: rToken.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('1'), }) - trade = await getTrade(rTokenTrader, cUSDToken.address) + trade = await getTrade(rTokenTrader, cUSDTokenVault.address) auctionId = await trade.auctionId() const [, , , auctionSellAmtRToken2, auctionbuyAmtRToken2] = await gnosis.auctions(auctionId) expect(sellAmtRToken2).to.be.closeTo(auctionSellAmtRToken2, point5Pct(auctionSellAmtRToken2)) @@ -900,13 +913,15 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(furnace.address)).to.equal(0) // Check funds in Market and Traders - expect(await cUSDToken.balanceOf(gnosis.address)).to.be.closeTo( + expect(await cUSDTokenVault.balanceOf(gnosis.address)).to.be.closeTo( sellAmt2.add(sellAmtRToken2), point5Pct(sellAmt2.add(sellAmtRToken2)) ) - expect(await cUSDToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader2.sub(sellAmt2)) - expect(await cUSDToken.balanceOf(rTokenTrader.address)).to.equal( + expect(await cUSDTokenVault.balanceOf(rsrTrader.address)).to.equal( + expectedToTrader2.sub(sellAmt2) + ) + expect(await cUSDTokenVault.balanceOf(rTokenTrader.address)).to.equal( expectedToFurnace2.sub(sellAmtRToken2) ) @@ -927,7 +942,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { buyAmount: auctionbuyAmtRToken2, }) - // Closing auction will create new auction for cWBTC + // Closing auction will create new auction for cWBTCVault // Set expected values const sellAmt5: BigNumber = expectedToTrader5 // everything is auctioned, below max auction const minBuyAmt5 = toMinBuyAmt( @@ -956,7 +971,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { { contract: rsrTrader, name: 'TradeSettled', - args: [anyValue, cUSDToken.address, rsr.address, auctionSellAmt2, auctionbuyAmt2], + args: [anyValue, cUSDTokenVault.address, rsr.address, auctionSellAmt2, auctionbuyAmt2], emitted: true, }, { @@ -964,7 +979,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { name: 'TradeSettled', args: [ anyValue, - cUSDToken.address, + cUSDTokenVault.address, rToken.address, auctionSellAmtRToken2, auctionbuyAmtRToken2, @@ -1005,51 +1020,51 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Check no more funds in Market and Traders - expect(await cUSDToken.balanceOf(gnosis.address)).to.equal(0) - expect(await cUSDToken.balanceOf(rsrTrader.address)).to.equal(0) - expect(await cUSDToken.balanceOf(rTokenTrader.address)).to.equal(0) + expect(await cUSDTokenVault.balanceOf(gnosis.address)).to.equal(0) + expect(await cUSDTokenVault.balanceOf(rsrTrader.address)).to.equal(0) + expect(await cUSDTokenVault.balanceOf(rTokenTrader.address)).to.equal(0) - // Check new auctions created for cWBTC + // Check new auctions created for cWBTCVault auctionTimestamp = await getLatestBlockTimestamp() // Check auctions registered - // cWBTC -> RSR Auction + // cWBTCVault -> RSR Auction await expectTrade(rsrTrader, { - sell: cWBTC.address, + sell: cWBTCVault.address, buy: rsr.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('6'), }) // Check trades - trade = await getTrade(rsrTrader, cWBTC.address) + trade = await getTrade(rsrTrader, cWBTCVault.address) auctionId = await trade.auctionId() const [, , , auctionSellAmt5, auctionbuyAmt5] = await gnosis.auctions(auctionId) expect(sellAmt5).to.be.closeTo(auctionSellAmt5, point5Pct(auctionSellAmt5)) expect(minBuyAmt5).to.be.closeTo(auctionbuyAmt5, point5Pct(auctionbuyAmt5)) - // cWBTC -> RToken Auction + // cWBTCVault -> RToken Auction await expectTrade(rTokenTrader, { - sell: cWBTC.address, + sell: cWBTCVault.address, buy: rToken.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('7'), }) - trade = await getTrade(rTokenTrader, cWBTC.address) + trade = await getTrade(rTokenTrader, cWBTCVault.address) auctionId = await trade.auctionId() const [, , , auctionSellAmtRToken5, auctionbuyAmtRToken5] = await gnosis.auctions(auctionId) expect(sellAmtRToken5).to.be.closeTo(auctionSellAmtRToken5, point5Pct(auctionSellAmtRToken5)) expect(minBuyAmtRToken5).to.be.closeTo(auctionbuyAmtRToken5, point5Pct(auctionbuyAmtRToken5)) // Check funds in Market and Traders - expect(await cWBTC.balanceOf(gnosis.address)).to.be.closeTo( + expect(await cWBTCVault.balanceOf(gnosis.address)).to.be.closeTo( sellAmt5.add(sellAmtRToken5), point5Pct(sellAmt5.add(sellAmtRToken5)) ) - expect(await cWBTC.balanceOf(rsrTrader.address)).to.equal(expectedToTrader5.sub(sellAmt5)) - expect(await cWBTC.balanceOf(rTokenTrader.address)).to.equal( + expect(await cWBTCVault.balanceOf(rsrTrader.address)).to.equal(expectedToTrader5.sub(sellAmt5)) + expect(await cWBTCVault.balanceOf(rTokenTrader.address)).to.equal( expectedToFurnace5.sub(sellAmtRToken5) ) @@ -1070,7 +1085,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { buyAmount: auctionbuyAmtRToken5, }) - // Closing auction will create new auction for cETH + // Closing auction will create new auction for cETHVault // Set expected values const sellAmt7: BigNumber = expectedToTrader7 // everything is auctioned, below max auction const minBuyAmt7 = toMinBuyAmt( @@ -1099,7 +1114,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { { contract: rsrTrader, name: 'TradeSettled', - args: [anyValue, cWBTC.address, rsr.address, auctionSellAmt5, auctionbuyAmt5], + args: [anyValue, cWBTCVault.address, rsr.address, auctionSellAmt5, auctionbuyAmt5], emitted: true, }, { @@ -1107,7 +1122,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { name: 'TradeSettled', args: [ anyValue, - cWBTC.address, + cWBTCVault.address, rToken.address, auctionSellAmtRToken5, auctionbuyAmtRToken5, @@ -1152,51 +1167,51 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Check no more funds in Market and Traders - expect(await cWBTC.balanceOf(gnosis.address)).to.equal(0) - expect(await cWBTC.balanceOf(rsrTrader.address)).to.equal(0) - expect(await cWBTC.balanceOf(rTokenTrader.address)).to.equal(0) + expect(await cWBTCVault.balanceOf(gnosis.address)).to.equal(0) + expect(await cWBTCVault.balanceOf(rsrTrader.address)).to.equal(0) + expect(await cWBTCVault.balanceOf(rTokenTrader.address)).to.equal(0) - // Check new auctions created for cWBTC + // Check new auctions created for cWBTCVault auctionTimestamp = await getLatestBlockTimestamp() // Check auctions registered - // cETH -> RSR Auction + // cETHVault -> RSR Auction await expectTrade(rsrTrader, { - sell: cETH.address, + sell: cETHVault.address, buy: rsr.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('10'), }) // Check trades - trade = await getTrade(rsrTrader, cETH.address) + trade = await getTrade(rsrTrader, cETHVault.address) auctionId = await trade.auctionId() const [, , , auctionSellAmt7, auctionbuyAmt7] = await gnosis.auctions(auctionId) expect(sellAmt7).to.be.closeTo(auctionSellAmt7, point5Pct(auctionSellAmt7)) expect(minBuyAmt7).to.be.closeTo(auctionbuyAmt7, point5Pct(auctionbuyAmt7)) - // cETH -> RToken Auction + // cETHVault -> RToken Auction await expectTrade(rTokenTrader, { - sell: cETH.address, + sell: cETHVault.address, buy: rToken.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('11'), }) - trade = await getTrade(rTokenTrader, cETH.address) + trade = await getTrade(rTokenTrader, cETHVault.address) auctionId = await trade.auctionId() const [, , , auctionSellAmtRToken7, auctionbuyAmtRToken7] = await gnosis.auctions(auctionId) expect(sellAmtRToken7).to.be.closeTo(auctionSellAmtRToken7, point5Pct(auctionSellAmtRToken7)) expect(minBuyAmtRToken7).to.be.closeTo(auctionbuyAmtRToken7, point5Pct(auctionbuyAmtRToken7)) // Check funds in Market and Traders - expect(await cETH.balanceOf(gnosis.address)).to.be.closeTo( + expect(await cETHVault.balanceOf(gnosis.address)).to.be.closeTo( sellAmt7.add(sellAmtRToken7), point5Pct(sellAmt7.add(sellAmtRToken7)) ) - expect(await cETH.balanceOf(rsrTrader.address)).to.equal(expectedToTrader7.sub(sellAmt7)) - expect(await cETH.balanceOf(rTokenTrader.address)).to.equal( + expect(await cETHVault.balanceOf(rsrTrader.address)).to.equal(expectedToTrader7.sub(sellAmt7)) + expect(await cETHVault.balanceOf(rTokenTrader.address)).to.equal( expectedToFurnace7.sub(sellAmtRToken7) ) @@ -1222,13 +1237,19 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { { contract: rsrTrader, name: 'TradeSettled', - args: [anyValue, cETH.address, rsr.address, auctionSellAmt7, auctionbuyAmt7], + args: [anyValue, cETHVault.address, rsr.address, auctionSellAmt7, auctionbuyAmt7], emitted: true, }, { contract: rTokenTrader, name: 'TradeSettled', - args: [anyValue, cETH.address, rToken.address, auctionSellAmtRToken7, auctionbuyAmtRToken7], + args: [ + anyValue, + cETHVault.address, + rToken.address, + auctionSellAmtRToken7, + auctionbuyAmtRToken7, + ], emitted: true, }, { @@ -1269,12 +1290,12 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Check no more funds in Market and Traders - expect(await cETH.balanceOf(gnosis.address)).to.equal(0) - expect(await cETH.balanceOf(rsrTrader.address)).to.equal(0) - expect(await cETH.balanceOf(rTokenTrader.address)).to.equal(0) + expect(await cETHVault.balanceOf(gnosis.address)).to.equal(0) + expect(await cETHVault.balanceOf(rsrTrader.address)).to.equal(0) + expect(await cETHVault.balanceOf(rTokenTrader.address)).to.equal(0) }) - it('Should recollateralize basket correctly - cWBTC', async () => { + it('Should recollateralize basket correctly - cWBTCVault', async () => { // Set RSR price to 25 cts for less auctions const rsrPrice = fp('0.25') // 0.25 usd await setOraclePrice(rsrAsset.address, toBNDecimals(rsrPrice, 8)) @@ -1302,24 +1323,24 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // cToken expect(quotes[4]).to.equal(fp('16')) // wBTC Target: 16 BTC expect(expectedTkn4).to.equal(quotes[4]) - expect(quotes[5]).to.equal(bn(1600e8)) // cWBTC Target: 32 BTC (1600 cWBTC @ 400 usd) + expect(quotes[5]).to.equal(bn(1600e8)) // cWBTCVault Target: 32 BTC (1600 cWBTCVault @ 400 usd) expect(expectedTkn5).to.equal(quotes[5]) - const cWBTCCollateral = collateral[5] // cWBTC + const cWBTCVaultCollateral = collateral[5] // cWBTCVault - // Set Backup for cWBTC to BTC + // Set Backup for cWBTCVault to BTC await basketHandler .connect(owner) .setBackupConfig(ethers.utils.formatBytes32String('BTC'), bn(1), [wbtc.address]) - // Basket Swapping - Default cWBTC - should be replaced by BTC + // Basket Swapping - Default cWBTCVault - should be replaced by BTC // Decrease rate to cause default in Ctoken - await cWBTC.setExchangeRate(fp('0.8')) + await cWBTCVault.setExchangeRate(fp('0.8')) // Mark Collateral as Defaulted - await cWBTCCollateral.refresh() + await cWBTCVaultCollateral.refresh() - expect(await cWBTCCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await cWBTCVaultCollateral.status()).to.equal(CollateralStatus.DISABLED) // Ensure valid basket await basketHandler.refreshBasket() @@ -1341,8 +1362,8 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) // Running auctions will trigger recollateralization - All balance of invalid tokens will be redeemed - const sellAmt: BigNumber = await cWBTC.balanceOf(backingManager.address) - // For cWBTC = price fo $400 (20k / 50), rate 0.8 = $320 + const sellAmt: BigNumber = await cWBTCVault.balanceOf(backingManager.address) + // For cWBTCVault = price fo $400 (20k / 50), rate 0.8 = $320 const minBuyAmt = toMinBuyAmt( sellAmt.mul(pow10(10)), fp('320'), @@ -1355,36 +1376,36 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { { contract: backingManager, name: 'TradeStarted', - args: [anyValue, cWBTC.address, wbtc.address, sellAmt, minBuyAmt], + args: [anyValue, cWBTCVault.address, wbtc.address, sellAmt, minBuyAmt], emitted: true, }, ]) let auctionTimestamp = await getLatestBlockTimestamp() - // cWBTC (Defaulted) -> wBTC (only valid backup token for that target) + // cWBTCVault (Defaulted) -> wBTC (only valid backup token for that target) await expectTrade(backingManager, { - sell: cWBTC.address, + sell: cWBTCVault.address, buy: wbtc.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('0'), }) // Check trade - let trade = await getTrade(backingManager, cWBTC.address) + let trade = await getTrade(backingManager, cWBTCVault.address) let auctionId = await trade.auctionId() const [, , , auctionSellAmt] = await gnosis.auctions(auctionId) expect(sellAmt).to.be.closeTo(auctionSellAmt, point5Pct(auctionSellAmt)) // Check funds in Market and Traders - expect(await cWBTC.balanceOf(gnosis.address)).to.be.closeTo(sellAmt, point5Pct(sellAmt)) - expect(await cWBTC.balanceOf(backingManager.address)).to.equal(bn(0)) + expect(await cWBTCVault.balanceOf(gnosis.address)).to.be.closeTo(sellAmt, point5Pct(sellAmt)) + expect(await cWBTCVault.balanceOf(backingManager.address)).to.equal(bn(0)) // Advance time till auction ended await advanceTime(config.auctionLength.add(100).toString()) // Mock auction - Get 80% of value - // 1600 cWTBC -> 80% = 1280 cWBTC @ 400 = 512K = 25 BTC + // 1600 cWTBC -> 80% = 1280 cWBTCVault @ 400 = 512K = 25 BTC const auctionbuyAmt = fp('25') await wbtc.connect(addr1).approve(gnosis.address, auctionbuyAmt) await gnosis.placeBid(0, { @@ -1408,7 +1429,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { { contract: backingManager, name: 'TradeSettled', - args: [anyValue, cWBTC.address, wbtc.address, auctionSellAmt, auctionbuyAmt], + args: [anyValue, cWBTCVault.address, wbtc.address, auctionSellAmt, auctionbuyAmt], emitted: true, }, { @@ -1502,7 +1523,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) }) - it('Should recollateralize basket correctly - cETH, multiple auctions', async () => { + it('Should recollateralize basket correctly - cETHVault, multiple auctions', async () => { // Set RSR price to 100 usd const rsrPrice = fp('100') // 100 usd for less auctions await setOraclePrice(rsrAsset.address, toBNDecimals(rsrPrice, 8)) @@ -1530,24 +1551,24 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) expect(quotes[6]).to.equal(fp('12800')) // wETH Target: 64 ETH * 200 expect(expectedTkn6).to.equal(quotes[6]) - expect(quotes[7]).to.equal(bn(1280000e8)) // cETH Target: 128 ETH * 200 (6400 * 200 cETH @ 24 usd) + expect(quotes[7]).to.equal(bn(1280000e8)) // cETHVault Target: 128 ETH * 200 (6400 * 200 cETHVault @ 24 usd) expect(expectedTkn7).to.equal(quotes[7]) - const cETHCollateral = collateral[7] // cETH + const cETHVaultCollateral = collateral[7] // cETHVault - // Set Backup for cETH to wETH + // Set Backup for cETHVault to wETH await basketHandler .connect(owner) .setBackupConfig(ethers.utils.formatBytes32String('ETH'), bn(1), [weth.address]) - // Basket Swapping - Default cETH - should be replaced by ETH + // Basket Swapping - Default cETHVault - should be replaced by ETH // Decrease rate to cause default in Ctoken - await cETH.setExchangeRate(fp('0.5')) + await cETHVault.setExchangeRate(fp('0.5')) // Mark Collateral as Defaulted - await cETHCollateral.refresh() + await cETHVaultCollateral.refresh() - expect(await cETHCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await cETHVaultCollateral.status()).to.equal(CollateralStatus.DISABLED) // Ensure valid basket await basketHandler.refreshBasket() @@ -1568,14 +1589,14 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(newBacking.length).to.equal(7) // One less token expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - // Running auctions will trigger recollateralization - cETH partial sale for weth - // Will sell about 841K of cETH, expect to receive 8167 wETH (minimum) - // We would still have about 438K to sell of cETH - let [, lotHigh] = await cETHCollateral.lotPrice() + // Running auctions will trigger recollateralization - cETHVault partial sale for weth + // Will sell about 841K of cETHVault, expect to receive 8167 wETH (minimum) + // We would still have about 438K to sell of cETHVault + let [, lotHigh] = await cETHVaultCollateral.lotPrice() const sellAmtUnscaled = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(lotHigh) const sellAmt = toBNDecimals(sellAmtUnscaled, 8) - const sellAmtRemainder = (await cETH.balanceOf(backingManager.address)).sub(sellAmt) - // Price for cETH = 1200 / 50 = $24 at rate 50% = $12 + const sellAmtRemainder = (await cETHVault.balanceOf(backingManager.address)).sub(sellAmt) + // Price for cETHVault = 1200 / 50 = $24 at rate 50% = $12 const minBuyAmt = toMinBuyAmt( sellAmtUnscaled, fp('12'), @@ -1588,30 +1609,36 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { { contract: backingManager, name: 'TradeStarted', - args: [anyValue, cETH.address, weth.address, withinQuad(sellAmt), withinQuad(minBuyAmt)], + args: [ + anyValue, + cETHVault.address, + weth.address, + withinQuad(sellAmt), + withinQuad(minBuyAmt), + ], emitted: true, }, ]) let auctionTimestamp = await getLatestBlockTimestamp() - // cETH (Defaulted) -> wETH (only valid backup token for that target) + // cETHVault (Defaulted) -> wETH (only valid backup token for that target) await expectTrade(backingManager, { - sell: cETH.address, + sell: cETHVault.address, buy: weth.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('0'), }) // Check trade - let trade = await getTrade(backingManager, cETH.address) + let trade = await getTrade(backingManager, cETHVault.address) let auctionId = await trade.auctionId() const [, , , auctionSellAmt] = await gnosis.auctions(auctionId) expect(sellAmt).to.be.closeTo(auctionSellAmt, point5Pct(auctionSellAmt)) // Check funds in Market and Traders - expect(await cETH.balanceOf(gnosis.address)).to.be.closeTo(sellAmt, point5Pct(sellAmt)) - expect(await cETH.balanceOf(backingManager.address)).to.be.closeTo( + expect(await cETHVault.balanceOf(gnosis.address)).to.be.closeTo(sellAmt, point5Pct(sellAmt)) + expect(await cETHVault.balanceOf(backingManager.address)).to.be.closeTo( sellAmtRemainder, point5Pct(sellAmtRemainder) ) @@ -1636,12 +1663,12 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { config.maxTradeSlippage ) // Run auctions again for remainder - // We will sell the remaining 438K of cETH, expecting about 4253 WETH + // We will sell the remaining 438K of cETHVault, expecting about 4253 WETH await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ { contract: backingManager, name: 'TradeSettled', - args: [anyValue, cETH.address, weth.address, auctionSellAmt, auctionbuyAmt], + args: [anyValue, cETHVault.address, weth.address, auctionSellAmt, auctionbuyAmt], emitted: true, }, { @@ -1649,7 +1676,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { name: 'TradeStarted', args: [ anyValue, - cETH.address, + cETHVault.address, weth.address, withinQuad(sellAmtRemainder), withinQuad(minBuyAmtRemainder), @@ -1660,16 +1687,16 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { auctionTimestamp = await getLatestBlockTimestamp() - // cETH (Defaulted) -> wETH (only valid backup token for that target) + // cETHVault (Defaulted) -> wETH (only valid backup token for that target) await expectTrade(backingManager, { - sell: cETH.address, + sell: cETHVault.address, buy: weth.address, endTime: auctionTimestamp + Number(config.auctionLength), externalId: bn('1'), }) // Check trade - trade = await getTrade(backingManager, cETH.address) + trade = await getTrade(backingManager, cETHVault.address) auctionId = await trade.auctionId() const [, , , auctionSellAmtRemainder] = await gnosis.auctions(auctionId) expect(sellAmtRemainder).to.be.closeTo( @@ -1678,17 +1705,17 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Check funds in Market and Traders - expect(await cETH.balanceOf(gnosis.address)).to.be.closeTo( + expect(await cETHVault.balanceOf(gnosis.address)).to.be.closeTo( sellAmtRemainder, point5Pct(sellAmtRemainder) ) - expect(await cETH.balanceOf(backingManager.address)).to.equal(bn(0)) + expect(await cETHVault.balanceOf(backingManager.address)).to.equal(bn(0)) // Advance time till auction ended await advanceTime(config.auctionLength.add(100).toString()) // Mock auction - // 438,000 cETH @ 12 = 5.25 M = approx 4255 ETH - Get 4400 WETH + // 438,000 cETHVault @ 12 = 5.25 M = approx 4255 ETH - Get 4400 WETH const auctionbuyAmtRemainder = toMinBuyAmt( auctionSellAmtRemainder, fp('12'), @@ -1728,7 +1755,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { name: 'TradeSettled', args: [ anyValue, - cETH.address, + cETHVault.address, weth.address, auctionSellAmtRemainder, auctionbuyAmtRemainder, diff --git a/test/scenario/MaxBasketSize.test.ts b/test/scenario/MaxBasketSize.test.ts index 1bb50d0ac1..35ceb24075 100644 --- a/test/scenario/MaxBasketSize.test.ts +++ b/test/scenario/MaxBasketSize.test.ts @@ -10,7 +10,7 @@ import { ATokenFiatCollateral, ComptrollerMock, CTokenFiatCollateral, - CTokenMock, + CTokenVaultMock, ERC20Mock, FacadeRead, FacadeTest, @@ -36,7 +36,6 @@ import { CollateralStatus } from '../../common/constants' import snapshotGasCost from '../utils/snapshotGasCost' import { expectTrade } from '../utils/trades' import { expectPrice, setOraclePrice } from '../utils/oracles' -import { expectEvents } from '../../common/events' const DEFAULT_THRESHOLD = fp('0.01') // 1% const DELAY_UNTIL_DEFAULT = bn('86400') // 24h @@ -212,9 +211,11 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { return atoken } - const makeCToken = async (tokenName: string): Promise => { + const makeCToken = async (tokenName: string): Promise => { const ERC20MockFactory: ContractFactory = await ethers.getContractFactory('ERC20Mock') - const CTokenMockFactory: ContractFactory = await ethers.getContractFactory('CTokenMock') + const CTokenVaultMockFactory: ContractFactory = await ethers.getContractFactory( + 'CTokenVaultMock' + ) const CTokenCollateralFactory: ContractFactory = await ethers.getContractFactory( 'CTokenFiatCollateral' ) @@ -223,8 +224,14 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { await ERC20MockFactory.deploy(tokenName, `${tokenName} symbol`) ) - const ctoken: CTokenMock = ( - await CTokenMockFactory.deploy('c' + tokenName, `${'c' + tokenName} symbol`, erc20.address) + const ctoken: CTokenVaultMock = ( + await CTokenVaultMockFactory.deploy( + 'c' + tokenName, + `${'c' + tokenName} symbol`, + erc20.address, + compToken.address, + compoundMock.address + ) ) const chainlinkFeed = ( @@ -243,8 +250,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) ) @@ -485,7 +491,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { await assetRegistry.toColl(backing[0]) ) for (let i = maxBasketSize - tokensToDefault; i < backing.length; i++) { - const erc20 = await ethers.getContractAt('CTokenMock', backing[i]) + const erc20 = await ethers.getContractAt('CTokenVaultMock', backing[i]) // Decrease rate to cause default in Ctoken await erc20.setExchangeRate(fp('0.8')) @@ -574,20 +580,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { if (REPORT_GAS) { await snapshotGasCost(backingManager.claimRewards()) } else { - await expectEvents(backingManager.claimRewards(), [ - { - contract: backingManager, - name: 'RewardsClaimed', - args: [compToken.address, rewardAmount.mul(20)], - emitted: true, - }, - { - contract: backingManager, - name: 'RewardsClaimed', - args: [aaveToken.address, rewardAmount], - emitted: true, - }, - ]) + await backingManager.claimRewards() } // Check balances after diff --git a/test/scenario/RevenueHiding.test.ts b/test/scenario/RevenueHiding.test.ts index 4eecf8a61a..3beabf3ef1 100644 --- a/test/scenario/RevenueHiding.test.ts +++ b/test/scenario/RevenueHiding.test.ts @@ -7,9 +7,8 @@ import { bn, fp, divCeil } from '../../common/numbers' import { IConfig } from '../../common/configuration' import { CollateralStatus } from '../../common/constants' import { - CTokenMock, + CTokenVaultMock, CTokenFiatCollateral, - ComptrollerMock, ERC20Mock, IAssetRegistry, SelfReferentialCollateral, @@ -42,13 +41,10 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME // Assets let collateral: Collateral[] - // Non-backing assets - let compoundMock: ComptrollerMock - // Tokens and Assets let dai: ERC20Mock let daiCollateral: SelfReferentialCollateral - let cDAI: CTokenMock + let cDAI: CTokenVaultMock let cDAICollateral: CTokenFiatCollateral // Config values @@ -96,7 +92,6 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME ;({ rsr, stRSR, - compoundMock, erc20s, collateral, config, @@ -111,7 +106,7 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME // Main ERC20 dai = erc20s[0] daiCollateral = collateral[0] - cDAI = erc20s[4] + cDAI = erc20s[4] cDAICollateral = await ( await ethers.getContractFactory('CTokenFiatCollateral') ).deploy( @@ -126,8 +121,7 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) // Basket configuration diff --git a/test/scenario/cETH.test.ts b/test/scenario/cETH.test.ts index fc85a62c9a..3488c3ddb5 100644 --- a/test/scenario/cETH.test.ts +++ b/test/scenario/cETH.test.ts @@ -21,6 +21,7 @@ import { TestIRevenueTrader, TestIRToken, WETH9, + CTokenVaultMock, } from '../../typechain' import { advanceTime } from '../utils/time' import { getTrade } from '../utils/trades' @@ -46,12 +47,13 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, let collateral: Collateral[] // Non-backing assets + let compToken: ERC20Mock let compoundMock: ComptrollerMock // Tokens and Assets let weth: WETH9 let wethCollateral: SelfReferentialCollateral - let cETH: CTokenMock + let cETH: CTokenVaultMock let cETHCollateral: CTokenSelfReferentialCollateral let token0: CTokenMock let collateral0: Collateral @@ -82,6 +84,7 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, ;({ rsr, stRSR, + compToken, compoundMock, erc20s, collateral, @@ -120,8 +123,8 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, // cETH cETH = await ( - await ethers.getContractFactory('CTokenMock') - ).deploy('cETH Token', 'cETH', weth.address) + await ethers.getContractFactory('CTokenVaultMock') + ).deploy('cETH Token', 'cETH', weth.address, compToken.address, compoundMock.address) cETHCollateral = await ( await ethers.getContractFactory('CTokenSelfReferentialCollateral') @@ -138,8 +141,7 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, REVENUE_HIDING, - await weth.decimals(), - compoundMock.address + await weth.decimals() ) // Backup diff --git a/test/scenario/cWBTC.test.ts b/test/scenario/cWBTC.test.ts index 4427d595df..631247c725 100644 --- a/test/scenario/cWBTC.test.ts +++ b/test/scenario/cWBTC.test.ts @@ -8,7 +8,7 @@ import { advanceTime } from '../utils/time' import { IConfig } from '../../common/configuration' import { CollateralStatus } from '../../common/constants' import { - CTokenMock, + CTokenVaultMock, CTokenNonFiatCollateral, ComptrollerMock, ERC20Mock, @@ -46,14 +46,15 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => let collateral: Collateral[] // Non-backing assets + let compToken: ERC20Mock let compoundMock: ComptrollerMock // Tokens and Assets let wbtc: ERC20Mock let wBTCCollateral: SelfReferentialCollateral - let cWBTC: CTokenMock + let cWBTC: CTokenVaultMock let cWBTCCollateral: CTokenNonFiatCollateral - let token0: CTokenMock + let token0: CTokenVaultMock let collateral0: Collateral let backupToken: ERC20Mock let backupCollateral: Collateral @@ -85,6 +86,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => ;({ rsr, stRSR, + compToken, compoundMock, erc20s, collateral, @@ -99,7 +101,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => } = await loadFixture(defaultFixture)) // Main ERC20 - token0 = erc20s[4] // cDai + token0 = erc20s[4] // cDai collateral0 = collateral[4] wbtc = await (await ethers.getContractFactory('ERC20Mock')).deploy('WBTC Token', 'WBTC') @@ -129,8 +131,8 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => // cWBTC cWBTC = await ( - await ethers.getContractFactory('CTokenMock') - ).deploy('cWBTC Token', 'cWBTC', wbtc.address) + await ethers.getContractFactory('CTokenVaultMock') + ).deploy('cWBTC Token', 'cWBTC', wbtc.address, compToken.address, compoundMock.address) cWBTCCollateral = await ( await ethers.getContractFactory('CTokenNonFiatCollateral') ).deploy( @@ -147,8 +149,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => }, targetUnitOracle.address, ORACLE_TIMEOUT, - REVENUE_HIDING, - compoundMock.address + REVENUE_HIDING ) // Backup diff --git a/test/utils/tokens.ts b/test/utils/tokens.ts new file mode 100644 index 0000000000..0d24fba285 --- /dev/null +++ b/test/utils/tokens.ts @@ -0,0 +1,34 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { CTokenVaultMock } from '@typechain/CTokenVaultMock' +import { ERC20Mock } from '@typechain/ERC20Mock' +import { StaticATokenMock } from '@typechain/StaticATokenMock' +import { USDCMock } from '@typechain/USDCMock' +import { BigNumber } from 'ethers' +import { Collateral } from '../fixtures' +import { ethers } from 'hardhat' + +export const mintCollaterals = async ( + owner: SignerWithAddress, + recipients: SignerWithAddress[], + amount: BigNumber, + basket: Collateral[] +) => { + const token0 = await ethers.getContractAt('ERC20Mock', await basket[0].erc20()) + const token1 = await ethers.getContractAt('USDCMock', await basket[1].erc20()) + const token2 = ( + await ethers.getContractAt('StaticATokenMock', await basket[2].erc20()) + ) + const token3 = ( + await ethers.getContractAt('CTokenVaultMock', await basket[3].erc20()) + ) + + for (const recipient of recipients) { + await token0.connect(owner).mint(recipient.address, amount) + + await token1.connect(owner).mint(recipient.address, amount) + + await token2.connect(owner).mint(recipient.address, amount) + + await token3.connect(owner).mint(recipient.address, amount) + } +} From fe6bebd6dcc4f3acfb50de91b0f26a28d9d30bc3 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 3 May 2023 18:54:20 -0400 Subject: [PATCH 151/462] StRSR: require basket ready --- contracts/p0/StRSR.sol | 4 ++-- contracts/p1/StRSR.sol | 4 ++-- test/ZZStRSR.test.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index c347ffcfce..7d3f35628c 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -192,7 +192,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { IBasketHandler bh = main.basketHandler(); require(bh.fullyCollateralized(), "RToken uncollateralized"); - require(bh.status() == CollateralStatus.SOUND, "basket defaulted"); + require(bh.isReady(), "basket not ready"); Withdrawal[] storage queue = withdrawals[account]; if (endId == 0) return; @@ -223,7 +223,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { // IBasketHandler bh = main.basketHandler(); // require(bh.fullyCollateralized(), "RToken uncollateralized"); - // require(bh.status() == CollateralStatus.SOUND, "basket defaulted"); + // require(bh.isReady(), "basket not ready"); Withdrawal[] storage queue = withdrawals[account]; if (endId == 0) return; diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 829a2c1ea1..7bc4bf67a2 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -302,7 +302,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // == Checks + Effects == require(basketHandler.fullyCollateralized(), "RToken uncollateralized"); - require(basketHandler.status() == CollateralStatus.SOUND, "basket defaulted"); + require(basketHandler.isReady(), "basket not ready"); uint256 firstId = firstRemainingDraft[draftEra][account]; CumulativeDraft[] storage queue = draftQueues[draftEra][account]; @@ -340,7 +340,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // We specifically allow unstaking when under collateralized // require(basketHandler.fullyCollateralized(), "RToken uncollateralized"); - // require(basketHandler.status() == CollateralStatus.SOUND, "basket defaulted"); + // require(basketHandler.isReady(), "basket not ready"); uint256 firstId = firstRemainingDraft[draftEra][account]; CumulativeDraft[] storage queue = draftQueues[draftEra][account]; diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index f725decf7c..09011b46be 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -786,7 +786,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { // Attempt to Withdraw await expect(stRSR.connect(addr1).withdraw(addr1.address, 1)).to.be.revertedWith( - 'basket defaulted' + 'basket not ready' ) // Nothing completed @@ -806,7 +806,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { // Attempt to Withdraw await expect(stRSR.connect(addr1).withdraw(addr1.address, 1)).to.be.revertedWith( - 'basket defaulted' + 'basket not ready' ) }) From 1913fc9d5128dc98a25a5c853acbf2b5dac467d3 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 3 May 2023 20:09:02 -0400 Subject: [PATCH 152/462] second implementation pass --- contracts/interfaces/IBasketHandler.sol | 2 +- contracts/interfaces/IRToken.sol | 20 +- contracts/p0/BasketHandler.sol | 26 +- contracts/p0/RToken.sol | 124 ++++--- contracts/p1/BasketHandler.sol | 28 +- contracts/p1/RToken.sol | 179 ++++++---- test/Main.test.ts | 20 +- test/RToken.test.ts | 434 ++++++++++++++---------- 8 files changed, 475 insertions(+), 358 deletions(-) diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 36fc8b6631..7f516ca918 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -126,7 +126,7 @@ interface IBasketHandler is IComponent { /// @param amount {BU} /// @return erc20s The backing collateral erc20s /// @return quantities {qTok} ERC20 token quantities equal to `amount` BUs - function quoteHistoricalRedemption( + function quoteCustomRedemption( uint48[] memory basketNonces, uint192[] memory portions, uint192 amount diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index 448039d3ec..3580b62276 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -79,23 +79,17 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea function issueTo(address recipient, uint256 amount) external; /// Redeem RToken for basket collateral - /// @dev Use customRedemption to restrict min token amounts received + /// @dev Use redeemToCustom for non-current baskets /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem - /// @param basketNonce The nonce of the basket the redemption should be from; else reverts /// @custom:interaction - function redeem(uint256 amount, uint48 basketNonce) external; + function redeem(uint256 amount) external; /// Redeem RToken for basket collateral to a particular recipient - /// @dev Use customRedemption to restrict min token amounts received + /// @dev Use redeemToCustom for non-current baskets /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem - /// @param basketNonce The nonce of the basket the redemption should be from; else reverts /// @custom:interaction - function redeemTo( - address recipient, - uint256 amount, - uint48 basketNonce - ) external; + function redeemTo(address recipient, uint256 amount) external; /// Redeem RToken for a linear combination of historical baskets, to a particular recipient /// @dev Allows partial redemptions up to the minAmounts @@ -103,15 +97,15 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @param basketNonces An array of basket nonces to do redemption from /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE - /// @param minERC20s An array of ERC20 addresses to require minAmounts for + /// @param erc20sOut An array of ERC20s expected out /// @param minAmounts {qTok} The minimum ERC20 quantities the caller should receive /// @custom:interaction - function customRedemption( + function redeemToCustom( address recipient, uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, - IERC20[] memory minERC20s, + IERC20[] memory erc20sOut, uint256[] memory minAmounts ) external; diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 2285dbb1d4..5a961d65cf 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -121,7 +121,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // basket is the current basket. Basket private basket; - uint48 public override nonce; // A unique identifier for this basket instance + uint48 public override nonce; // {basketNonce} A unique identifier for this basket instance uint48 public override timestamp; // The timestamp when this basket was last set // If disabled is true, status() is DISABLED, the basket is invalid, and the whole system should @@ -131,7 +131,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // These are effectively local variables of _switchBasket. // Nothing should use their values from previous transactions. EnumerableSet.Bytes32Set private targetNames; - Basket private newBasket; // Always empty + Basket private newBasket; uint48 public warmupPeriod; // {s} how long to wait until issuance/trading after regaining SOUND @@ -143,10 +143,10 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // Nonce of the first reference basket from the current history // A new historical record begins whenever the prime basket is changed // There can be 0 to any number of reference baskets from the current prime basket history - uint48 private primeNonce; // {nonce} + uint48 private primeNonce; // {basketNonce} - // The historical baskets by basket nonce; includes current basket - mapping(uint48 => Basket) private historicalBaskets; + // A history of baskets by basket nonce; includes current basket + mapping(uint48 => Basket) private basketHistory; // ==== Invariants ==== // basket is a valid Basket: @@ -440,14 +440,13 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } /// Return the redemption value of `amount` BUs for a linear combination of historical baskets - /// Checks `portions` sum to FIX_ONE /// @param basketNonces An array of basket nonces to do redemption from - /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE + /// @param portions {1} An array of Fix quantities /// @param amount {BU} /// @return erc20s The backing collateral erc20s /// @return quantities {qTok} ERC20 token quantities equal to `amount` BUs // Returns (erc20s, [quantity(e) * amount {as qTok} for e in erc20s]) - function quoteHistoricalRedemption( + function quoteCustomRedemption( uint48[] memory basketNonces, uint192[] memory portions, uint192 amount @@ -456,13 +455,6 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { require(primeNonce > 0, "primeNonce uninitialized"); require(basketNonces.length == portions.length, "portions does not mirror basketNonces"); - // Confirm portions sum to FIX_ONE - { - uint256 portionsSum; - for (uint256 i = 0; i < portions.length; ++i) portionsSum += portions[i]; - require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); - } - IERC20[] memory erc20sAll = new IERC20[](main.assetRegistry().size()); uint192[] memory refAmtsAll = new uint192[](erc20sAll.length); @@ -474,7 +466,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { basketNonces[i] >= primeNonce && basketNonces[i] <= nonce, "invalid basketNonce" ); // will always revert directly after setPrimeBasket() - Basket storage b = historicalBaskets[basketNonces[i]]; + Basket storage b = basketHistory[basketNonces[i]]; // Add-in refAmts contribution from historical basket for (uint256 j = 0; j < b.erc20s.length; ++j) { @@ -734,7 +726,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { if (!disabled) { nonce += 1; basket.setFrom(newBasket); - historicalBaskets[nonce].setFrom(newBasket); + basketHistory[nonce].setFrom(newBasket); timestamp = uint48(block.timestamp); } diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index d71338fb35..8277549106 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -81,14 +81,14 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { ); } - /// Issue an RToken with basket collateral + /// Issue an RToken on the current basket /// @param amount {qTok} The quantity of RToken to issue /// @custom:interaction function issue(uint256 amount) public { issueTo(_msgSender(), amount); } - /// Issue an RToken with basket collateral, to a particular recipient + /// Issue an RToken on the current basket, to a particular recipient /// @param recipient The address to receive the issued RTokens /// @param amount {qRTok} The quantity of RToken to issue /// @custom:interaction @@ -130,63 +130,81 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// Redeem RToken for basket collateral /// @param amount {qTok} The quantity {qRToken} of RToken to redeem - /// @param basketNonce The nonce of the basket the redemption should be from; else reverts /// @custom:interaction - function redeem(uint256 amount, uint48 basketNonce) external { - redeemTo(_msgSender(), amount, basketNonce); + function redeem(uint256 amount) external { + redeemTo(_msgSender(), amount); } /// Redeem RToken for basket collateral to a particular recipient /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem - /// @param basketNonce The nonce of the basket the redemption should be from /// @custom:interaction - function redeemTo( - address recipient, - uint256 amount, - uint48 basketNonce - ) public { - uint48[] memory basketNonces = new uint48[](1); - basketNonces[0] = basketNonce; - uint192[] memory portions = new uint192[](1); - portions[0] = FIX_ONE; - - customRedemption( - recipient, - amount, - basketNonces, - portions, - new IERC20[](0), - new uint256[](0) + function redeemTo(address recipient, uint256 amount) public notFrozen exchangeRateIsValidAfter { + require(amount > 0, "Cannot redeem zero"); + require(amount <= balanceOf(_msgSender()), "insufficient balance"); + require( + main.basketHandler().fullyCollateralized(), + "partial redemption; use redeemToCustom" + ); + // redemption while IFFY/DISABLED allowed + + // Call collective state keepers. + main.poke(); + + // Revert if redemption exceeds either supply throttle + issuanceThrottle.useAvailable(totalSupply(), -int256(amount)); + redemptionThrottle.useAvailable(totalSupply(), int256(amount)); // reverts on overuse + + // {BU} = {BU} * {qRTok} / {qRTok} + uint192 basketsRedeemed = basketsNeeded.muluDivu(amount, totalSupply()); + assert(basketsRedeemed.lte(basketsNeeded)); + emit Redemption(_msgSender(), recipient, amount, basketsRedeemed); + + (address[] memory erc20s, uint256[] memory amounts) = main.basketHandler().quote( + basketsRedeemed, + FLOOR ); + + emit BasketsNeededChanged(basketsNeeded, basketsNeeded.minus(basketsRedeemed)); + basketsNeeded = basketsNeeded.minus(basketsRedeemed); + + // ==== Send out balances ==== + for (uint256 i = 0; i < erc20s.length; i++) { + // Send withdrawal + if (amounts[i] > 0) { + IERC20(erc20s[i]).safeTransferFrom( + address(main.backingManager()), + recipient, + amounts[i] + ); + } + } + + // Accept and burn RToken, reverts if not enough balance + _burn(_msgSender(), amount); } - /// Redeem RToken for basket collateral to a particular recipient + /// Redeem RToken for a linear combination of historical baskets, to a particular recipient /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @param basketNonces An array of basket nonces to do redemption from /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE - /// @param minERC20s An array of ERC20 addresses to require minAmounts for + /// @param erc20sOut An array of ERC20s expected out /// @param minAmounts {qTok} The minimum ERC20 quantities the caller should receive /// @custom:interaction - function customRedemption( + function redeemToCustom( address recipient, uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, - IERC20[] memory minERC20s, + IERC20[] memory erc20sOut, uint256[] memory minAmounts - ) public notFrozen exchangeRateIsValidAfter { + ) external notFrozen exchangeRateIsValidAfter { require(amount > 0, "Cannot redeem zero"); require(amount <= balanceOf(_msgSender()), "insufficient balance"); // Call collective state keepers. - // notFrozen modifier requires we use only a subset of main.poke() - main.assetRegistry().refresh(); - - // Failure to melt results in a lower redemption price, so we can allow it when paused - // solhint-disable-next-line no-empty-blocks - try main.furnace().melt() {} catch {} + main.poke(); // Revert if redemption exceeds either supply throttle issuanceThrottle.useAvailable(totalSupply(), -int256(amount)); @@ -197,32 +215,28 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { assert(basketsRedeemed.lte(basketsNeeded)); emit Redemption(_msgSender(), recipient, amount, basketsRedeemed); - address[] memory erc20s; - uint256[] memory amounts; - if (basketNonces.length == 1 && main.basketHandler().nonce() == basketNonces[0]) { - // Current-basket redemption - - require(portions.length == 1, "portions does not mirror basketNonces"); - (erc20s, amounts) = main.basketHandler().quote(basketsRedeemed, FLOOR); - } else { - // Historical basket redemption - - // BasketHandler will handle the require that portions sum to FIX_ZERO - (erc20s, amounts) = main.basketHandler().quoteHistoricalRedemption( - basketNonces, - portions, - basketsRedeemed - ); + // === Get basket redemption amounts === + + { + uint256 portionsSum; + for (uint256 i = 0; i < portions.length; ++i) { + portionsSum += portions[i]; + } + require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); } + (address[] memory erc20s, uint256[] memory amounts) = main + .basketHandler() + .quoteCustomRedemption(basketNonces, portions, basketsRedeemed); + emit BasketsNeededChanged(basketsNeeded, basketsNeeded.minus(basketsRedeemed)); basketsNeeded = basketsNeeded.minus(basketsRedeemed); // === Save initial recipient balances === - uint256[] memory initialMinERC20Balances = new uint256[](minERC20s.length); - for (uint256 i = 0; i < minERC20s.length; ++i) { - initialMinERC20Balances[i] = minERC20s[i].balanceOf(recipient); + uint256[] memory erc20sOutBalances = new uint256[](erc20sOut.length); + for (uint256 i = 0; i < erc20sOut.length; ++i) { + erc20sOutBalances[i] = erc20sOut[i].balanceOf(recipient); } // ==== Prorate redemption + send out balances ==== @@ -257,9 +271,9 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { // === Post-checks === // Check post-balances - for (uint256 i = 0; i < minERC20s.length; ++i) { + for (uint256 i = 0; i < erc20sOut.length; ++i) { require( - minERC20s[i].balanceOf(recipient) - initialMinERC20Balances[i] >= minAmounts[i], + erc20sOut[i].balanceOf(recipient) - erc20sOutBalances[i] >= minAmounts[i], "redemption below minimum" ); } diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index fab38c7897..baa1991743 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -133,7 +133,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // basket is the current basket. Basket private basket; - uint48 public override nonce; // A unique identifier for this basket instance + uint48 public override nonce; // {basketNonce} A unique identifier for this basket instance uint48 public override timestamp; // The timestamp when this basket was last set // If disabled is true, status() is DISABLED, the basket is invalid, @@ -145,7 +145,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // These are effectively local variables of _switchBasket. // Nothing should use their values from previous transactions. EnumerableSet.Bytes32Set private _targetNames; - Basket private _newBasket; // Always empty + Basket private _newBasket; // === Warmup Period === // Added in 3.0.0 @@ -164,10 +164,10 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // Nonce of the first reference basket from the current prime basket history // A new historical record begins whenever the prime basket is changed // There can be 0 to any number of reference baskets from the current history - uint48 private primeNonce; // {nonce} + uint48 private primeNonce; // {basketNonce} - // The historical baskets by basket nonce; includes current basket - mapping(uint48 => Basket) private historicalBaskets; + // A history of baskets by basket nonce; includes current basket + mapping(uint48 => Basket) private basketHistory; // === @@ -444,7 +444,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } /// Return the current issuance/redemption value of `amount` BUs - /// @dev Subset of logic with quoteHistoricalRedemption; more gas efficient for current nonce + /// @dev Subset of logic of quoteCustomRedemption; more gas efficient for current nonce /// @param amount {BU} /// @return erc20s The backing collateral erc20s /// @return quantities {qTok} ERC20 token quantities equal to `amount` BUs @@ -473,14 +473,13 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } /// Return the redemption value of `amount` BUs for a linear combination of historical baskets - /// Checks `portions` sum to FIX_ONE /// @param basketNonces An array of basket nonces to do redemption from - /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE + /// @param portions {1} An array of Fix quantities /// @param amount {BU} /// @return erc20s The backing collateral erc20s /// @return quantities {qTok} ERC20 token quantities equal to `amount` BUs // Returns (erc20s, [quantity(e) * amount {as qTok} for e in erc20s]) - function quoteHistoricalRedemption( + function quoteCustomRedemption( uint48[] memory basketNonces, uint192[] memory portions, uint192 amount @@ -489,13 +488,6 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { require(primeNonce > 0, "primeNonce uninitialized"); require(basketNonces.length == portions.length, "portions does not mirror basketNonces"); - // Confirm portions sum to FIX_ONE - { - uint256 portionsSum; - for (uint256 i = 0; i < portions.length; ++i) portionsSum += portions[i]; - require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); - } - IERC20[] memory erc20sAll = new IERC20[](assetRegistry.size()); uint192[] memory refAmtsAll = new uint192[](erc20sAll.length); @@ -507,7 +499,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { basketNonces[i] >= primeNonce && basketNonces[i] <= nonce, "invalid basketNonce" ); // will always revert directly after setPrimeBasket() - Basket storage b = historicalBaskets[basketNonces[i]]; + Basket storage b = basketHistory[basketNonces[i]]; // Add-in refAmts contribution from historical basket for (uint256 j = 0; j < b.erc20s.length; ++j) { IERC20 erc20 = b.erc20s[j]; @@ -779,7 +771,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { if (!disabled) { nonce += 1; basket.setFrom(_newBasket); - historicalBaskets[nonce].setFrom(_newBasket); + basketHistory[nonce].setFrom(_newBasket); timestamp = uint48(block.timestamp); } diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 5455cbf3e8..53ac68c625 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -10,6 +10,7 @@ import "../libraries/Throttle.sol"; import "../vendor/ERC20PermitUpgradeable.sol"; import "./mixins/Component.sol"; import "hardhat/console.sol"; + /** * @title RTokenP1 * An ERC20 with an elastic supply and governable exchange rate to basket units. @@ -102,14 +103,14 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { require(low >= MIN_EXCHANGE_RATE && high <= MAX_EXCHANGE_RATE, "BU rate out of range"); } - /// Issue an RToken with basket collateral + /// Issue an RToken on current the basket /// @param amount {qTok} The quantity of RToken to issue /// @custom:interaction nearly CEI, but see comments around handling of refunds function issue(uint256 amount) public { issueTo(_msgSender(), amount); } - /// Issue an RToken with basket collateral, to a particular recipient + /// Issue an RToken on the current basket, to a particular recipient /// @param recipient The address to receive the issued RTokens /// @param amount {qRTok} The quantity of RToken to issue /// @custom:interaction @@ -124,9 +125,11 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { require(amount > 0, "Cannot issue zero"); // == Refresh == + assetRegistry.refresh(); // == Checks-effects block == + address issuer = _msgSender(); // OK to save: it can't be changed in reentrant runs // Ensure basket is ready, SOUND and not in warmup period @@ -165,6 +168,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { basketsNeeded = newBasketsNeeded; // == Interactions: mint + transfer tokens to BackingManager == + _mint(recipient, amount); for (uint256 i = 0; i < erc20s.length; ++i) { @@ -178,54 +182,94 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// Redeem RToken for basket collateral /// @param amount {qTok} The quantity {qRToken} of RToken to redeem - /// @param basketNonce The nonce of the basket the redemption should be from /// @custom:interaction CEI - function redeem(uint256 amount, uint48 basketNonce) external { - redeemTo(_msgSender(), amount, basketNonce); + function redeem(uint256 amount) external { + redeemTo(_msgSender(), amount); } /// Redeem RToken for basket collateral to a particular recipient + // checks: + // amount > 0 + // amount <= balanceOf(caller) + // + // effects: + // (so totalSupply -= amount and balanceOf(caller) -= amount) + // basketsNeeded' / totalSupply' >== basketsNeeded / totalSupply + // burn(caller, amount) + // + // actions: + // let erc20s = basketHandler.erc20s() + // for each token in erc20s: + // let tokenAmt = (amount * basketsNeeded / totalSupply) current baskets + // do token.transferFrom(backingManager, caller, tokenAmt) + // untestable: + // `else` branch of `exchangeRateIsValidAfter` (ie. revert) + // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem - /// @param basketNonce The nonce of the basket the redemption should be from - /// @custom:interaction - function redeemTo( - address recipient, - uint256 amount, - uint48 basketNonce - ) public { - uint48[] memory basketNonces = new uint48[](1); - basketNonces[0] = basketNonce; - uint192[] memory portions = new uint192[](1); - portions[0] = FIX_ONE; - - customRedemption( - recipient, - amount, - basketNonces, - portions, - new IERC20[](0), - new uint256[](0) + /// @custom:interaction RCEI + function redeemTo(address recipient, uint256 amount) public notFrozen exchangeRateIsValidAfter { + // == Refresh == + + assetRegistry.refresh(); + main.furnace().melt(); + + // == Checks and Effects == + + require(amount > 0, "Cannot redeem zero"); + require(amount <= balanceOf(_msgSender()), "insufficient balance"); + require(basketHandler.fullyCollateralized(), "partial redemption; use redeemToCustom"); + // redemption while IFFY/DISABLED allowed + + uint256 supply = totalSupply(); + + // Accept and burn RToken, reverts if not enough balance to burn + _burn(_msgSender(), amount); + + // Revert if redemption exceeds either supply throttle + issuanceThrottle.useAvailable(supply, -int256(amount)); + redemptionThrottle.useAvailable(supply, int256(amount)); // reverts on over-redemption + + // D18{BU} = D18{BU} * {qRTok} / {qRTok} + uint192 basketsRedeemed = basketsNeeded.muluDivu(amount, supply); // FLOOR + emit Redemption(_msgSender(), recipient, amount, basketsRedeemed); + emit BasketsNeededChanged(basketsNeeded, basketsNeeded - basketsRedeemed); + basketsNeeded = basketsNeeded - basketsRedeemed; + + (address[] memory erc20s, uint256[] memory amounts) = basketHandler.quote( + basketsRedeemed, + FLOOR ); + + // === Interactions === + + for (uint256 i = 0; i < erc20s.length; ++i) { + if (amounts[i] == 0) continue; + + // Send withdrawal + IERC20Upgradeable(erc20s[i]).safeTransferFrom( + address(backingManager), + recipient, + amounts[i] + ); + } } - /// Redeem RToken for basket collateral to a particular recipient - /// Handles both historical and present-basket redemptions + /// Redeem RToken for a linear combination of historical baskets, to a particular recipient // checks: // amount > 0 // amount <= balanceOf(caller) // sum(portions) == FIX_ONE - // nonce >= basketHandler.historicalNonce() for nonce in basketNonces + // nonce >= basketHandler.primeNonce() for nonce in basketNonces // // effects: // (so totalSupply -= amount and balanceOf(caller) -= amount) // basketsNeeded' / totalSupply' >== basketsNeeded / totalSupply + // burn(caller, amount) // // actions: - // let erc20s = basketHandler.erc20s() - // burn(caller, amount) // for each token in erc20s: - // let tokenAmt = (amount * basketsNeeded / totalSupply) baskets of support for token + // let tokenAmt = (amount * basketsNeeded / totalSupply) custom baskets // let prorataAmt = (amount / totalSupply) * token.balanceOf(backingManager) // do token.transferFrom(backingManager, caller, min(tokenAmt, prorataAmt)) // untestable: @@ -236,61 +280,58 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @param basketNonces An array of basket nonces to do redemption from /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE - /// @param minERC20s An array of ERC20 addresses to require minAmounts for + /// @param erc20sOut An array of ERC20s expected out /// @param minAmounts {qTok} The minimum ERC20 quantities the caller should receive - /// @custom:interaction - function customRedemption( + /// @custom:interaction RCEI + function redeemToCustom( address recipient, uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, - IERC20[] memory minERC20s, + IERC20[] memory erc20sOut, uint256[] memory minAmounts - ) public notFrozen exchangeRateIsValidAfter { + ) external notFrozen exchangeRateIsValidAfter { // == Refresh == + assetRegistry.refresh(); + main.furnace().melt(); // == Checks and Effects == + require(amount > 0, "Cannot redeem zero"); require(amount <= balanceOf(_msgSender()), "insufficient balance"); + uint256 portionsSum; + for (uint256 i = 0; i < portions.length; ++i) { + portionsSum += portions[i]; + } + require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); - // Failure to melt results in a lower redemption price, so we can allow it when paused - // solhint-disable-next-line no-empty-blocks - try main.furnace().melt() {} catch {} uint256 supply = totalSupply(); + // Accept and burn RToken, reverts if not enough balance to burn + _burn(_msgSender(), amount); + // Revert if redemption exceeds either supply throttle issuanceThrottle.useAvailable(supply, -int256(amount)); redemptionThrottle.useAvailable(supply, int256(amount)); // reverts on over-redemption - // ==== Get basket redemption ==== - // i.e, set (erc20s, amounts) = basketHandler.quote(amount * basketsNeeded / totalSupply) - // D18{BU} = D18{BU} * {qRTok} / {qRTok} uint192 basketsRedeemed = basketsNeeded.muluDivu(amount, supply); // FLOOR emit Redemption(_msgSender(), recipient, amount, basketsRedeemed); + emit BasketsNeededChanged(basketsNeeded, basketsNeeded - basketsRedeemed); + basketsNeeded = basketsNeeded - basketsRedeemed; - address[] memory erc20s; - uint256[] memory amounts; - if (basketNonces.length == 1 && basketHandler.nonce() == basketNonces[0]) { - // Current-basket redemption - - require(portions.length == 1, "portions does not mirror basketNonces"); - (erc20s, amounts) = basketHandler.quote(basketsRedeemed, FLOOR); - } else { - // Historical basket redemption - - // BasketHandler will handle the require that portions sum to FIX_ZERO - (erc20s, amounts) = basketHandler.quoteHistoricalRedemption( - basketNonces, - portions, - basketsRedeemed - ); - } + // === Get basket redemption amounts === + + (address[] memory erc20s, uint256[] memory amounts) = basketHandler.quoteCustomRedemption( + basketNonces, + portions, + basketsRedeemed + ); // ==== Prorate redemption ==== // i.e, set amounts = min(amounts, balances * amount / totalSupply) - // where balances[i] = erc20s[i].balanceOf(this) + // where balances[i] = erc20s[i].balanceOf(backingManager) // Bound each withdrawal by the prorata share, in case we're currently under-collateralized for (uint256 i = 0; i < erc20s.length; ++i) { @@ -304,19 +345,14 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { if (prorata < amounts[i]) amounts[i] = prorata; } - emit BasketsNeededChanged(basketsNeeded, basketsNeeded - basketsRedeemed); - basketsNeeded = basketsNeeded - basketsRedeemed; - // === Save initial recipient balances === - uint256[] memory initialMinERC20Balances = new uint256[](minERC20s.length); - for (uint256 i = 0; i < minERC20s.length; ++i) { - initialMinERC20Balances[i] = minERC20s[i].balanceOf(recipient); + uint256[] memory erc20sOutBalances = new uint256[](erc20sOut.length); + for (uint256 i = 0; i < erc20sOut.length; ++i) { + erc20sOutBalances[i] = erc20sOut[i].balanceOf(recipient); } // === Interactions === - // Accept and burn RToken, reverts if not enough balance to burn - _burn(_msgSender(), amount); // Distribute tokens; revert if empty redemption { @@ -338,12 +374,9 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // === Post-checks === // Check post-balances - for (uint256 i = 0; i < minERC20s.length; ++i) { - uint256 bal = minERC20s[i].balanceOf(recipient); - require( - bal - initialMinERC20Balances[i] >= minAmounts[i], - "redemption below minimum" - ); + for (uint256 i = 0; i < erc20sOut.length; ++i) { + uint256 bal = erc20sOut[i].balanceOf(recipient); + require(bal - erc20sOutBalances[i] >= minAmounts[i], "redemption below minimum"); } } diff --git a/test/Main.test.ts b/test/Main.test.ts index 5ae2aca564..14584d1c19 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1783,7 +1783,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const basketNonces = [1] const portions = [fp('1')] const amount = fp('10000') - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, amount) + const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, amount) expect(quote.erc20s.length).equal(4) expect(quote.quantities.length).equal(4) @@ -1817,7 +1817,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const balsBefore = await getBalances(addr1.address, expectedTokens) await rToken .connect(addr1) - .customRedemption( + .redeemToCustom( addr1.address, amount, basketNonces, @@ -1846,7 +1846,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const basketNonces = [1, 2] const portions = [fp('0.5'), fp('0.5')] const amount = fp('10000') - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, amount) + const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, amount) expect(quote.erc20s.length).equal(5) expect(quote.quantities.length).equal(5) @@ -1887,7 +1887,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .customRedemption( + .redeemToCustom( addr1.address, amount, basketNonces, @@ -1902,7 +1902,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await rToken .connect(addr1) - .customRedemption( + .redeemToCustom( addr1.address, amount, basketNonces, @@ -1931,7 +1931,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const basketNonces = [1, 2] const portions = [fp('0.5'), fp('0.5')] const amount = fp('10000') - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, amount) + const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, amount) expect(quote.erc20s.length).equal(4) expect(quote.quantities.length).equal(4) @@ -1965,7 +1965,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const balsBefore = await getBalances(addr1.address, expectedTokens) await rToken .connect(addr1) - .customRedemption( + .redeemToCustom( addr1.address, amount, basketNonces, @@ -2002,7 +2002,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const basketNonces = [1, 2] const portions = [fp('0.2'), fp('0.8')] const amount = fp('10000') - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, amount) + const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, amount) expect(quote.erc20s.length).equal(6) expect(quote.quantities.length).equal(6) @@ -2048,7 +2048,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .customRedemption( + .redeemToCustom( addr1.address, amount, basketNonces, @@ -2063,7 +2063,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await rToken .connect(addr1) - .customRedemption( + .redeemToCustom( addr1.address, amount, basketNonces, diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 040574f9f2..b779fe26b7 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -1435,15 +1435,21 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Redeem rTokens const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, redeemAmount) - await rToken.connect(addr1).customRedemption( - addr1.address, - redeemAmount, + const quote = await basketHandler.quoteCustomRedemption( basketNonces, portions, - quote.erc20s, - quote.quantities + redeemAmount ) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + redeemAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) // Check funds were transferred expect(await rToken.balanceOf(addr1.address)).to.equal(0) @@ -1468,16 +1474,18 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Redeem rTokens to another account const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount) + const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, issueAmount) await expect( - rToken.connect(addr1).customRedemption( - addr2.address, - issueAmount, - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) + rToken + .connect(addr1) + .redeemToCustom( + addr2.address, + issueAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) ) .to.emit(rToken, 'Redemption') .withArgs(addr1.address, addr2.address, issueAmount, issueAmount) @@ -1506,15 +1514,21 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Redeem rTokens const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, redeemAmount) - await rToken.connect(addr1).customRedemption( - addr1.address, - redeemAmount, + const quote = await basketHandler.quoteCustomRedemption( basketNonces, portions, - quote.erc20s, - quote.quantities + redeemAmount ) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + redeemAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) // Check asset value expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( @@ -1522,14 +1536,16 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ) // Redeem rTokens with another user - await rToken.connect(addr2).customRedemption( - addr2.address, - redeemAmount, - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) + await rToken + .connect(addr2) + .redeemToCustom( + addr2.address, + redeemAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) // Check funds were transferred expect(await rToken.balanceOf(addr1.address)).to.equal(0) @@ -1556,15 +1572,17 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount) - await rToken.connect(addr1).customRedemption( - addr1.address, - issueAmount, - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) + const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, issueAmount) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + issueAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) expect(await rToken.totalSupply()).to.equal(0) }) @@ -1573,15 +1591,17 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount) - await rToken.connect(addr1).customRedemption( - addr1.address, - issueAmount, - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) + const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, issueAmount) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + issueAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) expect(await rToken.totalSupply()).to.equal(0) }) @@ -1590,15 +1610,17 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await main.connect(owner).pauseIssuance() const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount) - await rToken.connect(addr1).customRedemption( - addr1.address, - issueAmount, - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) + const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, issueAmount) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + issueAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) expect(await rToken.totalSupply()).to.equal(0) }) @@ -1608,16 +1630,18 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Try to redeem const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount) + const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, issueAmount) await expect( - rToken.connect(addr1).customRedemption( - addr1.address, - issueAmount, - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) + rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + issueAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) ).to.be.revertedWith('frozen') // Check values @@ -1634,15 +1658,21 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Should not revert with empty redemption yet const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount.div(2)) - await rToken.connect(addr1).customRedemption( - addr1.address, - issueAmount.div(2), + const quote = await basketHandler.quoteCustomRedemption( basketNonces, portions, - quote.erc20s, - quote.quantities + issueAmount.div(2) ) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + issueAmount.div(2), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) // Burn the rest @@ -1652,14 +1682,16 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Now it should revert await expect( - rToken.connect(addr1).customRedemption( - addr1.address, - issueAmount.div(2), - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) + rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + issueAmount.div(2), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) ).to.be.revertedWith('empty redemption') // Check values @@ -1670,16 +1702,22 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Should fail if revertOnPartialRedemption is true const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount.div(2)) + const quote = await basketHandler.quoteCustomRedemption( + basketNonces, + portions, + issueAmount.div(2) + ) await expect( - rToken.connect(addr1).customRedemption( - addr1.address, - issueAmount.div(2), - [2], - portions, - quote.erc20s, - quote.quantities - ) + rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + issueAmount.div(2), + [2], + portions, + quote.erc20s, + quote.quantities + ) ).to.be.revertedWith('invalid basketNonce') }) @@ -1692,16 +1730,22 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // 1st redemption const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount.div(2)) + const quote = await basketHandler.quoteCustomRedemption( + basketNonces, + portions, + issueAmount.div(2) + ) await expect( - rToken.connect(addr1).customRedemption( - addr1.address, - issueAmount.div(2), - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) + rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + issueAmount.div(2), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) ).to.emit(rToken, 'Redemption') expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) expect(await token0.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(8))) @@ -1709,14 +1753,16 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // 2nd redemption await expect( - rToken.connect(addr1).customRedemption( - addr1.address, - issueAmount.div(2), - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) + rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + issueAmount.div(2), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) ).to.emit(rToken, 'Redemption') expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) expect(await token2.balanceOf(addr1.address)).to.equal(initialBal) @@ -1724,20 +1770,22 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { it('Should not redeem() unregistered collateral #fast', async function () { // Unregister collateral2 - + const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount) + const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, issueAmount) await assetRegistry.connect(owner).unregister(collateral2.address) await expect( - rToken.connect(addr1).customRedemption( - addr1.address, - issueAmount, - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) + rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + issueAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) ).revertedWith('erc20 unregistered') }) @@ -1748,16 +1796,18 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Redemption const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount) + const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, issueAmount) await expect( - rToken.connect(addr1).customRedemption( - addr1.address, - issueAmount, - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) + rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + issueAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) ).to.emit(rToken, 'Redemption') expect(await rToken.totalSupply()).to.equal(0) expect(await token0.balanceOf(addr1.address)).to.be.equal(initialBal) @@ -1770,15 +1820,21 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Leave only 1 RToken issue const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, issueAmount.sub(bn('1e18'))) - await rToken.connect(addr1).customRedemption( - addr1.address, - issueAmount.sub(bn('1e18')), + const quote = await basketHandler.quoteCustomRedemption( basketNonces, portions, - quote.erc20s, - quote.quantities + issueAmount.sub(bn('1e18')) ) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + issueAmount.sub(bn('1e18')), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) expect(await rToken.totalSupply()).to.equal(fp('1')) @@ -1790,16 +1846,22 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { const redeemAmount: BigNumber = bn('1.5e9') // Redeem rTokens successfully - const quote2 = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, bn(redeemAmount)) + const quote2 = await basketHandler.quoteCustomRedemption( + basketNonces, + portions, + bn(redeemAmount) + ) await expect( - rToken.connect(addr1).customRedemption( - addr1.address, - bn(redeemAmount), - basketNonces, - portions, - quote2.erc20s, - quote2.quantities - ) + rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + bn(redeemAmount), + basketNonces, + portions, + quote2.erc20s, + quote2.quantities + ) ).to.not.be.reverted }) @@ -1835,15 +1897,21 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, redeemAmount) - await rToken.connect(addr1).customRedemption( - addr1.address, - redeemAmount, + const quote = await basketHandler.quoteCustomRedemption( basketNonces, portions, - quote.erc20s, - quote.quantities + redeemAmount ) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + redeemAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) issueAmount = issueAmount.sub(redeemAmount) expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) expect(await rToken.totalSupply()).to.equal(issueAmount) @@ -1865,26 +1933,38 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { redeemAmount = issueAmount.mul(redemptionThrottleParams.pctRate).div(fp('1')) const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, redeemAmount.add(1)) + const quote = await basketHandler.quoteCustomRedemption( + basketNonces, + portions, + redeemAmount.add(1) + ) await expect( - rToken.connect(addr1).customRedemption( + rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + redeemAmount.add(1), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + ).to.be.revertedWith('supply change throttled') + const quote2 = await basketHandler.quoteCustomRedemption( + basketNonces, + portions, + redeemAmount + ) + await rToken + .connect(addr1) + .redeemToCustom( addr1.address, redeemAmount.add(1), basketNonces, portions, - quote.erc20s, - quote.quantities + quote2.erc20s, + quote2.quantities ) - ).to.be.revertedWith('supply change throttled') - const quote2 = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, redeemAmount) - await rToken.connect(addr1).customRedemption( - addr1.address, - redeemAmount.add(1), - basketNonces, - portions, - quote2.erc20s, - quote2.quantities - ) // Check updated redemption throttle expect(await rToken.redemptionAvailable()).to.equal(bn(0)) @@ -1922,15 +2002,21 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.redemptionAvailable()).to.equal(throttles.amtRate) const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, throttles.amtRate) - await rToken.connect(addr1).customRedemption( - addr1.address, - throttles.amtRate, + const quote = await basketHandler.quoteCustomRedemption( basketNonces, portions, - quote.erc20s, - quote.quantities + throttles.amtRate ) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + throttles.amtRate, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) }) @@ -1942,15 +2028,21 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Large redemption should fail const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteHistoricalRedemption(basketNonces, portions, redeemAmount.add(1)) - await rToken.connect(addr1).customRedemption( - addr1.address, - redeemAmount.add(1), + const quote = await basketHandler.quoteCustomRedemption( basketNonces, portions, - quote.erc20s, - quote.quantities + redeemAmount.add(1) ) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + redeemAmount.add(1), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) await expect( rToken.connect(addr1).redeem(redeemAmount.add(1), await basketHandler.nonce()) ).to.be.revertedWith('supply change throttled') From 57a2045490db9ad2b6d888b29e283f9934a19394 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 3 May 2023 20:13:31 -0400 Subject: [PATCH 153/462] lint clean --- contracts/p0/RToken.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 8277549106..1629e05171 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -242,7 +242,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { // ==== Prorate redemption + send out balances ==== { bool allZero = true; - // Bound each withdrawal by the prorata share, in case we're currently under-collateralized + // Bound each withdrawal by the prorata share, in case currently under-collateralized for (uint256 i = 0; i < erc20s.length; i++) { uint256 bal = IERC20Upgradeable(erc20s[i]).balanceOf( address(main.backingManager()) From 000ac1d1e20140ad14b5b5b66bd0cba81b359e81 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 3 May 2023 20:37:01 -0400 Subject: [PATCH 154/462] basket handler nits + add getHistoricalBasket() function --- contracts/interfaces/IBasketHandler.sol | 1 - contracts/p0/BasketHandler.sol | 6 ++--- contracts/p1/BasketHandler.sol | 32 +++++++++++++++++++++---- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 7f516ca918..7a84112cad 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -120,7 +120,6 @@ interface IBasketHandler is IComponent { returns (address[] memory erc20s, uint256[] memory quantities); /// Return the redemption value of `amount` BUs for a linear combination of historical baskets - /// Checks `portions` sum to FIX_ONE /// @param basketNonces An array of basket nonces to do redemption from /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE /// @param amount {BU} diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 5a961d65cf..a69b618c88 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -121,8 +121,8 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // basket is the current basket. Basket private basket; - uint48 public override nonce; // {basketNonce} A unique identifier for this basket instance - uint48 public override timestamp; // The timestamp when this basket was last set + uint48 public nonce; // {basketNonce} A unique identifier for this basket instance + uint48 public timestamp; // The timestamp when this basket was last set // If disabled is true, status() is DISABLED, the basket is invalid, and the whole system should // be paused. @@ -143,7 +143,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // Nonce of the first reference basket from the current history // A new historical record begins whenever the prime basket is changed // There can be 0 to any number of reference baskets from the current prime basket history - uint48 private primeNonce; // {basketNonce} + uint48 public primeNonce; // {basketNonce} // A history of baskets by basket nonce; includes current basket mapping(uint48 => Basket) private basketHistory; diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index baa1991743..8ea152ad12 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -164,7 +164,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // Nonce of the first reference basket from the current prime basket history // A new historical record begins whenever the prime basket is changed // There can be 0 to any number of reference baskets from the current history - uint48 private primeNonce; // {basketNonce} + uint48 public primeNonce; // {basketNonce} // A history of baskets by basket nonce; includes current basket mapping(uint48 => Basket) private basketHistory; @@ -822,9 +822,33 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } // ==== FacadeRead views ==== - // Not used in-protocol + // Not used in-protocol; helpful for reconstructing state - /// Getter pt1 for `config` struct variable + /// Get a reference basket in today's collateral tokens, by nonce + /// @param basketNonce {basketNonce} + /// @return erc20s The erc20s in the reference basket + /// @return quantities {qTok/BU} The quantity of whole tokens per whole basket unit + function getHistoricalBasket(uint48 basketNonce) + external + view + returns (IERC20[] memory erc20s, uint256[] memory quantities) + { + Basket storage b = basketHistory[basketNonce]; + erc20s = new IERC20[](b.erc20s.length); + quantities = new uint256[](erc20s.length); + + for (uint256 i = 0; i < b.erc20s.length; ++i) { + erc20s[i] = b.erc20s[i]; + + // {qTok/BU} = {tok/BU} * {qTok/tok} + quantities[i] = quantity(basket.erc20s[i]).shiftl_toUint( + int8(IERC20Metadata(address(basket.erc20s[i])).decimals()), + FLOOR + ); + } + } + + /// Getter part1 for `config` struct variable /// @dev Indices are shared across return values /// @return erc20s The erc20s in the prime basket /// @return targetNames The bytes32 name identifier of the target unit, per ERC20 @@ -849,7 +873,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } } - /// Getter pt2 for `config` struct variable + /// Getter part2 for `config` struct variable /// @param targetName The name of the target unit to lookup the backup for /// @return erc20s The backup erc20s for the target unit, in order of most to least desirable /// @return max The maximum number of tokens from the array to use at a single time From 60517add65b037aeadd4b5eee0c8ea44a8533df9 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 3 May 2023 21:14:06 -0400 Subject: [PATCH 155/462] small contract size wins --- contracts/facade/FacadeRead.sol | 2 +- contracts/interfaces/IBasketHandler.sol | 19 +++---- contracts/p0/BasketHandler.sol | 27 +++------- contracts/p1/BasketHandler.sol | 54 +++++++++---------- .../p1/mixins/RecollateralizationLib.sol | 2 +- contracts/plugins/assets/RTokenAsset.sol | 2 +- test/Main.test.ts | 14 ++--- 7 files changed, 50 insertions(+), 70 deletions(-) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index fd26c2e4ce..9d903cb75d 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -406,7 +406,7 @@ contract FacadeRead is IFacadeRead { uint192[] memory quantities = new uint192[](reg.erc20s.length); for (uint256 i = 0; i < reg.erc20s.length; ++i) { - quantities[i] = bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + quantities[i] = bh.quantity(reg.erc20s[i]); } (Price memory buPrice, ) = bh.prices(); diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 7a84112cad..f29e0d23f2 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -20,13 +20,19 @@ struct BasketRange { */ interface IBasketHandler is IComponent { /// Emitted when the prime basket is set + /// @param nonce {basketNonce} The first nonce under the prime basket; may not exist yet /// @param erc20s The collateral tokens for the prime basket /// @param targetAmts {target/BU} A list of quantities of target unit per basket unit /// @param targetNames Each collateral token's targetName - event PrimeBasketSet(IERC20[] erc20s, uint192[] targetAmts, bytes32[] targetNames); + event PrimeBasketSet( + uint256 indexed nonce, + IERC20[] erc20s, + uint192[] targetAmts, + bytes32[] targetNames + ); /// Emitted when the reference basket is set - /// @param nonce The basket nonce + /// @param nonce {basketNonce} The basket nonce /// @param erc20s The list of collateral tokens in the reference basket /// @param refAmts {ref/BU} The reference amounts of the basket collateral tokens /// @param disabled True when the list of erc20s + refAmts may not be correct @@ -102,15 +108,6 @@ interface IBasketHandler is IComponent { /// Otherwise, returns (token's basket.refAmts / token's Collateral.refPerTok()) function quantity(IERC20 erc20) external view returns (uint192); - /// Like quantity(), but unsafe because it DOES NOT CONFIRM THAT THE ASSET IS CORRECT - /// @param erc20 The ERC20 token contract for the asset - /// @param asset The registered asset plugin contract for the erc20 - /// @return {tok/BU} The whole token quantity of token in the reference basket - /// Returns 0 if erc20 is not registered or not in the basket - /// Returns FIX_MAX (in lieu of +infinity) if Collateral.refPerTok() is 0. - /// Otherwise, returns (token's basket.refAmts / token's Collateral.refPerTok()) - function quantityUnsafe(IERC20 erc20, IAsset asset) external view returns (uint192); - /// @param amount {BU} /// @return erc20s The addresses of the ERC20 tokens in the reference basket /// @return quantities {qTok} The quantity of each ERC20 token to issue `amount` baskets diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index a69b618c88..2bdd2108d6 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -141,8 +141,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { CollateralStatus private lastStatus; // Nonce of the first reference basket from the current history - // A new historical record begins whenever the prime basket is changed - // There can be 0 to any number of reference baskets from the current prime basket history + // There can be 0 to any number of baskets with nonce >= primeNonce uint48 public primeNonce; // {basketNonce} // A history of baskets by basket nonce; includes current basket @@ -267,7 +266,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } primeNonce = nonce + 1; // set primeNonce to the next nonce - emit PrimeBasketSet(erc20s, targetAmts, names); + emit PrimeBasketSet(primeNonce, erc20s, targetAmts, names); } /// Set the backup configuration for some target name @@ -339,18 +338,6 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } } - /// Like quantity(), but unsafe because it DOES NOT CONFIRM THAT THE ASSET IS CORRECT - /// @param erc20 The ERC20 token contract for the asset - /// @param asset The registered asset plugin contract for the erc20 - /// @return {tok/BU} The token-quantity of an ERC20 token in the basket. - // Returns 0 if erc20 is not registered or not in the basket - // Returns FIX_MAX (in lieu of +infinity) if Collateral.refPerTok() is 0. - // Otherwise returns (token's basket.refAmts / token's Collateral.refPerTok()) - function quantityUnsafe(IERC20 erc20, IAsset asset) public view returns (uint192) { - if (!asset.isCollateral()) return FIX_ZERO; - return _quantity(erc20, ICollateral(address(asset))); - } - /// @param erc20 The token contract /// @param coll The registered collateral plugin contract /// @return {tok/BU} The token-quantity of an ERC20 token in the basket. @@ -740,16 +727,16 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { /// Require that erc20s is a valid collateral array function requireValidCollArray(IERC20[] calldata erc20s) internal view { + IERC20 zero = IERC20(address(0)); IERC20 rsr = main.rsr(); IERC20 rToken = IERC20(address(main.rToken())); IERC20 stRSR = IERC20(address(main.stRSR())); - IERC20 zero = IERC20(address(0)); for (uint256 i = 0; i < erc20s.length; i++) { - require(erc20s[i] != rsr, "RSR is not valid collateral"); - require(erc20s[i] != rToken, "RToken is not valid collateral"); - require(erc20s[i] != stRSR, "stRSR is not valid collateral"); - require(erc20s[i] != zero, "address zero is not valid collateral"); + require( + erc20s[i] != zero && erc20s[i] != rsr && erc20s[i] != rToken && erc20s[i] != stRSR, + "invalid collateral" + ); } require(ArrayLib.allUnique(erc20s), "contains duplicates"); diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 8ea152ad12..a74e557ab5 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -162,8 +162,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // Added in 3.0.0 // Nonce of the first reference basket from the current prime basket history - // A new historical record begins whenever the prime basket is changed - // There can be 0 to any number of reference baskets from the current history + // There can be 0 to any number of baskets with nonce >= primeNonce uint48 public primeNonce; // {basketNonce} // A history of baskets by basket nonce; includes current basket @@ -262,10 +261,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // config'.erc20s = erc20s // config'.targetAmts[erc20s[i]] = targetAmts[i], for i from 0 to erc20s.length-1 // config'.targetNames[e] = assetRegistry.toColl(e).targetName, for e in erc20s - function setPrimeBasket(IERC20[] calldata erc20s, uint192[] calldata targetAmts) - external - governance - { + function setPrimeBasket(IERC20[] calldata erc20s, uint192[] calldata targetAmts) external { + governanceOnly(); require(erc20s.length > 0, "cannot empty basket"); require(erc20s.length == targetAmts.length, "must be same length"); requireValidCollArray(erc20s); @@ -294,7 +291,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } primeNonce = nonce + 1; // set primeNonce to the next nonce - emit PrimeBasketSet(erc20s, targetAmts, names); + emit PrimeBasketSet(primeNonce, erc20s, targetAmts, names); } /// Set the backup configuration for some target name @@ -310,7 +307,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { bytes32 targetName, uint256 max, IERC20[] calldata erc20s - ) external governance { + ) external { + governanceOnly(); requireValidCollArray(erc20s); BackupConfig storage conf = config.backups[targetName]; conf.max = max; @@ -367,18 +365,6 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } } - /// Like quantity(), but unsafe because it DOES NOT CONFIRM THAT THE ASSET IS CORRECT - /// @param erc20 The ERC20 token contract for the asset - /// @param asset The registered asset plugin contract for the erc20 - /// @return {tok/BU} The token-quantity of an ERC20 token in the basket. - // Returns 0 if erc20 is not registered or not in the basket - // Returns FIX_MAX (in lieu of +infinity) if Collateral.refPerTok() is 0. - // Otherwise returns (token's basket.refAmts / token's Collateral.refPerTok()) - function quantityUnsafe(IERC20 erc20, IAsset asset) public view returns (uint192) { - if (!asset.isCollateral()) return FIX_ZERO; - return _quantity(erc20, ICollateral(address(asset))); - } - /// @param erc20 The token contract /// @param coll The registered collateral plugin contract /// @return {tok/BU} The token-quantity of an ERC20 token in the basket. @@ -587,7 +573,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // === Governance Setters === /// @custom:governance - function setWarmupPeriod(uint48 val) public governance { + function setWarmupPeriod(uint48 val) public { + governanceOnly(); require(val >= MIN_WARMUP_PERIOD && val <= MAX_WARMUP_PERIOD, "invalid warmupPeriod"); emit WarmupPeriodSet(warmupPeriod, val); warmupPeriod = val; @@ -789,10 +776,14 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { IERC20 zero = IERC20(address(0)); for (uint256 i = 0; i < erc20s.length; i++) { - require(erc20s[i] != rsr, "RSR is not valid collateral"); - require(erc20s[i] != IERC20(address(rToken)), "RToken is not valid collateral"); - require(erc20s[i] != IERC20(address(stRSR)), "stRSR is not valid collateral"); - require(erc20s[i] != zero, "address zero is not valid collateral"); + // Require collateral is NOT in [0x0, RSR, RToken, StRSR] + require( + erc20s[i] != zero && + erc20s[i] != rsr && + erc20s[i] != IERC20(address(rToken)) && + erc20s[i] != IERC20(address(stRSR)), + "invalid collateral" + ); } require(ArrayLib.allUnique(erc20s), "contains duplicates"); @@ -805,10 +796,12 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // All calls to goodCollateral pass an erc20 from the config or the backup. // Both setPrimeBasket and setBackupConfig must pass a call to requireValidCollArray, // which runs the 4 checks below. - if (erc20 == IERC20(address(0))) return false; - if (erc20 == rsr) return false; - if (erc20 == IERC20(address(rToken))) return false; - if (erc20 == IERC20(address(stRSR))) return false; + if ( + erc20 == IERC20(address(0)) || + erc20 == rsr || + erc20 == IERC20(address(rToken)) || + erc20 == IERC20(address(stRSR)) + ) return false; try assetRegistry.toColl(erc20) returns (ICollateral coll) { return @@ -821,6 +814,9 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } } + // solhint-disable-next-line no-empty-blocks + function governanceOnly() private view governance {} + // ==== FacadeRead views ==== // Not used in-protocol; helpful for reconstructing state diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index 4ca6eab1c3..9f265b07aa 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -87,7 +87,7 @@ library RecollateralizationLibP1 { IBasketHandler bh = main.basketHandler(); ntpCtx.quantities = new uint192[](reg.erc20s.length); for (uint256 i = 0; i < reg.erc20s.length; ++i) { - ntpCtx.quantities[i] = bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + ntpCtx.quantities[i] = bh.quantity(reg.erc20s[i]); } // ============================ diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index b1c461878a..70d5e494e8 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -186,7 +186,7 @@ contract RTokenAsset is IAsset, VersionedAsset { uint192[] memory quantities = new uint192[](reg.erc20s.length); for (uint256 i = 0; i < reg.erc20s.length; ++i) { - quantities[i] = basketHandler.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + quantities[i] = basketHandler.quantity(reg.erc20s[i]); } // will exclude UoA value from RToken balances at BackingManager diff --git a/test/Main.test.ts b/test/Main.test.ts index 14584d1c19..d5ec7772a6 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1679,13 +1679,13 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { it('Should not allow to set prime Basket with 0 address tokens', async () => { await expect( basketHandler.connect(owner).setPrimeBasket([ZERO_ADDRESS], [fp('1')]) - ).to.be.revertedWith('address zero is not valid collateral') + ).to.be.revertedWith('invalid collateral') }) it('Should not allow to set prime Basket with stRSR', async () => { await expect( basketHandler.connect(owner).setPrimeBasket([stRSR.address], [fp('1')]) - ).to.be.revertedWith('stRSR is not valid collateral') + ).to.be.revertedWith('stinvalid collateral') }) it('Should not allow to set prime Basket with invalid target amounts', async () => { @@ -1709,20 +1709,20 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { it('Should not allow to set prime Basket with RSR/RToken', async () => { await expect( basketHandler.connect(owner).setPrimeBasket([rsr.address], [fp('1')]) - ).to.be.revertedWith('RSR is not valid collateral') + ).to.be.revertedWith('invalid collateral') await expect( basketHandler .connect(owner) .setPrimeBasket([token0.address, rToken.address], [fp('0.5'), fp('0.5')]) - ).to.be.revertedWith('RToken is not valid collateral') + ).to.be.revertedWith('invalid collateral') }) it('Should allow to set prime Basket if OWNER', async () => { // Set basket await expect(basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1')])) .to.emit(basketHandler, 'PrimeBasketSet') - .withArgs([token0.address], [fp('1')], [ethers.utils.formatBytes32String('USD')]) + .withArgs(2, [token0.address], [fp('1')], [ethers.utils.formatBytes32String('USD')]) }) describe.only('Historical Redemptions', () => { @@ -2118,7 +2118,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { basketHandler .connect(owner) .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [rsr.address]) - ).to.be.revertedWith('RSR is not valid collateral') + ).to.be.revertedWith('invalid collateral') it('Should not allow to set backup Config with duplicate ERC20s', async () => { await expect( @@ -2135,7 +2135,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { basketHandler .connect(owner) .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [rToken.address]) - ).to.be.revertedWith('RToken is not valid collateral') + ).to.be.revertedWith('invalid collateral') }) it('Should allow to set backup Config if OWNER', async () => { From 5c4912b58987be533c94558a21185a0d9cfe636c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 4 May 2023 15:26:20 -0400 Subject: [PATCH 156/462] lint clean --- test/plugins/individual-collateral/fixtures.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/plugins/individual-collateral/fixtures.ts b/test/plugins/individual-collateral/fixtures.ts index 977e0a7f19..c44b23b67c 100644 --- a/test/plugins/individual-collateral/fixtures.ts +++ b/test/plugins/individual-collateral/fixtures.ts @@ -150,7 +150,9 @@ export const getDefaultFixture = async function (salt: string) { const DistribImplFactory: ContractFactory = await ethers.getContractFactory('DistributorP1') const distribImpl: DistributorP1 = await DistribImplFactory.deploy() - const RevTraderImplFactory: ContractFactory = await ethers.getContractFactory('RevenueTraderP1') + const RevTraderImplFactory: ContractFactory = await ethers.getContractFactory( + 'RevenueTraderP1' + ) const revTraderImpl: RevenueTraderP1 = await RevTraderImplFactory.deploy() const FurnaceImplFactory: ContractFactory = await ethers.getContractFactory('FurnaceP1') From ca96dea5d0ddb7a7b520b01eb235cda83ee0e8c0 Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Thu, 4 May 2023 21:57:19 -0400 Subject: [PATCH 157/462] fix tests & linting. --- test/FacadeAct.test.ts | 58 ++----------------- test/Revenues.test.ts | 51 ---------------- .../plugins/individual-collateral/fixtures.ts | 4 +- test/utils/trades.ts | 14 +++-- 4 files changed, 17 insertions(+), 110 deletions(-) diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts index f30d5e80e8..5a20610009 100644 --- a/test/FacadeAct.test.ts +++ b/test/FacadeAct.test.ts @@ -604,6 +604,7 @@ describe('FacadeAct contract', () => { await owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) // Collect revenue @@ -623,6 +624,7 @@ describe('FacadeAct contract', () => { await owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) // Next call would start Revenue auction - RTokenTrader @@ -635,6 +637,7 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ) .to.emit(rTokenTrader, 'TradeStarted') @@ -656,6 +659,7 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ) .to.emit(rsrTrader, 'TradeStarted') @@ -691,6 +695,7 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ) .to.emit(rTokenTrader, 'TradeSettled') @@ -706,64 +711,13 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ) .to.emit(rsrTrader, 'TradeSettled') .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, minBuyAmt) }) - it('Revenues - Should handle assets with invalid claim logic', async () => { - // Redeem all RTokens - await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) - - // Setup a new aToken with invalid claim data - const ATokenCollateralFactory = await ethers.getContractFactory( - 'InvalidATokenFiatCollateralMock' - ) - const chainlinkFeed = ( - await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) - ) - - const invalidATokenCollateral: InvalidATokenFiatCollateralMock = < - InvalidATokenFiatCollateralMock - >await ATokenCollateralFactory.deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: aToken.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: await aTokenAsset.oracleTimeout(), - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: await aTokenAsset.delayUntilDefault(), - }, - REVENUE_HIDING - ) - - // Perform asset swap - await assetRegistry.connect(owner).swapRegistered(invalidATokenCollateral.address) - - // Setup new basket with the invalid AToken - await basketHandler.connect(owner).setPrimeBasket([aToken.address], [fp('1')]) - - // Switch basket - await basketHandler.connect(owner).refreshBasket() - - const rewardAmountAAVE = bn('0.5e18') - - // AAVE Rewards - await aToken.setRewards(backingManager.address, rewardAmountAAVE) - - // Via Facade get next call - will not attempt to claim - No action taken - const [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(ZERO_ADDRESS) - expect(data).to.equal('0x') - - // Check status - nothing claimed - expect(await aaveToken.balanceOf(backingManager.address)).to.equal(0) - }) - it('Revenues - Should handle multiple assets with same reward token', async () => { // Update Reward token for AToken to use same as CToken const ATokenCollateralFactory = await ethers.getContractFactory('ATokenFiatCollateral') diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index a23338b9da..b6a1cffe4a 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -39,7 +39,6 @@ import { TestIStRSR, USDCMock, FiatCollateral, - InvalidATokenFiatCollateralMock, } from '../typechain' import { whileImpersonating } from './utils/impersonation' import snapshotGasCost from './utils/snapshotGasCost' @@ -53,7 +52,6 @@ import { ORACLE_ERROR, ORACLE_TIMEOUT, PRICE_TIMEOUT, - REVENUE_HIDING, } from './fixtures' import { expectRTokenPrice, setOraclePrice } from './utils/oracles' import { dutchBuyAmount, expectTrade, getTrade } from './utils/trades' @@ -114,8 +112,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { let AssetFactory: ContractFactory - const DEFAULT_THRESHOLD = fp('0.01') // 1% - // Computes the minBuyAmt for a sellAmt at two prices // sellPrice + buyPrice should not be the low and high estimates, but rather the oracle prices const toMinBuyAmt = async ( @@ -2283,53 +2279,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) }) - it('Should revert on invalid claim logic', async () => { - // Setup a new aToken with invalid claim data - const ATokenCollateralFactory = await ethers.getContractFactory( - 'InvalidATokenFiatCollateralMock' - ) - const chainlinkFeed = ( - await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) - ) - - const invalidATokenCollateral: InvalidATokenFiatCollateralMock = < - InvalidATokenFiatCollateralMock - >((await ATokenCollateralFactory.deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: token2.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: await collateral2.delayUntilDefault(), - }, - REVENUE_HIDING - )) as unknown) - - // Perform asset swap - await assetRegistry.connect(owner).swapRegistered(invalidATokenCollateral.address) - - // Setup new basket with the invalid AToken - await basketHandler.connect(owner).setPrimeBasket([token2.address], [fp('1')]) - - // Switch basket - await basketHandler.connect(owner).refreshBasket() - - rewardAmountAAVE = bn('0.5e18') - - // AAVE Rewards - await token2.setRewards(backingManager.address, rewardAmountAAVE) - - // Claim and sweep rewards - should revert and bubble up msg - await expect(backingManager.claimRewards()).to.be.revertedWith('claimRewards() error') - - // Check status - nothing claimed - expect(await aaveToken.balanceOf(backingManager.address)).to.equal(0) - }) - context('DutchTrade', () => { const auctionLength = 300 beforeEach(async () => { diff --git a/test/plugins/individual-collateral/fixtures.ts b/test/plugins/individual-collateral/fixtures.ts index 977e0a7f19..c44b23b67c 100644 --- a/test/plugins/individual-collateral/fixtures.ts +++ b/test/plugins/individual-collateral/fixtures.ts @@ -150,7 +150,9 @@ export const getDefaultFixture = async function (salt: string) { const DistribImplFactory: ContractFactory = await ethers.getContractFactory('DistributorP1') const distribImpl: DistributorP1 = await DistribImplFactory.deploy() - const RevTraderImplFactory: ContractFactory = await ethers.getContractFactory('RevenueTraderP1') + const RevTraderImplFactory: ContractFactory = await ethers.getContractFactory( + 'RevenueTraderP1' + ) const revTraderImpl: RevenueTraderP1 = await RevTraderImplFactory.deploy() const FurnaceImplFactory: ContractFactory = await ethers.getContractFactory('FurnaceP1') diff --git a/test/utils/trades.ts b/test/utils/trades.ts index a6ce40ab23..9182a8885f 100644 --- a/test/utils/trades.ts +++ b/test/utils/trades.ts @@ -93,13 +93,15 @@ export const dutchBuyAmount = async ( if (inMaxTradeVolume.lt(maxTradeVolume)) maxTradeVolume = inMaxTradeVolume const auctionVolume = outAmount.mul(sellHigh).div(fp('1')) - const slippage = maxTradeSlippage - .mul( - fp('1').sub( - auctionVolume.sub(minTradeVolume).mul(fp('1')).div(maxTradeVolume.sub(minTradeVolume)) - ) + const slippage1e18 = maxTradeSlippage.mul( + fp('1').sub( + auctionVolume.sub(minTradeVolume).mul(fp('1')).div(maxTradeVolume.sub(minTradeVolume)) ) - .div(fp('1')) + ) + + // Adjust for rounding + const leftover = slippage1e18.mod(fp('1')) + const slippage = slippage1e18.div(fp('1')).add(leftover.gte(fp('0.5')) ? 1 : 0) const lowPrice = sellLow.mul(fp('1').sub(slippage)).div(buyHigh) const middlePrice = divCeil(sellHigh.mul(fp('1')), buyLow) From d5cd91af9ee13b6e542879addc2c3a44d14a11e6 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 4 May 2023 22:04:37 -0400 Subject: [PATCH 158/462] add invalid claim logic replacement tests --- contracts/plugins/mocks/CTokenVaultMock2.sol | 11 ++++++++++- contracts/plugins/trading/DutchTrade.sol | 1 - test/FacadeAct.test.ts | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/contracts/plugins/mocks/CTokenVaultMock2.sol b/contracts/plugins/mocks/CTokenVaultMock2.sol index 8704c674dc..d303f1440d 100644 --- a/contracts/plugins/mocks/CTokenVaultMock2.sol +++ b/contracts/plugins/mocks/CTokenVaultMock2.sol @@ -11,6 +11,8 @@ contract CTokenVaultMock is ERC20Mock, IRewardable { CTokenMock public asset; IComptroller public comptroller; + bool public revertClaimRewards; + constructor( string memory _name, string memory _symbol, @@ -40,6 +42,9 @@ contract CTokenVaultMock is ERC20Mock, IRewardable { } function claimRewards() external { + if (revertClaimRewards) { + revert("reverting claim rewards"); + } uint256 oldBal = comp.balanceOf(msg.sender); comptroller.claimComp(msg.sender); emit RewardsClaimed(IERC20(address(comp)), comp.balanceOf(msg.sender) - oldBal); @@ -48,4 +53,8 @@ contract CTokenVaultMock is ERC20Mock, IRewardable { function setExchangeRate(uint192 fiatcoinRedemptionRate) external { asset.setExchangeRate(fiatcoinRedemptionRate); } -} \ No newline at end of file + + function setRevertClaimRewards(bool newVal) external { + revertClaimRewards = newVal; + } +} diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index fef096c77a..8f2a06f9e6 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -109,7 +109,6 @@ contract DutchTrade is ITrade { slippage = 0; } } - // {buyTok/sellTok} = {1} * {UoA/sellTok} / {UoA/buyTok} lowPrice = sellLow.mulDiv(FIX_ONE - slippage, buyHigh, FLOOR); middlePrice = sellHigh.div(buyLow, CEIL); // no additional slippage diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts index 5a20610009..d5619e6a86 100644 --- a/test/FacadeAct.test.ts +++ b/test/FacadeAct.test.ts @@ -718,6 +718,24 @@ describe('FacadeAct contract', () => { .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, minBuyAmt) }) + it('Revenues - Should handle ERC20s with invalid claim logic', async () => { + // Set the cToken to revert + await cToken.setRevertClaimRewards(true) + + const rewardAmountCOMP = bn('0.5e18') + + // COMP Rewards + await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + + // Via Facade get next call - will attempt to claim + const [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) + expect(addr).to.equal(ZERO_ADDRESS) + expect(data).to.equal('0x') + + // Check status - nothing claimed + expect(await compToken.balanceOf(backingManager.address)).to.equal(0) + }) + it('Revenues - Should handle multiple assets with same reward token', async () => { // Update Reward token for AToken to use same as CToken const ATokenCollateralFactory = await ethers.getContractFactory('ATokenFiatCollateral') From 75c1094001d01b7776b9933a60a803deb65b54ea Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 4 May 2023 22:07:57 -0400 Subject: [PATCH 159/462] missing revenue test --- test/Revenues.test.ts | 54 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index b6a1cffe4a..3e181f86fc 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -25,6 +25,7 @@ import { FacadeTest, GnosisMock, IAssetRegistry, + InvalidATokenFiatCollateralMock, MockV3Aggregator, RTokenAsset, StaticATokenMock, @@ -49,6 +50,7 @@ import { defaultFixture, Implementation, IMPLEMENTATION, + REVENUE_HIDING, ORACLE_ERROR, ORACLE_TIMEOUT, PRICE_TIMEOUT, @@ -2279,6 +2281,58 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) }) + it('Should not revert on invalid claim logic', async () => { + // Here the aToken is going to have an invalid claimRewards on its asset, + // while the cToken will have it on the ERC20 + + // cToken + rewardAmountCOMP = bn('0.5e18') + await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + await token3.setRevertClaimRewards(true) + + // Setup a new aToken with invalid claim data + const ATokenCollateralFactory = await ethers.getContractFactory( + 'InvalidATokenFiatCollateralMock' + ) + const chainlinkFeed = ( + await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) + ) + + const invalidATokenCollateral: InvalidATokenFiatCollateralMock = < + InvalidATokenFiatCollateralMock + >((await ATokenCollateralFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: chainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: token2.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.05'), + delayUntilDefault: await collateral2.delayUntilDefault(), + }, + REVENUE_HIDING + )) as unknown) + + // Perform asset swap + await assetRegistry.connect(owner).swapRegistered(invalidATokenCollateral.address) + + // Setup new basket with the invalid AToken + await basketHandler.connect(owner).setPrimeBasket([token2.address], [fp('1')]) + + // Switch basket + await basketHandler.connect(owner).refreshBasket() + + rewardAmountAAVE = bn('0.5e18') + + // AAVE Rewards + await token2.setRewards(backingManager.address, rewardAmountAAVE) + + // Claim and sweep rewards -- should succeed + await expect(backingManager.claimRewards()).not.to.be.reverted + }) + context('DutchTrade', () => { const auctionLength = 300 beforeEach(async () => { From 7f7159d720cf951c9df98a5064e581521bb82a97 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 4 May 2023 22:11:05 -0400 Subject: [PATCH 160/462] try furnace.melt() again --- contracts/p1/RToken.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 53ac68c625..10502d1bca 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -212,7 +212,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // == Refresh == assetRegistry.refresh(); - main.furnace().melt(); + // solhint-disable-next-line no-empty-blocks + try main.furnace().melt() {} catch {} // == Checks and Effects == @@ -294,7 +295,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // == Refresh == assetRegistry.refresh(); - main.furnace().melt(); + // solhint-disable-next-line no-empty-blocks + try main.furnace().melt() {} catch {} // == Checks and Effects == From e965298e9f5f2cf7bd216e2565c38938799efae5 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 4 May 2023 22:12:43 -0400 Subject: [PATCH 161/462] cosmetics --- contracts/interfaces/IRToken.sol | 1 + contracts/p1/BasketHandler.sol | 6 +++--- contracts/p1/RToken.sol | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index 3580b62276..d8c8341fa2 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -91,6 +91,7 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea /// @custom:interaction function redeemTo(address recipient, uint256 amount) external; + // TODO should probably return the amounts it ends up with, for simulation purposes /// Redeem RToken for a linear combination of historical baskets, to a particular recipient /// @dev Allows partial redemptions up to the minAmounts /// @param recipient The address to receive the backing collateral tokens diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index a74e557ab5..852530deda 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -11,7 +11,7 @@ import "../interfaces/IMain.sol"; import "../libraries/Array.sol"; import "../libraries/Fixed.sol"; import "./mixins/Component.sol"; -import "hardhat/console.sol"; + // A "valid collateral array" is a an IERC20[] value without rtoken, rsr, or any duplicate values // A BackupConfig value is valid if erc20s is a valid collateral array @@ -133,8 +133,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // basket is the current basket. Basket private basket; - uint48 public override nonce; // {basketNonce} A unique identifier for this basket instance - uint48 public override timestamp; // The timestamp when this basket was last set + uint48 public nonce; // {basketNonce} A unique identifier for this basket instance + uint48 public timestamp; // The timestamp when this basket was last set // If disabled is true, status() is DISABLED, the basket is invalid, // and everything except redemption should be paused. diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 10502d1bca..048bd69f64 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -9,7 +9,6 @@ import "../libraries/Fixed.sol"; import "../libraries/Throttle.sol"; import "../vendor/ERC20PermitUpgradeable.sol"; import "./mixins/Component.sol"; -import "hardhat/console.sol"; /** * @title RTokenP1 From 396a357b0a24e3c78da7fa90e4ddbcf663072936 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 4 May 2023 22:45:32 -0400 Subject: [PATCH 162/462] external Basket lib --- contracts/interfaces/IBasketHandler.sol | 9 + contracts/p0/BasketHandler.sol | 12 + contracts/p1/BasketHandler.sol | 344 ++---------------- contracts/p1/mixins/Basket.sol | 302 +++++++++++++++ .../p1/mixins/RecollateralizationLib.sol | 2 +- contracts/plugins/assets/RTokenAsset.sol | 2 +- 6 files changed, 361 insertions(+), 310 deletions(-) create mode 100644 contracts/p1/mixins/Basket.sol diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index f29e0d23f2..aa3882af56 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -108,6 +108,15 @@ interface IBasketHandler is IComponent { /// Otherwise, returns (token's basket.refAmts / token's Collateral.refPerTok()) function quantity(IERC20 erc20) external view returns (uint192); + /// Like quantity(), but unsafe because it DOES NOT CONFIRM THAT THE ASSET IS CORRECT + /// @param erc20 The ERC20 token contract for the asset + /// @param asset The registered asset plugin contract for the erc20 + /// @return {tok/BU} The whole token quantity of token in the reference basket + /// Returns 0 if erc20 is not registered or not in the basket + /// Returns FIX_MAX (in lieu of +infinity) if Collateral.refPerTok() is 0. + /// Otherwise, returns (token's basket.refAmts / token's Collateral.refPerTok()) + function quantityUnsafe(IERC20 erc20, IAsset asset) external view returns (uint192); + /// @param amount {BU} /// @return erc20s The addresses of the ERC20 tokens in the reference basket /// @return quantities {qTok} The quantity of each ERC20 token to issue `amount` baskets diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 2bdd2108d6..fd1fcc2cdf 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -338,6 +338,18 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } } + /// Like quantity(), but unsafe because it DOES NOT CONFIRM THAT THE ASSET IS CORRECT + /// @param erc20 The ERC20 token contract for the asset + /// @param asset The registered asset plugin contract for the erc20 + /// @return {tok/BU} The token-quantity of an ERC20 token in the basket. + // Returns 0 if erc20 is not registered or not in the basket + // Returns FIX_MAX (in lieu of +infinity) if Collateral.refPerTok() is 0. + // Otherwise returns (token's basket.refAmts / token's Collateral.refPerTok()) + function quantityUnsafe(IERC20 erc20, IAsset asset) public view returns (uint192) { + if (!asset.isCollateral()) return FIX_ZERO; + return _quantity(erc20, ICollateral(address(asset))); + } + /// @param erc20 The token contract /// @param coll The registered collateral plugin contract /// @return {tok/BU} The token-quantity of an ERC20 token in the basket. diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 852530deda..86b4c658d5 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -10,108 +10,16 @@ import "../interfaces/IBasketHandler.sol"; import "../interfaces/IMain.sol"; import "../libraries/Array.sol"; import "../libraries/Fixed.sol"; +import "./mixins/Basket.sol"; import "./mixins/Component.sol"; -// A "valid collateral array" is a an IERC20[] value without rtoken, rsr, or any duplicate values - -// A BackupConfig value is valid if erc20s is a valid collateral array -struct BackupConfig { - uint256 max; // Maximum number of backup collateral erc20s to use in a basket - IERC20[] erc20s; // Ordered list of backup collateral ERC20s -} - -// What does a BasketConfig value mean? -// -// erc20s, targetAmts, and targetNames should be interpreted together. -// targetAmts[erc20] is the quantity of target units of erc20 that one BU should hold -// targetNames[erc20] is the name of erc20's target unit -// and then backups[tgt] is the BackupConfig to use for the target unit named tgt -// -// For any valid BasketConfig value: -// erc20s == keys(targetAmts) == keys(targetNames) -// if name is in values(targetNames), then backups[name] is a valid BackupConfig -// erc20s is a valid collateral array -// -// In the meantime, treat erc20s as the canonical set of keys for the target* maps -struct BasketConfig { - // The collateral erc20s in the prime (explicitly governance-set) basket - IERC20[] erc20s; - // Amount of target units per basket for each prime collateral token. {target/BU} - mapping(IERC20 => uint192) targetAmts; - // Cached view of the target unit for each erc20 upon setup - mapping(IERC20 => bytes32) targetNames; - // Backup configurations, per target name. - mapping(bytes32 => BackupConfig) backups; -} - -/// The type of BasketHandler.basket. -/// Defines a basket unit (BU) in terms of reference amounts of underlying tokens -// Logically, basket is just a mapping of erc20 addresses to ref-unit amounts. -// In the analytical comments I'll just refer to it that way. -// -// A Basket is valid if erc20s is a valid collateral array and erc20s == keys(refAmts) -struct Basket { - IERC20[] erc20s; // enumerated keys for refAmts - mapping(IERC20 => uint192) refAmts; // {ref/BU} -} - -/* - * @title BasketLibP1 - */ -library BasketLibP1 { - using BasketLibP1 for Basket; - using FixLib for uint192; - - /// Set self to a fresh, empty basket - // self'.erc20s = [] (empty list) - // self'.refAmts = {} (empty map) - function empty(Basket storage self) internal { - uint256 length = self.erc20s.length; - for (uint256 i = 0; i < length; ++i) self.refAmts[self.erc20s[i]] = FIX_ZERO; - delete self.erc20s; - } - - /// Set `self` equal to `other` - function setFrom(Basket storage self, Basket storage other) internal { - empty(self); - uint256 length = other.erc20s.length; - for (uint256 i = 0; i < length; ++i) { - self.erc20s.push(other.erc20s[i]); - self.refAmts[other.erc20s[i]] = other.refAmts[other.erc20s[i]]; - } - } - - /// Add `weight` to the refAmount of collateral token `tok` in the basket `self` - // self'.refAmts[tok] = self.refAmts[tok] + weight - // self'.erc20s is keys(self'.refAmts) - function add( - Basket storage self, - IERC20 tok, - uint192 weight - ) internal { - // untestable: - // Both calls to .add() use a weight that has been CEIL rounded in the - // Fixed library div function, so weight will never be 0 here. - // Additionally, setPrimeBasket() enforces prime-basket tokens must have a weight > 0. - if (weight == FIX_ZERO) return; - if (self.refAmts[tok].eq(FIX_ZERO)) { - self.erc20s.push(tok); - self.refAmts[tok] = weight; - } else { - self.refAmts[tok] = self.refAmts[tok].plus(weight); - } - } -} - /** * @title BasketHandler * @notice Handles the basket configuration, definition, and evolution over time. */ contract BasketHandlerP1 is ComponentP1, IBasketHandler { - using BasketLibP1 for Basket; + using BasketLib for Basket; using CollateralStatusComparator for CollateralStatus; - using EnumerableSet for EnumerableSet.AddressSet; - using EnumerableSet for EnumerableSet.Bytes32Set; using FixLib for uint192; uint192 public constant MAX_TARGET_AMT = 1e3 * FIX_ONE; // {target/BU} max basket weight @@ -261,8 +169,10 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // config'.erc20s = erc20s // config'.targetAmts[erc20s[i]] = targetAmts[i], for i from 0 to erc20s.length-1 // config'.targetNames[e] = assetRegistry.toColl(e).targetName, for e in erc20s - function setPrimeBasket(IERC20[] calldata erc20s, uint192[] calldata targetAmts) external { - governanceOnly(); + function setPrimeBasket(IERC20[] calldata erc20s, uint192[] calldata targetAmts) + external + governance + { require(erc20s.length > 0, "cannot empty basket"); require(erc20s.length == targetAmts.length, "must be same length"); requireValidCollArray(erc20s); @@ -307,8 +217,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { bytes32 targetName, uint256 max, IERC20[] calldata erc20s - ) external { - governanceOnly(); + ) external governance { requireValidCollArray(erc20s); BackupConfig storage conf = config.backups[targetName]; conf.max = max; @@ -365,6 +274,18 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } } + /// Like quantity(), but unsafe because it DOES NOT CONFIRM THAT THE ASSET IS CORRECT + /// @param erc20 The ERC20 token contract for the asset + /// @param asset The registered asset plugin contract for the erc20 + /// @return {tok/BU} The token-quantity of an ERC20 token in the basket. + // Returns 0 if erc20 is not registered or not in the basket + // Returns FIX_MAX (in lieu of +infinity) if Collateral.refPerTok() is 0. + // Otherwise returns (token's basket.refAmts / token's Collateral.refPerTok()) + function quantityUnsafe(IERC20 erc20, IAsset asset) public view returns (uint192) { + if (!asset.isCollateral()) return FIX_ZERO; + return _quantity(erc20, ICollateral(address(asset))); + } + /// @param erc20 The token contract /// @param coll The registered collateral plugin contract /// @return {tok/BU} The token-quantity of an ERC20 token in the basket. @@ -573,8 +494,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // === Governance Setters === /// @custom:governance - function setWarmupPeriod(uint48 val) public { - governanceOnly(); + function setWarmupPeriod(uint48 val) public governance { require(val >= MIN_WARMUP_PERIOD && val <= MAX_WARMUP_PERIOD, "invalid warmupPeriod"); emit WarmupPeriodSet(warmupPeriod, val); warmupPeriod = val; @@ -582,190 +502,30 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // === Private === - /* _switchBasket computes basket' from three inputs: - - the basket configuration (config: BasketConfig) - - the function (isGood: erc20 -> bool), implemented here by goodCollateral() - - the function (targetPerRef: erc20 -> Fix) implemented by the Collateral plugin - - ==== Definitions ==== - - We use e:IERC20 to mean any erc20 token address, and tgt:bytes32 to mean any target name - - // targetWeight(b, e) is the target-unit weight of token e in basket b - Let targetWeight(b, e) = b.refAmt[e] * targetPerRef(e) - - // backups(tgt) is the list of sound backup tokens we plan to use for target `tgt`. - Let backups(tgt) = config.backups[tgt].erc20s - .filter(isGood) - .takeUpTo(config.backups[tgt].max) - - Let primeWt(e) = if e in config.erc20s and isGood(e) - then config.targetAmts[e] - else 0 - Let backupWt(e) = if e in backups(tgt) - then unsoundPrimeWt(tgt) / len(Backups(tgt)) - else 0 - Let unsoundPrimeWt(tgt) = sum(config.targetAmts[e] - for e in config.erc20s - where config.targetNames[e] == tgt and !isGood(e)) - - ==== The correctness condition ==== - - If unsoundPrimeWt(tgt) > 0 and len(backups(tgt)) == 0 for some tgt, then disabled' == true. - Else, disabled' == false and targetWeight(basket', e) == primeWt(e) + backupWt(e) for all e. - - ==== Higher-level desideratum ==== - - The resulting total target weights should equal the configured target weight. Formally: - - let configTargetWeight(tgt) = sum(config.targetAmts[e] - for e in config.erc20s - where _targetNames[e] == tgt) - - let targetWeightSum(b, tgt) = sum(targetWeight(b, e) - for e in config.erc20s - where _targetNames[e] == tgt) - - Given all that, if disabled' == false, then for all tgt, - targetWeightSum(basket', tgt) == configTargetWeight(tgt) - - ==== Usual specs ==== - - Then, finally, given all that, the effects of _switchBasket() are: - nonce' = nonce + 1 - basket' = _newBasket, as defined above - timestamp' = now - */ - /// Select and save the next basket, based on the BasketConfig and Collateral statuses - /// (The mutator that actually does all the work in this contract.) function _switchBasket() private { - disabled = false; - - // _targetNames := {} - while (_targetNames.length() > 0) _targetNames.remove(_targetNames.at(0)); - - // _newBasket := {} - _newBasket.empty(); - - // _targetNames = set(values(config.targetNames)) - // (and this stays true; _targetNames is not touched again in this function) - uint256 basketLength = config.erc20s.length; - for (uint256 i = 0; i < basketLength; ++i) { - _targetNames.add(config.targetNames[config.erc20s[i]]); - } - uint256 targetsLength = _targetNames.length(); - - // "good" collateral is collateral with any status() other than DISABLED - // goodWeights and totalWeights are in index-correspondence with _targetNames - // As such, they're each interepreted as a map from target name -> target weight - - // {target/BU} total target weight of good, prime collateral with target i - // goodWeights := {} - uint192[] memory goodWeights = new uint192[](targetsLength); - - // {target/BU} total target weight of all prime collateral with target i - // totalWeights := {} - uint192[] memory totalWeights = new uint192[](targetsLength); - - // For each prime collateral token: - for (uint256 i = 0; i < basketLength; ++i) { - IERC20 erc20 = config.erc20s[i]; - - // Find collateral's targetName index - uint256 targetIndex; - for (targetIndex = 0; targetIndex < targetsLength; ++targetIndex) { - if (_targetNames.at(targetIndex) == config.targetNames[erc20]) break; - } - assert(targetIndex < targetsLength); - // now, _targetNames[targetIndex] == config.targetNames[config.erc20s[i]] - - // Set basket weights for good, prime collateral, - // and accumulate the values of goodWeights and targetWeights - uint192 targetWeight = config.targetAmts[erc20]; - totalWeights[targetIndex] = totalWeights[targetIndex].plus(targetWeight); - - if (goodCollateral(config.targetNames[erc20], erc20) && targetWeight.gt(FIX_ZERO)) { - goodWeights[targetIndex] = goodWeights[targetIndex].plus(targetWeight); - _newBasket.add( - erc20, - targetWeight.div(assetRegistry.toColl(erc20).targetPerRef(), CEIL) - ); - // this div is safe: targetPerRef() > 0: goodCollateral check - } - } - - // Analysis: at this point: - // for all tgt in target names, - // totalWeights(tgt) - // = sum(config.targetAmts[e] for e in config.erc20s where _targetNames[e] == tgt), and - // goodWeights(tgt) - // = sum(primeWt(e) for e in config.erc20s where _targetNames[e] == tgt) - // for all e in config.erc20s, - // targetWeight(_newBasket, e) - // = sum(primeWt(e) if goodCollateral(e), else 0) - - // For each tgt in target names, if we still need more weight for tgt then try to add the - // backup basket for tgt to make up that weight: - for (uint256 i = 0; i < targetsLength; ++i) { - if (totalWeights[i].lte(goodWeights[i])) continue; // Don't need any backup weight - - // "tgt" = _targetNames[i] - // Now, unsoundPrimeWt(tgt) > 0 - - uint256 size = 0; // backup basket size - BackupConfig storage backup = config.backups[_targetNames.at(i)]; - - // Find the backup basket size: min(backup.max, # of good backup collateral) - uint256 backupLength = backup.erc20s.length; - for (uint256 j = 0; j < backupLength && size < backup.max; ++j) { - if (goodCollateral(_targetNames.at(i), backup.erc20s[j])) size++; - } + // Mark basket disabled. Pause most protocol functions unless there is a next basket + disabled = true; - // Now, size = len(backups(tgt)). Do the disable check: - // Remove bad collateral and mark basket disabled. Pause most protocol functions - if (size == 0) disabled = true; - - // Set backup basket weights... - uint256 assigned = 0; - // needed = unsoundPrimeWt(tgt) - uint192 needed = totalWeights[i].minus(goodWeights[i]); - - // Loop: for erc20 in backups(tgt)... - for (uint256 j = 0; j < backupLength && assigned < size; ++j) { - IERC20 erc20 = backup.erc20s[j]; - if (goodCollateral(_targetNames.at(i), erc20)) { - // Across this .add(), targetWeight(_newBasket',erc20) - // = targetWeight(_newBasket,erc20) + unsoundPrimeWt(tgt) / len(backups(tgt)) - _newBasket.add( - erc20, - needed.div(assetRegistry.toColl(erc20).targetPerRef().mulu(size), CEIL) - // this div is safe: targetPerRef > 0: goodCollateral check - ); - assigned++; - } - } - // Here, targetWeight(_newBasket, e) = primeWt(e) + backupWt(e) for all e targeting tgt - } - // Now we've looped through all values of tgt, so for all e, - // targetWeight(_newBasket, e) = primeWt(e) + backupWt(e) + bool success = _newBasket.nextBasket(_targetNames, config, assetRegistry); + // if success is true: _newBasket is the next basket - // Notice if basket is actually empty - uint256 newBasketLength = _newBasket.erc20s.length; - if (newBasketLength == 0) disabled = true; + if (success) { + // nonce' = nonce + 1 + // basket' = _newBasket + // timestamp' = now - // Update the basket if it's not disabled - if (!disabled) { nonce += 1; basket.setFrom(_newBasket); basketHistory[nonce].setFrom(_newBasket); timestamp = uint48(block.timestamp); + disabled = false; } // Keep records, emit event - basketLength = basket.erc20s.length; - uint192[] memory refAmts = new uint192[](basketLength); - for (uint256 i = 0; i < basketLength; ++i) { + uint256 len = basket.erc20s.length; + uint192[] memory refAmts = new uint192[](len); + for (uint256 i = 0; i < len; ++i) { refAmts[i] = basket.refAmts[basket.erc20s[i]]; } emit BasketSet(nonce, basket.erc20s, refAmts, disabled); @@ -776,47 +536,15 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { IERC20 zero = IERC20(address(0)); for (uint256 i = 0; i < erc20s.length; i++) { - // Require collateral is NOT in [0x0, RSR, RToken, StRSR] - require( - erc20s[i] != zero && - erc20s[i] != rsr && - erc20s[i] != IERC20(address(rToken)) && - erc20s[i] != IERC20(address(stRSR)), - "invalid collateral" - ); + require(erc20s[i] != rsr, "RSR is not valid collateral"); + require(erc20s[i] != IERC20(address(rToken)), "RToken is not valid collateral"); + require(erc20s[i] != IERC20(address(stRSR)), "stRSR is not valid collateral"); + require(erc20s[i] != zero, "address zero is not valid collateral"); } require(ArrayLib.allUnique(erc20s), "contains duplicates"); } - /// Good collateral is registered, collateral, SOUND, has the expected targetName, - /// has nonzero targetPerRef() and refPerTok(), and is not a system token or 0 addr - function goodCollateral(bytes32 targetName, IERC20 erc20) private view returns (bool) { - // untestable: - // All calls to goodCollateral pass an erc20 from the config or the backup. - // Both setPrimeBasket and setBackupConfig must pass a call to requireValidCollArray, - // which runs the 4 checks below. - if ( - erc20 == IERC20(address(0)) || - erc20 == rsr || - erc20 == IERC20(address(rToken)) || - erc20 == IERC20(address(stRSR)) - ) return false; - - try assetRegistry.toColl(erc20) returns (ICollateral coll) { - return - targetName == coll.targetName() && - coll.status() == CollateralStatus.SOUND && - coll.refPerTok() > 0 && - coll.targetPerRef() > 0; - } catch { - return false; - } - } - - // solhint-disable-next-line no-empty-blocks - function governanceOnly() private view governance {} - // ==== FacadeRead views ==== // Not used in-protocol; helpful for reconstructing state diff --git a/contracts/p1/mixins/Basket.sol b/contracts/p1/mixins/Basket.sol new file mode 100644 index 0000000000..83591deeab --- /dev/null +++ b/contracts/p1/mixins/Basket.sol @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.17; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +import "../../interfaces/IAssetRegistry.sol"; +import "../../libraries/Fixed.sol"; + +// A "valid collateral array" is a an IERC20[] value without rtoken, rsr, or any duplicate values + +// A BackupConfig value is valid if erc20s is a valid collateral array +struct BackupConfig { + uint256 max; // Maximum number of backup collateral erc20s to use in a basket + IERC20[] erc20s; // Ordered list of backup collateral ERC20s +} + +// What does a BasketConfig value mean? +// +// erc20s, targetAmts, and targetNames should be interpreted together. +// targetAmts[erc20] is the quantity of target units of erc20 that one BU should hold +// targetNames[erc20] is the name of erc20's target unit +// and then backups[tgt] is the BackupConfig to use for the target unit named tgt +// +// For any valid BasketConfig value: +// erc20s == keys(targetAmts) == keys(targetNames) +// if name is in values(targetNames), then backups[name] is a valid BackupConfig +// erc20s is a valid collateral array +// +// In the meantime, treat erc20s as the canonical set of keys for the target* maps +struct BasketConfig { + // The collateral erc20s in the prime (explicitly governance-set) basket + IERC20[] erc20s; + // Amount of target units per basket for each prime collateral token. {target/BU} + mapping(IERC20 => uint192) targetAmts; + // Cached view of the target unit for each erc20 upon setup + mapping(IERC20 => bytes32) targetNames; + // Backup configurations, per target name. + mapping(bytes32 => BackupConfig) backups; +} + +/// The type of BasketHandler.basket. +/// Defines a basket unit (BU) in terms of reference amounts of underlying tokens +// Logically, basket is just a mapping of erc20 addresses to ref-unit amounts. +// In the analytical comments I'll just refer to it that way. +// +// A Basket is valid if erc20s is a valid collateral array and erc20s == keys(refAmts) +struct Basket { + IERC20[] erc20s; // enumerated keys for refAmts + mapping(IERC20 => uint192) refAmts; // {ref/BU} +} + +library BasketLib { + using BasketLib for Basket; + using EnumerableSet for EnumerableSet.AddressSet; + using EnumerableSet for EnumerableSet.Bytes32Set; + using FixLib for uint192; + + // === Basket Algebra === + + /// Set self to a fresh, empty basket + // self'.erc20s = [] (empty list) + // self'.refAmts = {} (empty map) + function empty(Basket storage self) internal { + uint256 length = self.erc20s.length; + for (uint256 i = 0; i < length; ++i) self.refAmts[self.erc20s[i]] = FIX_ZERO; + delete self.erc20s; + } + + /// Set `self` equal to `other` + function setFrom(Basket storage self, Basket storage other) internal { + empty(self); + uint256 length = other.erc20s.length; + for (uint256 i = 0; i < length; ++i) { + self.erc20s.push(other.erc20s[i]); + self.refAmts[other.erc20s[i]] = other.refAmts[other.erc20s[i]]; + } + } + + /// Add `weight` to the refAmount of collateral token `tok` in the basket `self` + // self'.refAmts[tok] = self.refAmts[tok] + weight + // self'.erc20s is keys(self'.refAmts) + function add( + Basket storage self, + IERC20 tok, + uint192 weight + ) internal { + // untestable: + // Both calls to .add() use a weight that has been CEIL rounded in the + // Fixed library div function, so weight will never be 0 here. + // Additionally, setPrimeBasket() enforces prime-basket tokens must have a weight > 0. + if (weight == FIX_ZERO) return; + if (self.refAmts[tok].eq(FIX_ZERO)) { + self.erc20s.push(tok); + self.refAmts[tok] = weight; + } else { + self.refAmts[tok] = self.refAmts[tok].plus(weight); + } + } + + // === Basket Selection === + + /* nextBasket() computes basket' from three inputs: + - the basket configuration (config: BasketConfig) + - the function (isGood: erc20 -> bool), implemented here by goodCollateral() + - the function (targetPerRef: erc20 -> Fix) implemented by the Collateral plugin + + ==== Definitions ==== + + We use e:IERC20 to mean any erc20 token address, and tgt:bytes32 to mean any target name + + // targetWeight(b, e) is the target-unit weight of token e in basket b + Let targetWeight(b, e) = b.refAmt[e] * targetPerRef(e) + + // backups(tgt) is the list of sound backup tokens we plan to use for target `tgt`. + Let backups(tgt) = config.backups[tgt].erc20s + .filter(isGood) + .takeUpTo(config.backups[tgt].max) + + Let primeWt(e) = if e in config.erc20s and isGood(e) + then config.targetAmts[e] + else 0 + Let backupWt(e) = if e in backups(tgt) + then unsoundPrimeWt(tgt) / len(Backups(tgt)) + else 0 + Let unsoundPrimeWt(tgt) = sum(config.targetAmts[e] + for e in config.erc20s + where config.targetNames[e] == tgt and !isGood(e)) + + ==== The correctness condition ==== + + If unsoundPrimeWt(tgt) > 0 and len(backups(tgt)) == 0 for some tgt, then return false. + Else, return true and targetWeight(basket', e) == primeWt(e) + backupWt(e) for all e. + + ==== Higher-level desideratum ==== + + The resulting total target weights should equal the configured target weight. Formally: + + let configTargetWeight(tgt) = sum(config.targetAmts[e] + for e in config.erc20s + where targetNames[e] == tgt) + + let targetWeightSum(b, tgt) = sum(targetWeight(b, e) + for e in config.erc20s + where targetNames[e] == tgt) + + Given all that, if nextBasket() returns true, then for all tgt, + targetWeightSum(basket', tgt) == configTargetWeight(tgt) + */ + + /// Select next reference basket from basket config + /// Works in-place on `newBasket` + /// @param targetNames Scratch space for computation; initial value unused + /// @param newBasket Scratch space for computation; initial value unused + /// @param config The current basket configuration + /// @return If successful; i.e the next argument contains a SOUND non-empty basket + function nextBasket( + Basket storage newBasket, + EnumerableSet.Bytes32Set storage targetNames, + BasketConfig storage config, + IAssetRegistry assetRegistry + ) external returns (bool) { + // targetNames := {} + while (targetNames.length() > 0) targetNames.remove(targetNames.at(0)); + + // newBasket := {} + newBasket.empty(); + + // targetNames = set(values(config.targetNames)) + // (and this stays true; targetNames is not touched again in this function) + for (uint256 i = 0; i < config.erc20s.length; ++i) { + targetNames.add(config.targetNames[config.erc20s[i]]); + } + uint256 targetsLength = targetNames.length(); + + // "good" collateral is collateral with any status() other than DISABLED + // goodWeights and totalWeights are in index-correspondence with targetNames + // As such, they're each interepreted as a map from target name -> target weight + + // {target/BU} total target weight of good, prime collateral with target i + // goodWeights := {} + uint192[] memory goodWeights = new uint192[](targetsLength); + + // {target/BU} total target weight of all prime collateral with target i + // totalWeights := {} + uint192[] memory totalWeights = new uint192[](targetsLength); + + // For each prime collateral token: + for (uint256 i = 0; i < config.erc20s.length; ++i) { + // Find collateral's targetName index + uint256 targetIndex; + for (targetIndex = 0; targetIndex < targetsLength; ++targetIndex) { + if (targetNames.at(targetIndex) == config.targetNames[config.erc20s[i]]) break; + } + assert(targetIndex < targetsLength); + // now, targetNames[targetIndex] == config.targetNames[erc20] + + // Set basket weights for good, prime collateral, + // and accumulate the values of goodWeights and targetWeights + uint192 targetWeight = config.targetAmts[config.erc20s[i]]; + totalWeights[targetIndex] = totalWeights[targetIndex].plus(targetWeight); + + if ( + goodCollateral( + config.targetNames[config.erc20s[i]], + config.erc20s[i], + assetRegistry + ) && targetWeight.gt(FIX_ZERO) + ) { + goodWeights[targetIndex] = goodWeights[targetIndex].plus(targetWeight); + newBasket.add( + config.erc20s[i], + targetWeight.div( + // this div is safe: targetPerRef() > 0: goodCollateral check + assetRegistry.toColl(config.erc20s[i]).targetPerRef(), + CEIL + ) + ); + } + } + + // Analysis: at this point: + // for all tgt in target names, + // totalWeights(tgt) + // = sum(config.targetAmts[e] for e in config.erc20s where targetNames[e] == tgt), and + // goodWeights(tgt) + // = sum(primeWt(e) for e in config.erc20s where targetNames[e] == tgt) + // for all e in config.erc20s, + // targetWeight(newBasket, e) + // = sum(primeWt(e) if goodCollateral(e), else 0) + + // For each tgt in target names, if we still need more weight for tgt then try to add the + // backup basket for tgt to make up that weight: + for (uint256 i = 0; i < targetsLength; ++i) { + if (totalWeights[i].lte(goodWeights[i])) continue; // Don't need any backup weight + + // "tgt" = targetNames[i] + // Now, unsoundPrimeWt(tgt) > 0 + + uint256 size = 0; // backup basket size + BackupConfig storage backup = config.backups[targetNames.at(i)]; + + // Find the backup basket size: min(backup.max, # of good backup collateral) + for (uint256 j = 0; j < backup.erc20s.length && size < backup.max; ++j) { + if (goodCollateral(targetNames.at(i), backup.erc20s[j], assetRegistry)) size++; + } + + // Now, size = len(backups(tgt)). If empty, fail. + if (size == 0) return false; + + // Set backup basket weights... + uint256 assigned = 0; + + // Loop: for erc20 in backups(tgt)... + for (uint256 j = 0; j < backup.erc20s.length && assigned < size; ++j) { + if (goodCollateral(targetNames.at(i), backup.erc20s[j], assetRegistry)) { + // Across this .add(), targetWeight(newBasket',erc20) + // = targetWeight(newBasket,erc20) + unsoundPrimeWt(tgt) / len(backups(tgt)) + newBasket.add( + backup.erc20s[j], + totalWeights[i].minus(goodWeights[i]).div( + // this div is safe: targetPerRef > 0: goodCollateral check + assetRegistry.toColl(backup.erc20s[j]).targetPerRef().mulu(size), + CEIL + ) + ); + assigned++; + } + } + // Here, targetWeight(newBasket, e) = primeWt(e) + backupWt(e) for all e targeting tgt + } + // Now we've looped through all values of tgt, so for all e, + // targetWeight(newBasket, e) = primeWt(e) + backupWt(e) + + return newBasket.erc20s.length > 0; + } + + // === Private === + + /// Good collateral is registered, collateral, SOUND, has the expected targetName, + /// has nonzero targetPerRef() and refPerTok(), and is not a system token or 0 addr + function goodCollateral( + bytes32 targetName, + IERC20 erc20, + IAssetRegistry assetRegistry + ) private view returns (bool) { + if (address(erc20) == address(0)) return false; + // P1 gas optimization + // We do not need to check that the token is not a system token + // BasketHandlerP1.requireValidCollArray() has been run on all ERC20s already + + try assetRegistry.toColl(erc20) returns (ICollateral coll) { + return + targetName == coll.targetName() && + coll.status() == CollateralStatus.SOUND && + coll.refPerTok() > 0 && + coll.targetPerRef() > 0; + } catch { + return false; + } + } +} diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index 9f265b07aa..4ca6eab1c3 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -87,7 +87,7 @@ library RecollateralizationLibP1 { IBasketHandler bh = main.basketHandler(); ntpCtx.quantities = new uint192[](reg.erc20s.length); for (uint256 i = 0; i < reg.erc20s.length; ++i) { - ntpCtx.quantities[i] = bh.quantity(reg.erc20s[i]); + ntpCtx.quantities[i] = bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); } // ============================ diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index 70d5e494e8..b1c461878a 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -186,7 +186,7 @@ contract RTokenAsset is IAsset, VersionedAsset { uint192[] memory quantities = new uint192[](reg.erc20s.length); for (uint256 i = 0; i < reg.erc20s.length; ++i) { - quantities[i] = basketHandler.quantity(reg.erc20s[i]); + quantities[i] = basketHandler.quantityUnsafe(reg.erc20s[i], reg.assets[i]); } // will exclude UoA value from RToken balances at BackingManager From 19c7478f4026d0ae5e6ee30758bf4806ab3d04da Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 11:29:11 -0400 Subject: [PATCH 163/462] Itrade dutch auction (#791) * implement DutchTrade * add hardhat-storage-layout plugin * first compile * tuning * prevent bidding in same block as auction creation * chain bid into new auction * add 1-block cooldown between trades of the same type * more tuning * rate-limit same-kind auctions based on endTime(), not time of settlement * switch order of TradeKind and TradeRequest * RevenueTrader * lint clean * RevenueTrader: expose distributeTokenToBuy directly * refresh()/melt() before RevenueTrader manageToken * update scripts * fix gap * DutchTrade asserts * Broker events/governance setters * Trading minor ternary expression optimization * RevenueTrader require bugfix * RevenueTrader: minimize melting + asset refresh() * keep RToken up to backingBuffer as well * do not manageToken() after handoutRevenue(); leave for facade * p0 + p1 tests * docs/system-design.md * scenario tests * integration tests * reorder FacadeAct tests * plugin integration tests * make backingBuffer handling more straightforward * tests back to passing * fix typo * move tradeEnd into BackingManager + bugfixes * initial revenue dutch tests * sync BackingManagerP0/P1 * lint clean * better error message + fix EasyAuction test * BackingManager tests * fix tests * guard against reentrancy + eliminate 12s magic number throughout codebase * reorder FacadeAct tests * confirm DutchTrade has starting balance * wrap up DutchTrade testing * gas tests for Broker / BackingManager * 80% -> 85% * phase3-rtoken/rTokenConfig.ts * set backing buffer to 0 in recollat test * clarify seemingly obsolete test * pull out as much as possible into beforeEach from Broker GnosisTrade/DutchTrade tests * nits * empty buffer block * add full recollateralization tests * RevenueTraderP1.manageToken() reentrancy nitpick * comment RCEI * label checks/effects/interactions * lint clean * fix tests & linting. * add invalid claim logic replacement tests * missing revenue test * fix plugin tests --------- Co-authored-by: pmckelvy1 --- common/configuration.ts | 10 +- common/constants.ts | 7 +- contracts/facade/FacadeAct.sol | 64 +- contracts/facade/FacadeMonitor.sol | 4 +- contracts/facade/FacadeTest.sol | 9 +- contracts/interfaces/IBackingManager.sol | 18 +- contracts/interfaces/IBroker.sol | 35 +- contracts/interfaces/IDeployer.sol | 10 +- contracts/interfaces/IMain.sol | 3 + contracts/interfaces/IRevenueTrader.sol | 12 +- contracts/interfaces/ITrade.sol | 21 +- contracts/interfaces/ITrading.sol | 4 +- contracts/p0/BackingManager.sol | 167 +- contracts/p0/Broker.sol | 128 +- contracts/p0/Deployer.sol | 9 +- contracts/p0/Furnace.sol | 2 +- contracts/p0/RevenueTrader.sol | 48 +- contracts/p0/StRSR.sol | 2 +- contracts/p0/mixins/Trading.sol | 20 +- contracts/p0/mixins/TradingLib.sol | 18 +- contracts/p1/BackingManager.sol | 243 +-- contracts/p1/Broker.sol | 150 +- contracts/p1/Deployer.sol | 12 +- contracts/p1/Furnace.sol | 2 +- contracts/p1/RevenueTrader.sol | 93 +- contracts/p1/StRSR.sol | 2 +- contracts/p1/mixins/TradeLib.sol | 16 +- contracts/p1/mixins/Trading.sol | 54 +- contracts/plugins/mocks/CTokenVaultMock2.sol | 11 +- contracts/plugins/mocks/InvalidBrokerMock.sol | 18 +- contracts/plugins/trading/DutchTrade.sol | 219 +++ contracts/plugins/trading/GnosisTrade.sol | 12 +- docs/system-design.md | 15 +- hardhat.config.ts | 1 + package.json | 1 + .../phase1-common/0_setup_deployments.ts | 7 +- .../phase1-common/2_deploy_implementations.ts | 28 +- .../phase1-common/3_deploy_rsrAsset.ts | 7 +- .../deployment/phase3-rtoken/rTokenConfig.ts | 12 +- scripts/deployment/utils.ts | 9 +- .../verification/1_verify_implementations.ts | 12 +- test/Broker.test.ts | 1398 ++++++++++------- test/Deployer.test.ts | 15 +- test/FacadeAct.test.ts | 122 +- test/FacadeMonitor.ts | 4 +- test/Main.test.ts | 16 +- test/Recollateralization.test.ts | 593 +++++-- test/Revenues.test.ts | 401 +++-- test/Upgradeability.test.ts | 24 +- test/ZTradingExtremes.test.ts | 6 +- test/__snapshots__/Broker.test.ts.snap | 20 +- .../Recollateralization.test.ts.snap | 16 +- test/fixtures.ts | 16 +- test/integration/EasyAuction.test.ts | 73 +- test/integration/fixtures.ts | 16 +- test/plugins/Collateral.test.ts | 1 + .../aave/ATokenFiatCollateral.test.ts | 3 +- .../compoundv2/CTokenFiatCollateral.test.ts | 3 +- .../plugins/individual-collateral/fixtures.ts | 11 +- test/scenario/BadCollateralPlugin.test.ts | 17 +- test/scenario/BadERC20.test.ts | 22 +- test/scenario/ComplexBasket.test.ts | 56 +- test/scenario/EURT.test.ts | 26 +- test/scenario/MaxBasketSize.test.ts | 4 +- test/scenario/NestedRTokens.test.ts | 14 +- test/scenario/NontrivialPeg.test.ts | 13 +- test/scenario/RevenueHiding.test.ts | 14 +- test/scenario/SetProtocol.test.ts | 14 +- test/scenario/WBTC.test.ts | 28 +- test/scenario/WETH.test.ts | 42 +- test/scenario/cETH.test.ts | 44 +- test/scenario/cWBTC.test.ts | 41 +- test/utils/trades.ts | 47 +- yarn.lock | 28 + 74 files changed, 3152 insertions(+), 1511 deletions(-) create mode 100644 contracts/plugins/trading/DutchTrade.sol diff --git a/common/configuration.ts b/common/configuration.ts index 9b8096a4a3..faf3ae1215 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -317,7 +317,8 @@ export interface IConfig { unstakingDelay: BigNumber warmupPeriod: BigNumber tradingDelay: BigNumber - auctionLength: BigNumber + batchAuctionLength: BigNumber + dutchAuctionLength: BigNumber backingBuffer: BigNumber maxTradeSlippage: BigNumber issuanceThrottle: ThrottleParams @@ -347,9 +348,14 @@ export interface IComponents { stRSR: string } +export interface ITradePlugins { + gnosisTrade: string + dutchTrade: string +} + export interface IImplementations { main: string - trade: string + trading: ITradePlugins components: IComponents } diff --git a/common/constants.ts b/common/constants.ts index 5de1f65d9d..a11a2b4b80 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -44,13 +44,18 @@ export enum RoundingMode { CEIL, } -// @dev Must match `GnosisTrade.TradeStatus`. +// @dev Must match `ITrade.TradeStatus`. export enum TradeStatus { NOT_STARTED, OPEN, CLOSED, } +export enum TradeKind { + DUTCH_AUCTION, + BATCH_AUCTION, +} + export const FURNACE_DEST = '0x0000000000000000000000000000000000000001' export const STRSR_DEST = '0x0000000000000000000000000000000000000002' diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 4d08276373..f6c01f13cc 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -92,11 +92,10 @@ contract FacadeAct is IFacadeAct { ) { // try: backingManager.manageTokens([]) - IERC20[] memory empty = new IERC20[](0); - try cache.bm.manageTokens(empty) { + try cache.bm.rebalance(TradeKind.BATCH_AUCTION) { return ( address(cache.bm), - abi.encodeWithSelector(cache.bm.manageTokens.selector, empty) + abi.encodeWithSelector(cache.bm.rebalance.selector, TradeKind.BATCH_AUCTION) ); } catch {} } else { @@ -138,14 +137,15 @@ contract FacadeAct is IFacadeAct { // rTokenTrader: check if we can start any trades uint48 tradesOpen = cache.rTokenTrader.tradesOpen(); - try cache.rTokenTrader.manageToken(erc20s[i]) { + try cache.rTokenTrader.manageToken(erc20s[i], TradeKind.BATCH_AUCTION) { if (cache.rTokenTrader.tradesOpen() - tradesOpen > 0) { // A trade started; do cache.rTokenTrader.manageToken return ( address(cache.rTokenTrader), abi.encodeWithSelector( cache.rTokenTrader.manageToken.selector, - erc20s[i] + erc20s[i], + TradeKind.BATCH_AUCTION ) ); } @@ -153,14 +153,15 @@ contract FacadeAct is IFacadeAct { // rsrTrader: check if we can start any trades tradesOpen = cache.rsrTrader.tradesOpen(); - try cache.rsrTrader.manageToken(erc20s[i]) { + try cache.rsrTrader.manageToken(erc20s[i], TradeKind.BATCH_AUCTION) { if (cache.rsrTrader.tradesOpen() - tradesOpen > 0) { // A trade started; do cache.rsrTrader.manageToken return ( address(cache.rsrTrader), abi.encodeWithSelector( cache.rsrTrader.manageToken.selector, - erc20s[i] + erc20s[i], + TradeKind.BATCH_AUCTION ) ); } @@ -172,7 +173,7 @@ contract FacadeAct is IFacadeAct { if (cache.bh.status() == CollateralStatus.SOUND) { IAsset rsrAsset = cache.reg.toAsset(cache.rsr); uint192 initialStRSRBal = rsrAsset.bal(address(cache.stRSR)); // {RSR} - try cache.bm.manageTokens(erc20s) { + try cache.bm.forwardRevenue(erc20s) { // if this unblocked an auction in either revenue trader, // then prepare backingManager.manageTokens for (uint256 i = 0; i < erc20s.length; i++) { @@ -183,7 +184,12 @@ contract FacadeAct is IFacadeAct { if (address(erc20s[i]) != address(rToken)) { // rTokenTrader: check if we can start any trades uint48 tradesOpen = cache.rTokenTrader.tradesOpen(); - try cache.rTokenTrader.manageToken(erc20s[i]) { + try + cache.rTokenTrader.manageToken( + erc20s[i], + TradeKind.BATCH_AUCTION + ) + { if (cache.rTokenTrader.tradesOpen() - tradesOpen > 0) { // always forward RToken + the ERC20 twoERC20s[0] = address(rToken); @@ -193,7 +199,7 @@ contract FacadeAct is IFacadeAct { return ( address(cache.bm), abi.encodeWithSelector( - cache.bm.manageTokens.selector, + cache.bm.forwardRevenue.selector, twoERC20s ) ); @@ -207,7 +213,12 @@ contract FacadeAct is IFacadeAct { if (erc20s[i] != cache.rsr) { // rsrTrader: check if we can start any trades uint48 tradesOpen = cache.rsrTrader.tradesOpen(); - try cache.rsrTrader.manageToken(erc20s[i]) { + try + cache.rsrTrader.manageToken( + erc20s[i], + TradeKind.BATCH_AUCTION + ) + { if (cache.rsrTrader.tradesOpen() - tradesOpen > 0) { // always forward RSR + the ERC20 twoERC20s[0] = address(cache.rsr); @@ -217,7 +228,7 @@ contract FacadeAct is IFacadeAct { return ( address(cache.bm), abi.encodeWithSelector( - cache.bm.manageTokens.selector, + cache.bm.forwardRevenue.selector, twoERC20s ) ); @@ -231,18 +242,22 @@ contract FacadeAct is IFacadeAct { { IAsset rTokenAsset = cache.reg.toAsset(IERC20(address(rToken))); (uint192 lotLow, ) = rTokenAsset.lotPrice(); - if ( - rTokenAsset.bal(address(cache.rTokenTrader)) > + rTokenAsset.bal(address(cache.rTokenTrader)) >= minTradeSize(cache.rTokenTrader.minTradeVolume(), lotLow) ) { - try cache.rTokenTrader.manageToken(IERC20(address(rToken))) { + try + cache.rTokenTrader.manageToken( + IERC20(address(rToken)), + TradeKind.BATCH_AUCTION + ) + { address[] memory oneERC20 = new address[](1); oneERC20[0] = address(rToken); return ( address(cache.bm), abi.encodeWithSelector( - cache.bm.manageTokens.selector, + cache.bm.forwardRevenue.selector, oneERC20 ) ); @@ -256,13 +271,13 @@ contract FacadeAct is IFacadeAct { (uint192 lotLow, ) = rsrAsset.lotPrice(); if ( - rsrAsset.bal(address(cache.stRSR)) - initialStRSRBal > + rsrAsset.bal(address(cache.stRSR)) - initialStRSRBal >= minTradeSize(cache.rsrTrader.minTradeVolume(), lotLow) ) { IERC20[] memory empty = new IERC20[](0); return ( address(cache.bm), - abi.encodeWithSelector(cache.bm.manageTokens.selector, empty) + abi.encodeWithSelector(cache.bm.forwardRevenue.selector, empty) ); } } @@ -374,7 +389,10 @@ contract FacadeAct is IFacadeAct { if (tradesOpen != 0) return false; // Try to launch auctions - bm.manageTokensSortedOrder(new IERC20[](0)); + // solhint-disable-next-line no-empty-blocks + try bm.rebalance(TradeKind.BATCH_AUCTION) {} catch { + return false; + } return bm.tradesOpen() > 0; } @@ -388,7 +406,7 @@ contract FacadeAct is IFacadeAct { Registry memory reg = revenueTrader.main().assetRegistry().getRegistry(); // Forward ALL revenue - revenueTrader.main().backingManager().manageTokens(reg.erc20s); + revenueTrader.main().backingManager().forwardRevenue(reg.erc20s); // Calculate which erc20s can have auctions started uint256 num; @@ -402,7 +420,7 @@ contract FacadeAct is IFacadeAct { uint256 tradesOpen = revenueTrader.tradesOpen(); - try revenueTrader.manageToken(reg.erc20s[i]) { + try revenueTrader.manageToken(reg.erc20s[i], TradeKind.BATCH_AUCTION) { if (revenueTrader.tradesOpen() - tradesOpen > 0) { unfiltered[num] = reg.erc20s[i]; ++num; @@ -438,11 +456,11 @@ contract FacadeAct is IFacadeAct { } // Transfer revenue backingManager -> revenueTrader - revenueTrader.main().backingManager().manageTokens(toStart); + revenueTrader.main().backingManager().forwardRevenue(toStart); // Start auctions for (uint256 i = 0; i < toStart.length; ++i) { - revenueTrader.manageToken(toStart[i]); + revenueTrader.manageToken(toStart[i], TradeKind.BATCH_AUCTION); } } } diff --git a/contracts/facade/FacadeMonitor.sol b/contracts/facade/FacadeMonitor.sol index 70f6b97dd5..eedfb30858 100644 --- a/contracts/facade/FacadeMonitor.sol +++ b/contracts/facade/FacadeMonitor.sol @@ -58,7 +58,7 @@ contract FacadeMonitor { // Let's check if there are any trades we can start. uint48 tradesOpen = backingManager.tradesOpen(); - backingManager.manageTokens(erc20s); + backingManager.rebalance(TradeKind.DUTCH_AUCTION); if (backingManager.tradesOpen() - tradesOpen != 0) { response.tradesToBeStarted = erc20s; } @@ -116,7 +116,7 @@ contract FacadeMonitor { IERC20[] memory tradesToBeStarted = new IERC20[](erc20Count); for (uint256 i = 0; i < erc20Count; ) { - trader.manageToken(erc20s[i]); + trader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION); uint48 newTradesOpen = trader.tradesOpen(); diff --git a/contracts/facade/FacadeTest.sol b/contracts/facade/FacadeTest.sol index cfb30f2a6b..de71f7542f 100644 --- a/contracts/facade/FacadeTest.sol +++ b/contracts/facade/FacadeTest.sol @@ -50,11 +50,14 @@ contract FacadeTest is IFacadeTest { } } - main.backingManager().manageTokens(erc20s); + // solhint-disable no-empty-blocks + try main.backingManager().rebalance(TradeKind.BATCH_AUCTION) {} catch {} + try main.backingManager().forwardRevenue(erc20s) {} catch {} for (uint256 i = 0; i < erc20s.length; i++) { - rsrTrader.manageToken(erc20s[i]); - rTokenTrader.manageToken(erc20s[i]); + try rsrTrader.manageToken(erc20s[i], TradeKind.BATCH_AUCTION) {} catch {} + try rTokenTrader.manageToken(erc20s[i], TradeKind.BATCH_AUCTION) {} catch {} } + // solhint-enable no-empty-blocks } /// Prompt all traders and the RToken itself to claim rewards and sweep to BackingManager diff --git a/contracts/interfaces/IBackingManager.sol b/contracts/interfaces/IBackingManager.sol index 169607fff3..e1161079d6 100644 --- a/contracts/interfaces/IBackingManager.sol +++ b/contracts/interfaces/IBackingManager.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "./IBroker.sol"; import "./IComponent.sol"; import "./ITrading.sol"; @@ -41,16 +42,15 @@ interface IBackingManager is IComponent, ITrading { /// @custom:interaction function grantRTokenAllowance(IERC20) external; - /// Maintain the overall backing policy; handout assets otherwise - /// @dev Performs a uniqueness check on the erc20s list in O(n^2) - /// @custom:interaction - function manageTokens(IERC20[] memory erc20s) external; + /// Apply the overall backing policy using the specified TradeKind, taking a haircut if unable + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION + /// @custom:interaction RCEI + function rebalance(TradeKind kind) external; - /// Maintain the overall backing policy; handout assets otherwise - /// @dev Tokens must be in sorted order! - /// @dev Performs a uniqueness check on the erc20s list in O(n) - /// @custom:interaction - function manageTokensSortedOrder(IERC20[] memory erc20s) external; + /// Forward revenue to RevenueTraders; reverts if not fully collateralized + /// @param erc20s The tokens to forward + /// @custom:interaction RCEI + function forwardRevenue(IERC20[] calldata erc20s) external; } interface TestIBackingManager is IBackingManager, TestITrading { diff --git a/contracts/interfaces/IBroker.sol b/contracts/interfaces/IBroker.sol index 81b0293862..13a8031098 100644 --- a/contracts/interfaces/IBroker.sol +++ b/contracts/interfaces/IBroker.sol @@ -6,6 +6,11 @@ import "./IComponent.sol"; import "./IGnosis.sol"; import "./ITrade.sol"; +enum TradeKind { + DUTCH_AUCTION, + BATCH_AUCTION +} + /// The data format that describes a request for trade with the Broker struct TradeRequest { IAsset sell; @@ -21,22 +26,26 @@ struct TradeRequest { */ interface IBroker is IComponent { event GnosisSet(IGnosis indexed oldVal, IGnosis indexed newVal); - event TradeImplementationSet(ITrade indexed oldVal, ITrade indexed newVal); - event AuctionLengthSet(uint48 indexed oldVal, uint48 indexed newVal); + event BatchTradeImplementationSet(ITrade indexed oldVal, ITrade indexed newVal); + event DutchTradeImplementationSet(ITrade indexed oldVal, ITrade indexed newVal); + event BatchAuctionLengthSet(uint48 indexed oldVal, uint48 indexed newVal); + event DutchAuctionLengthSet(uint48 indexed oldVal, uint48 indexed newVal); event DisabledSet(bool indexed prevVal, bool indexed newVal); // Initialization function init( IMain main_, IGnosis gnosis_, - ITrade tradeImplementation_, - uint48 auctionLength_ + ITrade batchTradeImplemention_, + uint48 batchAuctionLength_, + ITrade dutchTradeImplemention_, + uint48 dutchAuctionLength_ ) external; /// Request a trade from the broker /// @dev Requires setting an allowance in advance /// @custom:interaction - function openTrade(TradeRequest memory req) external returns (ITrade); + function openTrade(TradeKind kind, TradeRequest memory req) external returns (ITrade); /// Only callable by one of the trading contracts the broker deploys function reportViolation() external; @@ -47,15 +56,23 @@ interface IBroker is IComponent { interface TestIBroker is IBroker { function gnosis() external view returns (IGnosis); - function tradeImplementation() external view returns (ITrade); + function batchTradeImplementation() external view returns (ITrade); + + function dutchTradeImplementation() external view returns (ITrade); - function auctionLength() external view returns (uint48); + function batchAuctionLength() external view returns (uint48); + + function dutchAuctionLength() external view returns (uint48); function setGnosis(IGnosis newGnosis) external; - function setTradeImplementation(ITrade newTradeImplementation) external; + function setBatchTradeImplementation(ITrade newTradeImplementation) external; + + function setBatchAuctionLength(uint48 newAuctionLength) external; + + function setDutchTradeImplementation(ITrade newTradeImplementation) external; - function setAuctionLength(uint48 newAuctionLength) external; + function setDutchAuctionLength(uint48 newAuctionLength) external; function setDisabled(bool disabled_) external; } diff --git a/contracts/interfaces/IDeployer.sol b/contracts/interfaces/IDeployer.sol index 4e4477ef18..d7cd56a555 100644 --- a/contracts/interfaces/IDeployer.sol +++ b/contracts/interfaces/IDeployer.sol @@ -40,7 +40,8 @@ struct DeploymentParams { // // === BackingManager === uint48 tradingDelay; // {s} how long to wait until starting auctions after switching basket - uint48 auctionLength; // {s} the length of an auction + uint48 batchAuctionLength; // {s} the length of a Gnosis EasyAuction + uint48 dutchAuctionLength; // {s} the length of a falling-price dutch auction uint192 backingBuffer; // {1} how much extra backing collateral to keep uint192 maxTradeSlippage; // {1} max slippage acceptable in a trade // @@ -56,7 +57,12 @@ struct DeploymentParams { struct Implementations { IMain main; Components components; - ITrade trade; + TradePlugins trading; +} + +struct TradePlugins { + ITrade gnosisTrade; + ITrade dutchTrade; } /** diff --git a/contracts/interfaces/IMain.sol b/contracts/interfaces/IMain.sol index 06ff3639b3..9b08fd6c7d 100644 --- a/contracts/interfaces/IMain.sol +++ b/contracts/interfaces/IMain.sol @@ -16,6 +16,9 @@ import "./IStRSR.sol"; import "./ITrading.sol"; import "./IVersioned.sol"; +// Warning, assumption: Chain must have blocktimes >= 12s +uint48 constant ONE_BLOCK = 12; //{s} + // === Auth roles === bytes32 constant OWNER = bytes32(bytes("OWNER")); diff --git a/contracts/interfaces/IRevenueTrader.sol b/contracts/interfaces/IRevenueTrader.sol index abe3399bab..7f90f20b89 100644 --- a/contracts/interfaces/IRevenueTrader.sol +++ b/contracts/interfaces/IRevenueTrader.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.17; +import "./IBroker.sol"; import "./IComponent.sol"; import "./ITrading.sol"; @@ -19,10 +20,17 @@ interface IRevenueTrader is IComponent, ITrading { uint192 minTradeVolume_ ) external; - /// Processes a single token; unpermissioned + /// Process a single token /// @dev Intended to be used with multicall + /// @param erc20 The ERC20 token to manage; can be tokenToBuy or anything registered + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction - function manageToken(IERC20 sell) external; + function manageToken(IERC20 erc20, TradeKind kind) external; + + /// Distribute tokenToBuy to its destinations + /// @dev Special-case of manageToken(tokenToBuy, *) + /// @custom:interaction + function distributeTokenToBuy() external; } // solhint-disable-next-line no-empty-blocks diff --git a/contracts/interfaces/ITrade.sol b/contracts/interfaces/ITrade.sol index 99736768c6..57ae23b501 100644 --- a/contracts/interfaces/ITrade.sol +++ b/contracts/interfaces/ITrade.sol @@ -2,6 +2,15 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "./IBroker.sol"; + +enum TradeStatus { + NOT_STARTED, // before init() + OPEN, // after init() and before settle() + CLOSED, // after settle() + // === Intermediate-tx state === + PENDING // during init() or settle() (reentrancy protection) +} /** * Simple generalized trading interface for all Trade contracts to obey @@ -9,6 +18,11 @@ import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; * Usage: if (canSettle()) settle() */ interface ITrade { + /// Complete the trade and transfer tokens back to the origin trader + /// @return soldAmt {qSellTok} The quantity of tokens sold + /// @return boughtAmt {qBuyTok} The quantity of tokens bought + function settle() external returns (uint256 soldAmt, uint256 boughtAmt); + function sell() external view returns (IERC20Metadata); function buy() external view returns (IERC20Metadata); @@ -20,8 +34,7 @@ interface ITrade { /// @dev Should be guaranteed to be true eventually as an invariant function canSettle() external view returns (bool); - /// Complete the trade and transfer tokens back to the origin trader - /// @return soldAmt {qSellTok} The quantity of tokens sold - /// @return boughtAmt {qBuyTok} The quantity of tokens bought - function settle() external returns (uint256 soldAmt, uint256 boughtAmt); + /// @return TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION + // solhint-disable-next-line func-name-mixedcase + function KIND() external view returns (TradeKind); } diff --git a/contracts/interfaces/ITrading.sol b/contracts/interfaces/ITrading.sol index 6dc58ef41d..4f76877f07 100644 --- a/contracts/interfaces/ITrading.sol +++ b/contracts/interfaces/ITrading.sol @@ -45,8 +45,10 @@ interface ITrading is IComponent, IRewardableComponent { ); /// Settle a single trade, expected to be used with multicall for efficient mass settlement + /// @param sell The sell token in the trade + /// @return The trade settled /// @custom:refresher - function settleTrade(IERC20 sell) external; + function settleTrade(IERC20 sell) external returns (ITrade); /// @return {%} The maximum trade slippage acceptable function maxTradeSlippage() external view returns (uint192); diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index c552a05e44..1473d43b68 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -26,6 +26,8 @@ contract BackingManagerP0 is TradingP0, IBackingManager { uint48 public tradingDelay; // {s} how long to wait until resuming trading after switching uint192 public backingBuffer; // {%} how much extra backing collateral to keep + mapping(TradeKind => uint48) private tradeEnd; // {s} last endTime() of an auction per kind + function init( IMain main_, uint48 tradingDelay_, @@ -48,80 +50,103 @@ contract BackingManagerP0 is TradingP0, IBackingManager { erc20.safeApprove(address(main.rToken()), type(uint256).max); } - /// Maintain the overall backing policy; handout assets otherwise - /// @custom:interaction - function manageTokens(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { - // Token list must not contain duplicates - require(ArrayLib.allUnique(erc20s), "duplicate tokens"); - _manageTokens(erc20s); - } - - /// Maintain the overall backing policy; handout assets otherwise - /// @dev Tokens must be in sorted order! - /// @dev Performs a uniqueness check on the erc20s list in O(n) + /// Settle a single trade. If DUTCH_AUCTION, try rebalance() + /// @param sell The sell token in the trade + /// @return trade The ITrade contract settled /// @custom:interaction - function manageTokensSortedOrder(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { - // Token list must not contain duplicates - require(ArrayLib.sortedAndAllUnique(erc20s), "duplicate/unsorted tokens"); - _manageTokens(erc20s); + function settleTrade(IERC20 sell) public override(ITrading, TradingP0) returns (ITrade trade) { + trade = super.settleTrade(sell); + + // if the settler is the trade contract itself, try chaining with another rebalance() + if (_msgSender() == address(trade)) { + // solhint-disable-next-line no-empty-blocks + try this.rebalance(trade.KIND()) {} catch (bytes memory errData) { + // prevent MEV searchers from providing less gas on purpose by reverting if OOG + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + } + } } - function _manageTokens(IERC20[] calldata erc20s) private { - // Call keepers before - main.poke(); - - if (tradesOpen > 0) return; - - // Ensure basket is ready, SOUND and not in warmup period + /// Apply the overall backing policy using the specified TradeKind, taking a haircut if unable + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION + /// @custom:interaction RCEI + function rebalance(TradeKind kind) external notTradingPausedOrFrozen { + // == Refresh == + main.assetRegistry().refresh(); + main.furnace().melt(); + + // DoS prevention: unless caller is self, require 1 empty block between like-kind auctions + // Assumption: chain has <= 12s blocktimes + require( + _msgSender() == address(this) || tradeEnd[kind] + ONE_BLOCK < block.timestamp, + "already rebalancing" + ); + + require(tradesOpen == 0, "trade open"); require(main.basketHandler().isReady(), "basket not ready"); - - uint48 basketTimestamp = main.basketHandler().timestamp(); - require(block.timestamp >= basketTimestamp + tradingDelay, "trading delayed"); + require( + block.timestamp >= main.basketHandler().timestamp() + tradingDelay, + "trading delayed" + ); + require(!main.basketHandler().fullyCollateralized(), "already collateralized"); + + /* + * Recollateralization + * + * Strategy: iteratively move the system on a forgiving path towards collateralization + * through a narrowing BU price band. The initial large spread reflects the + * uncertainty associated with the market price of defaulted/volatile collateral, as + * well as potential losses due to trading slippage. In the absence of further + * collateral default, the size of the BU price band should decrease with each trade + * until it is 0, at which point collateralization is restored. + * + * If we run out of capital and are still undercollateralized, we compromise + * rToken.basketsNeeded to the current basket holdings. Haircut time. + */ BasketRange memory basketsHeld = main.basketHandler().basketsHeldBy(address(this)); + (bool doTrade, TradeRequest memory req) = TradingLibP0.prepareRecollateralizationTrade( + this, + basketsHeld + ); + + if (doTrade) { + // Seize RSR if needed + if (req.sell.erc20() == main.rsr()) { + uint256 bal = req.sell.erc20().balanceOf(address(this)); + if (req.sellAmount > bal) main.stRSR().seizeRSR(req.sellAmount - bal); + } - if (main.basketHandler().fullyCollateralized()) { - handoutExcessAssets(erc20s, basketsHeld.bottom); + // Execute Trade + tradeEnd[kind] = uint48(block.timestamp) + ONE_BLOCK; + ITrade trade = tryTrade(kind, req); + if (trade.endTime() > tradeEnd[kind]) tradeEnd[kind] = trade.endTime(); } else { - /* - * Recollateralization - * - * Strategy: iteratively move the system on a forgiving path towards capitalization - * through a narrowing BU price band. The initial large spread reflects the - * uncertainty associated with the market price of defaulted/volatile collateral, as - * well as potential losses due to trading slippage. In the absence of further - * collateral default, the size of the BU price band should decrease with each trade - * until it is 0, at which point capitalization is restored. - * - * ====== - * - * If we run out of capital and are still undercollateralized, we compromise - * rToken.basketsNeeded to the current basket holdings. Haircut time. - */ - - (bool doTrade, TradeRequest memory req) = TradingLibP0.prepareRecollateralizationTrade( - this, - basketsHeld - ); - - if (doTrade) { - // Seize RSR if needed - if (req.sell.erc20() == main.rsr()) { - uint256 bal = req.sell.erc20().balanceOf(address(this)); - if (req.sellAmount > bal) main.stRSR().seizeRSR(req.sellAmount - bal); - } - - tryTrade(req); - } else { - // Haircut time - compromiseBasketsNeeded(basketsHeld.bottom); - } + // Haircut time + compromiseBasketsNeeded(basketsHeld.bottom); } } - /// Send excess assets to the RSR and RToken traders - /// @param wholeBasketsHeld {BU} The number of full basket units held by the BackingManager - function handoutExcessAssets(IERC20[] calldata erc20s, uint192 wholeBasketsHeld) private { + /// Forward revenue to RevenueTraders; reverts if not fully collateralized + /// @param erc20s The tokens to forward + /// @custom:interaction RCEI + function forwardRevenue(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { + require(ArrayLib.allUnique(erc20s), "duplicate tokens"); + + // == Refresh == + main.assetRegistry().refresh(); + main.furnace().melt(); + + require(tradesOpen == 0, "trade open"); + require(main.basketHandler().isReady(), "basket not ready"); + require( + block.timestamp >= main.basketHandler().timestamp() + tradingDelay, + "trading delayed" + ); + require(main.basketHandler().fullyCollateralized(), "undercollateralized"); + + BasketRange memory basketsHeld = main.basketHandler().basketsHeldBy(address(this)); assert(main.basketHandler().status() == CollateralStatus.SOUND); // Special-case RSR to forward to StRSR pool @@ -131,28 +156,25 @@ contract BackingManagerP0 is TradingP0, IBackingManager { } // Mint revenue RToken - uint192 needed; // {BU} + uint192 needed = main.rToken().basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); // {BU} { IRToken rToken = main.rToken(); - needed = rToken.basketsNeeded(); // {BU} - if (wholeBasketsHeld.gt(needed)) { + if (basketsHeld.bottom.gt(needed)) { int8 decimals = int8(rToken.decimals()); uint192 totalSupply = shiftl_toFix(rToken.totalSupply(), -decimals); // {rTok} // {BU} = {BU} - {BU} - uint192 extraBUs = wholeBasketsHeld.minus(needed); + uint192 extraBUs = basketsHeld.bottom.minus(needed); // {qRTok: Fix} = {BU} * {qRTok / BU} (if needed == 0, conv rate is 1 qRTok/BU) uint192 rTok = (needed > 0) ? extraBUs.mulDiv(totalSupply, needed) : extraBUs; rToken.mint(address(this), rTok); - rToken.setBasketsNeeded(wholeBasketsHeld); + rToken.setBasketsNeeded(basketsHeld.bottom); + needed = basketsHeld.bottom; } } - // Keep a small surplus of individual collateral - needed = main.rToken().basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); - // Handout excess assets above what is needed, including any newly minted RToken RevenueTotals memory totals = main.distributor().totals(); for (uint256 i = 0; i < erc20s.length; i++) { @@ -172,8 +194,9 @@ contract BackingManagerP0 is TradingP0, IBackingManager { } { uint256 toRToken = tokensPerShare * totals.rTokenTotal; - if (toRToken > 0) + if (toRToken > 0) { erc20s[i].safeTransfer(address(main.rTokenTrader()), toRToken); + } } } } diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 9eb5e3ee85..f89beb626a 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "../plugins/trading/DutchTrade.sol"; import "../plugins/trading/GnosisTrade.sol"; import "../interfaces/IBroker.sol"; import "../interfaces/IMain.sol"; @@ -20,37 +21,48 @@ contract BrokerP0 is ComponentP0, IBroker { using EnumerableSet for EnumerableSet.AddressSet; using SafeERC20 for IERC20Metadata; - // The fraction of the supply of the bidding token that is the min bid size in case of default - uint192 public constant MIN_BID_SHARE_OF_TOTAL_SUPPLY = 1e9; // (1} = 1e-7% uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration -1 week + uint48 public constant MIN_AUCTION_LENGTH = ONE_BLOCK; // {s} min auction length - 1 block + // warning: blocktime <= 12s assumption // Added for interface compatibility with P1 - ITrade public tradeImplementation; + ITrade public batchTradeImplementation; + ITrade public dutchTradeImplementation; IGnosis public gnosis; mapping(address => bool) private trades; - uint48 public auctionLength; // {s} the length of an auction + uint48 public batchAuctionLength; // {s} the length of a Gnosis EasyAuction + uint48 public dutchAuctionLength; // {s} the length of a Dutch Auction bool public disabled; function init( IMain main_, IGnosis gnosis_, - ITrade tradeImplementation_, // Added for Interface compatibility with P1 - uint48 auctionLength_ + ITrade batchTradeImplementation_, // Added for Interface compatibility with P1 + uint48 batchAuctionLength_, + ITrade dutchTradeImplementation_, // Added for Interface compatibility with P1 + uint48 dutchAuctionLength_ ) public initializer { __Component_init(main_); setGnosis(gnosis_); - setTradeImplementation(tradeImplementation_); - setAuctionLength(auctionLength_); + setBatchTradeImplementation(batchTradeImplementation_); + setBatchAuctionLength(batchAuctionLength_); + setDutchTradeImplementation(dutchTradeImplementation_); + setDutchAuctionLength(dutchAuctionLength_); } /// Handle a trade request by deploying a customized disposable trading contract + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @dev Requires setting an allowance in advance /// @custom:protected - function openTrade(TradeRequest memory req) external notTradingPausedOrFrozen returns (ITrade) { + function openTrade(TradeKind kind, TradeRequest memory req) + external + notTradingPausedOrFrozen + returns (ITrade) + { require(!disabled, "broker disabled"); assert(req.sellAmount > 0); @@ -62,17 +74,13 @@ contract BrokerP0 is ComponentP0, IBroker { "only traders" ); - // In the future we'll have more sophisticated choice logic here, probably by trade size - GnosisTrade trade = new GnosisTrade(); - trades[address(trade)] = true; - - // Apply Gnosis EasyAuction-specific resizing - req = resizeTrade(req, GNOSIS_MAX_TOKENS); - - req.sell.erc20().safeTransferFrom(caller, address(trade), req.sellAmount); - - trade.init(this, caller, gnosis, auctionLength, req); - return trade; + // Must be updated when new TradeKinds are created + if (kind == TradeKind.BATCH_AUCTION) { + return newBatchAuction(req, caller); + } else { + // kind == TradeKind.DUTCH_AUCTION + return newDutchAuction(req, ITrading(caller)); + } } /// Disable the broker until re-enabled by governance @@ -114,24 +122,86 @@ contract BrokerP0 is ComponentP0, IBroker { } /// @custom:governance - function setTradeImplementation(ITrade newTradeImplementation) public governance { + function setBatchTradeImplementation(ITrade newTradeImplementation) public governance { + require( + address(newTradeImplementation) != address(0), + "invalid batchTradeImplementation address" + ); + + emit BatchTradeImplementationSet(batchTradeImplementation, newTradeImplementation); + batchTradeImplementation = newTradeImplementation; + } + + /// @custom:governance + function setBatchAuctionLength(uint48 newAuctionLength) public governance { + require( + newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH, + "invalid batchAuctionLength" + ); + emit BatchAuctionLengthSet(batchAuctionLength, newAuctionLength); + batchAuctionLength = newAuctionLength; + } + + /// @custom:governance + function setDutchTradeImplementation(ITrade newTradeImplementation) public governance { require( address(newTradeImplementation) != address(0), - "invalid Trade Implementation address" + "invalid dutchTradeImplementation address" ); - emit TradeImplementationSet(tradeImplementation, newTradeImplementation); - tradeImplementation = newTradeImplementation; + emit DutchTradeImplementationSet(dutchTradeImplementation, newTradeImplementation); + dutchTradeImplementation = newTradeImplementation; } /// @custom:governance - function setAuctionLength(uint48 newAuctionLength) public governance { + function setDutchAuctionLength(uint48 newAuctionLength) public governance { require( - newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, - "invalid auctionLength" + newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH, + "invalid dutchAuctionLength" ); - emit AuctionLengthSet(auctionLength, newAuctionLength); - auctionLength = newAuctionLength; + emit DutchAuctionLengthSet(dutchAuctionLength, newAuctionLength); + dutchAuctionLength = newAuctionLength; + } + + // === Private === + + function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { + require(batchAuctionLength > 0, "batchAuctionLength unset"); + GnosisTrade trade = new GnosisTrade(); + trades[address(trade)] = true; + + // Apply Gnosis EasyAuction-specific resizing of req, if needed: Ensure that + // max(sellAmount, minBuyAmount) <= maxTokensAllowed, while maintaining their proportion + uint256 maxQty = (req.minBuyAmount > req.sellAmount) ? req.minBuyAmount : req.sellAmount; + + if (maxQty > GNOSIS_MAX_TOKENS) { + req.sellAmount = mulDiv256(req.sellAmount, GNOSIS_MAX_TOKENS, maxQty, CEIL); + req.minBuyAmount = mulDiv256(req.minBuyAmount, GNOSIS_MAX_TOKENS, maxQty, FLOOR); + } + + IERC20Metadata(address(req.sell.erc20())).safeTransferFrom( + caller, + address(trade), + req.sellAmount + ); + + trade.init(this, caller, gnosis, batchAuctionLength, req); + return trade; + } + + function newDutchAuction(TradeRequest memory req, ITrading caller) private returns (ITrade) { + require(dutchAuctionLength > 0, "dutchAuctionLength unset"); + DutchTrade trade = new DutchTrade(); + trades[address(trade)] = true; + + IERC20Metadata(address(req.sell.erc20())).safeTransferFrom( + address(caller), + address(trade), + req.sellAmount + ); + + trade.init(caller, req.sell, req.buy, req.sellAmount, dutchAuctionLength); + return trade; } /// @custom:governance diff --git a/contracts/p0/Deployer.sol b/contracts/p0/Deployer.sol index ae4add1e54..652a557a83 100644 --- a/contracts/p0/Deployer.sol +++ b/contracts/p0/Deployer.sol @@ -110,7 +110,14 @@ contract DeployerP0 is IDeployer, Versioned { // Init Furnace main.furnace().init(main, params.rewardRatio); - main.broker().init(main, gnosis, ITrade(address(1)), params.auctionLength); + main.broker().init( + main, + gnosis, + ITrade(address(1)), + params.batchAuctionLength, + ITrade(address(1)), + params.dutchAuctionLength + ); // Init StRSR { diff --git a/contracts/p0/Furnace.sol b/contracts/p0/Furnace.sol index c4e0cb994a..a8e436b663 100644 --- a/contracts/p0/Furnace.sol +++ b/contracts/p0/Furnace.sol @@ -13,7 +13,7 @@ contract FurnaceP0 is ComponentP0, IFurnace { using FixLib for uint192; uint192 public constant MAX_RATIO = FIX_ONE; // {1} 100% - uint48 public constant PERIOD = 12; // {s} 12 seconds; 1 block on PoS Ethereum + uint48 public constant PERIOD = ONE_BLOCK; // {s} 12 seconds; 1 block on PoS Ethereum uint192 public ratio; // {1} What fraction of balance to melt each PERIOD diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index fb06ece1e4..fec1c0233c 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -28,22 +28,47 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { tokenToBuy = tokenToBuy_; } - /// Processes a single token; unpermissioned - /// @dev Intended to be used with multicall + /// Settle a single trade + distribute revenue + /// @param sell The sell token in the trade + /// @return trade The ITrade contract settled /// @custom:interaction - function manageToken(IERC20 erc20) external notTradingPausedOrFrozen { - if (address(trades[erc20]) != address(0)) return; + function settleTrade(IERC20 sell) + public + override(ITrading, TradingP0) + notTradingPausedOrFrozen + returns (ITrade trade) + { + trade = super.settleTrade(sell); + distributeTokenToBuy(); + // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely + } - uint256 bal = erc20.balanceOf(address(this)); - if (bal == 0) return; + /// Distribute tokenToBuy to its destinations + /// @dev Special-case of manageToken(tokenToBuy, *) + /// @custom:interaction + function distributeTokenToBuy() public { + uint256 bal = tokenToBuy.balanceOf(address(this)); + tokenToBuy.safeApprove(address(main.distributor()), 0); + tokenToBuy.safeApprove(address(main.distributor()), bal); + main.distributor().distribute(tokenToBuy, bal); + } + /// Processes a single token; unpermissioned + /// @dev Intended to be used with multicall + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION + /// @custom:interaction + function manageToken(IERC20 erc20, TradeKind kind) external notTradingPausedOrFrozen { if (erc20 == tokenToBuy) { - erc20.safeApprove(address(main.distributor()), 0); - erc20.safeApprove(address(main.distributor()), bal); - main.distributor().distribute(erc20, bal); + distributeTokenToBuy(); return; } + main.assetRegistry().refresh(); + main.furnace().melt(); + + require(address(trades[erc20]) == address(0), "trade open"); + require(erc20.balanceOf(address(this)) > 0, "0 balance"); + IAssetRegistry reg = main.assetRegistry(); IAsset sell = reg.toAsset(erc20); IAsset buy = reg.toAsset(tokenToBuy); @@ -69,8 +94,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { maxTradeSlippage ); - if (launch) { - tryTrade(req); - } + require(launch, "trade not worth launching"); + tryTrade(kind, req); } } diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index 7d3f35628c..521bceec80 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -31,7 +31,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { using EnumerableSet for EnumerableSet.AddressSet; using FixLib for uint192; - uint48 public constant PERIOD = 12; // {s} 12 seconds; 1 block on PoS Ethereum + uint48 public constant PERIOD = ONE_BLOCK; // {s} 12 seconds; 1 block on PoS Ethereum uint48 public constant MIN_UNSTAKING_DELAY = PERIOD * 2; // {s} uint48 public constant MAX_UNSTAKING_DELAY = 31536000; // {s} 1 year uint192 public constant MAX_REWARD_RATIO = 1e18; diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index 3f6c27494a..af0938158d 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -40,10 +40,17 @@ abstract contract TradingP0 is RewardableP0, ITrading { } /// Settle a single trade, expected to be used with multicall for efficient mass settlement + /// @param sell The sell token in the trade + /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) public notTradingPausedOrFrozen { - ITrade trade = trades[sell]; - if (address(trade) == address(0)) return; + function settleTrade(IERC20 sell) + public + virtual + notTradingPausedOrFrozen + returns (ITrade trade) + { + trade = trades[sell]; + require(address(trade) != address(0), "no trade open"); require(trade.canSettle(), "cannot settle yet"); delete trades[sell]; @@ -53,7 +60,9 @@ abstract contract TradingP0 is RewardableP0, ITrading { } /// Try to initiate a trade with a trading partner provided by the broker - function tryTrade(TradeRequest memory req) internal { + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION + /// @return trade The trade contract created + function tryTrade(TradeKind kind, TradeRequest memory req) internal returns (ITrade trade) { IBroker broker = main.broker(); assert(address(trades[req.sell.erc20()]) == address(0)); require(!broker.disabled(), "broker disabled"); @@ -61,8 +70,7 @@ abstract contract TradingP0 is RewardableP0, ITrading { req.sell.erc20().safeApprove(address(broker), 0); req.sell.erc20().safeApprove(address(broker), req.sellAmount); - ITrade trade = broker.openTrade(req); - + trade = broker.openTrade(kind, req); trades[req.sell.erc20()] = trade; tradesOpen++; emit TradeStarted( diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index dc37cff06b..0254cffd16 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -56,7 +56,7 @@ library TradingLibP0 { } // Cap sell amount - uint192 maxSell = maxTradeSize(trade.sell, lotHigh); // {sellTok} + uint192 maxSell = maxTradeSize(trade.sell, trade.buy, lotHigh); // {sellTok} uint192 s = trade.sellAmount > maxSell ? maxSell : trade.sellAmount; // {sellTok} // Calculate equivalent buyAmount within [0, FIX_MAX] @@ -577,10 +577,18 @@ library TradingLibP0 { return size > 0 ? size : 1; } - /// Calculates the maxTradeSize for an asset based on the asset's maxTradeVolume and price - /// @return {tok} The max trade size for the asset in whole tokens - function maxTradeSize(IAsset asset, uint192 price) private view returns (uint192) { - uint192 size = price == 0 ? FIX_MAX : asset.maxTradeVolume().div(price, FLOOR); + /// Calculates the maximum trade size for a trade pair of tokens + /// @return {tok} The max trade size for the trade overall + function maxTradeSize( + IAsset sell, + IAsset buy, + uint192 price + ) private view returns (uint192) { + // untestable: + // Price cannot be 0, it would've been filtered before in `prepareTradeSell` + uint192 size = price == 0 + ? FIX_MAX + : fixMin(sell.maxTradeVolume(), buy.maxTradeVolume()).div(price, FLOOR); return size > 0 ? size : 1; } } diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index db0035b05a..872d8202af 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.17; -import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../interfaces/IAsset.sol"; import "../interfaces/IBackingManager.sol"; @@ -19,7 +19,7 @@ import "./mixins/RecollateralizationLib.sol"; /// @custom:oz-upgrades-unsafe-allow external-library-linking contract BackingManagerP1 is TradingP1, IBackingManager { using FixLib for uint192; - using SafeERC20Upgradeable for IERC20Upgradeable; + using SafeERC20 for IERC20; // Cache of peer components IAssetRegistry private assetRegistry; @@ -34,7 +34,11 @@ contract BackingManagerP1 is TradingP1, IBackingManager { uint192 public constant MAX_BACKING_BUFFER = FIX_ONE; // {1} 100% uint48 public tradingDelay; // {s} how long to wait until resuming trading after switching - uint192 public backingBuffer; // {%} how much extra backing collateral to keep + uint192 public backingBuffer; // {1} how much extra backing collateral to keep + + // === 3.0.0 === + IFurnace private furnace; + mapping(TradeKind => uint48) private tradeEnd; // {s} last endTime() of an auction per kind // ==== Invariants ==== // tradingDelay <= MAX_TRADING_DELAY and backingBuffer <= MAX_BACKING_BUFFER @@ -59,6 +63,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { rTokenTrader = main_.rTokenTrader(); rToken = main_.rToken(); stRSR = main_.stRSR(); + furnace = main_.furnace(); setTradingDelay(tradingDelay_); setBackingBuffer(backingBuffer_); @@ -72,92 +77,109 @@ contract BackingManagerP1 is TradingP1, IBackingManager { function grantRTokenAllowance(IERC20 erc20) external notFrozen { require(assetRegistry.isRegistered(erc20), "erc20 unregistered"); // == Interaction == - IERC20Upgradeable(address(erc20)).safeApprove(address(main.rToken()), 0); - IERC20Upgradeable(address(erc20)).safeApprove(address(main.rToken()), type(uint256).max); - } - - /// Maintain the overall backing policy; handout assets otherwise - /// @custom:interaction - // checks: the addresses in `erc20s` are unique - // effect: _manageTokens(erc20s) - function manageTokens(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { - // Token list must not contain duplicates - require(ArrayLib.allUnique(erc20s), "duplicate tokens"); - _manageTokens(erc20s); + IERC20(address(erc20)).safeApprove(address(main.rToken()), 0); + IERC20(address(erc20)).safeApprove(address(main.rToken()), type(uint256).max); } - /// Maintain the overall backing policy; handout assets otherwise - /// @dev Tokens must be in sorted order! - /// @dev Performs a uniqueness check on the erc20s list in O(n) + /// Settle a single trade. If DUTCH_AUCTION, try rebalance() + /// @param sell The sell token in the trade + /// @return trade The ITrade contract settled /// @custom:interaction - // checks: the addresses in `erc20s` are unique (and sorted) - // effect: _manageTokens(erc20s) - function manageTokensSortedOrder(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { - // Token list must not contain duplicates - require(ArrayLib.sortedAndAllUnique(erc20s), "duplicate/unsorted tokens"); - _manageTokens(erc20s); + function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { + trade = super.settleTrade(sell); // modifier: notTradingPausedOrFrozen + + // if the settler is the trade contract itself, try chaining with another rebalance() + if (_msgSender() == address(trade)) { + // solhint-disable-next-line no-empty-blocks + try this.rebalance(trade.KIND()) {} catch (bytes memory errData) { + // prevent MEV searchers from providing less gas on purpose by reverting if OOG + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + } + } } - /// Maintain the overall backing policy; handout assets otherwise + /// Apply the overall backing policy using the specified TradeKind, taking a haircut if unable + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction RCEI - // only called internally, from manageTokens*, so erc20s has no duplicates unique - // (but not necessarily all registered or valid!) - function _manageTokens(IERC20[] calldata erc20s) private { + function rebalance(TradeKind kind) external notTradingPausedOrFrozen { // == Refresh == assetRegistry.refresh(); + furnace.melt(); - if (tradesOpen > 0) return; + // == Checks/Effects == - // Ensure basket is ready, SOUND and not in warmup period - require(basketHandler.isReady(), "basket not ready"); + // DoS prevention: unless caller is self, require 1 empty block between like-kind auctions + // Assumption: chain has <= 12s blocktimes + require( + _msgSender() == address(this) || tradeEnd[kind] + ONE_BLOCK < block.timestamp, + "already rebalancing" + ); - uint48 basketTimestamp = basketHandler.timestamp(); - require(block.timestamp >= basketTimestamp + tradingDelay, "trading delayed"); + require(tradesOpen == 0, "trade open"); + require(basketHandler.isReady(), "basket not ready"); + require(block.timestamp >= basketHandler.timestamp() + tradingDelay, "trading delayed"); BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(this)); - uint192 basketsNeeded = rToken.basketsNeeded(); // {BU} + require(basketsHeld.bottom < rToken.basketsNeeded(), "already collateralized"); + // require(!basketHandler.fullyCollateralized()) - // if (basketHandler.fullyCollateralized()) - if (basketsHeld.bottom >= basketsNeeded) { - // == Interaction (then return) == - handoutExcessAssets(erc20s, basketsHeld.bottom); - } else { - /* - * Recollateralization - * - * Strategy: iteratively move the system on a forgiving path towards collateralization - * through a narrowing BU price band. The initial large spread reflects the - * uncertainty associated with the market price of defaulted/volatile collateral, as - * well as potential losses due to trading slippage. In the absence of further - * collateral default, the size of the BU price band should decrease with each trade - * until it is 0, at which point collateralization is restored. - * - * If we run out of capital and are still undercollateralized, we compromise - * rToken.basketsNeeded to the current basket holdings. Haircut time. - */ - - (bool doTrade, TradeRequest memory req) = RecollateralizationLibP1 - .prepareRecollateralizationTrade(this, basketsHeld); - - if (doTrade) { - // Seize RSR if needed - if (req.sell.erc20() == rsr) { - uint256 bal = req.sell.erc20().balanceOf(address(this)); - if (req.sellAmount > bal) stRSR.seizeRSR(req.sellAmount - bal); - } - - tryTrade(req); - } else { - // Haircut time - compromiseBasketsNeeded(basketsHeld.bottom, basketsNeeded); + /* + * Recollateralization + * + * Strategy: iteratively move the system on a forgiving path towards collateralization + * through a narrowing BU price band. The initial large spread reflects the + * uncertainty associated with the market price of defaulted/volatile collateral, as + * well as potential losses due to trading slippage. In the absence of further + * collateral default, the size of the BU price band should decrease with each trade + * until it is 0, at which point collateralization is restored. + * + * If we run out of capital and are still undercollateralized, we compromise + * rToken.basketsNeeded to the current basket holdings. Haircut time. + */ + + (bool doTrade, TradeRequest memory req) = RecollateralizationLibP1 + .prepareRecollateralizationTrade(this, basketsHeld); + + // == Interactions == + + if (doTrade) { + tradeEnd[kind] = uint48(block.timestamp) + ONE_BLOCK; // reentrancy protection + + // Seize RSR if needed + if (req.sell.erc20() == rsr) { + uint256 bal = req.sell.erc20().balanceOf(address(this)); + if (req.sellAmount > bal) stRSR.seizeRSR(req.sellAmount - bal); } + + // Execute Trade + ITrade trade = tryTrade(kind, req); + uint48 endTime = trade.endTime(); + if (endTime > tradeEnd[kind]) tradeEnd[kind] = endTime; + } else { + // Haircut time + compromiseBasketsNeeded(basketsHeld.bottom); } } - /// Send excess assets to the RSR and RToken traders - /// @param basketsHeldBottom {BU} The number of full basket units held by the BackingManager - /// @custom:interaction CEI - function handoutExcessAssets(IERC20[] calldata erc20s, uint192 basketsHeldBottom) private { + /// Forward revenue to RevenueTraders; reverts if not fully collateralized + /// @param erc20s The tokens to forward + /// @custom:interaction RCEI + function forwardRevenue(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { + require(ArrayLib.allUnique(erc20s), "duplicate tokens"); + + // == Refresh == + assetRegistry.refresh(); + furnace.melt(); + + BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(this)); + + require(tradesOpen == 0, "trade open"); + require(basketHandler.isReady(), "basket not ready"); + require(block.timestamp >= basketHandler.timestamp() + tradingDelay, "trading delayed"); + require(basketsHeld.bottom >= rToken.basketsNeeded(), "undercollateralized"); + // require(basketHandler.fullyCollateralized()) + /** * Assumptions: * - Fully collateralized. All collateral meet balance requirements. @@ -171,17 +193,15 @@ contract BackingManagerP1 is TradingP1, IBackingManager { * 2. Using whatever balances of collateral are there, fast-issue all RToken possible. * (in detail: mint RToken and set basketsNeeded so that the BU/rtok exchange rate is * roughly constant, and strictly does not decrease, - * 3. Handout all surplus asset balances (including collateral and RToken) to the - * RSR and RToken traders according to the distribution totals. + * 3. Handout all RToken held above the backingBuffer portion of the supply, and all + * non-RToken surplus asset balances to the RSR and + * RToken traders according to the distribution totals. */ // Forward any RSR held to StRSR pool; RSR should never be sold for RToken yield if (rsr.balanceOf(address(this)) > 0) { // For CEI, this is an interaction "within our system" even though RSR is already live - IERC20Upgradeable(address(rsr)).safeTransfer( - address(stRSR), - rsr.balanceOf(address(this)) - ); + IERC20(address(rsr)).safeTransfer(address(stRSR), rsr.balanceOf(address(this))); } // Mint revenue RToken and update `basketsNeeded` @@ -189,36 +209,34 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // where rate(R) == R.basketsNeeded / R.totalSupply, // rate(rToken') >== rate(rToken) // (>== is "no less than, and nearly equal to") - // and rToken'.basketsNeeded <= basketsHeldBottom + // and rToken'.basketsNeeded <= basketsHeld.bottom // and rToken'.totalSupply is maximal satisfying this. - uint192 needed; // {BU} - { - needed = rToken.basketsNeeded(); // {BU} - if (basketsHeldBottom.gt(needed)) { - // gas-optimization: RToken is known to have 18 decimals, the same as FixLib - uint192 totalSupply = _safeWrap(rToken.totalSupply()); // {rTok} - - // {BU} = {BU} - {BU} - uint192 extraBUs = basketsHeldBottom.minus(needed); - - // {rTok} = {BU} * {rTok / BU} (if needed == 0, conv rate is 1 rTok/BU) - uint192 rTok = (needed > 0) ? extraBUs.mulDiv(totalSupply, needed) : extraBUs; - - // gas-optimization: RToken is known to have 18 decimals, same as FixLib - rToken.mint(address(this), uint256(rTok)); - rToken.setBasketsNeeded(basketsHeldBottom); - needed = basketsHeldBottom; - } + + // Keep backingBuffer worth of collateral before recognizing revenue + uint192 needed = rToken.basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); // {BU} + + if (basketsHeld.bottom.gt(needed)) { + // gas-optimization: RToken is known to have 18 decimals, the same as FixLib + uint192 totalSupply = _safeWrap(rToken.totalSupply()); // {rTok} + + // {BU} = {BU} - {BU} + uint192 extraBUs = basketsHeld.bottom.minus(needed); + + // {rTok} = {BU} * {rTok / BU} (if needed == 0, conv rate is 1 rTok/BU) + uint192 rTok = (needed > 0) ? extraBUs.mulDiv(totalSupply, needed) : extraBUs; + + // gas-optimization: RToken is known to have 18 decimals, same as FixLib + rToken.mint(address(this), uint256(rTok)); + rToken.setBasketsNeeded(basketsHeld.bottom); + needed = basketsHeld.bottom; } // At this point, even though basketsNeeded may have changed: // - We're fully collateralized // - The BU exchange rate {BU/rTok} did not decrease - // Keep a small buffer of individual collateral; "excess" assets are beyond the buffer. - needed = needed.mul(FIX_ONE.plus(backingBuffer)); + // Handout surplus assets + newly minted RToken - // Handout excess assets above what is needed, including any recently minted RToken uint256 length = erc20s.length; RevenueTotals memory totals = distributor.totals(); uint256[] memory toRSR = new uint256[](length); @@ -226,12 +244,14 @@ contract BackingManagerP1 is TradingP1, IBackingManager { for (uint256 i = 0; i < length; ++i) { IAsset asset = assetRegistry.toAsset(erc20s[i]); + // {tok} = {BU} * {tok/BU} uint192 req = needed.mul(basketHandler.quantity(erc20s[i]), CEIL); - if (asset.bal(address(this)).gt(req)) { + uint192 bal = asset.bal(address(this)); + + if (bal.gt(req)) { // delta: {qTok}, the excess quantity of this asset that we hold - uint256 delta = asset.bal(address(this)).minus(req).shiftl_toUint( - int8(IERC20Metadata(address(erc20s[i])).decimals()) - ); + uint256 delta = bal.minus(req).shiftl_toUint(int8(asset.erc20Decimals())); + // no div-by-0: Distributor guarantees (totals.rTokenTotal + totals.rsrTotal) > 0 // initial division is intentional here! We'd rather save the dust than be unfair toRSR[i] = (delta / (totals.rTokenTotal + totals.rsrTotal)) * totals.rsrTotal; @@ -241,9 +261,8 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // == Interactions == for (uint256 i = 0; i < length; ++i) { - IERC20Upgradeable erc20 = IERC20Upgradeable(address(erc20s[i])); - if (toRToken[i] > 0) erc20.safeTransfer(address(rTokenTrader), toRToken[i]); - if (toRSR[i] > 0) erc20.safeTransfer(address(rsrTrader), toRSR[i]); + if (toRToken[i] > 0) erc20s[i].safeTransfer(address(rTokenTrader), toRToken[i]); + if (toRSR[i] > 0) erc20s[i].safeTransfer(address(rsrTrader), toRSR[i]); } // It's okay if there is leftover dust for RToken or a surplus asset (not RSR) @@ -251,10 +270,9 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// Compromise on how many baskets are needed in order to recollateralize-by-accounting /// @param basketsHeldBottom {BU} The number of full basket units held by the BackingManager - /// @param basketsNeeded {BU} RToken.basketsNeeded() - function compromiseBasketsNeeded(uint192 basketsHeldBottom, uint192 basketsNeeded) private { + function compromiseBasketsNeeded(uint192 basketsHeldBottom) private { // assert(tradesOpen == 0 && !basketHandler.fullyCollateralized()); - assert(tradesOpen == 0 && basketsHeldBottom < basketsNeeded); + assert(tradesOpen == 0); rToken.setBasketsNeeded(basketsHeldBottom); } @@ -274,10 +292,15 @@ contract BackingManagerP1 is TradingP1, IBackingManager { backingBuffer = val; } + /// Call after upgrade to >= 3.0.0 + function cacheFurnace() public { + furnace = main.furnace(); + } + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[41] private __gap; + uint256[39] private __gap; } diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 36c57a2695..a8a34c121b 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -9,6 +9,7 @@ import "../interfaces/IMain.sol"; import "../interfaces/ITrade.sol"; import "../libraries/Fixed.sol"; import "./mixins/Component.sol"; +import "../plugins/trading/DutchTrade.sol"; import "../plugins/trading/GnosisTrade.sol"; // Gnosis: uint96 ~= 7e28 @@ -22,19 +23,23 @@ contract BrokerP1 is ComponentP1, IBroker { using Clones for address; uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration - 1 week + uint48 public constant MIN_AUCTION_LENGTH = ONE_BLOCK; // {s} min auction length - 1 block + // warning: blocktime <= 12s assumption IBackingManager private backingManager; IRevenueTrader private rsrTrader; IRevenueTrader private rTokenTrader; - // The trade contract to clone on openTrade(). Governance parameter. - ITrade public tradeImplementation; + /// @custom:oz-renamed-from tradeImplementation + // The Batch Auction Trade contract to clone on openTrade(). Governance parameter. + ITrade public batchTradeImplementation; - // The Gnosis contract to init each trade with. Governance parameter. + // The Gnosis contract to init batch auction trades with. Governance parameter. IGnosis public gnosis; - // {s} the length of an auction. Governance parameter. - uint48 public auctionLength; + /// @custom:oz-renamed-from auctionLength + // {s} the length of a Gnosis EasyAuction. Governance parameter. + uint48 public batchAuctionLength; // Whether trading is disabled. // Initially false. Settable by OWNER. A trade clone can set it to true via reportViolation() @@ -43,6 +48,14 @@ contract BrokerP1 is ComponentP1, IBroker { // The set of ITrade (clone) addresses this contract has created mapping(address => bool) private trades; + // === 3.0.0 === + + // The Dutch Auction Trade contract to clone on openTrade(). Governance parameter. + ITrade public dutchTradeImplementation; + + // {s} the length of a Dutch Auction. Governance parameter. + uint48 public dutchAuctionLength; + // ==== Invariant ==== // (trades[addr] == true) iff this contract has created an ITrade clone at addr @@ -50,8 +63,10 @@ contract BrokerP1 is ComponentP1, IBroker { function init( IMain main_, IGnosis gnosis_, - ITrade tradeImplementation_, - uint48 auctionLength_ + ITrade batchTradeImplementation_, + uint48 batchAuctionLength_, + ITrade dutchTradeImplementation_, + uint48 dutchAuctionLength_ ) external initializer { __Component_init(main_); @@ -60,11 +75,14 @@ contract BrokerP1 is ComponentP1, IBroker { rTokenTrader = main_.rTokenTrader(); setGnosis(gnosis_); - setTradeImplementation(tradeImplementation_); - setAuctionLength(auctionLength_); + setBatchTradeImplementation(batchTradeImplementation_); + setBatchAuctionLength(batchAuctionLength_); + setDutchTradeImplementation(dutchTradeImplementation_); + setDutchAuctionLength(dutchAuctionLength_); } /// Handle a trade request by deploying a customized disposable trading contract + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @dev Requires setting an allowance in advance /// @custom:interaction CEI // checks: @@ -76,7 +94,11 @@ contract BrokerP1 is ComponentP1, IBroker { // actions: // Transfers req.sellAmount of req.sell.erc20 from caller to `trade` // Calls trade.init() with appropriate parameters - function openTrade(TradeRequest memory req) external notTradingPausedOrFrozen returns (ITrade) { + function openTrade(TradeKind kind, TradeRequest memory req) + external + notTradingPausedOrFrozen + returns (ITrade) + { require(!disabled, "broker disabled"); address caller = _msgSender(); @@ -87,28 +109,11 @@ contract BrokerP1 is ComponentP1, IBroker { "only traders" ); - // In the future we'll have more sophisticated choice logic here, probably by trade size - GnosisTrade trade = GnosisTrade(address(tradeImplementation).clone()); - trades[address(trade)] = true; - - // Apply Gnosis EasyAuction-specific resizing of req, if needed: Ensure that - // max(sellAmount, minBuyAmount) <= maxTokensAllowed, while maintaining their proportion - uint256 maxQty = (req.minBuyAmount > req.sellAmount) ? req.minBuyAmount : req.sellAmount; - - if (maxQty > GNOSIS_MAX_TOKENS) { - req.sellAmount = mulDiv256(req.sellAmount, GNOSIS_MAX_TOKENS, maxQty, CEIL); - req.minBuyAmount = mulDiv256(req.minBuyAmount, GNOSIS_MAX_TOKENS, maxQty, FLOOR); + // Must be updated when new TradeKinds are created + if (kind == TradeKind.BATCH_AUCTION) { + return newBatchAuction(req, caller); } - - // == Interactions == - IERC20Upgradeable(address(req.sell.erc20())).safeTransferFrom( - caller, - address(trade), - req.sellAmount - ); - - trade.init(this, caller, gnosis, auctionLength, req); - return trade; + return newDutchAuction(req, ITrading(caller)); } /// Disable the broker until re-enabled by governance @@ -132,24 +137,45 @@ contract BrokerP1 is ComponentP1, IBroker { } /// @custom:governance - function setTradeImplementation(ITrade newTradeImplementation) public governance { + function setBatchTradeImplementation(ITrade newTradeImplementation) public governance { + require( + address(newTradeImplementation) != address(0), + "invalid batchTradeImplementation address" + ); + + emit BatchTradeImplementationSet(batchTradeImplementation, newTradeImplementation); + batchTradeImplementation = newTradeImplementation; + } + + /// @custom:governance + function setBatchAuctionLength(uint48 newAuctionLength) public governance { + require( + newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH, + "invalid batchAuctionLength" + ); + emit BatchAuctionLengthSet(batchAuctionLength, newAuctionLength); + batchAuctionLength = newAuctionLength; + } + + /// @custom:governance + function setDutchTradeImplementation(ITrade newTradeImplementation) public governance { require( address(newTradeImplementation) != address(0), - "invalid Trade Implementation address" + "invalid dutchTradeImplementation address" ); - emit TradeImplementationSet(tradeImplementation, newTradeImplementation); - tradeImplementation = newTradeImplementation; + emit DutchTradeImplementationSet(dutchTradeImplementation, newTradeImplementation); + dutchTradeImplementation = newTradeImplementation; } /// @custom:governance - function setAuctionLength(uint48 newAuctionLength) public governance { + function setDutchAuctionLength(uint48 newAuctionLength) public governance { require( - newAuctionLength > 0 && newAuctionLength <= MAX_AUCTION_LENGTH, - "invalid auctionLength" + newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH, + "invalid dutchAuctionLength" ); - emit AuctionLengthSet(auctionLength, newAuctionLength); - auctionLength = newAuctionLength; + emit DutchAuctionLengthSet(dutchAuctionLength, newAuctionLength); + dutchAuctionLength = newAuctionLength; } /// @custom:governance @@ -158,10 +184,52 @@ contract BrokerP1 is ComponentP1, IBroker { disabled = disabled_; } + // === Private === + + function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { + require(batchAuctionLength > 0, "batchAuctionLength unset"); + GnosisTrade trade = GnosisTrade(address(batchTradeImplementation).clone()); + trades[address(trade)] = true; + + // Apply Gnosis EasyAuction-specific resizing of req, if needed: Ensure that + // max(sellAmount, minBuyAmount) <= maxTokensAllowed, while maintaining their proportion + uint256 maxQty = (req.minBuyAmount > req.sellAmount) ? req.minBuyAmount : req.sellAmount; + + if (maxQty > GNOSIS_MAX_TOKENS) { + req.sellAmount = mulDiv256(req.sellAmount, GNOSIS_MAX_TOKENS, maxQty, CEIL); + req.minBuyAmount = mulDiv256(req.minBuyAmount, GNOSIS_MAX_TOKENS, maxQty, FLOOR); + } + + // == Interactions == + IERC20Upgradeable(address(req.sell.erc20())).safeTransferFrom( + caller, + address(trade), + req.sellAmount + ); + trade.init(this, caller, gnosis, batchAuctionLength, req); + return trade; + } + + function newDutchAuction(TradeRequest memory req, ITrading caller) private returns (ITrade) { + require(dutchAuctionLength > 0, "dutchAuctionLength unset"); + DutchTrade trade = DutchTrade(address(dutchTradeImplementation).clone()); + trades[address(trade)] = true; + + // == Interactions == + IERC20Upgradeable(address(req.sell.erc20())).safeTransferFrom( + address(caller), + address(trade), + req.sellAmount + ); + + trade.init(caller, req.sell, req.buy, req.sellAmount, dutchAuctionLength); + return trade; + } + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[44] private __gap; + uint256[43] private __gap; } diff --git a/contracts/p1/Deployer.sol b/contracts/p1/Deployer.sol index 29cf01ee8b..41472dd020 100644 --- a/contracts/p1/Deployer.sol +++ b/contracts/p1/Deployer.sol @@ -50,7 +50,8 @@ contract DeployerP1 is IDeployer, Versioned { address(gnosis_) != address(0) && address(rsrAsset_) != address(0) && address(implementations_.main) != address(0) && - address(implementations_.trade) != address(0) && + address(implementations_.trading.gnosisTrade) != address(0) && + address(implementations_.trading.dutchTrade) != address(0) && address(implementations_.components.assetRegistry) != address(0) && address(implementations_.components.backingManager) != address(0) && address(implementations_.components.basketHandler) != address(0) && @@ -199,7 +200,14 @@ contract DeployerP1 is IDeployer, Versioned { // Init Furnace components.furnace.init(main, params.rewardRatio); - components.broker.init(main, gnosis, implementations.trade, params.auctionLength); + components.broker.init( + main, + gnosis, + implementations.trading.gnosisTrade, + params.batchAuctionLength, + implementations.trading.dutchTrade, + params.dutchAuctionLength + ); // Init StRSR { diff --git a/contracts/p1/Furnace.sol b/contracts/p1/Furnace.sol index 14edfacbd1..1596942cf5 100644 --- a/contracts/p1/Furnace.sol +++ b/contracts/p1/Furnace.sol @@ -13,7 +13,7 @@ contract FurnaceP1 is ComponentP1, IFurnace { using FixLib for uint192; uint192 public constant MAX_RATIO = FIX_ONE; // {1} 100% - uint48 public constant PERIOD = 12; // {s} 12 seconds; 1 block on PoS Ethereum + uint48 public constant PERIOD = ONE_BLOCK; // {s} 12 seconds; 1 block on PoS Ethereum IRToken private rToken; diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 28c09281a7..a7af82f26b 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.17; -import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../interfaces/IMain.sol"; import "../interfaces/IAssetRegistry.sol"; @@ -13,12 +13,15 @@ import "./mixins/TradeLib.sol"; /// @custom:oz-upgrades-unsafe-allow external-library-linking contract RevenueTraderP1 is TradingP1, IRevenueTrader { using FixLib for uint192; - using SafeERC20Upgradeable for IERC20Upgradeable; + using SafeERC20 for IERC20; // Immutable after init() IERC20 public tokenToBuy; IAssetRegistry private assetRegistry; IDistributor private distributor; + IBackingManager private backingManager; + IFurnace private furnace; + IRToken private rToken; function init( IMain main_, @@ -29,14 +32,34 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { require(address(tokenToBuy_) != address(0), "invalid token address"); __Component_init(main_); __Trading_init(main_, maxTradeSlippage_, minTradeVolume_); - assetRegistry = main_.assetRegistry(); - distributor = main_.distributor(); tokenToBuy = tokenToBuy_; + cacheComponents(); + } + + /// Settle a single trade + distribute revenue + /// @param sell The sell token in the trade + /// @return trade The ITrade contract settled + /// @custom:interaction + function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { + trade = super.settleTrade(sell); // modifier: notTradingPausedOrFrozen + distributeTokenToBuy(); + // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely + } + + /// Distribute tokenToBuy to its destinations + /// @dev Special-case of manageToken(tokenToBuy, *) + /// @custom:interaction + function distributeTokenToBuy() public { + uint256 bal = tokenToBuy.balanceOf(address(this)); + tokenToBuy.safeApprove(address(distributor), 0); + tokenToBuy.safeApprove(address(distributor), bal); + distributor.distribute(tokenToBuy, bal); } /// If erc20 is tokenToBuy, distribute it; else, sell it for tokenToBuy /// @dev Intended to be used with multicall - /// @custom:interaction CEI + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION + /// @custom:interaction RCEI // let bal = this contract's balance of erc20 // checks: !paused (trading), !frozen // does nothing if erc20 == addr(0) or bal == 0 @@ -48,24 +71,41 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { // // If erc20 is any other registered asset (checked): // actions: - // tryTrade(prepareTradeSell(toAsset(erc20), toAsset(tokenToBuy), bal)) + // tryTrade(kind, prepareTradeSell(toAsset(erc20), toAsset(tokenToBuy), bal)) // (i.e, start a trade, selling as much of our bal of erc20 as we can, to buy tokenToBuy) - function manageToken(IERC20 erc20) external notTradingPausedOrFrozen { - if (address(trades[erc20]) != address(0)) return; - - uint256 bal = erc20.balanceOf(address(this)); - if (bal == 0) return; - + function manageToken(IERC20 erc20, TradeKind kind) external notTradingPausedOrFrozen { if (erc20 == tokenToBuy) { - // == Interactions then return == - IERC20Upgradeable(address(erc20)).safeApprove(address(distributor), 0); - IERC20Upgradeable(address(erc20)).safeApprove(address(distributor), bal); - distributor.distribute(erc20, bal); + distributeTokenToBuy(); return; } - IAsset sell = assetRegistry.toAsset(erc20); - IAsset buy = assetRegistry.toAsset(tokenToBuy); + IAsset sell; + IAsset buy; + + // == Refresh == + // do not need to refresh when caller is BackingManager.forwardRevenue() + if (_msgSender() != address(backingManager)) { + if (erc20 == IERC20(address(rToken)) || tokenToBuy == IERC20(address(rToken))) { + // if either token is the RToken, refresh everything + assetRegistry.refresh(); + furnace.melt(); + + sell = assetRegistry.toAsset(erc20); + buy = assetRegistry.toAsset(tokenToBuy); + } else { + // otherwise, refresh just buy + sell + sell = assetRegistry.toAsset(erc20); + buy = assetRegistry.toAsset(tokenToBuy); + sell.refresh(); + buy.refresh(); + } + } + + // == Checks/Effects == + + require(address(trades[erc20]) == address(0), "trade open"); + require(erc20.balanceOf(address(this)) > 0, "0 balance"); + (uint192 sellPrice, ) = sell.price(); // {UoA/tok} (, uint192 buyPrice) = buy.price(); // {UoA/tok} @@ -87,10 +127,19 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { minTradeVolume, maxTradeSlippage ); + require(launch, "trade not worth launching"); - if (launch) { - tryTrade(req); - } + // == Interactions == + tryTrade(kind, req); + } + + /// Call after upgrade to >= 3.0.0 + function cacheComponents() public { + assetRegistry = main.assetRegistry(); + distributor = main.distributor(); + backingManager = main.backingManager(); + furnace = main.furnace(); + rToken = main.rToken(); } /** @@ -98,5 +147,5 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[47] private __gap; + uint256[44] private __gap; } diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 7bc4bf67a2..b85e7ec9ce 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -34,7 +34,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab using CountersUpgradeable for CountersUpgradeable.Counter; using SafeERC20Upgradeable for IERC20Upgradeable; - uint48 public constant PERIOD = 12; // {s} 12 seconds; 1 block on PoS Ethereum + uint48 public constant PERIOD = ONE_BLOCK; // {s} 12 seconds; 1 block on PoS Ethereum uint48 public constant MIN_UNSTAKING_DELAY = PERIOD * 2; // {s} uint48 public constant MAX_UNSTAKING_DELAY = 31536000; // {s} 1 year uint192 public constant MAX_REWARD_RATIO = FIX_ONE; // {1} 100% diff --git a/contracts/p1/mixins/TradeLib.sol b/contracts/p1/mixins/TradeLib.sol index 5a634fbe97..fe225d6be4 100644 --- a/contracts/p1/mixins/TradeLib.sol +++ b/contracts/p1/mixins/TradeLib.sol @@ -59,7 +59,7 @@ library TradeLib { } // Cap sell amount - uint192 maxSell = maxTradeSize(trade.sell, lotHigh); // {sellTok} + uint192 maxSell = maxTradeSize(trade.sell, trade.buy, lotHigh); // {sellTok} uint192 s = trade.sellAmount > maxSell ? maxSell : trade.sellAmount; // {sellTok} // Calculate equivalent buyAmount within [0, FIX_MAX] @@ -185,12 +185,18 @@ library TradeLib { return size > 0 ? size : 1; } - /// Calculates the maxTradeSize for an asset based on the asset's maxTradeVolume and price - /// @return {tok} The max trade size for the asset in whole tokens - function maxTradeSize(IAsset asset, uint192 price) private view returns (uint192) { + /// Calculates the maximum trade size for a trade pair of tokens + /// @return {tok} The max trade size for the trade overall + function maxTradeSize( + IAsset sell, + IAsset buy, + uint192 price + ) private view returns (uint192) { // untestable: // Price cannot be 0, it would've been filtered before in `prepareTradeSell` - uint192 size = price == 0 ? FIX_MAX : asset.maxTradeVolume().div(price, FLOOR); + uint192 size = price == 0 + ? FIX_MAX + : fixMin(sell.maxTradeVolume(), buy.maxTradeVolume()).div(price, FLOOR); return size > 0 ? size : 1; } } diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 8a162be298..2461bd15dc 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -51,7 +51,24 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl setMinTradeVolume(minTradeVolume_); } + /// Claim all rewards + /// Collective Action + /// @custom:interaction CEI + function claimRewards() external notTradingPausedOrFrozen { + RewardableLibP1.claimRewards(main.assetRegistry()); + } + + /// Claim rewards for a single asset + /// Collective Action + /// @param erc20 The ERC20 to claimRewards on + /// @custom:interaction CEI + function claimRewardsSingle(IERC20 erc20) external notTradingPausedOrFrozen { + RewardableLibP1.claimRewardsSingle(main.assetRegistry().toAsset(erc20)); + } + /// Settle a single trade, expected to be used with multicall for efficient mass settlement + /// @param sell The sell token in the trade + /// @return trade The ITrade contract settled /// @custom:interaction (only reads or writes trades, and is marked `nonReentrant`) // checks: // !paused (trading), !frozen @@ -63,9 +80,15 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // tradesOpen' = tradesOpen - 1 // untested: // OZ nonReentrant line is assumed to be working. cost/benefit of direct testing is high - function settleTrade(IERC20 sell) external notTradingPausedOrFrozen nonReentrant { - ITrade trade = trades[sell]; - if (address(trade) == address(0)) return; + function settleTrade(IERC20 sell) + public + virtual + notTradingPausedOrFrozen + nonReentrant + returns (ITrade trade) + { + trade = trades[sell]; + require(address(trade) != address(0), "no trade open"); require(trade.canSettle(), "cannot settle yet"); delete trades[sell]; @@ -76,22 +99,9 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl emit TradeSettled(trade, trade.sell(), trade.buy(), soldAmt, boughtAmt); } - /// Claim all rewards - /// Collective Action - /// @custom:interaction CEI - function claimRewards() external notTradingPausedOrFrozen { - RewardableLibP1.claimRewards(main.assetRegistry()); - } - - /// Claim rewards for a single asset - /// Collective Action - /// @param erc20 The ERC20 to claimRewards on - /// @custom:interaction CEI - function claimRewardsSingle(IERC20 erc20) external notTradingPausedOrFrozen { - RewardableLibP1.claimRewardsSingle(main.assetRegistry().toAsset(erc20)); - } - /// Try to initiate a trade with a trading partner provided by the broker + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION + /// @return trade The trade contract created /// @custom:interaction (only reads or writes `trades`, and is marked `nonReentrant`) // checks: // (not external, so we don't need auth or pause checks) @@ -108,15 +118,19 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // This is reentrancy-safe because we're using the `nonReentrant` modifier on every method of // this contract that changes state this function refers to. // slither-disable-next-line reentrancy-vulnerabilities-1 - function tryTrade(TradeRequest memory req) internal nonReentrant { + function tryTrade(TradeKind kind, TradeRequest memory req) + internal + nonReentrant + returns (ITrade trade) + { /* */ IERC20 sell = req.sell.erc20(); assert(address(trades[sell]) == address(0)); IERC20Upgradeable(address(sell)).safeApprove(address(broker), 0); IERC20Upgradeable(address(sell)).safeApprove(address(broker), req.sellAmount); - ITrade trade = broker.openTrade(req); + trade = broker.openTrade(kind, req); trades[sell] = trade; tradesOpen++; emit TradeStarted(trade, sell, req.buy.erc20(), req.sellAmount, req.minBuyAmount); diff --git a/contracts/plugins/mocks/CTokenVaultMock2.sol b/contracts/plugins/mocks/CTokenVaultMock2.sol index 8704c674dc..d303f1440d 100644 --- a/contracts/plugins/mocks/CTokenVaultMock2.sol +++ b/contracts/plugins/mocks/CTokenVaultMock2.sol @@ -11,6 +11,8 @@ contract CTokenVaultMock is ERC20Mock, IRewardable { CTokenMock public asset; IComptroller public comptroller; + bool public revertClaimRewards; + constructor( string memory _name, string memory _symbol, @@ -40,6 +42,9 @@ contract CTokenVaultMock is ERC20Mock, IRewardable { } function claimRewards() external { + if (revertClaimRewards) { + revert("reverting claim rewards"); + } uint256 oldBal = comp.balanceOf(msg.sender); comptroller.claimComp(msg.sender); emit RewardsClaimed(IERC20(address(comp)), comp.balanceOf(msg.sender) - oldBal); @@ -48,4 +53,8 @@ contract CTokenVaultMock is ERC20Mock, IRewardable { function setExchangeRate(uint192 fiatcoinRedemptionRate) external { asset.setExchangeRate(fiatcoinRedemptionRate); } -} \ No newline at end of file + + function setRevertClaimRewards(bool newVal) external { + revertClaimRewards = newVal; + } +} diff --git a/contracts/plugins/mocks/InvalidBrokerMock.sol b/contracts/plugins/mocks/InvalidBrokerMock.sol index c0ccb988b0..7bbe3b7284 100644 --- a/contracts/plugins/mocks/InvalidBrokerMock.sol +++ b/contracts/plugins/mocks/InvalidBrokerMock.sol @@ -19,7 +19,8 @@ contract InvalidBrokerMock is ComponentP0, IBroker { mapping(address => bool) private trades; - uint48 public auctionLength; // {s} the length of an auction + uint48 public batchAuctionLength; // {s} the length of a batch auction + uint48 public dutchAuctionLength; // {s} the length of a dutch auction bool public disabled = false; @@ -27,15 +28,18 @@ contract InvalidBrokerMock is ComponentP0, IBroker { IMain main_, IGnosis gnosis_, ITrade, - uint48 auctionLength_ + uint48 batchAuctionLength_, + ITrade, + uint48 dutchAuctionLength_ ) public initializer { __Component_init(main_); gnosis = gnosis_; - auctionLength = auctionLength_; + batchAuctionLength = batchAuctionLength_; + dutchAuctionLength = dutchAuctionLength_; } /// Invalid implementation - Reverts - function openTrade(TradeRequest memory req) + function openTrade(TradeKind, TradeRequest memory req) external view notTradingPausedOrFrozen @@ -54,7 +58,11 @@ contract InvalidBrokerMock is ComponentP0, IBroker { /// Dummy implementation /* solhint-disable no-empty-blocks */ - function setAuctionLength(uint48 newAuctionLength) external governance {} + function setBatchAuctionLength(uint48 newAuctionLength) external governance {} + + /// Dummy implementation + /* solhint-disable no-empty-blocks */ + function setDutchAuctionLength(uint48 newAuctionLength) external governance {} /// Dummy implementation /* solhint-disable no-empty-blocks */ diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol new file mode 100644 index 0000000000..8f2a06f9e6 --- /dev/null +++ b/contracts/plugins/trading/DutchTrade.sol @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.17; + +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "../../libraries/Fixed.sol"; +import "../../interfaces/IAsset.sol"; +import "../../interfaces/ITrade.sol"; + +uint192 constant FIFTEEN_PERCENT = 15e16; // {1} +uint192 constant FIFTY_PERCENT = 50e16; // {1} +uint192 constant EIGHTY_FIVE_PERCENT = 85e16; // {1} + +/** + * @title DutchTrade + * @notice Implements a wholesale dutch auction via a piecewise falling-price mechansim. + * Over the first 15% of the auction the price falls from the ~150% pricepoint to the + * best price, as given by the price range. Over the last 85% of the auction it falls + * from the best price to the worst price. The worst price is additionally discounted by + * the maxTradeSlippage based on how far between minTradeVolume and maxTradeVolume the trade is. + * + * To bid: + * - Call `bidAmount()` to check price at various timestamps + * - Wait until desirable block is reached + * - Provide approval of buy tokens and call bid(). Swap will be atomic + */ +contract DutchTrade is ITrade { + using FixLib for uint192; + using SafeERC20 for IERC20Metadata; + + TradeKind public constant KIND = TradeKind.DUTCH_AUCTION; + + TradeStatus public status; // reentrancy protection + + ITrading public origin; // creator + + // === Auction === + IERC20Metadata public sell; + IERC20Metadata public buy; + uint192 public sellAmount; // {sellTok} + + uint48 public startTime; // {s} when the dutch auction began + uint48 public endTime; // {s} when the dutch auction ends, if no bids are received + + uint192 public middlePrice; // {buyTok/sellTok} The price at which the function is piecewise + uint192 public lowPrice; // {buyTok/sellTok} The price the auction ends at + // highPrice is always 1.5x the middlePrice + + // === Bid === + address public bidder; + // the bid amount is just whatever token balance is in the contract at settlement time + + // This modifier both enforces the state-machine pattern and guards against reentrancy. + modifier stateTransition(TradeStatus begin, TradeStatus end) { + require(status == begin, "Invalid trade state"); + status = TradeStatus.PENDING; + _; + assert(status == TradeStatus.PENDING); + status = end; + } + + /// @param origin_ The Trader that originated the trade + /// @param sell_ The asset being sold by the protocol + /// @param buy_ The asset being bought by the protocol + /// @param sellAmount_ {qSellTok} The amount to sell in the auction, in token quanta + /// @param auctionLength {s} How many seconds the dutch auction should run for + function init( + ITrading origin_, + IAsset sell_, + IAsset buy_, + uint256 sellAmount_, + uint48 auctionLength + ) external stateTransition(TradeStatus.NOT_STARTED, TradeStatus.OPEN) { + assert(address(sell_) != address(0) && address(buy_) != address(0)); // misuse of contract + + // Only start dutch auctions under well-defined prices + // + // In the BackingManager this may end up recalculating the RToken price + (uint192 sellLow, uint192 sellHigh) = sell_.price(); // {UoA/sellTok} + (uint192 buyLow, uint192 buyHigh) = buy_.price(); // {UoA/buyTok} + require(sellLow > 0 && sellHigh < FIX_MAX, "bad sell pricing"); + require(buyLow > 0 && buyHigh < FIX_MAX, "bad buy pricing"); + + origin = origin_; + sell = sell_.erc20(); + buy = buy_.erc20(); + + require(sellAmount_ <= sell.balanceOf(address(this)), "unfunded trade"); + sellAmount = shiftl_toFix(sellAmount_, -int8(sell.decimals())); // {sellTok} + startTime = uint48(block.timestamp); + endTime = uint48(block.timestamp) + auctionLength; + + // {UoA} + uint192 maxTradeVolume = fixMin(sell_.maxTradeVolume(), buy_.maxTradeVolume()); + uint192 minTradeVolume = origin.minTradeVolume(); + + // Apply sliding slippage from 0% at maxTradeVolume to maxTradeSlippage() at minTradeVolume + uint192 slippage = origin.maxTradeSlippage(); // {1} + if (minTradeVolume < maxTradeVolume) { + // {UoA} = {sellTok} * {UoA/sellTok} + uint192 auctionVolume = sellAmount.mul(sellHigh, FLOOR); + + if (auctionVolume > minTradeVolume && auctionVolume <= maxTradeVolume) { + // {1} = {1} * ({UoA} - {UoA}} / ({UoA} - {UoA}) + slippage = slippage.mul( + FIX_ONE - divuu(auctionVolume - minTradeVolume, maxTradeVolume - minTradeVolume) + ); + } else if (auctionVolume > maxTradeVolume) { + slippage = 0; + } + } + // {buyTok/sellTok} = {1} * {UoA/sellTok} / {UoA/buyTok} + lowPrice = sellLow.mulDiv(FIX_ONE - slippage, buyHigh, FLOOR); + middlePrice = sellHigh.div(buyLow, CEIL); // no additional slippage + // highPrice = 1.5 * middlePrice + + require(lowPrice <= middlePrice, "asset inverted pricing"); + } + + /// Bid for the auction lot at the current price; settling atomically via a callback + /// @dev Caller must have provided approval + /// @return amountIn {qBuyTok} The quantity of tokens the bidder paid + function bid() external returns (uint256 amountIn) { + require(bidder == address(0), "bid received"); + + // {qBuyTok} + amountIn = bidAmount(uint48(block.timestamp)); + + // Transfer in buy tokens + bidder = msg.sender; + buy.safeTransferFrom(bidder, address(this), amountIn); + + // TODO examine reentrancy - think it's probably ok + // the other candidate design is ditch the bid() function entirely and have them transfer + // tokens directly into this contract followed by origin.settleTrade(), but I don't like + // that pattern because it means humans cannot bid without a smart contract helper. + // also requires changing the function signature of settle() to accept the caller address + + // settle() via callback; may also start a new Trade + assert(status == TradeStatus.OPEN); + origin.settleTrade(sell); + assert(status == TradeStatus.CLOSED); + } + + /// Settle the auction, emptying the contract of balances + /// @return soldAmt {qSellTok} Token quantity sold by the protocol + /// @return boughtAmt {qBuyTok} Token quantity purchased by the protocol + function settle() + external + stateTransition(TradeStatus.OPEN, TradeStatus.CLOSED) + returns (uint256 soldAmt, uint256 boughtAmt) + { + require(msg.sender == address(origin), "only origin can settle"); // origin.settleTrade() + + // Received bid + if (bidder != address(0)) { + sell.safeTransfer(bidder, sellAmount); + } else { + require(block.timestamp >= endTime, "auction not over"); + } + + uint256 sellBal = sell.balanceOf(address(this)); + soldAmt = sellAmount > sellBal ? sellAmount - sellBal : 0; + boughtAmt = buy.balanceOf(address(this)); + + // Transfer balances back to origin + buy.safeTransfer(address(origin), boughtAmt); + sell.safeTransfer(address(origin), sellBal); + } + + /// Anyone can transfer any ERC20 back to the origin after the trade has been closed + /// @dev Escape hatch in case of accidentally transferred tokens after auction end + /// @custom:interaction CEI (and respects the state lock) + function transferToOriginAfterTradeComplete(IERC20Metadata erc20) external { + require(status == TradeStatus.CLOSED, "only after trade is closed"); + erc20.safeTransfer(address(origin), erc20.balanceOf(address(this))); + } + + /// @return True if the trade can be settled. + // Guaranteed to be true some time after init(), until settle() is called + function canSettle() external view returns (bool) { + return status == TradeStatus.OPEN && (bidder != address(0) || block.timestamp >= endTime); + } + + // === Bid Helper === + + /// Calculates how much buy token is needed to purchase the lot, at a particular timestamp + /// Price Curve: + /// - 1.5 * middlePrice down to the middlePrice for first 15% of auction + /// - middlePrice down to lowPrice for the last 85% of auction + /// @param timestamp {s} The block timestamp to get price for + /// @return {qBuyTok} The amount of buy tokens required to purchase the lot + function bidAmount(uint48 timestamp) public view returns (uint256) { + require(timestamp > startTime, "cannot bid block auction was created"); + require(timestamp < endTime, "auction over"); + + uint192 progression = divuu(timestamp - startTime, endTime - startTime); + // assert(progression <= FIX_ONE); + + // {buyTok/sellTok} + uint192 price; + + if (progression < FIFTEEN_PERCENT) { + // Fast decay -- 15th percentile case + + // highPrice is 1.5x middlePrice + uint192 highPrice = middlePrice + middlePrice.mul(FIFTY_PERCENT); + price = highPrice - (highPrice - middlePrice).mulDiv(progression, FIFTEEN_PERCENT); + } else { + // Slow decay -- 85th percentile case + price = + middlePrice - + (middlePrice - lowPrice).mulDiv(progression - FIFTEEN_PERCENT, EIGHTY_FIVE_PERCENT); + } + + // {qBuyTok} = {sellTok} * {buyTok/sellTok} + return sellAmount.mul(price, CEIL).shiftl_toUint(int8(buy.decimals()), CEIL); + } +} diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index c9258f500b..56bc5c5592 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -9,13 +9,6 @@ import "../../interfaces/IBroker.sol"; import "../../interfaces/IGnosis.sol"; import "../../interfaces/ITrade.sol"; -enum TradeStatus { - NOT_STARTED, // before init() - OPEN, // after init() and before settle() - CLOSED, // after settle() - PENDING // during init() or settle() (reentrancy protection) -} - // Modifications to this contract's state must only ever be made when status=PENDING! /// Trade contract against the Gnosis EasyAuction mechanism @@ -24,6 +17,7 @@ contract GnosisTrade is ITrade { using SafeERC20Upgradeable for IERC20Upgradeable; // ==== Constants + TradeKind public constant KIND = TradeKind.BATCH_AUCTION; uint256 public constant FEE_DENOMINATOR = 1000; // Upper bound for the max number of orders we're happy to have the auction clear in; @@ -82,7 +76,7 @@ contract GnosisTrade is ITrade { IBroker broker_, address origin_, IGnosis gnosis_, - uint48 auctionLength, + uint48 batchAuctionLength, TradeRequest calldata req ) external stateTransition(TradeStatus.NOT_STARTED, TradeStatus.OPEN) { require(req.sellAmount <= type(uint96).max, "sellAmount too large"); @@ -100,7 +94,7 @@ contract GnosisTrade is ITrade { broker = broker_; origin = origin_; gnosis = gnosis_; - endTime = uint48(block.timestamp) + auctionLength; + endTime = uint48(block.timestamp) + batchAuctionLength; // {buyTok/sellTok} worstCasePrice = shiftl_toFix(req.minBuyAmount, -int8(buy.decimals())).div( diff --git a/docs/system-design.md b/docs/system-design.md index 33d6581233..ea7e42927b 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -158,7 +158,7 @@ The minimum sized trade that can be performed, in terms of the unit of account. Setting this too high will result in auctions happening infrequently or the RToken taking a haircut when it cannot be sure it has enough staked RSR to succeed in rebalancing at par. -Setting this too low may allow griefers to delay important auctions. The variable should be set such that donations of size `minTradeVolume` would be worth delaying trading `auctionLength` seconds. +Setting this too low may allow griefers to delay important auctions. The variable should be set such that donations of size `minTradeVolume` would be worth delaying trading `batchAuctionLength` seconds. This variable should NOT be interpreted to mean that auction sizes above this value will necessarily clear. It could be the case that gas frictions are so high that auctions launched at this size are not worthy of bids. @@ -217,7 +217,7 @@ The warmup period is how many seconds should pass after the basket regained the Default value: `900` = 15 minutes Mainnet reasonable range: 0 to 604800 -### `auctionLength` +### `batchAuctionLength` Dimension: `{seconds}` @@ -226,11 +226,20 @@ The auction length is how many seconds long Gnosis EasyAuctions should be. Default value: `900` = 15 minutes Mainnet reasonable range: 60 to 3600 +### `dutchAuctionLength` + +Dimension: `{seconds}` + +The dutch auction length is how many seconds long falling-price dutch auctions should be. A longer period will result in less slippage due to better price granularity. + +Default value: `600` = 10 minutes +Mainnet reasonable range: 120 to 3600 + ### `backingBuffer` Dimension: `{1}` -The backing buffer is a percentage value that describes how much additional collateral tokens to keep in the BackingManager before forwarding tokens to the RevenueTraders. This buffer allows collateral tokens to be periodically converted into the RToken, which is a more efficient form of revenue production than trading each individual collateral for the desired RToken. It also adds a small buffer that can prevent RSR from being seized when there are small losses due to slippage during rebalancing. +The backing buffer is a percentage value that describes how much overcollateralization to hold in the form of RToken. This buffer allows collateral tokens to be converted into RToken, which is a more efficient form of revenue production than trading each individual collateral for the desired RToken, and also adds a small buffer that can prevent RSR from being seized when there are small losses due to slippage during rebalancing. Default value: `1e15` = 0.1% Mainnet reasonable range: 1e12 to 1e18 diff --git a/hardhat.config.ts b/hardhat.config.ts index f463f81e1c..b4e964b673 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -7,6 +7,7 @@ import '@openzeppelin/hardhat-upgrades' import '@typechain/hardhat' import 'hardhat-contract-sizer' import 'hardhat-gas-reporter' +import 'hardhat-storage-layout' import 'solidity-coverage' import { useEnv } from '#/utils/env' diff --git a/package.json b/package.json index e4402c9013..2e4d6c546c 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "hardhat": "^2.12.2", "hardhat-contract-sizer": "^2.4.0", "hardhat-gas-reporter": "^1.0.8", + "hardhat-storage-layout": "^0.1.7", "husky": "^7.0.0", "lodash": "^4.17.21", "lodash.get": "^4.4.2", diff --git a/scripts/deployment/phase1-common/0_setup_deployments.ts b/scripts/deployment/phase1-common/0_setup_deployments.ts index 7203616726..603da02a07 100644 --- a/scripts/deployment/phase1-common/0_setup_deployments.ts +++ b/scripts/deployment/phase1-common/0_setup_deployments.ts @@ -54,15 +54,20 @@ async function main() { GNOSIS_EASY_AUCTION: gnosisAddr, }, tradingLib: '', + cvxMiningLib: '', facadeRead: '', facadeAct: '', facadeWriteLib: '', facadeWrite: '', + facadeMonitor: '', deployer: '', rsrAsset: '', implementations: { main: '', - trade: '', + trading: { + gnosisTrade: '', + dutchTrade: '', + }, components: { assetRegistry: '', backingManager: '', diff --git a/scripts/deployment/phase1-common/2_deploy_implementations.ts b/scripts/deployment/phase1-common/2_deploy_implementations.ts index ec1442314c..311ac237a3 100644 --- a/scripts/deployment/phase1-common/2_deploy_implementations.ts +++ b/scripts/deployment/phase1-common/2_deploy_implementations.ts @@ -10,6 +10,7 @@ import { BasketHandlerP1, BrokerP1, DistributorP1, + DutchTrade, FurnaceP1, GnosisTrade, MainP1, @@ -28,7 +29,8 @@ let mainImpl: MainP1 let revTraderImpl: RevenueTraderP1 let rTokenImpl: RTokenP1 let stRSRImpl: StRSRP1Votes -let tradeImpl: GnosisTrade +let gnosisTradeImpl: GnosisTrade +let dutchTradeImpl: DutchTrade const writeComponentDeployment = ( deployments: IDeployments, @@ -80,17 +82,29 @@ async function main() { console.log(`Deployed to ${hre.network.name} (${chainId}): Main Implementation: ${mainImpl.address}`) - // ******************** Deploy Trade ********************************/ + // ******************** Deploy GnosisTrade ********************************/ - const TradeImplFactory = await ethers.getContractFactory('GnosisTrade') - tradeImpl = await TradeImplFactory.connect(burner).deploy() - await tradeImpl.deployed() + const GnosisTradeImplFactory = await ethers.getContractFactory('GnosisTrade') + gnosisTradeImpl = await GnosisTradeImplFactory.connect(burner).deploy() + await gnosisTradeImpl.deployed() // Write temporary deployments file - deployments.implementations.trade = tradeImpl.address + deployments.implementations.trading.gnosisTrade = gnosisTradeImpl.address fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) - console.log(` Trade Implementation: ${tradeImpl.address}`) + console.log(`GnosisTrade Implementation: ${gnosisTradeImpl.address}`) + + // ******************** Deploy DutchTrade ********************************/ + + const DutchTradeImplFactory = await ethers.getContractFactory('DutchTrade') + dutchTradeImpl = await DutchTradeImplFactory.connect(burner).deploy() + await dutchTradeImpl.deployed() + + // Write temporary deployments file + deployments.implementations.trading.dutchTrade = dutchTradeImpl.address + fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) + + console.log(`DutchTrade Implementation: ${dutchTradeImpl.address}`) // ******************** Deploy Components ********************************/ diff --git a/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts b/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts index 5a80bd25a9..e67a33f602 100644 --- a/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts +++ b/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts @@ -6,12 +6,7 @@ import { networkConfig } from '../../../common/configuration' import { ZERO_ADDRESS } from '../../../common/constants' import { fp } from '../../../common/numbers' import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../../deployment/common' -import { - priceTimeout, - longOracleTimeout, - oracleTimeout, - validateImplementations, -} from '../../deployment/utils' +import { priceTimeout, oracleTimeout, validateImplementations } from '../../deployment/utils' import { Asset } from '../../../typechain' let rsrAsset: Asset diff --git a/scripts/deployment/phase3-rtoken/rTokenConfig.ts b/scripts/deployment/phase3-rtoken/rTokenConfig.ts index 4fb1b47e8c..c9f6967537 100644 --- a/scripts/deployment/phase3-rtoken/rTokenConfig.ts +++ b/scripts/deployment/phase3-rtoken/rTokenConfig.ts @@ -20,8 +20,10 @@ export const rTokenConfig: { [key: string]: IRToken } = { }, rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks + warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('14400'), // (the delay _after_ default has been confirmed) 4 hours - auctionLength: bn('900'), // 15 minutes + batchAuctionLength: bn('900'), // 15 minutes + dutchAuctionLength: bn('600'), // 10 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days @@ -57,8 +59,10 @@ export const rTokenConfig: { [key: string]: IRToken } = { }, rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks + warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('14400'), // (the delay _after_ default has been confirmed) 4 hours - auctionLength: bn('900'), // 15 minutes + batchAuctionLength: bn('900'), // 15 minutes + dutchAuctionLength: bn('600'), // 10 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days @@ -93,8 +97,10 @@ export const rTokenConfig: { [key: string]: IRToken } = { }, rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks + warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) - auctionLength: bn('900'), // 15 minutes + batchAuctionLength: bn('900'), // 15 minutes + dutchAuctionLength: bn('600'), // 10 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days diff --git a/scripts/deployment/utils.ts b/scripts/deployment/utils.ts index 140b5534a1..9679c0b15d 100644 --- a/scripts/deployment/utils.ts +++ b/scripts/deployment/utils.ts @@ -56,7 +56,8 @@ export const validateImplementations = async (deployments: IDeployments) => { // Check implementations if ( !deployments.implementations.main || - !deployments.implementations.trade || + !deployments.implementations.trading.gnosisTrade || + !deployments.implementations.trading.dutchTrade || !deployments.implementations.components.assetRegistry || !deployments.implementations.components.backingManager || !deployments.implementations.components.basketHandler || @@ -71,8 +72,10 @@ export const validateImplementations = async (deployments: IDeployments) => { throw new Error(`Missing deployed implementations in network ${hre.network.name}`) } else if (!(await isValidContract(hre, deployments.implementations.main))) { throw new Error(`Main implementation not found in network ${hre.network.name}`) - } else if (!(await isValidContract(hre, deployments.implementations.trade))) { - throw new Error(`Trade implementation not found in network ${hre.network.name}`) + } else if (!(await isValidContract(hre, deployments.implementations.trading.gnosisTrade))) { + throw new Error(`GnosisTrade implementation not found in network ${hre.network.name}`) + } else if (!(await isValidContract(hre, deployments.implementations.trading.dutchTrade))) { + throw new Error(`DutchTrade implementation not found in network ${hre.network.name}`) } else if (!(await validComponents(deployments.implementations.components))) { throw new Error(`Component implementation(s) not found in network ${hre.network.name}`) } diff --git a/scripts/verification/1_verify_implementations.ts b/scripts/verification/1_verify_implementations.ts index f79e8a8a6f..1e5094c1b1 100644 --- a/scripts/verification/1_verify_implementations.ts +++ b/scripts/verification/1_verify_implementations.ts @@ -28,14 +28,22 @@ async function main() { 'contracts/p1/Main.sol:MainP1' ) - // /** ******************** Verify Trade implementation ****************************************/ + // /** ******************** Verify GnosisTrade implementation ****************************************/ await verifyContract( chainId, - deployments.implementations.trade, + deployments.implementations.trading.gnosisTrade, [], 'contracts/plugins/trading/GnosisTrade.sol:GnosisTrade' ) + // /** ******************** Verify DutchTrade implementation ****************************************/ + await verifyContract( + chainId, + deployments.implementations.trading.dutchTrade, + [], + 'contracts/plugins/trading/DutchTrade.sol:DutchTrade' + ) + /** ******************** Verify Components ****************************************/ // Define interface required for each component interface ComponentInfo { diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 34edb5af00..3e69b65e02 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -5,9 +5,10 @@ import { expect } from 'chai' import { BigNumber, ContractFactory } from 'ethers' import { ethers, upgrades } from 'hardhat' import { IConfig, MAX_AUCTION_LENGTH } from '../common/configuration' -import { MAX_UINT96, TradeStatus, ZERO_ADDRESS } from '../common/constants' -import { bn, toBNDecimals } from '../common/numbers' +import { MAX_UINT96, TradeKind, TradeStatus, ZERO_ADDRESS, ONE_ADDRESS } from '../common/constants' +import { bn, fp, divCeil, toBNDecimals } from '../common/numbers' import { + DutchTrade, ERC20Mock, GnosisMock, GnosisMockReentrant, @@ -94,7 +95,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { describe('Deployment', () => { it('Should setup Broker correctly', async () => { expect(await broker.gnosis()).to.equal(gnosis.address) - expect(await broker.auctionLength()).to.equal(config.auctionLength) + expect(await broker.batchAuctionLength()).to.equal(config.batchAuctionLength) expect(await broker.disabled()).to.equal(false) expect(await broker.main()).to.equal(main.address) }) @@ -114,11 +115,28 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { } await expect( - newBroker.init(main.address, ZERO_ADDRESS, ZERO_ADDRESS, bn('100')) + newBroker.init(main.address, ZERO_ADDRESS, ZERO_ADDRESS, bn('100'), ZERO_ADDRESS, bn('100')) ).to.be.revertedWith('invalid Gnosis address') await expect( - newBroker.init(main.address, gnosis.address, ZERO_ADDRESS, bn('100')) - ).to.be.revertedWith('invalid Trade Implementation address') + newBroker.init( + main.address, + gnosis.address, + ZERO_ADDRESS, + bn('100'), + ZERO_ADDRESS, + bn('100') + ) + ).to.be.revertedWith('invalid batchTradeImplementation address') + await expect( + newBroker.init( + main.address, + gnosis.address, + ONE_ADDRESS, + bn('100'), + ZERO_ADDRESS, + bn('100') + ) + ).to.be.revertedWith('invalid dutchTradeImplementation address') }) }) @@ -149,77 +167,151 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await broker.gnosis()).to.equal(mock.address) }) - it('Should allow to update Trade Implementation if Owner and perform validations', async () => { + it('Should allow to update BatchTrade Implementation if Owner and perform validations', async () => { // Create a Trade const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') const tradeImpl: GnosisTrade = await TradeFactory.deploy() // Update to a trade implementation to use as baseline for tests - await expect(broker.connect(owner).setTradeImplementation(tradeImpl.address)) - .to.emit(broker, 'TradeImplementationSet') + await expect(broker.connect(owner).setBatchTradeImplementation(tradeImpl.address)) + .to.emit(broker, 'BatchTradeImplementationSet') .withArgs(anyValue, tradeImpl.address) // Check existing value - expect(await broker.tradeImplementation()).to.equal(tradeImpl.address) + expect(await broker.batchTradeImplementation()).to.equal(tradeImpl.address) // If not owner cannot update - await expect(broker.connect(other).setTradeImplementation(mock.address)).to.be.revertedWith( - 'governance only' - ) + await expect( + broker.connect(other).setBatchTradeImplementation(mock.address) + ).to.be.revertedWith('governance only') // Check value did not change - expect(await broker.tradeImplementation()).to.equal(tradeImpl.address) + expect(await broker.batchTradeImplementation()).to.equal(tradeImpl.address) // Attempt to update with Owner but zero address - not allowed - await expect(broker.connect(owner).setTradeImplementation(ZERO_ADDRESS)).to.be.revertedWith( - 'invalid Trade Implementation address' - ) + await expect( + broker.connect(owner).setBatchTradeImplementation(ZERO_ADDRESS) + ).to.be.revertedWith('invalid batchTradeImplementation address') // Update with owner - await expect(broker.connect(owner).setTradeImplementation(mock.address)) - .to.emit(broker, 'TradeImplementationSet') + await expect(broker.connect(owner).setBatchTradeImplementation(mock.address)) + .to.emit(broker, 'BatchTradeImplementationSet') .withArgs(tradeImpl.address, mock.address) // Check value was updated - expect(await broker.tradeImplementation()).to.equal(mock.address) + expect(await broker.batchTradeImplementation()).to.equal(mock.address) + }) + + it('Should allow to update DutchTrade Implementation if Owner and perform validations', async () => { + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + const tradeImpl: DutchTrade = await TradeFactory.deploy() + + // Update to a trade implementation to use as baseline for tests + await expect(broker.connect(owner).setDutchTradeImplementation(tradeImpl.address)) + .to.emit(broker, 'DutchTradeImplementationSet') + .withArgs(anyValue, tradeImpl.address) + + // Check existing value + expect(await broker.dutchTradeImplementation()).to.equal(tradeImpl.address) + + // If not owner cannot update + await expect( + broker.connect(other).setDutchTradeImplementation(mock.address) + ).to.be.revertedWith('governance only') + + // Check value did not change + expect(await broker.dutchTradeImplementation()).to.equal(tradeImpl.address) + + // Attempt to update with Owner but zero address - not allowed + await expect( + broker.connect(owner).setDutchTradeImplementation(ZERO_ADDRESS) + ).to.be.revertedWith('invalid dutchTradeImplementation address') + + // Update with owner + await expect(broker.connect(owner).setDutchTradeImplementation(mock.address)) + .to.emit(broker, 'DutchTradeImplementationSet') + .withArgs(tradeImpl.address, mock.address) + + // Check value was updated + expect(await broker.dutchTradeImplementation()).to.equal(mock.address) + }) + + it('Should allow to update batchAuctionLength if Owner', async () => { + const newValue: BigNumber = bn('360') + + // Check existing value + expect(await broker.batchAuctionLength()).to.equal(config.batchAuctionLength) + + // If not owner cannot update + await expect(broker.connect(other).setBatchAuctionLength(newValue)).to.be.revertedWith( + 'governance only' + ) + + // Check value did not change + expect(await broker.batchAuctionLength()).to.equal(config.batchAuctionLength) + + // Update with owner + await expect(broker.connect(owner).setBatchAuctionLength(newValue)) + .to.emit(broker, 'BatchAuctionLengthSet') + .withArgs(config.batchAuctionLength, newValue) + + // Check value was updated + expect(await broker.batchAuctionLength()).to.equal(newValue) }) - it('Should allow to update auctionLength if Owner', async () => { + it('Should allow to update dutchAuctionLength if Owner', async () => { const newValue: BigNumber = bn('360') // Check existing value - expect(await broker.auctionLength()).to.equal(config.auctionLength) + expect(await broker.dutchAuctionLength()).to.equal(config.dutchAuctionLength) // If not owner cannot update - await expect(broker.connect(other).setAuctionLength(newValue)).to.be.revertedWith( + await expect(broker.connect(other).setDutchAuctionLength(newValue)).to.be.revertedWith( 'governance only' ) // Check value did not change - expect(await broker.auctionLength()).to.equal(config.auctionLength) + expect(await broker.dutchAuctionLength()).to.equal(config.dutchAuctionLength) // Update with owner - await expect(broker.connect(owner).setAuctionLength(newValue)) - .to.emit(broker, 'AuctionLengthSet') - .withArgs(config.auctionLength, newValue) + await expect(broker.connect(owner).setDutchAuctionLength(newValue)) + .to.emit(broker, 'DutchAuctionLengthSet') + .withArgs(config.dutchAuctionLength, newValue) // Check value was updated - expect(await broker.auctionLength()).to.equal(newValue) + expect(await broker.dutchAuctionLength()).to.equal(newValue) + }) + + it('Should perform validations on batchAuctionLength', async () => { + let invalidValue: BigNumber = bn(0) + + // Attempt to update + await expect(broker.connect(owner).setBatchAuctionLength(invalidValue)).to.be.revertedWith( + 'invalid batchAuctionLength' + ) + + invalidValue = bn(MAX_AUCTION_LENGTH + 1) + + // Attempt to update + await expect(broker.connect(owner).setBatchAuctionLength(invalidValue)).to.be.revertedWith( + 'invalid batchAuctionLength' + ) }) - it('Should perform validations on auctionLength', async () => { + it('Should perform validations on dutchAuctionLength', async () => { let invalidValue: BigNumber = bn(0) // Attempt to update - await expect(broker.connect(owner).setAuctionLength(invalidValue)).to.be.revertedWith( - 'invalid auctionLength' + await expect(broker.connect(owner).setDutchAuctionLength(invalidValue)).to.be.revertedWith( + 'invalid dutchAuctionLength' ) invalidValue = bn(MAX_AUCTION_LENGTH + 1) // Attempt to update - await expect(broker.connect(owner).setAuctionLength(invalidValue)).to.be.revertedWith( - 'invalid auctionLength' + await expect(broker.connect(owner).setDutchAuctionLength(invalidValue)).to.be.revertedWith( + 'invalid dutchAuctionLength' ) }) @@ -267,13 +359,16 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { } await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(broker.connect(bmSigner).openTrade(tradeRequest)).to.be.revertedWith( - 'broker disabled' - ) + await expect( + broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ).to.be.revertedWith('broker disabled') + await expect( + broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + ).to.be.revertedWith('broker disabled') }) }) - it('Should not allow to open trade if paused', async () => { + it('Should not allow to open trade if trading paused', async () => { await main.connect(owner).pauseTrading() // Attempt to open trade @@ -285,9 +380,12 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { } await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(broker.connect(bmSigner).openTrade(tradeRequest)).to.be.revertedWith( - 'frozen or trading paused' - ) + await expect( + broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ).to.be.revertedWith('frozen or trading paused') + await expect( + broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + ).to.be.revertedWith('frozen or trading paused') }) }) @@ -303,13 +401,16 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { } await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(broker.connect(bmSigner).openTrade(tradeRequest)).to.be.revertedWith( - 'frozen or trading paused' - ) + await expect( + broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ).to.be.revertedWith('frozen or trading paused') + await expect( + broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + ).to.be.revertedWith('frozen or trading paused') }) }) - it('Should not allow to open trade if a trader', async () => { + it('Should only allow to open trade if a trader', async () => { const amount: BigNumber = bn('100e18') const tradeRequest: ITradeRequest = { @@ -327,25 +428,30 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // Attempt to open trade from non-trader await token0.connect(addr1).approve(broker.address, amount) - await expect(broker.connect(addr1).openTrade(tradeRequest)).to.be.revertedWith('only traders') + await expect( + broker.connect(addr1).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ).to.be.revertedWith('only traders') // Open from traders - Should work // Backing Manager await whileImpersonating(backingManager.address, async (bmSigner) => { await token0.connect(bmSigner).approve(broker.address, amount) - await expect(broker.connect(bmSigner).openTrade(tradeRequest)).to.not.be.reverted + await expect(broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest)).to + .not.be.reverted }) // RSR Trader await whileImpersonating(rsrTrader.address, async (rsrSigner) => { await token0.connect(rsrSigner).approve(broker.address, amount) - await expect(broker.connect(rsrSigner).openTrade(tradeRequest)).to.not.be.reverted + await expect(broker.connect(rsrSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest)).to + .not.be.reverted }) // RToken Trader await whileImpersonating(rTokenTrader.address, async (rtokSigner) => { await token0.connect(rtokSigner).approve(broker.address, amount) - await expect(broker.connect(rtokSigner).openTrade(tradeRequest)).to.not.be.reverted + await expect(broker.connect(rtokSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest)).to + .not.be.reverted }) }) @@ -393,588 +499,786 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) describe('Trades', () => { - it('Should initialize trade correctly - only once', async () => { - const amount: BigNumber = bn('100e18') - - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() - - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } - - // Fund trade and initialize - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.auctionLength, - tradeRequest + context('GnosisTrade', () => { + const amount = bn('100e18') + let trade: GnosisTrade + + beforeEach(async () => { + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + trade = await TradeFactory.deploy() + + // Check state + expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + expect(await trade.canSettle()).to.equal(false) + }) + it('Should initialize GnosisTrade correctly - only once', async () => { + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } + + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.not.be.reverted + + // Check trade values + expect(await trade.KIND()).to.equal(TradeKind.BATCH_AUCTION) + expect(await trade.gnosis()).to.equal(gnosis.address) + expect(await trade.auctionId()).to.equal(0) + expect(await trade.status()).to.equal(TradeStatus.OPEN) + expect(await trade.broker()).to.equal(broker.address) + expect(await trade.origin()).to.equal(backingManager.address) + expect(await trade.sell()).to.equal(token0.address) + expect(await trade.buy()).to.equal(token1.address) + expect(await trade.initBal()).to.equal(amount) + expect(await trade.endTime()).to.equal( + (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength) ) - ).to.not.be.reverted - - // Check trade values - expect(await trade.gnosis()).to.equal(gnosis.address) - expect(await trade.auctionId()).to.equal(0) - expect(await trade.status()).to.equal(TradeStatus.OPEN) - expect(await trade.broker()).to.equal(broker.address) - expect(await trade.origin()).to.equal(backingManager.address) - expect(await trade.sell()).to.equal(token0.address) - expect(await trade.buy()).to.equal(token1.address) - expect(await trade.initBal()).to.equal(amount) - expect(await trade.endTime()).to.equal( - (await getLatestBlockTimestamp()) + Number(config.auctionLength) - ) - expect(await trade.worstCasePrice()).to.equal(bn('0')) - expect(await trade.canSettle()).to.equal(false) + expect(await trade.worstCasePrice()).to.equal(bn('0')) + expect(await trade.canSettle()).to.equal(false) + + // Attempt to initialize again + await expect( + trade.init( + await trade.broker(), + await trade.origin(), + await trade.gnosis(), + await broker.batchAuctionLength(), + tradeRequest + ) + ).to.be.revertedWith('Invalid trade state') + }) - // Attempt to initialize again - await expect( - trade.init( - await trade.broker(), - await trade.origin(), - await trade.gnosis(), - await broker.auctionLength(), - tradeRequest + // This test is only here for coverage + it('Should initialize GnosisTrade - zero decimal token', async () => { + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateralZ.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } + + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.not.be.reverted + + // Check trade values + expect(await trade.gnosis()).to.equal(gnosis.address) + expect(await trade.auctionId()).to.equal(0) + expect(await trade.status()).to.equal(TradeStatus.OPEN) + expect(await trade.broker()).to.equal(broker.address) + expect(await trade.origin()).to.equal(backingManager.address) + expect(await trade.sell()).to.equal(token0.address) + expect(await trade.buy()).to.equal(tokenZ.address) + expect(await trade.initBal()).to.equal(amount) + expect(await trade.endTime()).to.equal( + (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength) ) - ).to.be.revertedWith('Invalid trade state') - }) - - it('Should initialize trade with minimum buy amount of at least 1', async () => { - const amount: BigNumber = bn('100e18') - - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() - - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateralZ.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } + expect(await trade.worstCasePrice()).to.equal(bn('0')) + expect(await trade.canSettle()).to.equal(false) + }) - // Fund trade and initialize - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.auctionLength, - tradeRequest + it('Should protect against reentrancy when initializing GnosisTrade', async () => { + // Create a Reetrant Gnosis + const GnosisReentrantFactory: ContractFactory = await ethers.getContractFactory( + 'GnosisMockReentrant' ) - ).to.not.be.reverted - - // Check trade values - expect(await trade.gnosis()).to.equal(gnosis.address) - expect(await trade.auctionId()).to.equal(0) - expect(await trade.status()).to.equal(TradeStatus.OPEN) - expect(await trade.broker()).to.equal(broker.address) - expect(await trade.origin()).to.equal(backingManager.address) - expect(await trade.sell()).to.equal(token0.address) - expect(await trade.buy()).to.equal(tokenZ.address) - expect(await trade.initBal()).to.equal(amount) - expect(await trade.endTime()).to.equal( - (await getLatestBlockTimestamp()) + Number(config.auctionLength) - ) - expect(await trade.worstCasePrice()).to.equal(bn('0')) - expect(await trade.canSettle()).to.equal(false) - }) + const reentrantGnosis: GnosisMockReentrant = ( + await GnosisReentrantFactory.deploy() + ) + await reentrantGnosis.setReenterOnInit(true) + + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } + + // Fund trade and initialize with reentrant Gnosis + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + broker.address, + backingManager.address, + reentrantGnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.be.revertedWith('Invalid trade state') + }) - it('Should protect against reentrancy when initializing trade', async () => { - const amount: BigNumber = bn('100e18') + it('Should perform balance and amounts validations on init', async () => { + const invalidAmount: BigNumber = MAX_UINT96.add(1) + + // Initialize trade - Sell Amount too large + // Fund trade + await token0.connect(owner).mint(trade.address, invalidAmount) + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: invalidAmount, + minBuyAmount: bn('0'), + } + + // Attempt to initialize + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.be.revertedWith('sellAmount too large') + + // Initialize trade - MinBuyAmount too large + tradeRequest.sellAmount = amount + tradeRequest.minBuyAmount = invalidAmount + + // Attempt to initialize + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.be.revertedWith('minBuyAmount too large') + + // Restore value + tradeRequest.minBuyAmount = bn('0') + + // Fund trade with large balance + await token0.connect(owner).mint(trade.address, invalidAmount) + + // Attempt to initialize + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.be.revertedWith('initBal too large') + }) - // Create a Reetrant Gnosis - const GnosisReentrantFactory: ContractFactory = await ethers.getContractFactory( - 'GnosisMockReentrant' - ) - const reentrantGnosis: GnosisMockReentrant = ( - await GnosisReentrantFactory.deploy() - ) - await reentrantGnosis.setReenterOnInit(true) + it('Should not allow to initialize an unfunded trade', async () => { + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } + + // Attempt to initialize without funding + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.be.revertedWith('unfunded trade') + }) - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + it('Should be able to settle a trade - performing validations', async () => { + // Check state - cannot be settled + expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + expect(await trade.canSettle()).to.equal(false) + + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } + + // Attempt to settle (will fail) + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.be.revertedWith('Invalid trade state') + }) - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.not.be.reverted + + // Check trade is initialized but still cannot be settled + expect(await trade.status()).to.equal(TradeStatus.OPEN) + expect(await trade.canSettle()).to.equal(false) + + // Advance time till trade can be settled + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Check status - can be settled now + expect(await trade.status()).to.equal(TradeStatus.OPEN) + expect(await trade.canSettle()).to.equal(true) + + // Attempt to settle from other address (not origin) + await expect(trade.connect(addr1).settle()).to.be.revertedWith('only origin can settle') + + // Settle trade + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.not.be.reverted + }) - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } + // Check status + expect(await trade.status()).to.equal(TradeStatus.CLOSED) + expect(await trade.canSettle()).to.equal(false) + }) - // Fund trade and initialize with reentrant Gnosis - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - broker.address, - backingManager.address, - reentrantGnosis.address, - config.auctionLength, - tradeRequest + it('Should protect against reentrancy when settling GnosisTrade', async () => { + // Create a Reetrant Gnosis + const GnosisReentrantFactory: ContractFactory = await ethers.getContractFactory( + 'GnosisMockReentrant' ) - ).to.be.revertedWith('Invalid trade state') - }) + const reentrantGnosis: GnosisMockReentrant = ( + await GnosisReentrantFactory.deploy() + ) + await reentrantGnosis.setReenterOnInit(false) + await reentrantGnosis.setReenterOnSettle(true) + + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } + + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + broker.address, + backingManager.address, + reentrantGnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.not.be.reverted + + // Advance time till trade can be settled + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Attempt Settle trade + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.be.revertedWith('Invalid trade state') + }) + }) - it('Should perform balance and amounts validations on init', async () => { - const amount: BigNumber = bn('100e18') - const invalidAmount: BigNumber = MAX_UINT96.add(1) + it('Should be able to settle a GnosisTrade - handles arbitrary funds being sent to trade', async () => { + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } + + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.not.be.reverted + + // Check trade is initialized but still cannot be settled + expect(await trade.status()).to.equal(TradeStatus.OPEN) + expect(await trade.canSettle()).to.equal(false) + + // Advance time till trade can be settled + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Check status - can be settled now + expect(await trade.status()).to.equal(TradeStatus.OPEN) + expect(await trade.canSettle()).to.equal(true) + + // Perform mock bid - do not cover full amount + const bidAmount: BigNumber = amount.sub(bn('1e18')) + const minBuyAmt: BigNumber = toBNDecimals(bidAmount, 6) + await token1.connect(owner).mint(addr1.address, minBuyAmt) + await token1.connect(addr1).approve(gnosis.address, minBuyAmt) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: bidAmount, + buyAmount: minBuyAmt, + }) - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + // Settle auction directly in Gnosis + await gnosis.settleAuction(0) - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + // Send tokens to the trade to try to disable it (Potential attack) + const additionalFundsSell: BigNumber = amount + const additionalFundsBuy: BigNumber = toBNDecimals(amount.div(2), 6) - // Initialize trade - Sell Amount too large - // Fund trade - await token0.connect(owner).mint(trade.address, invalidAmount) - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: invalidAmount, - minBuyAmount: bn('0'), - } + await token0.connect(owner).mint(trade.address, amount) + await token1.connect(owner).mint(trade.address, toBNDecimals(amount.div(2), 6)) - // Attempt to initialize - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.auctionLength, - tradeRequest - ) - ).to.be.revertedWith('sellAmount too large') + // Settle trade + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.not.be.reverted + }) - // Initialize trade - MinBuyAmount too large - tradeRequest.sellAmount = amount - tradeRequest.minBuyAmount = invalidAmount + // Check status + expect(await trade.status()).to.equal(TradeStatus.CLOSED) + expect(await trade.canSettle()).to.equal(false) - // Attempt to initialize - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.auctionLength, - tradeRequest + // Additional funds transfered to origin + expect(await token0.balanceOf(backingManager.address)).to.equal( + bn('1e18').add(additionalFundsSell) ) - ).to.be.revertedWith('minBuyAmount too large') - - // Restore value - tradeRequest.minBuyAmount = bn('0') - - // Fund trade with large balance - await token0.connect(owner).mint(trade.address, invalidAmount) - - // Attempt to initialize - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.auctionLength, - tradeRequest + expect(await token1.balanceOf(backingManager.address)).to.equal( + minBuyAmt.add(additionalFundsBuy) ) - ).to.be.revertedWith('initBal too large') - }) - - it('Should not allow to initialize an unfunded trade', async () => { - const amount: BigNumber = bn('100e18') - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() - - // Check state - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } + // Funds sent to bidder + expect(await token0.balanceOf(addr1.address)).to.equal(bidAmount) + }) - // Attempt to initialize without funding - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.auctionLength, - tradeRequest + it('Should allow anyone to transfer to origin after a GnosisTrade is complete', async () => { + // Initialize trade - simulate from backingManager + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: amount, + minBuyAmount: bn('0'), + } + + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ).to.not.be.reverted + + // Check balances on Trade and Origin + expect(await token0.balanceOf(trade.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + + // Attempt to transfer the new funds to origin while the Trade is still open + await expect(trade.transferToOriginAfterTradeComplete(token0.address)).to.be.revertedWith( + 'only after trade is closed' ) - ).to.be.revertedWith('unfunded trade') - }) - - it('Should be able to settle a trade - performing validations', async () => { - const amount: BigNumber = bn('100e18') - - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() - // Check state - cannot be settled - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - expect(await trade.canSettle()).to.equal(false) + // Advance time till trade can be settled + await advanceTime(config.batchAuctionLength.add(100).toString()) - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } - - // Attempt to settle (will fail) - await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(trade.connect(bmSigner).settle()).to.be.revertedWith('Invalid trade state') - }) + // Settle trade + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.not.be.reverted + }) - // Fund trade and initialize - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.auctionLength, - tradeRequest - ) - ).to.not.be.reverted + // Check status + expect(await trade.status()).to.equal(TradeStatus.CLOSED) - // Check trade is initialized but still cannot be settled - expect(await trade.status()).to.equal(TradeStatus.OPEN) - expect(await trade.canSettle()).to.equal(false) + // Check balances on Trade and Origin - Funds sent back to origin (no bids) + expect(await token0.balanceOf(trade.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(amount) - // Advance time till trade can be settled - await advanceTime(config.auctionLength.add(100).toString()) + // Send arbitrary funds to Trade + const newFunds: BigNumber = amount.div(2) + await token0.connect(owner).mint(trade.address, newFunds) - // Check status - can be settled now - expect(await trade.status()).to.equal(TradeStatus.OPEN) - expect(await trade.canSettle()).to.equal(true) + // Check balances again + expect(await token0.balanceOf(trade.address)).to.equal(newFunds) + expect(await token0.balanceOf(backingManager.address)).to.equal(amount) - // Attempt to settle from other address (not origin) - await expect(trade.connect(addr1).settle()).to.be.revertedWith('only origin can settle') + // Transfer to origin + await expect(trade.transferToOriginAfterTradeComplete(token0.address)) + .to.emit(token0, 'Transfer') + .withArgs(trade.address, backingManager.address, newFunds) - // Settle trade - await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(trade.connect(bmSigner).settle()).to.not.be.reverted + // Check balances again - funds sent to origin + expect(await token0.balanceOf(trade.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(amount.add(newFunds)) }) - - // Check status - expect(await trade.status()).to.equal(TradeStatus.CLOSED) - expect(await trade.canSettle()).to.equal(false) }) - it('Should protect against reentrancy when settling trade', async () => { - const amount: BigNumber = bn('100e18') + context('DutchTrade', () => { + let amount: BigNumber + let trade: DutchTrade - // Create a Reetrant Gnosis - const GnosisReentrantFactory: ContractFactory = await ethers.getContractFactory( - 'GnosisMockReentrant' - ) - const reentrantGnosis: GnosisMockReentrant = ( - await GnosisReentrantFactory.deploy() - ) - await reentrantGnosis.setReenterOnInit(false) - await reentrantGnosis.setReenterOnSettle(true) + beforeEach(async () => { + amount = config.rTokenMaxTradeVolume - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + // Create a Trade + const TradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + trade = await TradeFactory.deploy() - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } + // Check state + expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) + expect(await trade.canSettle()).to.equal(false) + }) - // Fund trade and initialize - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - broker.address, - backingManager.address, - reentrantGnosis.address, - config.auctionLength, - tradeRequest + it('Should initialize DutchTrade correctly - only once', async () => { + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ).to.not.be.reverted + + // Check trade values + expect(await trade.KIND()).to.equal(TradeKind.DUTCH_AUCTION) + expect(await trade.status()).to.equal(TradeStatus.OPEN) + expect(await trade.origin()).to.equal(backingManager.address) + expect(await trade.sell()).to.equal(token0.address) + expect(await trade.buy()).to.equal(token1.address) + expect(await trade.sellAmount()).to.equal(amount) + expect(await trade.startTime()).to.equal(await getLatestBlockTimestamp()) + expect(await trade.endTime()).to.equal( + (await getLatestBlockTimestamp()) + Number(config.dutchAuctionLength) ) - ).to.not.be.reverted + const [sellLow, sellHigh] = await collateral0.price() + const [buyLow, buyHigh] = await collateral1.price() + expect(await trade.middlePrice()).to.equal(divCeil(sellHigh.mul(fp('1')), buyLow)) + expect(await trade.lowPrice()).to.equal(sellLow.mul(fp('1')).div(buyHigh)) + expect(await trade.canSettle()).to.equal(false) + + // Attempt to initialize again + await expect( + trade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ).to.be.revertedWith('Invalid trade state') + }) - // Advance time till trade can be settled - await advanceTime(config.auctionLength.add(100).toString()) + it('Should apply full maxTradeSlippage to lowPrice at minTradeVolume', async () => { + amount = config.minTradeVolume + + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ).to.not.be.reverted + + // Check trade values + const [sellLow, sellHigh] = await collateral0.price() + const [buyLow, buyHigh] = await collateral1.price() + expect(await trade.middlePrice()).to.equal(divCeil(sellHigh.mul(fp('1')), buyLow)) + const withoutSlippage = sellLow.mul(fp('1')).div(buyHigh) + const withSlippage = withoutSlippage.sub( + withoutSlippage.mul(config.maxTradeSlippage).div(fp('1')) + ) + expect(await trade.lowPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) + }) - // Attempt Settle trade - await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(trade.connect(bmSigner).settle()).to.be.revertedWith('Invalid trade state') + it('Should not allow to initialize an unfunded trade', async () => { + // Attempt to initialize without funding + await expect( + trade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ).to.be.revertedWith('unfunded trade') }) - }) - it('Should be able to settle a trade - handles arbitrary funds being sent to trade', async () => { - const amount: BigNumber = bn('100e18') + it('Should allow anyone to transfer to origin after a DutchTrade is complete', async () => { + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ).to.not.be.reverted + + // Check balances on Trade and Origin + expect(await token0.balanceOf(trade.address)).to.equal(amount) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + + // Attempt to transfer the new funds to origin while the Trade is still open + await expect(trade.transferToOriginAfterTradeComplete(token0.address)).to.be.revertedWith( + 'only after trade is closed' + ) - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + // Advance time till trade can be settled + await advanceTime(config.batchAuctionLength.add(100).toString()) - // Check state - cannot be settled - expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) - expect(await trade.canSettle()).to.equal(false) + // Settle trade + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.not.be.reverted + }) - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } + // Check status + expect(await trade.status()).to.equal(TradeStatus.CLOSED) - // Fund trade and initialize - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - broker.address, - backingManager.address, - gnosis.address, - config.auctionLength, - tradeRequest - ) - ).to.not.be.reverted - - // Check trade is initialized but still cannot be settled - expect(await trade.status()).to.equal(TradeStatus.OPEN) - expect(await trade.canSettle()).to.equal(false) - - // Advance time till trade can be settled - await advanceTime(config.auctionLength.add(100).toString()) - - // Check status - can be settled now - expect(await trade.status()).to.equal(TradeStatus.OPEN) - expect(await trade.canSettle()).to.equal(true) - - // Perform mock bid - do not cover full amount - const bidAmount: BigNumber = amount.sub(bn('1e18')) - const minBuyAmt: BigNumber = toBNDecimals(bidAmount, 6) - await token1.connect(owner).mint(addr1.address, minBuyAmt) - await token1.connect(addr1).approve(gnosis.address, minBuyAmt) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: bidAmount, - buyAmount: minBuyAmt, - }) + // Check balances on Trade and Origin - Funds sent back to origin (no bids) + expect(await token0.balanceOf(trade.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(amount) - // Settle auction directly in Gnosis - await gnosis.settleAuction(0) + // Send arbitrary funds to Trade + const newFunds: BigNumber = amount.div(2) + await token0.connect(owner).mint(trade.address, newFunds) - // Send tokens to the trade to try to disable it (Potential attack) - const additionalFundsSell: BigNumber = amount - const additionalFundsBuy: BigNumber = toBNDecimals(amount.div(2), 6) + // Check balances again + expect(await token0.balanceOf(trade.address)).to.equal(newFunds) + expect(await token0.balanceOf(backingManager.address)).to.equal(amount) - await token0.connect(owner).mint(trade.address, amount) - await token1.connect(owner).mint(trade.address, toBNDecimals(amount.div(2), 6)) + // Transfer to origin + await expect(trade.transferToOriginAfterTradeComplete(token0.address)) + .to.emit(token0, 'Transfer') + .withArgs(trade.address, backingManager.address, newFunds) - // Settle trade - await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(trade.connect(bmSigner).settle()).to.not.be.reverted + // Check balances again - funds sent to origin + expect(await token0.balanceOf(trade.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(amount.add(newFunds)) }) + }) + }) - // Check status - expect(await trade.status()).to.equal(TradeStatus.CLOSED) - expect(await trade.canSettle()).to.equal(false) - - // Additional funds transfered to origin - expect(await token0.balanceOf(backingManager.address)).to.equal( - bn('1e18').add(additionalFundsSell) - ) - expect(await token1.balanceOf(backingManager.address)).to.equal( - minBuyAmt.add(additionalFundsBuy) - ) + describeGas('Gas Reporting', () => { + context('GnosisTrade', () => { + let amount: BigNumber + let tradeRequest: ITradeRequest + let TradeFactory: ContractFactory + let newTrade: GnosisTrade + + beforeEach(async () => { + amount = bn('100e18') + + tradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: bn('100e18'), + minBuyAmount: bn('0'), + } + + // Mint required tokens + await token0.connect(owner).mint(backingManager.address, amount) + await token0.connect(owner).mint(rsrTrader.address, amount) + await token0.connect(owner).mint(rTokenTrader.address, amount) + await token0.connect(owner).mint(addr1.address, amount) + + // Create a new trade + TradeFactory = await ethers.getContractFactory('GnosisTrade') + newTrade = await TradeFactory.deploy() + }) - // Funds sent to bidder - expect(await token0.balanceOf(addr1.address)).to.equal(bidAmount) - }) + it('Open Trade ', async () => { + // Open from traders + // Backing Manager + await whileImpersonating(backingManager.address, async (bmSigner) => { + await token0.connect(bmSigner).approve(broker.address, amount) + await snapshotGasCost( + broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ) + }) - it('Should allow anyone to transfer to origin after a trade is complete', async () => { - const amount: BigNumber = bn('100e18') + // RSR Trader + await whileImpersonating(rsrTrader.address, async (rsrSigner) => { + await token0.connect(rsrSigner).approve(broker.address, amount) + await snapshotGasCost( + broker.connect(rsrSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ) + }) - // Create a Trade - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + // RToken Trader + await whileImpersonating(rTokenTrader.address, async (rtokSigner) => { + await token0.connect(rtokSigner).approve(broker.address, amount) + await snapshotGasCost( + broker.connect(rtokSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + ) + }) + }) - // Initialize trade - simulate from backingManager - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: amount, - minBuyAmount: bn('0'), - } + it('Initialize Trade ', async () => { + // Fund trade and initialize + await token0.connect(owner).mint(newTrade.address, amount) + await snapshotGasCost( + newTrade.init( + broker.address, + backingManager.address, + gnosis.address, + config.batchAuctionLength, + tradeRequest + ) + ) + }) - // Fund trade and initialize - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( + it('Settle Trade ', async () => { + // Fund trade and initialize + await token0.connect(owner).mint(newTrade.address, amount) + await newTrade.init( broker.address, backingManager.address, gnosis.address, - config.auctionLength, + config.batchAuctionLength, tradeRequest ) - ).to.not.be.reverted - - // Check balances on Trade and Origin - expect(await token0.balanceOf(trade.address)).to.equal(0) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - // Attempt to transfer the new funds to origin while the Trade is still open - await expect(trade.transferToOriginAfterTradeComplete(token0.address)).to.be.revertedWith( - 'only after trade is closed' - ) + // Advance time till trade can be settled + await advanceTime(config.batchAuctionLength.add(100).toString()) - // Advance time till trade can be settled - await advanceTime(config.auctionLength.add(100).toString()) + // Settle trade + await whileImpersonating(backingManager.address, async (bmSigner) => { + await snapshotGasCost(newTrade.connect(bmSigner).settle()) + }) - // Settle trade - await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect(trade.connect(bmSigner).settle()).to.not.be.reverted + // Check status + expect(await newTrade.status()).to.equal(TradeStatus.CLOSED) + expect(await newTrade.canSettle()).to.equal(false) }) - - // Check status - expect(await trade.status()).to.equal(TradeStatus.CLOSED) - - // Check balances on Trade and Origin - Funds sent back to origin (no bids) - expect(await token0.balanceOf(trade.address)).to.equal(0) - expect(await token0.balanceOf(backingManager.address)).to.equal(amount) - - // Send arbitrary funds to Trade - const newFunds: BigNumber = amount.div(2) - await token0.connect(owner).mint(trade.address, newFunds) - - // Check balances again - expect(await token0.balanceOf(trade.address)).to.equal(newFunds) - expect(await token0.balanceOf(backingManager.address)).to.equal(amount) - - // Transfer to origin - await expect(trade.transferToOriginAfterTradeComplete(token0.address)) - .to.emit(token0, 'Transfer') - .withArgs(trade.address, backingManager.address, newFunds) - - // Check balances again - funds sent to origin - expect(await token0.balanceOf(trade.address)).to.equal(0) - expect(await token0.balanceOf(backingManager.address)).to.equal(amount.add(newFunds)) }) - }) - - describeGas('Gas Reporting', () => { - let amount: BigNumber - let tradeRequest: ITradeRequest - let TradeFactory: ContractFactory - let newTrade: GnosisTrade - beforeEach(async () => { - amount = bn('100e18') - - tradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: bn('100e18'), - minBuyAmount: bn('0'), - } - - // Mint required tokens - await token0.connect(owner).mint(backingManager.address, amount) - await token0.connect(owner).mint(rsrTrader.address, amount) - await token0.connect(owner).mint(rTokenTrader.address, amount) - await token0.connect(owner).mint(addr1.address, amount) + context('DutchTrade', () => { + let amount: BigNumber + let tradeRequest: ITradeRequest + let TradeFactory: ContractFactory + let newTrade: DutchTrade + + beforeEach(async () => { + amount = bn('100e18') + + tradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: bn('100e18'), + minBuyAmount: bn('0'), + } + + // Mint required tokens + await token0.connect(owner).mint(backingManager.address, amount) + await token0.connect(owner).mint(rsrTrader.address, amount) + await token0.connect(owner).mint(rTokenTrader.address, amount) + await token0.connect(owner).mint(addr1.address, amount) + + // Create a new trade + TradeFactory = await ethers.getContractFactory('DutchTrade') + newTrade = await TradeFactory.deploy() + }) - // Create a new trade - TradeFactory = await ethers.getContractFactory('GnosisTrade') - newTrade = await TradeFactory.deploy() - }) + it('Open Trade ', async () => { + // Open from traders + // Backing Manager + await whileImpersonating(backingManager.address, async (bmSigner) => { + await token0.connect(bmSigner).approve(broker.address, amount) + await snapshotGasCost( + broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + ) + }) - it('Open Trade ', async () => { - // Open from traders - // Backing Manager - await whileImpersonating(backingManager.address, async (bmSigner) => { - await token0.connect(bmSigner).approve(broker.address, amount) - await snapshotGasCost(broker.connect(bmSigner).openTrade(tradeRequest)) - }) + // RSR Trader + await whileImpersonating(rsrTrader.address, async (rsrSigner) => { + await token0.connect(rsrSigner).approve(broker.address, amount) + await snapshotGasCost( + broker.connect(rsrSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + ) + }) - // RSR Trader - await whileImpersonating(rsrTrader.address, async (rsrSigner) => { - await token0.connect(rsrSigner).approve(broker.address, amount) - await snapshotGasCost(broker.connect(rsrSigner).openTrade(tradeRequest)) + // RToken Trader + await whileImpersonating(rTokenTrader.address, async (rtokSigner) => { + await token0.connect(rtokSigner).approve(broker.address, amount) + await snapshotGasCost( + broker.connect(rtokSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + ) + }) }) - // RToken Trader - await whileImpersonating(rTokenTrader.address, async (rtokSigner) => { - await token0.connect(rtokSigner).approve(broker.address, amount) - await snapshotGasCost(broker.connect(rtokSigner).openTrade(tradeRequest)) + it('Initialize Trade ', async () => { + // Fund trade and initialize + await token0.connect(owner).mint(newTrade.address, amount) + await snapshotGasCost( + newTrade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ) }) - }) - it('Initialize Trade ', async () => { - // Fund trade and initialize - await token0.connect(owner).mint(newTrade.address, amount) - await snapshotGasCost( - newTrade.init( - broker.address, + it('Settle Trade ', async () => { + // Fund trade and initialize + await token0.connect(owner).mint(newTrade.address, amount) + await newTrade.init( backingManager.address, - gnosis.address, - config.auctionLength, - tradeRequest + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength ) - ) - }) - it('Settle Trade ', async () => { - // Fund trade and initialize - await token0.connect(owner).mint(newTrade.address, amount) - await newTrade.init( - broker.address, - backingManager.address, - gnosis.address, - config.auctionLength, - tradeRequest - ) + // Advance time till trade can be settled + await advanceTime(config.dutchAuctionLength.add(100).toString()) - // Advance time till trade can be settled - await advanceTime(config.auctionLength.add(100).toString()) + // Settle trade + await whileImpersonating(backingManager.address, async (bmSigner) => { + await snapshotGasCost(newTrade.connect(bmSigner).settle()) + }) - // Settle trade - await whileImpersonating(backingManager.address, async (bmSigner) => { - await snapshotGasCost(newTrade.connect(bmSigner).settle()) + // Check status + expect(await newTrade.status()).to.equal(TradeStatus.CLOSED) + expect(await newTrade.canSettle()).to.equal(false) }) - - // Check status - expect(await newTrade.status()).to.equal(TradeStatus.CLOSED) - expect(await newTrade.canSettle()).to.equal(false) }) }) }) diff --git a/test/Deployer.test.ts b/test/Deployer.test.ts index c2764a5bc7..1f14da64ea 100644 --- a/test/Deployer.test.ts +++ b/test/Deployer.test.ts @@ -157,7 +157,7 @@ describe(`DeployerP${IMPLEMENTATION} contract #fast`, () => { rsrTrader: mock.address, rTokenTrader: mock.address, }, - trade: mock.address, + trading: { gnosisTrade: mock.address, dutchTrade: mock.address }, } await expect( @@ -180,12 +180,19 @@ describe(`DeployerP${IMPLEMENTATION} contract #fast`, () => { ).to.be.revertedWith('invalid address') implementations.main = mock.address - // Trade - implementations.trade = ZERO_ADDRESS + // GnosisTrade + implementations.trading.gnosisTrade = ZERO_ADDRESS await expect( deployNewDeployer(rsr.address, gnosis.address, rsrAsset.address, implementations) ).to.be.revertedWith('invalid address') - implementations.trade = mock.address + implementations.trading.gnosisTrade = mock.address + + // DutchTrade + implementations.trading.dutchTrade = ZERO_ADDRESS + await expect( + deployNewDeployer(rsr.address, gnosis.address, rsrAsset.address, implementations) + ).to.be.revertedWith('invalid address') + implementations.trading.dutchTrade = mock.address await validateComponent(implementations, 'assetRegistry') await validateComponent(implementations, 'backingManager') diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts index 429d733bd9..d5619e6a86 100644 --- a/test/FacadeAct.test.ts +++ b/test/FacadeAct.test.ts @@ -197,6 +197,51 @@ describe('FacadeAct contract', () => { await rToken.connect(addr1).issue(issueAmount) }) + context('getRevenueAuctionERC20s/runRevenueAuctions', () => { + it('Revenues/Rewards', async () => { + const rewardAmountAAVE = bn('0.5e18') + const rewardAmountCOMP = bn('1e18') + + // Setup AAVE + COMP rewards + await aToken.setRewards(backingManager.address, rewardAmountAAVE) + await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + await backingManager.claimRewards() + + // getRevenueAuctionERC20s should return reward token + const rTokenERC20s = await facadeAct.callStatic.getRevenueAuctionERC20s( + rTokenTrader.address + ) + expect(rTokenERC20s.length).to.equal(2) + expect(rTokenERC20s[0]).to.equal(aaveToken.address) + expect(rTokenERC20s[1]).to.equal(compToken.address) + const rsrERC20s = await facadeAct.callStatic.getRevenueAuctionERC20s(rsrTrader.address) + expect(rsrERC20s.length).to.equal(2) + expect(rsrERC20s[0]).to.equal(aaveToken.address) + expect(rsrERC20s[1]).to.equal(compToken.address) + + // Run revenue auctions for both traders + await facadeAct.runRevenueAuctions(rTokenTrader.address, [], rTokenERC20s) + await facadeAct.runRevenueAuctions(rsrTrader.address, [], rsrERC20s) + + // Nothing should be settleable + expect((await facade.auctionsSettleable(rTokenTrader.address)).length).to.equal(0) + expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(0) + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Now both should be settleable + const rTokenSettleable = await facade.auctionsSettleable(rTokenTrader.address) + expect(rTokenSettleable.length).to.equal(2) + expect(rTokenSettleable[0]).to.equal(aaveToken.address) + expect(rTokenSettleable[1]).to.equal(compToken.address) + const rsrSettleable = await facade.auctionsSettleable(rsrTrader.address) + expect(rsrSettleable.length).to.equal(2) + expect(rsrSettleable[0]).to.equal(aaveToken.address) + expect(rsrSettleable[1]).to.equal(compToken.address) + }) + }) + context('getActCalldata', () => { it('No call required', async () => { // Via Facade get next call - No action required @@ -336,7 +381,7 @@ describe('FacadeAct contract', () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Confirm auctionsSettleable returns trade const settleable = await facade.auctionsSettleable(backingManager.address) @@ -435,7 +480,7 @@ describe('FacadeAct contract', () => { expect(await aaveToken.balanceOf(gnosis.address)).to.equal(rewardAmountAAVE) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction by minting the buy tokens (in this case RSR and RToken) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -487,7 +532,7 @@ describe('FacadeAct contract', () => { expect(data).to.equal('0x') // distribute Revenue from RToken trader - await rTokenTrader.manageToken(rToken.address) + await rTokenTrader.distributeTokenToBuy() // Claim additional Revenue but only send to RSR (to trigger RSR trader directly) // Set f = 1 @@ -559,6 +604,7 @@ describe('FacadeAct contract', () => { await owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) // Collect revenue @@ -578,6 +624,7 @@ describe('FacadeAct contract', () => { await owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) // Next call would start Revenue auction - RTokenTrader @@ -590,6 +637,7 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ) .to.emit(rTokenTrader, 'TradeStarted') @@ -611,6 +659,7 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ) .to.emit(rsrTrader, 'TradeStarted') @@ -620,7 +669,7 @@ describe('FacadeAct contract', () => { expect(await aaveToken.balanceOf(gnosis.address)).to.equal(rewardAmountAAVE) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction by minting the buy tokens (in this case RSR and RToken) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -646,6 +695,7 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ) .to.emit(rTokenTrader, 'TradeSettled') @@ -661,12 +711,31 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ) .to.emit(rsrTrader, 'TradeSettled') .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, minBuyAmt) }) + it('Revenues - Should handle ERC20s with invalid claim logic', async () => { + // Set the cToken to revert + await cToken.setRevertClaimRewards(true) + + const rewardAmountCOMP = bn('0.5e18') + + // COMP Rewards + await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + + // Via Facade get next call - will attempt to claim + const [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) + expect(addr).to.equal(ZERO_ADDRESS) + expect(data).to.equal('0x') + + // Check status - nothing claimed + expect(await compToken.balanceOf(backingManager.address)).to.equal(0) + }) + it('Revenues - Should handle multiple assets with same reward token', async () => { // Update Reward token for AToken to use same as CToken const ATokenCollateralFactory = await ethers.getContractFactory('ATokenFiatCollateral') @@ -835,51 +904,6 @@ describe('FacadeAct contract', () => { expect(await rToken.balanceOf(rTokenTrader.address)).to.equal(hndAmt) }) }) - - context('getRevenueAuctionERC20s/runRevenueAuctions', () => { - it('Revenues/Rewards', async () => { - const rewardAmountAAVE = bn('0.5e18') - const rewardAmountCOMP = bn('1e18') - - // Setup AAVE + COMP rewards - await aToken.setRewards(backingManager.address, rewardAmountAAVE) - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) - await backingManager.claimRewards() - - // getRevenueAuctionERC20s should return reward token - const rTokenERC20s = await facadeAct.callStatic.getRevenueAuctionERC20s( - rTokenTrader.address - ) - expect(rTokenERC20s.length).to.equal(2) - expect(rTokenERC20s[0]).to.equal(aaveToken.address) - expect(rTokenERC20s[1]).to.equal(compToken.address) - const rsrERC20s = await facadeAct.callStatic.getRevenueAuctionERC20s(rsrTrader.address) - expect(rsrERC20s.length).to.equal(2) - expect(rsrERC20s[0]).to.equal(aaveToken.address) - expect(rsrERC20s[1]).to.equal(compToken.address) - - // Run revenue auctions for both traders - await facadeAct.runRevenueAuctions(rTokenTrader.address, [], rTokenERC20s) - await facadeAct.runRevenueAuctions(rsrTrader.address, [], rsrERC20s) - - // Nothing should be settleable - expect((await facade.auctionsSettleable(rTokenTrader.address)).length).to.equal(0) - expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(0) - - // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) - - // Now both should be settleable - const rTokenSettleable = await facade.auctionsSettleable(rTokenTrader.address) - expect(rTokenSettleable.length).to.equal(2) - expect(rTokenSettleable[0]).to.equal(aaveToken.address) - expect(rTokenSettleable[1]).to.equal(compToken.address) - const rsrSettleable = await facade.auctionsSettleable(rsrTrader.address) - expect(rsrSettleable.length).to.equal(2) - expect(rsrSettleable[0]).to.equal(aaveToken.address) - expect(rsrSettleable[1]).to.equal(compToken.address) - }) - }) }) describeGas('Gas Reporting', () => { diff --git a/test/FacadeMonitor.ts b/test/FacadeMonitor.ts index 66f8525088..e64ac004c7 100644 --- a/test/FacadeMonitor.ts +++ b/test/FacadeMonitor.ts @@ -216,7 +216,7 @@ describe('FacadeMonitor Contract', () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) const response2 = await facadeMonitor.callStatic.getTradesForBackingManager(rToken.address) @@ -335,7 +335,7 @@ describe('FacadeMonitor Contract', () => { expect(await aaveToken.balanceOf(gnosis.address)).to.equal(rewardAmountAAVE) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction by minting the buy tokens (in this case RSR and RToken) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) diff --git a/test/Main.test.ts b/test/Main.test.ts index 7b02d54357..4021f797a6 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -32,6 +32,7 @@ import { Asset, ATokenFiatCollateral, CTokenFiatCollateral, + DutchTrade, CTokenVaultMock, ERC20Mock, FacadeRead, @@ -378,10 +379,19 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { ) // Attempt to reinitialize - Broker - const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const trade: GnosisTrade = await TradeFactory.deploy() + const GnosisTradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const gnosisTrade: GnosisTrade = await GnosisTradeFactory.deploy() + const DutchTradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + const dutchTrade: DutchTrade = await DutchTradeFactory.deploy() await expect( - broker.init(main.address, gnosis.address, trade.address, config.auctionLength) + broker.init( + main.address, + gnosis.address, + gnosisTrade.address, + config.batchAuctionLength, + dutchTrade.address, + config.dutchAuctionLength + ) ).to.be.revertedWith('Initializable: contract is already initialized') // Attempt to reinitialize - RToken diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 182545cb9a..b030eb1a05 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -5,13 +5,20 @@ import { expect } from 'chai' import { BigNumber, ContractFactory } from 'ethers' import { ethers } from 'hardhat' import { IConfig } from '../common/configuration' -import { BN_SCALE_FACTOR, CollateralStatus, MAX_UINT256 } from '../common/constants' +import { + BN_SCALE_FACTOR, + CollateralStatus, + TradeKind, + MAX_UINT256, + ZERO_ADDRESS, +} from '../common/constants' import { expectEvents } from '../common/events' import { bn, fp, pow10, toBNDecimals, divCeil } from '../common/numbers' import { Asset, ATokenFiatCollateral, CTokenMock, + DutchTrade, CTokenVaultMock, ERC20Mock, FacadeRead, @@ -24,12 +31,13 @@ import { StaticATokenMock, TestIBackingManager, TestIBasketHandler, + TestIBroker, TestIMain, TestIRToken, TestIStRSR, USDCMock, } from '../typechain' -import { advanceTime, getLatestBlockTimestamp } from './utils/time' +import { advanceTime, advanceToTimestamp, getLatestBlockTimestamp } from './utils/time' import { Collateral, defaultFixture, @@ -40,7 +48,7 @@ import { PRICE_TIMEOUT, } from './fixtures' import snapshotGasCost from './utils/snapshotGasCost' -import { expectTrade, getTrade } from './utils/trades' +import { expectTrade, getTrade, dutchBuyAmount } from './utils/trades' import { withinQuad } from './utils/matchers' import { expectRTokenPrice, setOraclePrice } from './utils/oracles' import { useEnv } from '#/utils/env' @@ -96,6 +104,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { let backingManager: TestIBackingManager let basketHandler: TestIBasketHandler let main: TestIMain + let broker: TestIBroker interface IBackingInfo { tokens: string[] @@ -159,6 +168,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { basketHandler, main, rTokenAsset, + broker, } = await loadFixture(defaultFixture)) token0 = erc20s[collateral.indexOf(basket[0])] token1 = erc20s[collateral.indexOf(basket[1])] @@ -799,26 +809,35 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await stRSR.connect(addr1).stake(stakeAmount) }) - it('Should not trade if paused', async () => { + it('Should not trade if trading paused', async () => { await main.connect(owner).pauseTrading() - await expect(backingManager.manageTokens([])).to.be.revertedWith('frozen or trading paused') - await expect(backingManager.manageTokensSortedOrder([])).to.be.revertedWith( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'frozen or trading paused' ) }) it('Should not trade if frozen', async () => { await main.connect(owner).freezeShort() - await expect(backingManager.manageTokens([])).to.be.revertedWith('frozen or trading paused') - await expect(backingManager.manageTokensSortedOrder([])).to.be.revertedWith( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'frozen or trading paused' ) }) + it('Should trade if issuance paused', async () => { + // Setup new prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + await basketHandler.refreshBasket() + + await main.connect(owner).pauseIssuance() + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) + }) + it('Should not trade if UNPRICED', async () => { await advanceTime(ORACLE_TIMEOUT.toString()) - await expect(backingManager.manageTokens([])).to.be.revertedWith('basket not ready') - await expect(backingManager.manageTokensSortedOrder([])).to.be.revertedWith( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'basket not ready' ) }) @@ -829,8 +848,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Set warmup period await basketHandler.connect(owner).setWarmupPeriod(warmupPeriod) - await expect(backingManager.manageTokens([])).to.be.revertedWith('basket not ready') - await expect(backingManager.manageTokensSortedOrder([])).to.be.revertedWith( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'basket not ready' ) }) @@ -872,7 +890,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) }) @@ -900,7 +918,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('0.5'), fp('1')) // Attempt to trigger before warmup period - will revert - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.be.revertedWith( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'basket not ready' ) @@ -925,7 +943,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) }) @@ -948,7 +966,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) // Attempt to trigger before trading delay - Should revert - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.be.revertedWith( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'trading delayed' ) @@ -973,7 +991,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) }) @@ -1029,7 +1047,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1072,7 +1090,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expect(backingManager.settleTrade(token3Vault.address)).to.not.emit // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should not start any new auctions await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1100,7 +1118,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await token1.balanceOf(backingManager.address)).to.equal( toBNDecimals(issueAmount, 6).add(1) ) - expect(await rToken.totalSupply()).to.equal(totalValue) + expect(await rToken.totalSupply()).to.equal(issueAmount) // assets kept in backing buffer // Check price in USD of the current RToken await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) @@ -1144,7 +1162,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1168,7 +1186,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should not start any new auctions await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1195,6 +1213,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) it('Should recollateralize correctly when switching basket - Taking no Haircut - No RSR', async () => { + await backingManager.connect(owner).setBackingBuffer(0) + // Empty out the staking pool await stRSR.connect(addr1).unstake(stakeAmount) await advanceTime(config.unstakingDelay.toString()) @@ -1247,7 +1267,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1292,7 +1312,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should not start any new auctions await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1371,7 +1391,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1416,7 +1436,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should not start any new auctions await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1434,10 +1454,13 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await basketHandler.fullyCollateralized()).to.equal(true) expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( issueAmount, - issueAmount.div(1000000) // 1 part in a million + issueAmount.mul(520).div(100000) // 520 parts in 1 miliion + ) + expect(await token0.balanceOf(backingManager.address)).to.be.closeTo(0, 10) + expect(await token1.balanceOf(backingManager.address)).to.be.closeTo( + 0, + toBNDecimals(sellAmt, 6) ) - expect(await token0.balanceOf(backingManager.address)).to.equal(remainder) - expect(await token1.balanceOf(backingManager.address)).to.equal(toBNDecimals(sellAmt, 6)) expect(await rToken.totalSupply()).to.equal(issueAmount) // Supply remains constant // Check price in USD of the current RToken @@ -1501,7 +1524,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1547,7 +1570,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should not start any new auctions await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1564,8 +1587,11 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(true) const remainingValue = toBNDecimals(minBuyAmt, 6).mul(bn('1e12')).add(remainder) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(remainingValue) - expect(await token0.balanceOf(backingManager.address)).to.equal(remainder) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( + remainingValue, + remainingValue.div(bn('5e3')) + ) + expect(await token0.balanceOf(backingManager.address)).to.be.closeTo(0, 10) expect(await token1.balanceOf(backingManager.address)).to.equal(toBNDecimals(minBuyAmt, 6)) expect(await rToken.totalSupply()).to.equal(issueAmount) // Supply remains constant expect(await rToken.basketsNeeded()).to.be.closeTo( @@ -1646,7 +1672,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1678,7 +1704,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start a new one to sell RSR for collateral // ~3e18 Tokens left to buy - Sets Buy amount as independent value @@ -1716,7 +1742,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -1754,7 +1780,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start final dust auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1833,7 +1859,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1863,7 +1889,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start a new one to sell a new revenue token instead of RSR // About 3e18 Tokens left to buy - Sets Buy amount as independent value @@ -1906,7 +1932,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: aaveToken.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -1941,7 +1967,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2079,7 +2105,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2106,7 +2132,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current, and will open a new auction for the same amount await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2130,7 +2156,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.auctionLength), + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -2157,14 +2183,14 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Check staking situation remains unchanged expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current, and will open a new auction for the same amount const leftoverMinBuyAmt = await toMinBuyAmt(leftoverSellAmt, fp('0.5'), fp('1')) @@ -2194,7 +2220,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.auctionLength), + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), externalId: bn('2'), }) @@ -2221,7 +2247,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Check staking situation remains unchanged expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) @@ -2260,7 +2286,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('3'), }) @@ -2296,7 +2322,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction; should not start a new one await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2323,7 +2349,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { issueAmount.add(1) ) expect(await backupToken1.balanceOf(backingManager.address)).to.equal(issueAmount.add(1)) - expect(await rToken.totalSupply()).to.equal(issueAmount.add(1)) + expect(await rToken.totalSupply()).to.equal(issueAmount) // Check price in USD of the current RToken - Remains the same await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) @@ -2397,7 +2423,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2423,7 +2449,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Raise minTradeVolume to the whole issueAmount await backingManager.connect(owner).setMinTradeVolume(issueAmount) @@ -2475,7 +2501,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - NO RSR Auction launched but haircut taken await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2508,8 +2534,10 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(bonus) expect(await backupToken1.balanceOf(backingManager.address)).to.equal(bonus) + const supply = bonus.sub(bonus.mul(config.backingBuffer).div(fp('1'))) // Should mint the excess in order to re-handout to RToken holders and stakers - expect(await rToken.totalSupply()).to.equal(bonus) + expect(await rToken.totalSupply()).to.be.closeTo(supply, supply.div(bn('1e6'))) + expect(await rToken.totalSupply()).to.be.gte(supply) // Check price in USD of the current RToken - overcollateralized and still targeting 1 await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) @@ -2598,7 +2626,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2612,7 +2640,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current, and will open a new auction to buy the remaining backup tokens const buyAmtBidRSR: BigNumber = issueAmount.div(2).add(1) // other half to buy @@ -2638,7 +2666,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: backupToken2.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -2665,7 +2693,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2762,7 +2790,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2776,7 +2804,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current, and will open a new auction to sell RSR for collateral // 50e18 Tokens left to buy - Sets Buy amount as independent value @@ -2803,7 +2831,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -2828,7 +2856,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Settle auction with no bids - will return RSR to Backing Manager // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2854,7 +2882,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), }) @@ -2871,7 +2899,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions again - Will close the pending auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2939,7 +2967,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2969,7 +2997,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start a new one to sell a new revenue token instead of RSR // But the revenue token will have price = 0 so it wont be sold, will use RSR @@ -3013,7 +3041,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -3048,7 +3076,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -3088,6 +3116,213 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmtRemToken)) expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) }) + + context('DutchTrade', () => { + const auctionLength = 300 + beforeEach(async () => { + await broker.connect(owner).setDutchAuctionLength(auctionLength) + + // Set up rebalancing scenario + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + await basketHandler.refreshBasket() + }) + + it('Should not trade when paused', async () => { + await main.connect(owner).pauseTrading() + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.be.revertedWith( + 'frozen or trading paused' + ) + }) + + it('Should not trade when frozen', async () => { + await main.connect(owner).freezeLong() + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.be.revertedWith( + 'frozen or trading paused' + ) + }) + + it('Should trade if issuance paused', async () => { + await main.connect(owner).pauseIssuance() + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) + }) + + it('Should only run 1 trade at a time, including into the empty buffer block', async () => { + await backingManager.rebalance(TradeKind.DUTCH_AUCTION) + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.be.revertedWith( + 'already rebalancing' + ) + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'trade open' + ) + + // Check the empty buffer block as well + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength) + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.be.revertedWith( + 'already rebalancing' + ) + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'trade open' + ) + }) + + it('Should quote piecewise-falling price correctly throughout entirety of auction', async () => { + await backingManager.rebalance(TradeKind.DUTCH_AUCTION) + const trade = await ethers.getContractAt( + 'DutchTrade', + await backingManager.trades(token0.address) + ) + await token1.connect(addr1).approve(trade.address, initialBal) + + const start = await trade.startTime() + const end = await trade.endTime() + + // Simulate 5 minutes of blocks, should swap at right price each time + for (let now = await getLatestBlockTimestamp(); now < end; now += 12) { + const actual = await trade.connect(addr1).bidAmount(now) + const expected = divCeil( + await dutchBuyAmount( + fp(now - start).div(end - start), + rTokenAsset.address, + collateral0.address, + issueAmount, + config.minTradeVolume, + config.maxTradeSlippage + ), + bn('1e12') // fix for decimals + ) + expect(actual).to.equal(expected) + + const staticResult = await trade.connect(addr1).callStatic.bid() + expect(staticResult).to.equal(expected) + await advanceToTimestamp((await getLatestBlockTimestamp()) + 12) + } + }) + + it('Should handle no bid case correctly', async () => { + await backingManager.rebalance(TradeKind.DUTCH_AUCTION) + const trade = await ethers.getContractAt( + 'DutchTrade', + await backingManager.trades(token0.address) + ) + await token1.connect(addr1).approve(trade.address, initialBal) + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 1) + await expect( + trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + ).to.be.revertedWith('auction over') + await expect(trade.connect(addr1).bid()).be.revertedWith('auction over') + + // Should be able to settle + await expect(trade.settle()).to.be.revertedWith('only origin can settle') + await expect(backingManager.settleTrade(token0.address)) + .to.emit(backingManager, 'TradeSettled') + .withArgs(trade.address, token0.address, token1.address, 0, 0) + + // Should NOT start another auction, since caller was not DutchTrade + expect(await backingManager.tradesOpen()).to.equal(0) + }) + + context('Should successfully recollateralize after default', () => { + let trade1: DutchTrade // token0 -> token1 + let trade2: DutchTrade // RSR -> token1 + + beforeEach(async () => { + await backingManager.rebalance(TradeKind.DUTCH_AUCTION) + trade1 = await ethers.getContractAt( + 'DutchTrade', + await backingManager.trades(token0.address) + ) + await token1.connect(addr1).approve(trade1.address, initialBal) + + // Snipe auction at 1s left + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 3) + await trade1.connect(addr1).bid() + expect(await trade1.canSettle()).to.equal(false) + expect(await trade1.status()).to.equal(2) // Status.CLOSED + expect(await trade1.bidder()).to.equal(addr1.address) + expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) + + const expected = divCeil( + await dutchBuyAmount( + fp('299').div(300), // after all txs so far, at 299/300s + collateral0.address, + collateral1.address, + issueAmount, + config.minTradeVolume, + config.maxTradeSlippage + ), + bn('1e12') // decimals + ) + expect(await backingManager.tradesOpen()).to.equal(1) + expect(await token1.balanceOf(backingManager.address)).to.equal(expected) + + // Should launch RSR recapitalization auction to fill ~3% + expect(await backingManager.trades(token0.address)).to.equal(ZERO_ADDRESS) + trade2 = await ethers.getContractAt( + 'DutchTrade', + await backingManager.trades(rsr.address) + ) + expect(trade2.address).to.not.equal(ZERO_ADDRESS) + }) + + afterEach(async () => { + // Should be fully capitalized again + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await backingManager.tradesOpen()).to.equal(0) + }) + + it('even under worst-possible bids', async () => { + // Advance to final second of auction + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 3) + expect(await trade2.status()).to.equal(1) // TradeStatus.OPEN + expect(await trade2.canSettle()).to.equal(false) + + // Bid + settle RSR auction + await token1.connect(addr1).approve(trade2.address, initialBal) + await expect(trade2.connect(addr1).bid()).to.emit(backingManager, 'TradeSettled') + }) + + it('via fallback to Batch Auction', async () => { + // Advance past auction timeout + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength) + expect(await trade2.status()).to.equal(1) // TradeStatus.OPEN + expect(await trade2.canSettle()).to.equal(true) + + // Settle trade + await backingManager.settleTrade(rsr.address) + expect(await backingManager.tradesOpen()).to.equal(0) + expect(await rsr.balanceOf(trade2.address)).to.equal(0) + expect(await token0.balanceOf(trade2.address)).to.equal(0) + expect(await token1.balanceOf(trade2.address)).to.equal(0) + + // Only BATCH_AUCTION can be launched + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.be.revertedWith( + 'already rebalancing' + ) + await backingManager.rebalance(TradeKind.BATCH_AUCTION) + expect(await backingManager.tradesOpen()).to.equal(1) + + // Bid in Gnosis + const t = await getTrade(backingManager, rsr.address) + const sellAmt = await t.initBal() + const minBuyAmt = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + expect(await t.KIND()).to.equal(TradeKind.BATCH_AUCTION) + await token1.connect(addr1).approve(gnosis.address, minBuyAmt) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: minBuyAmt, + }) + await advanceTime(config.batchAuctionLength.toNumber()) + await expect(backingManager.settleTrade(rsr.address)).not.to.emit( + backingManager, + 'TradeStarted' + ) + }) + }) + }) }) context('With issued Rtokens', function () { @@ -3245,7 +3480,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token2.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -3265,7 +3500,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start a new one to sell RSR for collateral // ~0.25 Tokens left to buy - Sets Buy amount as independent value @@ -3293,7 +3528,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -3336,7 +3571,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -3488,7 +3723,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -3507,7 +3742,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction for another token const minBuyAmt2 = await toMinBuyAmt(sellAmt2, fp('0.8'), fp('1')) @@ -3533,7 +3768,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token2.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -3563,7 +3798,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction const minBuyAmt3 = await toMinBuyAmt(sellAmt3, fp('0.8').div(50), fp('1')) @@ -3595,7 +3830,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token3Vault.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), }) @@ -3626,7 +3861,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction with the rebalancing await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -3657,7 +3892,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token1.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('3'), }) const t = await getTrade(backingManager, token1.address) @@ -3697,7 +3932,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close 2nd to final auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -3722,7 +3957,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token1.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('4'), }) const t2 = await getTrade(backingManager, token1.address) @@ -3756,7 +3991,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close final auction - should take haircut await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -3776,23 +4011,18 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Check final state - Haircut taken, stable but price of RToken has been reduced expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - issueAmount - .div(4) - .add(minBuyAmt0) - .add(minBuyAmt2) - .add(minBuyAmt3) - .sub(sellAmt4.mul(bn('1e12')).sub(minBuyAmt4)) - .sub(sellAmt5.mul(bn('1e12')).sub(minBuyAmt5)) - ) - await expectCurrentBacking({ - tokens: newTokens, - quantities: [ - newQuantities[0].sub(sellAmt4).sub(sellAmt5), - minBuyAmt0.add(minBuyAmt2).add(minBuyAmt3).add(minBuyAmt4).add(minBuyAmt5), - ], - }) + const expAmount = issueAmount + .div(4) + .add(minBuyAmt0) + .add(minBuyAmt2) + .add(minBuyAmt3) + .sub(sellAmt4.mul(bn('1e12')).sub(minBuyAmt4)) + .sub(sellAmt5.mul(bn('1e12')).sub(minBuyAmt5)) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( + expAmount, + expAmount.div(bn('5e3')) // 1 part in 5000 + ) expect(await rToken.totalSupply()).to.equal(issueAmount) // Check price in USD of the current RToken @@ -3811,8 +4041,10 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(quotes[1]).to.be.closeTo(finalQuotes[1], finalQuotes[1].div(bn('1e5'))) // 1 part in 100k // Check Backup tokens available - expect(await backupToken1.balanceOf(backingManager.address)).to.equal( - minBuyAmt0.add(minBuyAmt2).add(minBuyAmt3).add(minBuyAmt4).add(minBuyAmt5) + const backup1 = minBuyAmt0.add(minBuyAmt2).add(minBuyAmt3).add(minBuyAmt4).add(minBuyAmt5) + expect(await backupToken1.balanceOf(backingManager.address)).to.be.closeTo( + backup1, + backup1.div(bn('1e5')) ) }) @@ -3899,7 +4131,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token2.address, buy: token0.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -3912,7 +4144,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction for token1 const sellAmt1: BigNumber = (await token1.balanceOf(backingManager.address)).mul(pow10(12)) // convert to 18 decimals @@ -3939,7 +4171,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token1.address, buy: token0.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -3966,7 +4198,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction for token3Vault // We only need now about 11.8 tokens for Token0 to be fully collateralized @@ -3992,7 +4224,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token3Vault.address, buy: token0.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), }) @@ -4026,7 +4258,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close final auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -4185,7 +4417,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token0.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -4199,7 +4431,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -4224,7 +4456,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token2.address, buy: backupToken2.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -4258,7 +4490,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction const minBuyAmt3: BigNumber = await toMinBuyAmt(sellAmt3, fp('0.5').div(50), fp('1')) // sell cToken @@ -4290,7 +4522,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token3Vault.address, buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), }) @@ -4324,7 +4556,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current and open a new auction // We need to rebalance our backing, we have an excess of Token1 now and we need more backupToken2 @@ -4357,7 +4589,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token1.address, buy: backupToken2.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('3'), }) @@ -4401,7 +4633,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ { contract: backingManager, @@ -4424,7 +4656,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: backupToken1.address, buy: backupToken2.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('4'), }) @@ -4469,7 +4701,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -4504,7 +4736,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: token1.address, buy: backupToken2.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('5'), }) @@ -4527,7 +4759,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close final auction - takes haircut await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -4555,7 +4787,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: backupToken1.address, buy: backupToken2.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('6'), }) @@ -4578,7 +4810,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close final auction - takes haircut await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -4609,31 +4841,17 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Check final state - Haircut taken, stable but price of RToken has been reduced expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(fp('62.5')) - - await expectCurrentBacking({ - tokens: newTokens, - quantities: [ - newQuantities[0].sub(sellAmt4).sub(sellAmt5), - sellAmt0 - .div(2) - .add(sellAmt3.div(50).div(2)) - .sub(sellAmtRebalance) - .sub(sellAmtRebalance2), - sellAmt2 - .div(2) - .add(buyAmt4) - .add(sellAmtRebalance) - .add(sellAmtRebalance2) - .add(minBuyAmt5), - ], - }) - - expect(await rToken.totalSupply()).to.equal(issueAmount) // Check price in USD of the current RToken - Haircut of ~37.52% taken // The default was for 37.5% of backing, so this is pretty awesome const exactRTokenPrice = fp('0.6247979797979798') + const totalAssetValue = issueAmount.mul(exactRTokenPrice).div(fp('1')) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( + totalAssetValue, + totalAssetValue.div(bn('1e6')) + ) + + expect(await rToken.totalSupply()).to.equal(issueAmount) await expectRTokenPrice( rTokenAsset.address, exactRTokenPrice, @@ -4650,11 +4868,24 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(quotes).to.eql(finalQuotes) // Check Backup tokens available - expect(await backupToken1.balanceOf(backingManager.address)).to.equal( - sellAmt0.div(2).add(sellAmt3.div(50).div(2)).sub(sellAmtRebalance).sub(sellAmtRebalance2) + const expBackup1 = sellAmt0 + .div(2) + .add(sellAmt3.div(50).div(2)) + .sub(sellAmtRebalance) + .sub(sellAmtRebalance2) + expect(await backupToken1.balanceOf(backingManager.address)).to.be.closeTo( + expBackup1, + expBackup1.div(bn('1e3')) // 1 part in a thousand ) - expect(await backupToken2.balanceOf(backingManager.address)).to.equal( - sellAmt2.div(2).add(buyAmt4).add(sellAmtRebalance).add(minBuyAmt5).add(sellAmtRebalance2) + const expBackup2 = sellAmt2 + .div(2) + .add(buyAmt4) + .add(sellAmtRebalance) + .add(minBuyAmt5) + .add(sellAmtRebalance2) + expect(await backupToken2.balanceOf(backingManager.address)).to.be.closeTo( + expBackup2, + expBackup2.div(bn('1e3')) // 1 part in a thousand ) }) }) @@ -4688,11 +4919,10 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await rsr.connect(owner).mint(addr1.address, initialBal) }) - it('Settle Trades / Manage Funds', async () => { + it('rebalance() - GnosisTrade ', async () => { // Register Collateral await assetRegistry.connect(owner).register(backupCollateral1.address) await assetRegistry.connect(owner).register(backupCollateral2.address) - const registeredERC20s = await assetRegistry.erc20s() // Set backup configuration - USDT and aUSDT as backup await basketHandler @@ -4715,18 +4945,20 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Mark Default - Perform basket switch await assetRegistry.refresh() await expect(basketHandler.refreshBasket()).to.emit(basketHandler, 'BasketSet') + await advanceTime(config.tradingDelay.toNumber()) + await advanceTime(config.warmupPeriod.toNumber()) + await advanceTime(config.batchAuctionLength.toNumber()) // Run auctions - First Settle trades then Manage Funds // Will sell all balance of token2 const sellAmt2 = await token2.balanceOf(backingManager.address) - await snapshotGasCost(backingManager.settleTrade(token2.address)) - await snapshotGasCost(backingManager.manageTokens(registeredERC20s)) + await expect(backingManager.settleTrade(token2.address)).to.be.revertedWith('no trade open') + await snapshotGasCost(backingManager.rebalance(TradeKind.BATCH_AUCTION)) // Another call should not create any new auctions if still ongoing await expect(backingManager.settleTrade(token2.address)).to.be.revertedWith( 'cannot settle yet' ) - await snapshotGasCost(backingManager.manageTokens(registeredERC20s)) // Perform Mock Bids for the new Token (addr1 has balance) // Get minBuyAmt, we will have now surplus of backupToken1 @@ -4739,7 +4971,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start a new one to sell the new surplus of Backup Token 1 const requiredBkpToken: BigNumber = issueAmount.mul(bkpTokenRefAmt).div(BN_SCALE_FACTOR) @@ -4748,7 +4980,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Run auctions - First Settle trades then Manage Funds await snapshotGasCost(backingManager.settleTrade(token2.address)) - await snapshotGasCost(backingManager.manageTokens(registeredERC20s)) + await advanceTime(12) + await snapshotGasCost(backingManager.rebalance(TradeKind.BATCH_AUCTION)) // Perform Mock Bids for the new Token (addr1 has balance) // Assume fair price, get all of them @@ -4760,7 +4993,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start a new one to sell RSR for collateral const buyAmtBidRSR: BigNumber = requiredBkpToken.sub(minBuyAmtBkp1) @@ -4768,7 +5001,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Run auctions - First Settle trades then Manage Funds await snapshotGasCost(backingManager.settleTrade(backupToken1.address)) - await snapshotGasCost(backingManager.manageTokens(registeredERC20s)) + await advanceTime(12) + await snapshotGasCost(backingManager.rebalance(TradeKind.BATCH_AUCTION)) // Perform Mock Bids for RSR (addr1 has balance) // Assume fair price RSR = 1 get all of them - Leave a surplus of RSR to be returned @@ -4780,12 +5014,59 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) expect(await backingManager.tradesOpen()).to.equal(1) // End current auction await snapshotGasCost(backingManager.settleTrade(rsr.address)) expect(await backingManager.tradesOpen()).to.equal(0) }) + + it('rebalance() - DutchTrade ', async () => { + // Register Collateral + await assetRegistry.connect(owner).register(backupCollateral1.address) + + // Set backup configuration - USDT and aUSDT as backup + await basketHandler + .connect(owner) + .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [backupToken1.address]) + + // Perform stake + const stakeAmount: BigNumber = bn('10000e18') + await rsr.connect(addr1).approve(stRSR.address, stakeAmount) + await stRSR.connect(addr1).stake(stakeAmount) + + // Set Token2 to hard default - Reducing rate + await token2.setExchangeRate(fp('0.25')) + + // Mark Default - Perform basket switch + await assetRegistry.refresh() + await expect(basketHandler.refreshBasket()).to.emit(basketHandler, 'BasketSet') + await advanceTime(config.tradingDelay.toNumber()) + await advanceTime(config.warmupPeriod.toNumber()) + await advanceTime(config.dutchAuctionLength.toNumber()) + + // Run auctions - First Settle trades then Manage Funds + // Will sell all balance of token2 + await expect(backingManager.settleTrade(token2.address)).to.be.revertedWith('no trade open') + await snapshotGasCost(backingManager.rebalance(TradeKind.DUTCH_AUCTION)) + + // Another call should not create any new auctions if still ongoing + await expect(backingManager.settleTrade(token2.address)).to.be.revertedWith( + 'cannot settle yet' + ) + + // Bid + settle DutchTrade + const tradeAddr = await backingManager.trades(token2.address) + const trade = await ethers.getContractAt('DutchTrade', tradeAddr) + await backupToken1.connect(addr1).approve(trade.address, initialBal) + await snapshotGasCost(trade.connect(addr1).bid()) + + // Expect new trade started + expect(await backingManager.tradesOpen()).to.equal(1) + expect(await backingManager.trades(token2.address)).to.equal(ZERO_ADDRESS) + expect(await backingManager.trades(rsr.address)).to.not.equal(ZERO_ADDRESS) + await expect(backingManager.settleTrade(rsr.address)).to.be.revertedWith('cannot settle yet') + }) }) }) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 580dee5686..3e181f86fc 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -11,6 +11,7 @@ import { STRSR_DEST, ZERO_ADDRESS, CollateralStatus, + TradeKind, } from '../common/constants' import { expectEvents } from '../common/events' import { bn, divCeil, fp, near } from '../common/numbers' @@ -24,6 +25,7 @@ import { FacadeTest, GnosisMock, IAssetRegistry, + InvalidATokenFiatCollateralMock, MockV3Aggregator, RTokenAsset, StaticATokenMock, @@ -41,19 +43,20 @@ import { } from '../typechain' import { whileImpersonating } from './utils/impersonation' import snapshotGasCost from './utils/snapshotGasCost' -import { advanceTime, getLatestBlockTimestamp } from './utils/time' +import { advanceTime, advanceToTimestamp, getLatestBlockTimestamp } from './utils/time' import { withinQuad } from './utils/matchers' import { Collateral, defaultFixture, Implementation, IMPLEMENTATION, + REVENUE_HIDING, ORACLE_ERROR, ORACLE_TIMEOUT, PRICE_TIMEOUT, } from './fixtures' import { expectRTokenPrice, setOraclePrice } from './utils/oracles' -import { expectTrade, getTrade } from './utils/trades' +import { dutchBuyAmount, expectTrade, getTrade } from './utils/trades' import { useEnv } from '#/utils/env' import { mintCollaterals } from './utils/tokens' @@ -371,16 +374,16 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should not trade if paused', async () => { await main.connect(owner).pauseTrading() - await expect(rsrTrader.manageToken(ZERO_ADDRESS)).to.be.revertedWith( - 'frozen or trading paused' - ) + await expect( + rsrTrader.manageToken(ZERO_ADDRESS, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('frozen or trading paused') }) it('Should not trade if frozen', async () => { await main.connect(owner).freezeShort() - await expect(rTokenTrader.manageToken(ZERO_ADDRESS)).to.be.revertedWith( - 'frozen or trading paused' - ) + await expect( + rTokenTrader.manageToken(ZERO_ADDRESS, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('frozen or trading paused') }) it('Should not claim rewards if paused', async () => { @@ -437,7 +440,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const rtokenPrice = await basketHandler.price() const realRtokenPrice = rtokenPrice.low.add(rtokenPrice.high).div(2) const minBuyAmt = await toMinBuyAmt(issueAmount, fp('0.7'), realRtokenPrice) - await expect(rTokenTrader.manageToken(token0.address)) + await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)) .to.emit(rTokenTrader, 'TradeStarted') .withArgs(anyValue, token0.address, rToken.address, issueAmount, withinQuad(minBuyAmt)) }) @@ -445,9 +448,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should not launch revenue auction if UNPRICED', async () => { await advanceTime(ORACLE_TIMEOUT.toString()) await rsr.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await expect(rTokenTrader.manageToken(rsr.address)).to.be.revertedWith( - 'buy asset price unknown' - ) + await expect( + rTokenTrader.manageToken(rsr.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('buy asset price unknown') }) it('Should launch revenue auction if DISABLED with nonzero minBuyAmount', async () => { @@ -456,7 +459,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await advanceTime((await collateral0.delayUntilDefault()).toString()) expect(await collateral0.status()).to.equal(CollateralStatus.DISABLED) await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await expect(rTokenTrader.manageToken(token0.address)).to.emit(rTokenTrader, 'TradeStarted') + await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) // Trade should have extremely nonzero worst-case price const trade = await getTrade(rTokenTrader, token0.address) @@ -526,7 +532,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: compToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -534,7 +540,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: compToken.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -555,7 +561,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expect(rTokenTrader.settleTrade(aaveToken.address)).to.not.emit // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -919,7 +925,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: aaveToken.address, buy: rsr.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.auctionLength), + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -927,7 +933,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: aaveToken.address, buy: rToken.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.auctionLength), + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -935,7 +941,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await aaveToken.balanceOf(gnosis.address)).to.equal(rewardAmountAAVE) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction by minting the buy tokens (in this case RSR and RToken) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -1078,7 +1084,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: aaveToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1109,7 +1115,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions const remainderSellAmt = rewardAmountAAVE.sub(sellAmt) @@ -1145,7 +1151,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: aaveToken.address, buy: rsr.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.auctionLength), + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -1162,7 +1168,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close auctions await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1280,7 +1286,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: aaveToken.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1301,7 +1307,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Another call will create a new auction and close existing await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1336,7 +1342,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: aaveToken.address, buy: rToken.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.auctionLength), + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -1349,7 +1355,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Close auction await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -1381,7 +1387,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // StRSR expect(await rsr.balanceOf(stRSR.address)).to.equal(0) // Furnace - expect(await rToken.balanceOf(furnace.address)).to.equal(minBuyAmt.add(minBuyAmtRemainder)) + expect(await rToken.balanceOf(furnace.address)).to.be.closeTo( + minBuyAmt.add(minBuyAmtRemainder), + minBuyAmt.add(minBuyAmtRemainder).div(bn('1e4')) // melting + ) }) it('Should handle large auctions using maxTradeVolume with revenue split RSR/RToken', async () => { @@ -1484,7 +1493,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: aaveToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1492,12 +1501,12 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: aaveToken.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) // Advance time till auctions ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -1564,7 +1573,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Run final auction until all funds are converted // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, minBuyAmtRemainder) @@ -1599,7 +1608,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { minBuyAmt.add(minBuyAmtRemainder), 15 ) - expect(await rToken.balanceOf(furnace.address)).to.be.closeTo(minBuyAmtRToken, 15) + expect(await rToken.balanceOf(furnace.address)).to.be.closeTo( + minBuyAmtRToken, + minBuyAmtRToken.div(bn('1e4')) // melting + ) }) it('Should not distribute if paused or frozen', async () => { @@ -1787,7 +1799,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) // Handout AAVE tokens to Traders - await backingManager.manageTokens([aaveToken.address]) + await backingManager.forwardRevenue([aaveToken.address]) // Check funds sent to traders expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader) @@ -1797,9 +1809,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await setOraclePrice(rsrAsset.address, bn('0')) // Should revert - await expect(rsrTrader.manageToken(aaveToken.address)).to.be.revertedWith( - 'buy asset price unknown' - ) + await expect( + rsrTrader.manageToken(aaveToken.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('buy asset price unknown') // Funds still in Trader expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader) @@ -1869,7 +1881,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: aaveToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1877,12 +1889,12 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: aaveToken.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) // In order to force deactivation we provide an amount below minBuyAmt, this will represent for our tests an invalid behavior although in a real scenario would retrigger auction @@ -1980,11 +1992,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const sellAmtRToken: BigNumber = rewardAmountAAVE.sub(sellAmt) // Remainder // Attempt to run auctions - await backingManager.manageTokens([aaveToken.address]) - await expect(rsrTrader.manageToken(aaveToken.address)).to.be.revertedWith('broker disabled') - await expect(rTokenTrader.manageToken(aaveToken.address)).to.be.revertedWith( - 'broker disabled' - ) + await backingManager.forwardRevenue([aaveToken.address]) + await expect( + rsrTrader.manageToken(aaveToken.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('broker disabled') + await expect( + rTokenTrader.manageToken(aaveToken.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('broker disabled') // Check funds - remain in traders expect(await rsr.balanceOf(stRSR.address)).to.equal(0) @@ -2108,7 +2122,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: aaveToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2116,7 +2130,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: aaveToken.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -2124,7 +2138,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await aaveToken.balanceOf(gnosis.address)).to.equal(rewardAmountAAVE) // Advance time till auctions ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -2266,6 +2280,186 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { rewardAmountAAVE.mul(2).add(1) ) }) + + it('Should not revert on invalid claim logic', async () => { + // Here the aToken is going to have an invalid claimRewards on its asset, + // while the cToken will have it on the ERC20 + + // cToken + rewardAmountCOMP = bn('0.5e18') + await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + await token3.setRevertClaimRewards(true) + + // Setup a new aToken with invalid claim data + const ATokenCollateralFactory = await ethers.getContractFactory( + 'InvalidATokenFiatCollateralMock' + ) + const chainlinkFeed = ( + await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) + ) + + const invalidATokenCollateral: InvalidATokenFiatCollateralMock = < + InvalidATokenFiatCollateralMock + >((await ATokenCollateralFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: chainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: token2.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.05'), + delayUntilDefault: await collateral2.delayUntilDefault(), + }, + REVENUE_HIDING + )) as unknown) + + // Perform asset swap + await assetRegistry.connect(owner).swapRegistered(invalidATokenCollateral.address) + + // Setup new basket with the invalid AToken + await basketHandler.connect(owner).setPrimeBasket([token2.address], [fp('1')]) + + // Switch basket + await basketHandler.connect(owner).refreshBasket() + + rewardAmountAAVE = bn('0.5e18') + + // AAVE Rewards + await token2.setRewards(backingManager.address, rewardAmountAAVE) + + // Claim and sweep rewards -- should succeed + await expect(backingManager.claimRewards()).not.to.be.reverted + }) + + context('DutchTrade', () => { + const auctionLength = 300 + beforeEach(async () => { + await broker.connect(owner).setDutchAuctionLength(auctionLength) + }) + + it('Should not trade when paused', async () => { + await main.connect(owner).pauseTrading() + await expect( + rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + ).to.be.revertedWith('frozen or trading paused') + }) + + it('Should not trade when frozen', async () => { + await main.connect(owner).freezeLong() + await expect( + rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + ).to.be.revertedWith('frozen or trading paused') + }) + + it('Should trade if issuance paused', async () => { + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await main.connect(owner).pauseIssuance() + await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + }) + + it('Should only run 1 trade per ERC20 at a time', async () => { + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + await expect( + rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + ).to.be.revertedWith('trade open') + await expect( + rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('trade open') + + // Other ERC20 should be able to open trade + await token1.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await rTokenTrader.manageToken(token1.address, TradeKind.DUTCH_AUCTION) + }) + + it('Should quote piecewise-falling price correctly throughout entirety of auction', async () => { + issueAmount = issueAmount.div(2) + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + const trade = await ethers.getContractAt( + 'DutchTrade', + await rTokenTrader.trades(token0.address) + ) + await rToken.connect(addr1).approve(trade.address, initialBal) + + const start = await trade.startTime() + const end = await trade.endTime() + + // Simulate 5 minutes of blocks, should swap at right price each time + for (let now = await getLatestBlockTimestamp(); now < end; now += 12) { + const actual = await trade.connect(addr1).bidAmount(now) + const expected = await dutchBuyAmount( + fp(now - start).div(end - start), + rTokenAsset.address, + collateral0.address, + issueAmount, + config.minTradeVolume, + config.maxTradeSlippage + ) + expect(actual).to.equal(expected) + + const staticResult = await trade.connect(addr1).callStatic.bid() + expect(staticResult).to.equal(expected) + await advanceToTimestamp((await getLatestBlockTimestamp()) + 12) + } + }) + + it('Should handle no bid case correctly', async () => { + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + const trade = await ethers.getContractAt( + 'DutchTrade', + await rTokenTrader.trades(token0.address) + ) + await rToken.connect(addr1).approve(trade.address, initialBal) + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 1) + await expect( + trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + ).to.be.revertedWith('auction over') + await expect(trade.connect(addr1).bid()).be.revertedWith('auction over') + + // Should be able to settle + await expect(trade.settle()).to.be.revertedWith('only origin can settle') + await expect(rTokenTrader.settleTrade(token0.address)) + .to.emit(rTokenTrader, 'TradeSettled') + .withArgs(trade.address, token0.address, rToken.address, 0, 0) + + // Should NOT start another auction, since caller was not DutchTrade + expect(await backingManager.tradesOpen()).to.equal(0) + }) + + it('Should bid in final second of auction and not launch another auction', async () => { + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + const trade = await ethers.getContractAt( + 'DutchTrade', + await rTokenTrader.trades(token0.address) + ) + await rToken.connect(addr1).approve(trade.address, initialBal) + + // Snipe auction at 1s left + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 3) + await trade.connect(addr1).bid() + expect(await trade.canSettle()).to.equal(false) + expect(await trade.status()).to.equal(2) // Status.CLOSED + expect(await trade.bidder()).to.equal(addr1.address) + expect(await token0.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(4))) + + const expected = await dutchBuyAmount( + fp('299').div(300), // after all txs in this test, will be left at 299/300s + rTokenAsset.address, + collateral0.address, + issueAmount, + config.minTradeVolume, + config.maxTradeSlippage + ) + expect(await rTokenTrader.tradesOpen()).to.equal(0) + expect(await rToken.balanceOf(rTokenTrader.address)).to.be.closeTo(0, 100) + expect(await rToken.balanceOf(furnace.address)).to.equal(expected) + }) + }) }) context('With simple basket of ATokens and CTokens', function () { @@ -2362,7 +2556,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: token2.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2370,7 +2564,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: token2.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -2382,7 +2576,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction by minting the buy tokens (in this case RSR and RToken) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -2522,7 +2716,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: token2.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2530,7 +2724,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: token2.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -2545,7 +2739,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await token2.balanceOf(rTokenTrader.address)).to.equal(0) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction by minting the buy tokens (in this case RSR and RToken) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -2611,11 +2805,11 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await token2.setExchangeRate(fp('1.2')) await expect( - backingManager.manageTokens([token2.address, token2.address]) + backingManager.forwardRevenue([token2.address, token2.address]) ).to.be.revertedWith('duplicate tokens') await expect( - backingManager.manageTokens([ + backingManager.forwardRevenue([ token2.address, token2.address, token2.address, @@ -2624,11 +2818,11 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ).to.be.revertedWith('duplicate tokens') await expect( - backingManager.manageTokens([token2.address, token1.address, token2.address]) + backingManager.forwardRevenue([token2.address, token1.address, token2.address]) ).to.be.revertedWith('duplicate tokens') await expect( - backingManager.manageTokens([ + backingManager.forwardRevenue([ token1.address, token2.address, token3.address, @@ -2637,7 +2831,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ).to.be.revertedWith('duplicate tokens') await expect( - backingManager.manageTokens([ + backingManager.forwardRevenue([ token1.address, token2.address, token3.address, @@ -2646,66 +2840,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ).to.be.revertedWith('duplicate tokens') // Remove duplicates, should work - await expect(backingManager.manageTokens([token1.address, token2.address, token3.address])) - .to.not.be.reverted - }) - - it('Should not overspend if backingManager.manageTokensSortedOrder() is called with duplicate tokens', async () => { - expect(await basketHandler.fullyCollateralized()).to.be.true - - // Change redemption rate for AToken and CToken to double - await token2.setExchangeRate(fp('1.2')) - await expect( - backingManager.manageTokensSortedOrder([token2.address, token2.address]) - ).to.be.revertedWith('duplicate/unsorted tokens') - - await expect( - backingManager.manageTokensSortedOrder([ - token2.address, - token2.address, - token2.address, - token2.address, - ]) - ).to.be.revertedWith('duplicate/unsorted tokens') - - await expect( - backingManager.manageTokensSortedOrder([token2.address, token1.address, token2.address]) - ).to.be.revertedWith('duplicate/unsorted tokens') - - await expect( - backingManager.manageTokensSortedOrder([ - token1.address, - token2.address, - token3.address, - token2.address, - ]) - ).to.be.revertedWith('duplicate/unsorted tokens') - - await expect( - backingManager.manageTokensSortedOrder([ - token1.address, - token2.address, - token3.address, - token3.address, - ]) - ).to.be.revertedWith('duplicate/unsorted tokens') - - // Remove duplicates but unsort - const sorted = [token1.address, token2.address, token3.address].sort((a, b) => { - const x = BigNumber.from(a) - const y = BigNumber.from(b) - if (x.lt(y)) return -1 - if (x.gt(y)) return 1 - return 0 - }) - const unsorted = [sorted[2], sorted[0], sorted[1]] - await expect(backingManager.manageTokensSortedOrder(unsorted)).to.be.revertedWith( - 'duplicate/unsorted tokens' - ) - - // Remove duplicates and sort, should work - await expect(backingManager.manageTokensSortedOrder(sorted)).to.not.be.reverted + backingManager.forwardRevenue([token1.address, token2.address, token3.address]) + ).to.not.be.reverted }) it('Should mint RTokens when collateral appreciates and handle revenue auction correctly - Even quantity', async () => { @@ -2780,7 +2917,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: rToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2793,7 +2930,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction - will not start new one await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -2967,7 +3104,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: rToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -2975,7 +3112,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: token2.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -2983,7 +3120,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: token2.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), }) @@ -3011,7 +3148,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should start a new one with same amount await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -3109,7 +3246,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await token2.connect(owner).mint(backingManager.address, mintAmt) await token3.connect(owner).mint(backingManager.address, mintAmt) - await expect(backingManager.manageTokens([])).revertedWith('BU rate out of range') + await expect(backingManager.forwardRevenue([])).revertedWith('BU rate out of range') }) }) }) @@ -3212,12 +3349,12 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await backingManager.claimRewards() // Manage Funds - await backingManager.manageTokens([compToken.address]) - await snapshotGasCost(rsrTrader.manageToken(compToken.address)) - await snapshotGasCost(rTokenTrader.manageToken(compToken.address)) + await backingManager.forwardRevenue([compToken.address]) + await snapshotGasCost(rsrTrader.manageToken(compToken.address, TradeKind.BATCH_AUCTION)) + await snapshotGasCost(rTokenTrader.manageToken(compToken.address, TradeKind.BATCH_AUCTION)) // Advance time till auctions ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -3244,12 +3381,12 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await snapshotGasCost(rTokenTrader.settleTrade(compToken.address)) // Manage Funds - await snapshotGasCost(rsrTrader.manageToken(compToken.address)) - await snapshotGasCost(rTokenTrader.manageToken(compToken.address)) + await snapshotGasCost(rsrTrader.manageToken(compToken.address, TradeKind.BATCH_AUCTION)) + await snapshotGasCost(rTokenTrader.manageToken(compToken.address, TradeKind.BATCH_AUCTION)) // Run final auction until all funds are converted // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, minBuyAmtRemainder) diff --git a/test/Upgradeability.test.ts b/test/Upgradeability.test.ts index 6f8f3a9721..f72ce7e462 100644 --- a/test/Upgradeability.test.ts +++ b/test/Upgradeability.test.ts @@ -18,6 +18,7 @@ import { BrokerP1V2, DistributorP1, DistributorP1V2, + DutchTrade, ERC20Mock, FurnaceP1, FurnaceP1V2, @@ -86,7 +87,8 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { let BasketHandlerFactory: ContractFactory let DistributorFactory: ContractFactory let BrokerFactory: ContractFactory - let TradeFactory: ContractFactory + let GnosisTradeFactory: ContractFactory + let DutchTradeFactory: ContractFactory let StRSRFactory: ContractFactory let notWallet: Wallet @@ -138,7 +140,8 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { BasketHandlerFactory = await ethers.getContractFactory('BasketHandlerP1') DistributorFactory = await ethers.getContractFactory('DistributorP1') BrokerFactory = await ethers.getContractFactory('BrokerP1') - TradeFactory = await ethers.getContractFactory('GnosisTrade') + GnosisTradeFactory = await ethers.getContractFactory('GnosisTrade') + DutchTradeFactory = await ethers.getContractFactory('DutchTrade') StRSRFactory = await ethers.getContractFactory('StRSRP1Votes') // Import deployed proxies @@ -249,11 +252,19 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { }) it('Should deploy valid implementation - Broker / Trade', async () => { - const trade: GnosisTrade = await TradeFactory.deploy() + const gnosisTrade: GnosisTrade = await GnosisTradeFactory.deploy() + const dutchTrade: DutchTrade = await DutchTradeFactory.deploy() const newBroker: BrokerP1 = await upgrades.deployProxy( BrokerFactory, - [main.address, gnosis.address, trade.address, config.auctionLength], + [ + main.address, + gnosis.address, + gnosisTrade.address, + config.batchAuctionLength, + dutchTrade.address, + config.dutchAuctionLength, + ], { initializer: 'init', kind: 'uups', @@ -262,7 +273,8 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { await newBroker.deployed() expect(await newBroker.gnosis()).to.equal(gnosis.address) - expect(await newBroker.auctionLength()).to.equal(config.auctionLength) + expect(await newBroker.batchAuctionLength()).to.equal(config.batchAuctionLength) + expect(await newBroker.dutchAuctionLength()).to.equal(config.dutchAuctionLength) expect(await newBroker.disabled()).to.equal(false) expect(await newBroker.main()).to.equal(main.address) }) @@ -519,7 +531,7 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { // Check state is preserved expect(await brokerV2.gnosis()).to.equal(gnosis.address) - expect(await brokerV2.auctionLength()).to.equal(config.auctionLength) + expect(await brokerV2.batchAuctionLength()).to.equal(config.batchAuctionLength) expect(await brokerV2.disabled()).to.equal(false) expect(await brokerV2.main()).to.equal(main.address) diff --git a/test/ZTradingExtremes.test.ts b/test/ZTradingExtremes.test.ts index 0f89cef3c1..4a22c27806 100644 --- a/test/ZTradingExtremes.test.ts +++ b/test/ZTradingExtremes.test.ts @@ -278,7 +278,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, } // Advance time till auction ends - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) } } @@ -347,7 +347,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, // Mint any excess possible before increasing exchange rate to avoid blowing through max BU exchange rate // Explanation: For low-decimal tokens it's possible to begin overcollateralized when // the amount transferred in on RToken minting is 1 attoToken - await backingManager.manageTokens([]) + await backingManager.forwardRevenue([]) // === Execution === @@ -573,7 +573,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, } // Advance time till auction ends - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) uncollateralized = !(await basketHandler.fullyCollateralized()) } diff --git a/test/__snapshots__/Broker.test.ts.snap b/test/__snapshots__/Broker.test.ts.snap index 56bcf28e86..561e9b8d22 100644 --- a/test/__snapshots__/Broker.test.ts.snap +++ b/test/__snapshots__/Broker.test.ts.snap @@ -1,11 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`BrokerP1 contract #fast Gas Reporting Initialize Trade 1`] = `451349`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `233033`; -exports[`BrokerP1 contract #fast Gas Reporting Open Trade 1`] = `551875`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `350111`; -exports[`BrokerP1 contract #fast Gas Reporting Open Trade 2`] = `539713`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `352247`; -exports[`BrokerP1 contract #fast Gas Reporting Open Trade 3`] = `541851`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `354385`; -exports[`BrokerP1 contract #fast Gas Reporting Settle Trade 1`] = `113056`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63429`; + +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Initialize Trade 1`] = `451337`; + +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 1`] = `552332`; + +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 2`] = `540170`; + +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 3`] = `542308`; + +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Settle Trade 1`] = `113056`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index bf151f3b37..16319f1692 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `63713`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1386334`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1667295`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1499583`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `386392`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1754914`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `184737`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `1648298`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1699249`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `184745`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184833`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 7`] = `1737343`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1789003`; -exports[`Recollateralization - P1 Gas Reporting Settle Trades / Manage Funds 8`] = `212855`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `212933`; diff --git a/test/fixtures.ts b/test/fixtures.ts index ac40a9a4cc..4cf3cbf432 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -20,6 +20,7 @@ import { ERC20Mock, DeployerP0, DeployerP1, + DutchTrade, FacadeRead, FacadeAct, FacadeTest, @@ -429,7 +430,8 @@ export const defaultFixture: Fixture = async function (): Promis unstakingDelay: bn('1209600'), // 2 weeks warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) - auctionLength: bn('900'), // 15 minutes + batchAuctionLength: bn('900'), // 15 minutes + dutchAuctionLength: bn('600'), // 10 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { @@ -532,8 +534,11 @@ export const defaultFixture: Fixture = async function (): Promis const FurnaceImplFactory: ContractFactory = await ethers.getContractFactory('FurnaceP1') const furnaceImpl: FurnaceP1 = await FurnaceImplFactory.deploy() - const TradeImplFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const tradeImpl: GnosisTrade = await TradeImplFactory.deploy() + const GnosisTradeImplFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const gnosisTrade: GnosisTrade = await GnosisTradeImplFactory.deploy() + + const DutchTradeImplFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + const dutchTrade: DutchTrade = await DutchTradeImplFactory.deploy() const BrokerImplFactory: ContractFactory = await ethers.getContractFactory('BrokerP1') const brokerImpl: BrokerP1 = await BrokerImplFactory.deploy() @@ -559,7 +564,10 @@ export const defaultFixture: Fixture = async function (): Promis rsrTrader: revTraderImpl.address, rTokenTrader: revTraderImpl.address, }, - trade: tradeImpl.address, + trading: { + gnosisTrade: gnosisTrade.address, + dutchTrade: dutchTrade.address, + }, } const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP1') diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 2bc56bee98..96596a6721 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -18,6 +18,7 @@ import { IConfig, networkConfig } from '../../common/configuration' import { BN_SCALE_FACTOR, CollateralStatus, + TradeKind, QUEUE_START, MAX_UINT48, MAX_UINT192, @@ -147,7 +148,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await token0.connect(owner).mint(addr2.address, issueAmount.mul(1e9)) // Create auction - await expect(backingManager.manageTokens([])) + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)) .to.emit(backingManager, 'TradeStarted') .withArgs(anyValue, rsr.address, token0.address, anyValue, withinQuad(buyAmt)) @@ -162,7 +163,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await expectTrade(backingManager, { sell: rsr.address, buy: token0.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: auctionId, }) @@ -175,7 +176,9 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function // Check Gnosis expect(await rsr.balanceOf(easyAuction.address)).to.equal(sellAmt) - await expect(backingManager.manageTokens([])).to.not.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already rebalancing' + ) // Auction should not be able to be settled await expect(easyAuction.settleAuction(auctionId)).to.be.reverted @@ -205,7 +208,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function it('no volume', async () => { // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction, should restart await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ @@ -238,7 +241,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(rsr.address), [ @@ -274,7 +277,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(rsr.address), [ @@ -303,7 +306,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function .placeSellOrders(auctionId, [sellAmt], [bidAmt], [QUEUE_START], ethers.constants.HashZero) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(rsr.address), [ @@ -333,7 +336,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function .placeSellOrders(auctionId, [sellAmt], [bidAmt], [QUEUE_START], ethers.constants.HashZero) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(rsr.address), [ @@ -363,7 +366,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function .placeSellOrders(auctionId, [sellAmt], [bidAmt], [QUEUE_START], ethers.constants.HashZero) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction -- should trade at lower worst-case price await expectEvents(backingManager.settleTrade(rsr.address), [ @@ -407,7 +410,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(rsr.address), [ @@ -431,7 +434,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function it('/w non-trivial prices', async () => { // End first auction, since it is at old prices - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) await backingManager.settleTrade(rsr.address) // $0.007 RSR at $4k ETH @@ -443,7 +446,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function buyAmt = issueAmount.add(1) // rounding up from prepareTradeToCoverDeficit // Start next auction - await expectEvents(backingManager.manageTokens([]), [ + await expectEvents(backingManager.rebalance(TradeKind.BATCH_AUCTION), [ { contract: backingManager, name: 'TradeStarted', @@ -469,7 +472,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(rsr.address), [ @@ -498,7 +501,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function .placeSellOrders(auctionId, [sellAmt], [bidAmt], [QUEUE_START], ethers.constants.HashZero) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Settle auction directly await easyAuction.connect(addr2).settleAuction(auctionId) @@ -574,7 +577,10 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await advanceTime(Number(config.warmupPeriod) + 1) // Should launch auction for token1 - await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) const auctionTimestamp: number = await getLatestBlockTimestamp() const auctionId = await getAuctionId(backingManager, token0.address) @@ -583,7 +589,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: auctionId, }) const trade = await getTrade(backingManager, token0.address) @@ -597,7 +603,9 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function // Check Gnosis expect(await token0.balanceOf(easyAuction.address)).to.equal(issueAmount) - await expect(backingManager.manageTokens([])).to.not.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already rebalancing' + ) // Auction should not be able to be settled await expect(easyAuction.settleAuction(auctionId)).to.be.reverted @@ -631,7 +639,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(token0.address), [ @@ -672,7 +680,10 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await advanceTime(Number(config.warmupPeriod) + 1) // Should launch auction for token1 - await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) const auctionTimestamp: number = await getLatestBlockTimestamp() const auctionId = await getAuctionId(backingManager, token0.address) @@ -681,7 +692,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await expectTrade(backingManager, { sell: token0.address, buy: token1.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: auctionId, }) const trade = await getTrade(backingManager, token0.address) @@ -695,7 +706,9 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function // Check Gnosis expect(await token0.balanceOf(easyAuction.address)).to.be.closeTo(issueAmount, 1) - await expect(backingManager.manageTokens([])).to.not.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already rebalancing' + ) // Auction should not be able to be settled await expect(easyAuction.settleAuction(auctionId)).to.be.reverted @@ -715,7 +728,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction await expectEvents(backingManager.settleTrade(token0.address), [ @@ -775,8 +788,14 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await main.connect(owner).grantRole(PAUSER, owner.address) await main.connect(owner).unpauseTrading() await main.connect(owner).unpauseIssuance() - - await broker.init(main.address, easyAuction.address, ONE_ADDRESS, config.auctionLength) + await broker.init( + main.address, + easyAuction.address, + ONE_ADDRESS, + config.batchAuctionLength, + ONE_ADDRESS, + config.dutchAuctionLength + ) const sellTok = await ERC20Factory.deploy('Sell Token', 'SELL', sellTokDecimals) const buyTok = await ERC20Factory.deploy('Buy Token', 'BUY', buyTokDecimals) const sellColl = await CollFactory.deploy({ @@ -811,7 +830,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function // First simulate opening the trade to get where it will be deployed await sellTok.connect(addr1).approve(broker.address, auctionSellAmt) - const tradeAddr = await broker.connect(addr1).callStatic.openTrade({ + const tradeAddr = await broker.connect(addr1).callStatic.openTrade(TradeKind.BATCH_AUCTION, { sell: sellColl.address, buy: buyColl.address, sellAmount: auctionSellAmt, @@ -819,7 +838,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function }) // Start auction! - await broker.connect(addr1).openTrade({ + await broker.connect(addr1).openTrade(TradeKind.BATCH_AUCTION, { sell: sellColl.address, buy: buyColl.address, sellAmount: auctionSellAmt, @@ -854,7 +873,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function } // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // End Auction await expect(trade.connect(addr1).settle()).to.not.emit(broker, 'DisabledSet') diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index eb970a34aa..72a82d89d3 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -22,6 +22,7 @@ import { DeployerP0, DeployerP1, DistributorP1, + DutchTrade, EasyAuction, ERC20Mock, EURFiatCollateral, @@ -635,7 +636,8 @@ export const defaultFixture: Fixture = async function (): Promis unstakingDelay: bn('1209600'), // 2 weeks warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) - auctionLength: bn('900'), // 15 minutes + batchAuctionLength: bn('900'), // 15 minutes + dutchAuctionLength: bn('600'), // 10 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { @@ -722,8 +724,11 @@ export const defaultFixture: Fixture = async function (): Promis const FurnaceImplFactory: ContractFactory = await ethers.getContractFactory('FurnaceP1') const furnaceImpl: FurnaceP1 = await FurnaceImplFactory.deploy() - const TradeImplFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const tradeImpl: GnosisTrade = await TradeImplFactory.deploy() + const GnosisTradeImplFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const gnosisTrade: GnosisTrade = await GnosisTradeImplFactory.deploy() + + const DutchTradeImplFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + const dutchTrade: DutchTrade = await DutchTradeImplFactory.deploy() const BrokerImplFactory: ContractFactory = await ethers.getContractFactory('BrokerP1') const brokerImpl: BrokerP1 = await BrokerImplFactory.deploy() @@ -737,7 +742,6 @@ export const defaultFixture: Fixture = async function (): Promis // Setup Implementation addresses const implementations: IImplementations = { main: mainImpl.address, - trade: tradeImpl.address, components: { assetRegistry: assetRegImpl.address, backingManager: backingMgrImpl.address, @@ -750,6 +754,10 @@ export const defaultFixture: Fixture = async function (): Promis rToken: rTokenImpl.address, stRSR: stRSRImpl.address, }, + trading: { + gnosisTrade: gnosisTrade.address, + dutchTrade: dutchTrade.address, + }, } const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP1') diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index d04693ec54..dae6d7d079 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -26,6 +26,7 @@ import { StaticATokenMock, TestIBackingManager, TestIRToken, + UnpricedAppreciatingFiatCollateralMock, USDCMock, WETH9, UnpricedAppreciatingFiatCollateralMock, diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index d7c7701077..3b044abaf4 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -118,7 +118,8 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) - auctionLength: bn('900'), // 15 minutes + batchAuctionLength: bn('900'), // 15 minutes + dutchAuctionLength: bn('600'), // 10 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index ecfdf11fcc..1984a0d5d4 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -119,7 +119,8 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi unstakingDelay: bn('1209600'), // 2 weeks warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) - auctionLength: bn('900'), // 15 minutes + batchAuctionLength: bn('900'), // 15 minutes + dutchAuctionLength: bn('600'), // 10 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { diff --git a/test/plugins/individual-collateral/fixtures.ts b/test/plugins/individual-collateral/fixtures.ts index 5e6e9675e4..c44b23b67c 100644 --- a/test/plugins/individual-collateral/fixtures.ts +++ b/test/plugins/individual-collateral/fixtures.ts @@ -13,6 +13,7 @@ import { DeployerP0, DeployerP1, DistributorP1, + DutchTrade, ERC20Mock, FacadeRead, FacadeAct, @@ -157,8 +158,11 @@ export const getDefaultFixture = async function (salt: string) { const FurnaceImplFactory: ContractFactory = await ethers.getContractFactory('FurnaceP1') const furnaceImpl: FurnaceP1 = await FurnaceImplFactory.deploy() - const TradeImplFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') - const tradeImpl: GnosisTrade = await TradeImplFactory.deploy() + const GnosisTradeImplFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') + const gnosisTrade: GnosisTrade = await GnosisTradeImplFactory.deploy() + + const DutchTradeImplFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') + const dutchTrade: DutchTrade = await DutchTradeImplFactory.deploy() const BrokerImplFactory: ContractFactory = await ethers.getContractFactory('BrokerP1') const brokerImpl: BrokerP1 = await BrokerImplFactory.deploy() @@ -172,7 +176,7 @@ export const getDefaultFixture = async function (salt: string) { // Setup Implementation addresses const implementations: IImplementations = { main: mainImpl.address, - trade: tradeImpl.address, + trading: { gnosisTrade: gnosisTrade.address, dutchTrade: dutchTrade.address }, components: { assetRegistry: assetRegImpl.address, backingManager: backingMgrImpl.address, @@ -186,7 +190,6 @@ export const getDefaultFixture = async function (salt: string) { stRSR: stRSRImpl.address, }, } - const DeployerFactory: ContractFactory = await ethers.getContractFactory('DeployerP1') deployer = ( await DeployerFactory.deploy(rsr.address, gnosis.address, rsrAsset.address, implementations) diff --git a/test/scenario/BadCollateralPlugin.test.ts b/test/scenario/BadCollateralPlugin.test.ts index 2202378e99..01892c2429 100644 --- a/test/scenario/BadCollateralPlugin.test.ts +++ b/test/scenario/BadCollateralPlugin.test.ts @@ -5,7 +5,7 @@ import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { IConfig } from '../../common/configuration' import { expectEvents } from '../../common/events' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { bn, fp, divCeil } from '../../common/numbers' import { BadCollateralPlugin, @@ -190,7 +190,10 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { }) it('should use RSR to recollateralize, breaking the economic model fundamentally', async () => { - await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) const trade = await getTrade(backingManager, rsr.address) expect(await trade.sell()).to.equal(rsr.address) expect(await trade.buy()).to.equal(token0.address) @@ -265,12 +268,10 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { expect(await basketHandler.fullyCollateralized()).to.equal(true) // Should not launch auctions or create revenue - await expectEvents(backingManager.manageTokens([token0.address]), [ - { - contract: backingManager, - name: 'TradeStarted', - emitted: false, - }, + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' + ) + await expectEvents(backingManager.forwardRevenue([token0.address]), [ { contract: token0, name: 'Transfer', diff --git a/test/scenario/BadERC20.test.ts b/test/scenario/BadERC20.test.ts index f9ba9e8762..0b1b604252 100644 --- a/test/scenario/BadERC20.test.ts +++ b/test/scenario/BadERC20.test.ts @@ -5,6 +5,7 @@ import { expect } from 'chai' import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { IConfig } from '../../common/configuration' +import { TradeKind } from '../../common/constants' import { bn, divCeil, fp } from '../../common/numbers' import { BadERC20, @@ -216,7 +217,8 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { await expect(basketHandler.refreshBasket()) .to.emit(basketHandler, 'BasketSet') .withArgs(3, [backupToken.address], [fp('1')], false) - await expect(backingManager.manageTokens([])).to.be.reverted // can't catch No Decimals + await expect(backingManager.forwardRevenue([])).to.be.reverted // can't catch No Decimals + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.reverted // can't catch No Decimals }) it('should keep collateral working', async () => { @@ -254,7 +256,10 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) - await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) // Should be trading RSR for backup token const trade = await getTrade(backingManager, rsr.address) @@ -318,11 +323,11 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) - await expect(backingManager.manageTokens([])).to.be.revertedWith('censored') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith('censored') // Should work now await token0.setCensored(backingManager.address, false) - await backingManager.manageTokens([]) + await backingManager.rebalance(TradeKind.BATCH_AUCTION) }) it('should keep collateral working', async () => { @@ -364,7 +369,10 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) - await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) // Should be trading RSR for backup token const trade = await getTrade(backingManager, rsr.address) @@ -376,10 +384,10 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { it('should be able to process any uncensored assets already accumulated at RevenueTraders', async () => { await rToken.connect(addr1).transfer(rTokenTrader.address, issueAmt.div(2)) await rToken.connect(addr1).transfer(rsrTrader.address, issueAmt.div(2)) - await expect(rTokenTrader.manageToken(rToken.address)) + await expect(rTokenTrader.manageToken(rToken.address, TradeKind.BATCH_AUCTION)) .to.emit(rToken, 'Transfer') .withArgs(rTokenTrader.address, furnace.address, issueAmt.div(2)) - await expect(rsrTrader.manageToken(rToken.address)) + await expect(rsrTrader.manageToken(rToken.address, TradeKind.BATCH_AUCTION)) .to.emit(rsrTrader, 'TradeStarted') .withArgs( anyValue, diff --git a/test/scenario/ComplexBasket.test.ts b/test/scenario/ComplexBasket.test.ts index 5484924838..0dfc4a3b1c 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -616,7 +616,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: compToken.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -624,7 +624,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await compToken.balanceOf(gnosis.address)).to.equal(rewardAmount) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Perform Mock Bids for RSR and RToken (addr1 has balance) await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) @@ -868,7 +868,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: cUSDTokenVault.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -883,7 +883,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: cUSDTokenVault.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -926,7 +926,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auctions await rsr.connect(addr1).approve(gnosis.address, auctionbuyAmt2) @@ -1032,7 +1032,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: cWBTCVault.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('6'), }) @@ -1047,7 +1047,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: cWBTCVault.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('7'), }) @@ -1069,7 +1069,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auctions await rsr.connect(addr1).approve(gnosis.address, auctionbuyAmt5) @@ -1154,7 +1154,10 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { origAssetValue, point5Pct(origAssetValue) ) - expect(await rToken.totalSupply()).to.equal(currentTotalSupply) + expect(await rToken.totalSupply()).to.be.closeTo( + currentTotalSupply, + currentTotalSupply.div(bn('1e9')) // within 1 billionth + ) // Check destinations at this stage - RSR and RTokens already in StRSR and Furnace expect(await rsr.balanceOf(stRSR.address)).to.be.closeTo( @@ -1179,7 +1182,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(rsrTrader, { sell: cETHVault.address, buy: rsr.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('10'), }) @@ -1194,7 +1197,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(rTokenTrader, { sell: cETHVault.address, buy: rToken.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('11'), }) @@ -1216,7 +1219,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auctions await rsr.connect(addr1).approve(gnosis.address, auctionbuyAmt7) @@ -1277,7 +1280,10 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { origAssetValue, point5Pct(origAssetValue) ) - expect(await rToken.totalSupply()).to.equal(currentTotalSupply) + expect(await rToken.totalSupply()).to.be.closeTo( + currentTotalSupply, + currentTotalSupply.div(bn('1e5')) + ) // Check destinations at this stage - RSR and RTokens already in StRSR and Furnace expect(await rsr.balanceOf(stRSR.address)).to.be.closeTo( @@ -1387,7 +1393,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: cWBTCVault.address, buy: wbtc.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1402,7 +1408,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await cWBTCVault.balanceOf(backingManager.address)).to.equal(bn(0)) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction - Get 80% of value // 1600 cWTBC -> 80% = 1280 cWBTCVault @ 400 = 512K = 25 BTC @@ -1462,7 +1468,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: wbtc.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -1477,7 +1483,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await rsr.balanceOf(gnosis.address)).to.be.closeTo(sellAmtRSR, point5Pct(sellAmtRSR)) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction - Get all tokens await wbtc.connect(addr1).approve(gnosis.address, auctionBuyAmtRSR) @@ -1626,7 +1632,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: cETHVault.address, buy: weth.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) @@ -1644,7 +1650,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction - Get 8.2K ETH const auctionbuyAmt = fp('8200') @@ -1691,7 +1697,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: cETHVault.address, buy: weth.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) @@ -1712,7 +1718,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(await cETHVault.balanceOf(backingManager.address)).to.equal(bn(0)) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction // 438,000 cETHVault @ 12 = 5.25 M = approx 4255 ETH - Get 4400 WETH @@ -1786,7 +1792,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: weth.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('2'), }) @@ -1798,7 +1804,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(auctionBuyAmtRSR1).to.be.closeTo(buyAmtBidRSR1, point5Pct(buyAmtBidRSR1)) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction - Get 8500 WETH tokens // const auctionbuyAmtRSR1 = fp('8500') @@ -1870,7 +1876,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: rsr.address, buy: weth.address, - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('3'), }) @@ -1882,7 +1888,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(auctionBuyAmtRSR2).to.be.closeTo(buyAmtRSR2, point5Pct(buyAmtRSR2)) // Advance time till auction ended - await advanceTime(config.auctionLength.add(100).toString()) + await advanceTime(config.batchAuctionLength.add(100).toString()) // Mock auction - Get all tokens await weth.connect(addr1).approve(gnosis.address, auctionBuyAmtRSR2) diff --git a/test/scenario/EURT.test.ts b/test/scenario/EURT.test.ts index 46059f966e..2da3a3c75d 100644 --- a/test/scenario/EURT.test.ts +++ b/test/scenario/EURT.test.ts @@ -6,7 +6,7 @@ import { ethers } from 'hardhat' import { bn, fp } from '../../common/numbers' import { advanceTime } from '../utils/time' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { ERC20Mock, EURFiatCollateral, @@ -156,22 +156,32 @@ describe(`EUR fiatcoins (eg EURT) - P${IMPLEMENTATION}`, () => { it('should sell appreciating stable collateral and ignore eurt', async () => { await token0.setExchangeRate(fp('1.1')) // 10% appreciation - await expect(backingManager.manageTokens([token0.address])).to.not.emit( - backingManager, - 'TradeStarted' + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) + await backingManager.forwardRevenue([eurt.address, token0.address]) expect(await eurt.balanceOf(rTokenTrader.address)).to.equal(0) expect(await eurt.balanceOf(rsrTrader.address)).to.equal(0) - await expect(rTokenTrader.manageToken(eurt.address)).to.not.emit(rTokenTrader, 'TradeStarted') - await expect(rTokenTrader.manageToken(token0.address)).to.emit(rTokenTrader, 'TradeStarted') + await expect( + rTokenTrader.manageToken(eurt.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('0 balance') + await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) // RTokenTrader should be selling token0 and buying RToken const trade = await getTrade(rTokenTrader, token0.address) expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(rToken.address) - await expect(rsrTrader.manageToken(eurt.address)).to.not.emit(rsrTrader, 'TradeStarted') - await expect(rsrTrader.manageToken(token0.address)).to.emit(rsrTrader, 'TradeStarted') + await expect(rsrTrader.manageToken(eurt.address, TradeKind.BATCH_AUCTION)).to.be.revertedWith( + '0 balance' + ) + await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rsrTrader, + 'TradeStarted' + ) // RSRTrader should be selling token0 and buying RToken const trade2 = await getTrade(rsrTrader, token0.address) diff --git a/test/scenario/MaxBasketSize.test.ts b/test/scenario/MaxBasketSize.test.ts index 35ceb24075..8ee85a3c8d 100644 --- a/test/scenario/MaxBasketSize.test.ts +++ b/test/scenario/MaxBasketSize.test.ts @@ -425,7 +425,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: firstDefaultedToken.address, buy: backing[0], - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) }) @@ -563,7 +563,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { await expectTrade(backingManager, { sell: firstDefaultedToken.address, buy: backing[0], - endTime: auctionTimestamp + Number(config.auctionLength), + endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('0'), }) }) diff --git a/test/scenario/NestedRTokens.test.ts b/test/scenario/NestedRTokens.test.ts index e75eb3859b..1645114118 100644 --- a/test/scenario/NestedRTokens.test.ts +++ b/test/scenario/NestedRTokens.test.ts @@ -3,7 +3,7 @@ import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { ethers } from 'hardhat' -import { ONE_PERIOD, ZERO_ADDRESS, CollateralStatus } from '../../common/constants' +import { ONE_PERIOD, ZERO_ADDRESS, CollateralStatus, TradeKind } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { withinQuad } from '../utils/matchers' import { toSellAmt, toMinBuyAmt } from '../utils/trades' @@ -207,9 +207,8 @@ describe(`Nested RTokens - P${IMPLEMENTATION}`, () => { await two.assetRegistry.refresh() expect(await two.basketHandler.fullyCollateralized()).to.equal(true) expect(await two.basketHandler.status()).to.equal(CollateralStatus.SOUND) - await expect(two.backingManager.manageTokens([])).to.not.emit( - two.backingManager, - 'TradeStarted' + await expect(two.backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) // Launch recollateralization auction in inner RToken @@ -221,7 +220,7 @@ describe(`Nested RTokens - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, await one.backingManager.maxTradeSlippage() ) - await expect(one.backingManager.manageTokens([])) + await expect(one.backingManager.rebalance(TradeKind.BATCH_AUCTION)) .to.emit(one.backingManager, 'TradeStarted') .withArgs( anyValue, @@ -235,9 +234,8 @@ describe(`Nested RTokens - P${IMPLEMENTATION}`, () => { await two.assetRegistry.refresh() expect(await two.basketHandler.fullyCollateralized()).to.equal(true) expect(await two.basketHandler.status()).to.equal(CollateralStatus.SOUND) - await expect(two.backingManager.manageTokens([])).to.not.emit( - two.backingManager, - 'TradeStarted' + await expect(two.backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) // Prices should be aware diff --git a/test/scenario/NontrivialPeg.test.ts b/test/scenario/NontrivialPeg.test.ts index 4c4d229560..2cc2d1ab1b 100644 --- a/test/scenario/NontrivialPeg.test.ts +++ b/test/scenario/NontrivialPeg.test.ts @@ -3,7 +3,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { ethers } from 'hardhat' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { expectRTokenPrice, setOraclePrice } from '../utils/oracles' import { expectEvents } from '../../common/events' @@ -171,16 +171,14 @@ describe(`The peg (target/ref) should be arbitrary - P${IMPLEMENTATION}`, () => await backingManager.maxTradeSlippage(), config.minTradeVolume.mul((await assetRegistry.erc20s()).length) ) + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' + ) await expectEvents( backingManager .connect(owner) - .manageTokens([token0.address, token1.address, rsr.address, rToken.address]), + .forwardRevenue([token0.address, token1.address, rsr.address, rToken.address]), [ - { - contract: backingManager, - name: 'TradeStarted', - emitted: false, - }, { contract: token0, name: 'Transfer', @@ -203,6 +201,7 @@ describe(`The peg (target/ref) should be arbitrary - P${IMPLEMENTATION}`, () => }, ] ) + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(true) // sum of target amounts diff --git a/test/scenario/RevenueHiding.test.ts b/test/scenario/RevenueHiding.test.ts index 3beabf3ef1..21f46eb30f 100644 --- a/test/scenario/RevenueHiding.test.ts +++ b/test/scenario/RevenueHiding.test.ts @@ -5,7 +5,7 @@ import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { bn, fp, divCeil } from '../../common/numbers' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { CTokenVaultMock, CTokenFiatCollateral, @@ -251,9 +251,15 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME it('auction should be launched at low price ignoring revenueHiding', async () => { // Double exchange rate and launch auctions await cDAI.setExchangeRate(fp('2')) // double rate - await backingManager.manageTokens([cDAI.address]) // transfers tokens to Traders - await expect(rTokenTrader.manageToken(cDAI.address)).to.emit(rTokenTrader, 'TradeStarted') - await expect(rsrTrader.manageToken(cDAI.address)).to.emit(rsrTrader, 'TradeStarted') + await backingManager.forwardRevenue([cDAI.address]) // transfers tokens to Traders + await expect(rTokenTrader.manageToken(cDAI.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) + await expect(rsrTrader.manageToken(cDAI.address, TradeKind.BATCH_AUCTION)).to.emit( + rsrTrader, + 'TradeStarted' + ) // Auctions launched should be at discounted low price const t = await getTrade(rsrTrader, cDAI.address) diff --git a/test/scenario/SetProtocol.test.ts b/test/scenario/SetProtocol.test.ts index 614e7fc29f..e0d944e3e1 100644 --- a/test/scenario/SetProtocol.test.ts +++ b/test/scenario/SetProtocol.test.ts @@ -4,7 +4,7 @@ import { expect } from 'chai' import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { expectPrice, setOraclePrice } from '../utils/oracles' import { expectEvents } from '../../common/events' @@ -175,10 +175,13 @@ describe(`Linear combination of self-referential collateral - P${IMPLEMENTATION} await expectPrice(basketHandler.address, price, ORACLE_ERROR, true) expect(await basketHandler.fullyCollateralized()).to.equal(true) expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' + ) await expectEvents( backingManager .connect(owner) - .manageTokens([ + .forwardRevenue([ token0.address, token1.address, token2.address, @@ -186,11 +189,6 @@ describe(`Linear combination of self-referential collateral - P${IMPLEMENTATION} rToken.address, ]), [ - { - contract: backingManager, - name: 'TradeStarted', - emitted: false, - }, { contract: token0, name: 'Transfer', @@ -257,7 +255,7 @@ describe(`Linear combination of self-referential collateral - P${IMPLEMENTATION} // Should send donated token to revenue traders await expectEvents( - backingManager.manageTokens([ + backingManager.forwardRevenue([ token0.address, token1.address, token2.address, diff --git a/test/scenario/WBTC.test.ts b/test/scenario/WBTC.test.ts index 09781a15a3..da26e50680 100644 --- a/test/scenario/WBTC.test.ts +++ b/test/scenario/WBTC.test.ts @@ -6,7 +6,7 @@ import { ethers } from 'hardhat' import { bn, fp } from '../../common/numbers' import { advanceTime } from '../utils/time' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { ERC20Mock, IAssetRegistry, @@ -170,22 +170,32 @@ describe(`Non-fiat collateral (eg WBTC) - P${IMPLEMENTATION}`, () => { it('should sell appreciating stable collateral and ignore wbtc', async () => { await token0.setExchangeRate(fp('1.1')) // 10% appreciation - await expect(backingManager.manageTokens([token0.address])).to.not.emit( - backingManager, - 'TradeStarted' + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) + await backingManager.forwardRevenue([wbtc.address, token0.address]) expect(await wbtc.balanceOf(rTokenTrader.address)).to.equal(0) expect(await wbtc.balanceOf(rsrTrader.address)).to.equal(0) - await expect(rTokenTrader.manageToken(wbtc.address)).to.not.emit(rTokenTrader, 'TradeStarted') - await expect(rTokenTrader.manageToken(token0.address)).to.emit(rTokenTrader, 'TradeStarted') + await expect( + rTokenTrader.manageToken(wbtc.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('0 balance') + await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) // RTokenTrader should be selling token0 and buying RToken const trade = await getTrade(rTokenTrader, token0.address) expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(rToken.address) - await expect(rsrTrader.manageToken(wbtc.address)).to.not.emit(rsrTrader, 'TradeStarted') - await expect(rsrTrader.manageToken(token0.address)).to.emit(rsrTrader, 'TradeStarted') + await expect(rsrTrader.manageToken(wbtc.address, TradeKind.BATCH_AUCTION)).to.be.revertedWith( + '0 balance' + ) + await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rsrTrader, + 'TradeStarted' + ) // RSRTrader should be selling token0 and buying RToken const trade2 = await getTrade(rsrTrader, token0.address) @@ -200,7 +210,7 @@ describe(`Non-fiat collateral (eg WBTC) - P${IMPLEMENTATION}`, () => { // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) - await expect(backingManager.manageTokens([token0.address, wbtc.address])).to.emit( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( backingManager, 'TradeStarted' ) diff --git a/test/scenario/WETH.test.ts b/test/scenario/WETH.test.ts index 0d12c375fc..0e7015cedd 100644 --- a/test/scenario/WETH.test.ts +++ b/test/scenario/WETH.test.ts @@ -5,7 +5,7 @@ import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { bn, fp } from '../../common/numbers' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { CTokenMock, SelfReferentialCollateral, @@ -166,22 +166,32 @@ describe(`Self-referential collateral (eg ETH via WETH) - P${IMPLEMENTATION}`, ( it('should sell appreciating collateral and ignore WETH', async () => { await token0.setExchangeRate(fp('1.1')) // 10% appreciation - await expect(backingManager.manageTokens([token0.address])).to.not.emit( - backingManager, - 'TradeStarted' + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) + await backingManager.forwardRevenue([weth.address, token0.address]) expect(await weth.balanceOf(rTokenTrader.address)).to.equal(0) expect(await weth.balanceOf(rsrTrader.address)).to.equal(0) - await expect(rTokenTrader.manageToken(weth.address)).to.not.emit(rTokenTrader, 'TradeStarted') - await expect(rTokenTrader.manageToken(token0.address)).to.emit(rTokenTrader, 'TradeStarted') + await expect( + rTokenTrader.manageToken(weth.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('0 balance') + await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) // RTokenTrader should be selling token0 and buying RToken const trade = await getTrade(rTokenTrader, token0.address) expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(rToken.address) - await expect(rsrTrader.manageToken(weth.address)).to.not.emit(rsrTrader, 'TradeStarted') - await expect(rsrTrader.manageToken(token0.address)).to.emit(rsrTrader, 'TradeStarted') + await expect(rsrTrader.manageToken(weth.address, TradeKind.BATCH_AUCTION)).to.be.revertedWith( + '0 balance' + ) + await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rsrTrader, + 'TradeStarted' + ) // RSRTrader should be selling token0 and buying RToken const trade2 = await getTrade(rsrTrader, token0.address) @@ -196,7 +206,7 @@ describe(`Self-referential collateral (eg ETH via WETH) - P${IMPLEMENTATION}`, ( // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) - await expect(backingManager.manageTokens([token0.address, weth.address])).to.emit( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( backingManager, 'TradeStarted' ) @@ -252,12 +262,18 @@ describe(`Self-referential collateral (eg ETH via WETH) - P${IMPLEMENTATION}`, ( ) // Should view WETH as surplus - await expect(backingManager.manageTokens([token0.address, weth.address])).to.not.emit( - backingManager, + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' + ) + await backingManager.forwardRevenue([weth.address]) + await expect(rsrTrader.manageToken(weth.address, TradeKind.BATCH_AUCTION)).to.emit( + rsrTrader, + 'TradeStarted' + ) + await expect(rTokenTrader.manageToken(weth.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, 'TradeStarted' ) - await expect(rsrTrader.manageToken(weth.address)).to.emit(rsrTrader, 'TradeStarted') - await expect(rTokenTrader.manageToken(weth.address)).to.emit(rTokenTrader, 'TradeStarted') }) }) }) diff --git a/test/scenario/cETH.test.ts b/test/scenario/cETH.test.ts index 3488c3ddb5..be02aef4d3 100644 --- a/test/scenario/cETH.test.ts +++ b/test/scenario/cETH.test.ts @@ -5,7 +5,7 @@ import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { bn, fp } from '../../common/numbers' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { CTokenMock, CTokenSelfReferentialCollateral, @@ -203,22 +203,32 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, it('should sell appreciating stable collateral and ignore cETH', async () => { await token0.setExchangeRate(fp('1.1')) // 10% appreciation - await expect(backingManager.manageTokens([token0.address])).to.not.emit( - backingManager, - 'TradeStarted' + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) + await backingManager.forwardRevenue([token0.address, cETH.address]) expect(await cETH.balanceOf(rTokenTrader.address)).to.equal(0) expect(await cETH.balanceOf(rsrTrader.address)).to.equal(0) - await expect(rTokenTrader.manageToken(cETH.address)).to.not.emit(rTokenTrader, 'TradeStarted') - await expect(rTokenTrader.manageToken(token0.address)).to.emit(rTokenTrader, 'TradeStarted') + await expect( + rTokenTrader.manageToken(cETH.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('0 balance') + await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) // RTokenTrader should be selling token0 and buying RToken const trade = await getTrade(rTokenTrader, token0.address) expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(rToken.address) - await expect(rsrTrader.manageToken(cETH.address)).to.not.emit(rsrTrader, 'TradeStarted') - await expect(rsrTrader.manageToken(token0.address)).to.emit(rsrTrader, 'TradeStarted') + await expect(rsrTrader.manageToken(cETH.address, TradeKind.BATCH_AUCTION)).to.be.revertedWith( + '0 balance' + ) + await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rsrTrader, + 'TradeStarted' + ) // RSRTrader should be selling token0 and buying RToken const trade2 = await getTrade(rsrTrader, token0.address) @@ -233,7 +243,7 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) - await expect(backingManager.manageTokens([token0.address, cETH.address])).to.emit( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( backingManager, 'TradeStarted' ) @@ -271,13 +281,16 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, it('should sell cETH for RToken after redemption rate increase', async () => { await cETH.setExchangeRate(fp('2')) // doubling of price await basketHandler.refreshBasket() - await expect(backingManager.manageTokens([cETH.address])).to.not.emit( - backingManager, - 'TradeStarted' + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) + await backingManager.forwardRevenue([cETH.address]) // RTokenTrader should be selling cETH and buying RToken - await expect(rTokenTrader.manageToken(cETH.address)).to.emit(rTokenTrader, 'TradeStarted') + await expect(rTokenTrader.manageToken(cETH.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) const trade = await getTrade(rTokenTrader, cETH.address) expect(await trade.sell()).to.equal(cETH.address) expect(await trade.buy()).to.equal(rToken.address) @@ -325,7 +338,10 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, ) // Should view WETH as surplus - await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) // BackingManager should be selling cETH and buying WETH const trade = await getTrade(backingManager, cETH.address) diff --git a/test/scenario/cWBTC.test.ts b/test/scenario/cWBTC.test.ts index 631247c725..56092f98ea 100644 --- a/test/scenario/cWBTC.test.ts +++ b/test/scenario/cWBTC.test.ts @@ -6,7 +6,7 @@ import { ethers } from 'hardhat' import { bn, fp } from '../../common/numbers' import { advanceTime } from '../utils/time' import { IConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, TradeKind } from '../../common/constants' import { CTokenVaultMock, CTokenNonFiatCollateral, @@ -211,25 +211,32 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => it('should sell appreciating stable collateral and ignore cWBTC', async () => { await token0.setExchangeRate(fp('1.1')) // 10% appreciation - await expect(backingManager.manageTokens([token0.address])).to.not.emit( - backingManager, - 'TradeStarted' + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) + await backingManager.forwardRevenue([cWBTC.address, token0.address]) expect(await cWBTC.balanceOf(rTokenTrader.address)).to.equal(0) expect(await cWBTC.balanceOf(rsrTrader.address)).to.equal(0) - await expect(rTokenTrader.manageToken(cWBTC.address)).to.not.emit( + await expect( + rTokenTrader.manageToken(cWBTC.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('0 balance') + await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( rTokenTrader, 'TradeStarted' ) - await expect(rTokenTrader.manageToken(token0.address)).to.emit(rTokenTrader, 'TradeStarted') // RTokenTrader should be selling token0 and buying RToken const trade = await getTrade(rTokenTrader, token0.address) expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(rToken.address) - await expect(rsrTrader.manageToken(cWBTC.address)).to.not.emit(rsrTrader, 'TradeStarted') - await expect(rsrTrader.manageToken(token0.address)).to.emit(rsrTrader, 'TradeStarted') + await expect( + rsrTrader.manageToken(cWBTC.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('0 balance') + await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rsrTrader, + 'TradeStarted' + ) // RSRTrader should be selling token0 and buying RToken const trade2 = await getTrade(rsrTrader, token0.address) @@ -244,7 +251,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) - await expect(backingManager.manageTokens([token0.address, cWBTC.address])).to.emit( + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( backingManager, 'TradeStarted' ) @@ -297,13 +304,16 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => it('should sell cWBTC for RToken after redemption rate increase', async () => { await cWBTC.setExchangeRate(fp('2')) // doubling of price await basketHandler.refreshBasket() - await expect(backingManager.manageTokens([cWBTC.address])).to.not.emit( - backingManager, - 'TradeStarted' + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( + 'already collateralized' ) + await backingManager.forwardRevenue([cWBTC.address]) // RTokenTrader should be selling cWBTC and buying RToken - await expect(rTokenTrader.manageToken(cWBTC.address)).to.emit(rTokenTrader, 'TradeStarted') + await expect(rTokenTrader.manageToken(cWBTC.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) const trade = await getTrade(rTokenTrader, cWBTC.address) expect(await trade.sell()).to.equal(cWBTC.address) expect(await trade.buy()).to.equal(rToken.address) @@ -352,7 +362,10 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => ) // Should view cWBTC as surplus - await expect(backingManager.manageTokens([])).to.emit(backingManager, 'TradeStarted') + await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + backingManager, + 'TradeStarted' + ) // BackingManager should be selling cWBTC and buying cWBTC const trade = await getTrade(backingManager, cWBTC.address) diff --git a/test/utils/trades.ts b/test/utils/trades.ts index a05254f2c7..9182a8885f 100644 --- a/test/utils/trades.ts +++ b/test/utils/trades.ts @@ -2,7 +2,7 @@ import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { expect } from 'chai' import { TestITrading, GnosisTrade } from '../../typechain' -import { fp, divCeil } from '../../common/numbers' +import { bn, fp, divCeil } from '../../common/numbers' export const expectTrade = async (trader: TestITrading, auctionInfo: Partial) => { if (!auctionInfo.sell) throw new Error('Must provide sell token to find trade') @@ -73,3 +73,48 @@ export const toMinBuyAmt = ( return divCeil(divCeil(product, highBuyPrice), fp('1')) // (c) } + +// Returns the buy amount in the auction for the given progression +export const dutchBuyAmount = async ( + progression: BigNumber, + assetInAddr: string, + assetOutAddr: string, + outAmount: BigNumber, + minTradeVolume: BigNumber, + maxTradeSlippage: BigNumber +): Promise => { + const assetIn = await ethers.getContractAt('IAsset', assetInAddr) + const assetOut = await ethers.getContractAt('IAsset', assetOutAddr) + const [sellLow, sellHigh] = await assetOut.price() // {UoA/sellTok} + const [buyLow, buyHigh] = await assetIn.price() // {UoA/buyTok} + + const inMaxTradeVolume = await assetIn.maxTradeVolume() + let maxTradeVolume = await assetOut.maxTradeVolume() + if (inMaxTradeVolume.lt(maxTradeVolume)) maxTradeVolume = inMaxTradeVolume + + const auctionVolume = outAmount.mul(sellHigh).div(fp('1')) + const slippage1e18 = maxTradeSlippage.mul( + fp('1').sub( + auctionVolume.sub(minTradeVolume).mul(fp('1')).div(maxTradeVolume.sub(minTradeVolume)) + ) + ) + + // Adjust for rounding + const leftover = slippage1e18.mod(fp('1')) + const slippage = slippage1e18.div(fp('1')).add(leftover.gte(fp('0.5')) ? 1 : 0) + + const lowPrice = sellLow.mul(fp('1').sub(slippage)).div(buyHigh) + const middlePrice = divCeil(sellHigh.mul(fp('1')), buyLow) + const highPrice = middlePrice.add(divCeil(middlePrice, bn('2'))) // 50% above middlePrice + + const price = progression.lt(fp('0.15')) + ? highPrice.sub(highPrice.sub(middlePrice).mul(progression).div(fp('0.15'))) + : middlePrice.sub( + middlePrice + .sub(lowPrice) + .mul(progression.sub(fp('0.15'))) + .div(fp('0.85')) + ) + + return divCeil(outAmount.mul(price), fp('1')) +} diff --git a/yarn.lock b/yarn.lock index d59002b339..ccf2644e47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3667,6 +3667,15 @@ __metadata: languageName: node linkType: hard +"console-table-printer@npm:^2.9.0": + version: 2.11.1 + resolution: "console-table-printer@npm:2.11.1" + dependencies: + simple-wcswidth: ^1.0.1 + checksum: 1c5ec6c5edb8f454ac78ce2708c96b29986db4c1f369b32acc1931f87658a65984a8cf8731ca548dea670ff04119c740a7d658eeba9633d0202983c71d957096 + languageName: node + linkType: hard + "convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.7.0": version: 1.8.0 resolution: "convert-source-map@npm:1.8.0" @@ -5614,6 +5623,17 @@ fsevents@~2.1.1: languageName: node linkType: hard +"hardhat-storage-layout@npm:^0.1.7": + version: 0.1.7 + resolution: "hardhat-storage-layout@npm:0.1.7" + dependencies: + console-table-printer: ^2.9.0 + peerDependencies: + hardhat: ^2.0.3 + checksum: 8d27d6b16c1ebdffa032ba6b99c61996df4601dcbaf7d770c474806b492a56de9d21b4190086ab40f50a3a1f2e9851cc81034a9cfd2e21368941977324f96fd4 + languageName: node + linkType: hard + "hardhat@npm:^2.12.2": version: 2.12.2 resolution: "hardhat@npm:2.12.2" @@ -8286,6 +8306,7 @@ fsevents@~2.1.1: hardhat: ^2.12.2 hardhat-contract-sizer: ^2.4.0 hardhat-gas-reporter: ^1.0.8 + hardhat-storage-layout: ^0.1.7 husky: ^7.0.0 lodash: ^4.17.21 lodash.get: ^4.4.2 @@ -8723,6 +8744,13 @@ resolve@1.17.0: languageName: node linkType: hard +"simple-wcswidth@npm:^1.0.1": + version: 1.0.1 + resolution: "simple-wcswidth@npm:1.0.1" + checksum: dc5bf4cb131d9c386825d1355add2b1ecc408b37dc2c2334edd7a1a4c9f527e6b594dedcdbf6d949bce2740c3a332e39af1183072a2d068e40d9e9146067a37f + languageName: node + linkType: hard + "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" From 4d8cfbd871c5d472f11329ec1372d0fa3a565206 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 5 May 2023 16:47:35 +0100 Subject: [PATCH 164/462] add a way to manually start sell off tokens via the RevenueTrader --- contracts/p1/RevenueTrader.sol | 75 +++++++++++++++++++++++++++------- test/Revenues.test.ts | 57 ++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 15 deletions(-) diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index a7af82f26b..acd165e57f 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -56,6 +56,49 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { distributor.distribute(tokenToBuy, bal); } + /// If erc20 is tokenToBuy, do nothing; otherwise, sell it for tokenToBuy + /// @dev Intended to be used to manually start an auction for some token that the + /// RevenueTrader has a balance of but cannot be sold via a normal manageTokens(...) call + /// it will allow user to start trade even if trade size is < minTradeVolume + /// OR a BATCH_AUCTION trade in case the price feed is broken. + /// Works identially to manageToken(...) otherwise + /// @param erc20 The token the revenue trader has a balance of but cannot + /// be sold via a normal manageTokens(...) call + /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION. + /// DUTCH_AUCTION can only be used if oracles are working + /// @custom:interaction RCEI + // + function startDustAuction(IERC20 erc20, TradeKind kind) external notTradingPausedOrFrozen { + require(erc20 != tokenToBuy, "invalid token"); + // == Refresh + basic Checks/Effects == + TradeInfo memory trade = getTrade(erc20); + require(trade.sellAmount != 0, "zero balance"); + + uint192 minBuyAmount = (trade.sellPrice != 0 && trade.sellPrice != FIX_MAX) + ? trade.buyPrice.div(trade.sellPrice, CEIL).mul(trade.sellAmount) + : 0; + + // == Checks/Effects == + // Specific to starting dust auctions + if (minBuyAmount == 0) { + require(kind != TradeKind.DUTCH_AUCTION, "infinite slippage DUTCH_AUCTION not allowed"); + } + uint192 maxSellTradeSize = trade.sell.maxTradeVolume(); + uint192 maxBuyTradeSize = trade.buy.maxTradeVolume(); + require(minBuyAmount <= maxBuyTradeSize, "buy amount too large"); + require(trade.sellAmount <= maxSellTradeSize, "sell amount too large"); + + TradeRequest memory req = TradeRequest({ + sellAmount: trade.sellAmount.shiftl_toUint(int8(trade.sell.erc20Decimals()), FLOOR), + minBuyAmount: minBuyAmount.shiftl_toUint(int8(trade.buy.erc20Decimals()), CEIL), + sell: trade.sell, + buy: trade.buy + }); + + // == Interactions == + tryTrade(kind, req); + } + /// If erc20 is tokenToBuy, distribute it; else, sell it for tokenToBuy /// @dev Intended to be used with multicall /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION @@ -79,6 +122,22 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { return; } + TradeInfo memory trade = getTrade(erc20); + + // If not dust, trade the non-target asset for the target asset + // Any asset with a broken price feed will trigger a revert here + (bool launch, TradeRequest memory req) = TradeLib.prepareTradeSell( + trade, + minTradeVolume, + maxTradeSlippage + ); + require(launch, "trade not worth launching"); + + // == Interactions == + tryTrade(kind, req); + } + + function getTrade(IERC20 erc20) internal returns (TradeInfo memory trade) { IAsset sell; IAsset buy; @@ -105,13 +164,11 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { require(address(trades[erc20]) == address(0), "trade open"); require(erc20.balanceOf(address(this)) > 0, "0 balance"); - (uint192 sellPrice, ) = sell.price(); // {UoA/tok} (, uint192 buyPrice) = buy.price(); // {UoA/tok} - require(buyPrice > 0 && buyPrice < FIX_MAX, "buy asset price unknown"); - TradeInfo memory trade = TradeInfo({ + trade = TradeInfo({ sell: sell, buy: buy, sellAmount: sell.bal(address(this)), @@ -119,18 +176,6 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { sellPrice: sellPrice, buyPrice: buyPrice }); - - // If not dust, trade the non-target asset for the target asset - // Any asset with a broken price feed will trigger a revert here - (bool launch, TradeRequest memory req) = TradeLib.prepareTradeSell( - trade, - minTradeVolume, - maxTradeSlippage - ); - require(launch, "trade not worth launching"); - - // == Interactions == - tryTrade(kind, req); } /// Call after upgrade to >= 3.0.0 diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 3e181f86fc..98f1fbdfac 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -40,6 +40,7 @@ import { TestIStRSR, USDCMock, FiatCollateral, + RevenueTraderP1__factory, } from '../typechain' import { whileImpersonating } from './utils/impersonation' import snapshotGasCost from './utils/snapshotGasCost' @@ -615,6 +616,62 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { minBuyAmtRToken.div(bn('1e15')) ) }) + it('Should be able to start a dust auction BATCH_AUCTION', async () => { + const minTrade = bn('1e18') + + await rTokenTrader.connect(owner).setMinTradeVolume(minTrade) + + await collateral0.refresh() + + const dustAmount = bn('1e17') + await token0.connect(addr1).transfer(rTokenTrader.address, dustAmount) + + await expect( + rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) + ).to.revertedWith('trade not worth launching') + + const p1RevenueTrader = await ethers.getContractAt('RevenueTraderP1', rTokenTrader.address) + await expect( + await p1RevenueTrader.startDustAuction(token0.address, TradeKind.BATCH_AUCTION) + ).to.emit(rTokenTrader, 'TradeStarted') + }) + + it('Should be able to start a dust auction DUTCH_AUCTION', async () => { + const minTrade = bn('1e18') + + await rTokenTrader.connect(owner).setMinTradeVolume(minTrade) + + await collateral0.refresh() + + const dustAmount = bn('1e17') + await token0.connect(addr1).transfer(rTokenTrader.address, dustAmount) + + const p1RevenueTrader = await ethers.getContractAt('RevenueTraderP1', rTokenTrader.address) + await expect( + await p1RevenueTrader.startDustAuction(token0.address, TradeKind.DUTCH_AUCTION) + ).to.emit(rTokenTrader, 'TradeStarted') + }) + + it('Should only be able to start a dust auction BATCH_AUCTION if oracle has failed', async () => { + const minTrade = bn('1e18') + + await rTokenTrader.connect(owner).setMinTradeVolume(minTrade) + + + + const dustAmount = bn('1e17') + await token0.connect(addr1).transfer(rTokenTrader.address, dustAmount) + + const p1RevenueTrader = await ethers.getContractAt('RevenueTraderP1', rTokenTrader.address) + await setOraclePrice(collateral0.address, bn(0)) + await collateral0.refresh() + await expect( + p1RevenueTrader.startDustAuction(token0.address, TradeKind.DUTCH_AUCTION) + ).to.revertedWith('infinite slippage DUTCH_AUCTION not allowed') + await expect( + await p1RevenueTrader.startDustAuction(token0.address, TradeKind.BATCH_AUCTION) + ).to.emit(rTokenTrader, 'TradeStarted') + }) it('Should not auction 1qTok - Amount too small', async () => { // Set min trade volume for COMP to 1 qtok From bb19dd0712e967b87c6f68e1c2672ac015a257c8 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 14:04:06 -0400 Subject: [PATCH 165/462] minor renaming --- contracts/facade/FacadeRead.sol | 4 ++-- contracts/p1/BasketHandler.sol | 4 ++-- contracts/p1/mixins/{Basket.sol => BasketLib.sol} | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename contracts/p1/mixins/{Basket.sol => BasketLib.sol} (99%) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 9d903cb75d..0f4403d15b 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -409,10 +409,10 @@ contract FacadeRead is IFacadeRead { quantities[i] = bh.quantity(reg.erc20s[i]); } - (Price memory buPrice, ) = bh.prices(); + (uint192 low, uint192 high) = bh.price(); // {UoA/BU} // will exclude UoA value from RToken balances at BackingManager - range = RecollateralizationLibP1.basketRange(ctx, reg, quantities, buPrice); + range = RecollateralizationLibP1.basketRange(ctx, reg, quantities, Price(low, high)); } } } diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 86b4c658d5..b7ccbbbe2f 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -10,7 +10,7 @@ import "../interfaces/IBasketHandler.sol"; import "../interfaces/IMain.sol"; import "../libraries/Array.sol"; import "../libraries/Fixed.sol"; -import "./mixins/Basket.sol"; +import "./mixins/BasketLib.sol"; import "./mixins/Component.sol"; /** @@ -18,7 +18,7 @@ import "./mixins/Component.sol"; * @notice Handles the basket configuration, definition, and evolution over time. */ contract BasketHandlerP1 is ComponentP1, IBasketHandler { - using BasketLib for Basket; + using BasketLibP1 for Basket; using CollateralStatusComparator for CollateralStatus; using FixLib for uint192; diff --git a/contracts/p1/mixins/Basket.sol b/contracts/p1/mixins/BasketLib.sol similarity index 99% rename from contracts/p1/mixins/Basket.sol rename to contracts/p1/mixins/BasketLib.sol index 83591deeab..8e6f933587 100644 --- a/contracts/p1/mixins/Basket.sol +++ b/contracts/p1/mixins/BasketLib.sol @@ -50,8 +50,8 @@ struct Basket { mapping(IERC20 => uint192) refAmts; // {ref/BU} } -library BasketLib { - using BasketLib for Basket; +library BasketLibP1 { + using BasketLibP1 for Basket; using EnumerableSet for EnumerableSet.AddressSet; using EnumerableSet for EnumerableSet.Bytes32Set; using FixLib for uint192; From cb03de742ee237dcf0ba04535951f4308bd74466 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 14:04:24 -0400 Subject: [PATCH 166/462] tests --- test/FacadeAct.test.ts | 2 +- test/FacadeRead.test.ts | 6 +- test/Main.test.ts | 11 +- test/RToken.test.ts | 155 +++++++----------- test/Upgradeability.test.ts | 14 +- test/fixtures.ts | 8 +- test/integration/AssetPlugins.test.ts | 18 +- test/integration/fixtures.ts | 8 +- test/plugins/Asset.test.ts | 5 +- .../aave/ATokenFiatCollateral.test.ts | 5 +- .../compoundv2/CTokenFiatCollateral.test.ts | 5 +- .../plugins/individual-collateral/fixtures.ts | 8 +- test/scenario/BadCollateralPlugin.test.ts | 10 +- test/scenario/BadERC20.test.ts | 12 +- test/scenario/ComplexBasket.test.ts | 2 +- test/scenario/EURT.test.ts | 2 +- test/scenario/MaxBasketSize.test.ts | 8 +- test/scenario/NestedRTokens.test.ts | 4 +- test/scenario/NontrivialPeg.test.ts | 2 +- test/scenario/RevenueHiding.test.ts | 2 +- test/scenario/SetProtocol.test.ts | 2 +- test/scenario/WBTC.test.ts | 2 +- test/scenario/WETH.test.ts | 2 +- test/scenario/cETH.test.ts | 4 +- test/scenario/cWBTC.test.ts | 4 +- 25 files changed, 139 insertions(+), 162 deletions(-) diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts index d5619e6a86..8d3ecc71e2 100644 --- a/test/FacadeAct.test.ts +++ b/test/FacadeAct.test.ts @@ -296,7 +296,7 @@ describe('FacadeAct contract', () => { it('Basket - Should handle no valid basket after refresh', async () => { // Redeem all RTokens - await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmount) // Set simple basket with only one collateral await basketHandler.connect(owner).setPrimeBasket([aToken.address], [fp('1')]) diff --git a/test/FacadeRead.test.ts b/test/FacadeRead.test.ts index cbf5b2f842..0887fdc011 100644 --- a/test/FacadeRead.test.ts +++ b/test/FacadeRead.test.ts @@ -140,7 +140,7 @@ describe('FacadeRead contract', () => { expect(await facade.callStatic.maxIssuable(rToken.address, other.address)).to.equal(0) // Redeem all RTokens - await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmount) // With 0 baskets needed - Returns correct value expect(await facade.callStatic.maxIssuable(rToken.address, addr2.address)).to.equal( @@ -231,7 +231,7 @@ describe('FacadeRead contract', () => { expect(overCollateralization).to.equal(fp('1')) // Redeem all RTokens - await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmount) // Check values = 0 (no supply) ;[backing, overCollateralization] = await facade.callStatic.backingOverview(rToken.address) @@ -401,7 +401,7 @@ describe('FacadeRead contract', () => { it('Should return basketBreakdown correctly when RToken supply = 0', async () => { // Redeem all RTokens - await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmount) expect(await rToken.totalSupply()).to.equal(bn(0)) diff --git a/test/Main.test.ts b/test/Main.test.ts index 3fd5d53e7c..73b3bc5c37 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1689,13 +1689,13 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { it('Should not allow to set prime Basket with 0 address tokens', async () => { await expect( basketHandler.connect(owner).setPrimeBasket([ZERO_ADDRESS], [fp('1')]) - ).to.be.revertedWith('invalid collateral') + ).to.be.revertedWith('address zero is not valid collateral') }) it('Should not allow to set prime Basket with stRSR', async () => { await expect( basketHandler.connect(owner).setPrimeBasket([stRSR.address], [fp('1')]) - ).to.be.revertedWith('stinvalid collateral') + ).to.be.revertedWith('stRSR is not valid collateral') }) it('Should not allow to set prime Basket with invalid target amounts', async () => { @@ -1735,7 +1735,8 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { .withArgs(2, [token0.address], [fp('1')], [ethers.utils.formatBytes32String('USD')]) }) - describe.only('Historical Redemptions', () => { + // TODO + describe.skip('Historical Redemptions', () => { const issueAmount = fp('10000') let usdcChainlink: MockV3Aggregator let daiChainlink: MockV3Aggregator @@ -2128,7 +2129,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { basketHandler .connect(owner) .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [rsr.address]) - ).to.be.revertedWith('invalid collateral') + ).to.be.revertedWith('RSR is not valid collateral') it('Should not allow to set backup Config with duplicate ERC20s', async () => { await expect( @@ -2145,7 +2146,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { basketHandler .connect(owner) .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [rToken.address]) - ).to.be.revertedWith('invalid collateral') + ).to.be.revertedWith('RToken is not valid collateral') }) it('Should allow to set backup Config if OWNER', async () => { diff --git a/test/RToken.test.ts b/test/RToken.test.ts index b779fe26b7..2821e773ac 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -905,17 +905,15 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { describe('Redeem', function () { it('Should revert if zero amount #fast', async function () { const zero: BigNumber = bn('0') - await expect( - rToken.connect(addr1).redeem(zero, await basketHandler.nonce()) - ).to.be.revertedWith('Cannot redeem zero') + await expect(rToken.connect(addr1).redeem(zero)).to.be.revertedWith('Cannot redeem zero') }) it('Should revert if no balance of RToken #fast', async function () { const redeemAmount: BigNumber = bn('20000e18') - await expect( - rToken.connect(addr1).redeem(redeemAmount, await basketHandler.nonce()) - ).to.be.revertedWith('insufficient balance') + await expect(rToken.connect(addr1).redeem(redeemAmount)).to.be.revertedWith( + 'insufficient balance' + ) }) context('With issued RTokens', function () { @@ -940,7 +938,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) // Redeem rTokens - await rToken.connect(addr1).redeem(redeemAmount, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(redeemAmount) // Check funds were transferred expect(await rToken.balanceOf(addr1.address)).to.equal(0) @@ -963,9 +961,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, initialBal))) // Redeem rTokens to another account - await expect( - rToken.connect(addr1).redeemTo(addr2.address, issueAmount, await basketHandler.nonce()) - ) + await expect(rToken.connect(addr1).redeemTo(addr2.address, issueAmount)) .to.emit(rToken, 'Redemption') .withArgs(addr1.address, addr2.address, issueAmount, issueAmount) expect(await rToken.balanceOf(addr1.address)).to.equal(0) @@ -991,7 +987,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ) // Redeem rTokens - await rToken.connect(addr1).redeem(redeemAmount, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(redeemAmount) // Check asset value expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( @@ -999,7 +995,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ) // Redeem rTokens with another user - await rToken.connect(addr2).redeem(redeemAmount, await basketHandler.nonce()) + await rToken.connect(addr2).redeem(redeemAmount) // Check funds were transferred expect(await rToken.balanceOf(addr1.address)).to.equal(0) @@ -1024,21 +1020,21 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Default one of the tokens - 50% price reduction and mark default as probable await setOraclePrice(collateral3.address, bn('0.5e8')) - await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmount) expect(await rToken.totalSupply()).to.equal(0) }) it('Should redeem if basket is UNPRICED #fast', async function () { await advanceTime(ORACLE_TIMEOUT.toString()) - await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmount) expect(await rToken.totalSupply()).to.equal(0) }) it('Should redeem if paused #fast', async function () { await main.connect(owner).pauseTrading() await main.connect(owner).pauseIssuance() - await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmount) expect(await rToken.totalSupply()).to.equal(0) }) @@ -1046,15 +1042,14 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await main.connect(owner).freezeShort() // Try to redeem - await expect( - rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) - ).to.be.revertedWith('frozen') + await expect(rToken.connect(addr1).redeem(issueAmount)).to.be.revertedWith('frozen') // Check values expect(await rToken.totalSupply()).to.equal(issueAmount) }) - it('Should revert if empty redemption #fast', async function () { + // TODO + it.skip('Should revert if empty redemption #fast', async function () { // Eliminate most token balances const bal = issueAmount.div(4) await token0.connect(owner).burn(backingManager.address, bal) @@ -1062,7 +1057,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await token2.connect(owner).burn(backingManager.address, bal) // Should not revert with empty redemption yet - await rToken.connect(addr1).redeem(issueAmount.div(2), await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmount.div(2)) expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) // Burn the rest @@ -1071,19 +1066,19 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { .burn(backingManager.address, await token3.balanceOf(backingManager.address)) // Now it should revert - await expect( - rToken.connect(addr1).redeem(issueAmount.div(2), await basketHandler.nonce()) - ).to.be.revertedWith('empty redemption') + await expect(rToken.connect(addr1).redeem(issueAmount.div(2))).to.be.revertedWith( + 'empty redemption' + ) // Check values expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) }) - it('Should revert if different basketNonce #fast', async function () { - // Should fail if revertOnPartialRedemption is true - await expect( - rToken.connect(addr1).redeem(issueAmount.div(2), 1 + (await basketHandler.nonce())) - ).to.be.revertedWith('invalid basketNonce') + it('Should revert if undercollateralized #fast', async function () { + await token0.connect(owner).burn(backingManager.address, issueAmount.div(4)) + await expect(rToken.connect(addr1).redeem(issueAmount.div(2))).to.be.revertedWith( + 'partial redemption; use redeemToCustom' + ) }) it('Should prorate redemption if basket is DISABLED from fallen refPerTok() #fast', async function () { @@ -1093,17 +1088,13 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Even though a single BU requires 10x token2 as before, it should still hand out evenly // 1st redemption - await expect( - rToken.connect(addr1).redeem(issueAmount.div(2), await basketHandler.nonce()) - ).to.emit(rToken, 'Redemption') + await expect(rToken.connect(addr1).redeem(issueAmount.div(2))).to.emit(rToken, 'Redemption') expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) expect(await token0.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(8))) expect(await token2.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(8))) // 2nd redemption - await expect( - rToken.connect(addr1).redeem(issueAmount.div(2), await basketHandler.nonce()) - ).to.emit(rToken, 'Redemption') + await expect(rToken.connect(addr1).redeem(issueAmount.div(2))).to.emit(rToken, 'Redemption') expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) expect(await token2.balanceOf(addr1.address)).to.equal(initialBal) }) @@ -1112,9 +1103,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Unregister collateral2 await assetRegistry.connect(owner).unregister(collateral2.address) - await expect( - rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) - ).revertedWith('erc20 unregistered') + await expect(rToken.connect(addr1).redeem(issueAmount)).revertedWith('erc20 unregistered') }) it('Should redeem prorata when refPerTok() is 0 #fast', async function () { @@ -1122,9 +1111,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await token2.setExchangeRate(fp('0')) // Redemption - await expect( - rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) - ).to.emit(rToken, 'Redemption') + await expect(rToken.connect(addr1).redeem(issueAmount)).to.emit(rToken, 'Redemption') expect(await rToken.totalSupply()).to.equal(0) expect(await token0.balanceOf(addr1.address)).to.be.equal(initialBal) expect(await token1.balanceOf(addr1.address)).to.be.equal(initialBal) @@ -1134,7 +1121,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { it('Should not overflow BU exchange rate above 1e9 on redeem', async function () { // Leave only 1 RToken issue - await rToken.connect(addr1).redeem(issueAmount.sub(bn('1e18')), await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmount.sub(bn('1e18'))) expect(await rToken.totalSupply()).to.equal(fp('1')) @@ -1146,9 +1133,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { const redeemAmount: BigNumber = bn('1.5e9') // Redeem rTokens successfully - await expect( - rToken.connect(addr1).redeem(bn(redeemAmount), await basketHandler.nonce()) - ).to.not.be.reverted + await expect(rToken.connect(addr1).redeem(bn(redeemAmount))).to.not.be.reverted }) context('And redemption throttling', function () { @@ -1186,7 +1171,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await advanceTime(3600) redeemAmount = await rToken.redemptionAvailable() - await rToken.connect(addr1).redeem(redeemAmount, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(redeemAmount) issueAmount = issueAmount.sub(redeemAmount) expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) expect(await rToken.totalSupply()).to.equal(issueAmount) @@ -1206,10 +1191,10 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) redeemAmount = issueAmount.mul(redemptionThrottleParams.pctRate).div(fp('1')) - await expect( - rToken.connect(addr1).redeem(redeemAmount.add(1), await basketHandler.nonce()) - ).to.be.revertedWith('supply change throttled') - await rToken.connect(addr1).redeem(redeemAmount, await basketHandler.nonce()) + await expect(rToken.connect(addr1).redeem(redeemAmount.add(1))).to.be.revertedWith( + 'supply change throttled' + ) + await rToken.connect(addr1).redeem(redeemAmount) // Check updated redemption throttle expect(await rToken.redemptionAvailable()).to.equal(bn(0)) @@ -1245,7 +1230,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Redeem expect(await rToken.redemptionAvailable()).to.equal(throttles.amtRate) - await rToken.connect(addr1).redeem(throttles.amtRate, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(throttles.amtRate) expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) }) @@ -1255,14 +1240,12 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) // Large redemption should fail - await expect( - rToken.connect(addr1).redeem(redeemAmount.add(1), await basketHandler.nonce()) - ).to.be.revertedWith('supply change throttled') + await expect(rToken.connect(addr1).redeem(redeemAmount.add(1))).to.be.revertedWith( + 'supply change throttled' + ) // amtRate redemption should succeed - await expect( - rToken.connect(addr1).redeem(redeemAmount, await basketHandler.nonce()) - ).to.emit(rToken, 'Redemption') + await expect(rToken.connect(addr1).redeem(redeemAmount)).to.emit(rToken, 'Redemption') // Check redemption throttle is 0 expect(await rToken.redemptionAvailable()).to.equal(bn(0)) @@ -1277,7 +1260,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) // Redeem #1 - await rToken.connect(addr1).redeem(redeemAmount.div(2), await basketHandler.nonce()) + await rToken.connect(addr1).redeem(redeemAmount.div(2)) // Check redemption throttle updated expect(await rToken.redemptionAvailable()).to.equal(redeemAmount.div(2)) @@ -1286,7 +1269,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) // Redeem #2 - await rToken.connect(addr1).redeem(redeemAmount.div(2), await basketHandler.nonce()) + await rToken.connect(addr1).redeem(redeemAmount.div(2)) // Check redemption throttle updated - very small expect(await rToken.redemptionAvailable()).to.be.closeTo(fp('0.002638'), fp('0.000001')) @@ -1295,9 +1278,9 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) // Redeem #3 - should not be processed - await expect( - rToken.connect(addr1).redeem(redeemAmount.div(100), await basketHandler.nonce()) - ).to.be.revertedWith('supply change throttled') + await expect(rToken.connect(addr1).redeem(redeemAmount.div(100))).to.be.revertedWith( + 'supply change throttled' + ) // Advance time significantly await advanceTime(10000000000) @@ -1328,7 +1311,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Redeem #1 - Will be processed redeemAmount = fp('12.5') - await rToken.connect(addr1).redeem(redeemAmount, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(redeemAmount) // Check redemption throttle updated expect(await rToken.redemptionAvailable()).to.equal(redeemAmount) @@ -1338,9 +1321,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Attempt to redeem max amt, should not be processed await expect( - rToken - .connect(addr1) - .redeem(redemptionThrottleParams.amtRate, await basketHandler.nonce()) + rToken.connect(addr1).redeem(redemptionThrottleParams.amtRate) ).to.be.revertedWith('supply change throttled') // Advance one hour. Redemption should be fully rechardged @@ -1353,9 +1334,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) // Redeem #2 - will be processed - await rToken - .connect(addr1) - .redeem(redemptionThrottleParams.amtRate, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(redemptionThrottleParams.amtRate) // Check redemption throttle emptied expect(await rToken.redemptionAvailable()).to.equal(bn(0)) @@ -1395,7 +1374,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Redeem #1 - Will be processed redeemAmount = fp('10000') await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) - await rToken.connect(addr1).redeem(redeemAmount, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(redeemAmount) // Check redemption throttle updated expect(await rToken.redemptionAvailable()).to.equal(redeemAmountUpd.sub(redeemAmount)) @@ -2043,14 +2022,12 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { quote.erc20s, quote.quantities ) - await expect( - rToken.connect(addr1).redeem(redeemAmount.add(1), await basketHandler.nonce()) - ).to.be.revertedWith('supply change throttled') + await expect(rToken.connect(addr1).redeem(redeemAmount.add(1))).to.be.revertedWith( + 'supply change throttled' + ) // amtRate redemption should succeed - await expect( - rToken.connect(addr1).redeem(redeemAmount, await basketHandler.nonce()) - ).to.emit(rToken, 'Redemption') + await expect(rToken.connect(addr1).redeem(redeemAmount)).to.emit(rToken, 'Redemption') // Check redemption throttle is 0 expect(await rToken.redemptionAvailable()).to.equal(bn(0)) @@ -2065,7 +2042,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) // Redeem #1 - await rToken.connect(addr1).redeem(redeemAmount.div(2), await basketHandler.nonce()) + await rToken.connect(addr1).redeem(redeemAmount.div(2)) // Check redemption throttle updated expect(await rToken.redemptionAvailable()).to.equal(redeemAmount.div(2)) @@ -2074,7 +2051,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) // Redeem #2 - await rToken.connect(addr1).redeem(redeemAmount.div(2), await basketHandler.nonce()) + await rToken.connect(addr1).redeem(redeemAmount.div(2)) // Check redemption throttle updated - very small expect(await rToken.redemptionAvailable()).to.be.closeTo(fp('0.002638'), fp('0.000001')) @@ -2083,9 +2060,9 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) // Redeem #3 - should not be processed - await expect( - rToken.connect(addr1).redeem(redeemAmount.div(100), await basketHandler.nonce()) - ).to.be.revertedWith('supply change throttled') + await expect(rToken.connect(addr1).redeem(redeemAmount.div(100))).to.be.revertedWith( + 'supply change throttled' + ) // Advance time significantly await advanceTime(10000000000) @@ -2116,7 +2093,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Redeem #1 - Will be processed redeemAmount = fp('12.5') - await rToken.connect(addr1).redeem(redeemAmount, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(redeemAmount) // Check redemption throttle updated expect(await rToken.redemptionAvailable()).to.equal(redeemAmount) @@ -2126,9 +2103,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Attempt to redeem max amt, should not be processed await expect( - rToken - .connect(addr1) - .redeem(redemptionThrottleParams.amtRate, await basketHandler.nonce()) + rToken.connect(addr1).redeem(redemptionThrottleParams.amtRate) ).to.be.revertedWith('supply change throttled') // Advance one hour. Redemption should be fully rechardged @@ -2141,9 +2116,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) // Redeem #2 - will be processed - await rToken - .connect(addr1) - .redeem(redemptionThrottleParams.amtRate, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(redemptionThrottleParams.amtRate) // Check redemption throttle emptied expect(await rToken.redemptionAvailable()).to.equal(bn(0)) @@ -2183,7 +2156,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Redeem #1 - Will be processed redeemAmount = fp('10000') await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) - await rToken.connect(addr1).redeem(redeemAmount, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(redeemAmount) // Check redemption throttle updated expect(await rToken.redemptionAvailable()).to.equal(redeemAmountUpd.sub(redeemAmount)) @@ -2870,7 +2843,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // ==== Redeem tokens - await rToken.connect(addr2).redeem(toRedeem, await basketHandler.nonce()) + await rToken.connect(addr2).redeem(toRedeem) expect(await rToken.balanceOf(addr2.address)).to.equal(0) } @@ -2954,9 +2927,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { it('Redemption', async () => { // Issue rTokens await rToken.connect(addr1).issue(issueAmount.div(2)) - await snapshotGasCost( - rToken.connect(addr1).redeem(issueAmount.div(2), await basketHandler.nonce()) - ) + await snapshotGasCost(rToken.connect(addr1).redeem(issueAmount.div(2))) }) }) }) diff --git a/test/Upgradeability.test.ts b/test/Upgradeability.test.ts index f72ce7e462..d681e0413d 100644 --- a/test/Upgradeability.test.ts +++ b/test/Upgradeability.test.ts @@ -14,6 +14,7 @@ import { BackingManagerP1V2, BasketHandlerP1, BasketHandlerP1V2, + BasketLibP1, BrokerP1, BrokerP1V2, DistributorP1, @@ -76,6 +77,7 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { let rsrTrader: TestIRevenueTrader let rTokenTrader: TestIRevenueTrader let tradingLib: RecollateralizationLibP1 + let basketLib: BasketLibP1 // Factories let MainFactory: ContractFactory @@ -126,6 +128,10 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { ) tradingLib = await TradingLibFactory.deploy() + // Deploy BasketLib external library + const BasketLibFactory: ContractFactory = await ethers.getContractFactory('BasketLibP1') + basketLib = await BasketLibFactory.deploy() + // Setup factories MainFactory = await ethers.getContractFactory('MainP1') RTokenFactory = await ethers.getContractFactory('RTokenP1') @@ -137,7 +143,9 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { }, }) AssetRegistryFactory = await ethers.getContractFactory('AssetRegistryP1') - BasketHandlerFactory = await ethers.getContractFactory('BasketHandlerP1') + BasketHandlerFactory = await ethers.getContractFactory('BasketHandlerP1', { + libraries: { BasketLibP1: basketLib.address }, + }) DistributorFactory = await ethers.getContractFactory('DistributorP1') BrokerFactory = await ethers.getContractFactory('BrokerP1') GnosisTradeFactory = await ethers.getContractFactory('GnosisTrade') @@ -244,6 +252,7 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { { initializer: 'init', kind: 'uups', + unsafeAllow: ['external-library-linking'], } ) await newBasketHandler.deployed() @@ -499,7 +508,8 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { it('Should upgrade correctly - BasketHandler', async () => { // Upgrading const BasketHandlerV2Factory: ContractFactory = await ethers.getContractFactory( - 'BasketHandlerP1V2' + 'BasketHandlerP1V2', + { libraries: { BasketLibP1: basketLib.address } } ) const bskHndlrV2: BasketHandlerP1V2 = ( await upgrades.upgradeProxy(basketHandler.address, BasketHandlerV2Factory) diff --git a/test/fixtures.ts b/test/fixtures.ts index 4cf3cbf432..7f89fa2d41 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -12,6 +12,7 @@ import { ATokenFiatCollateral, BackingManagerP1, BasketHandlerP1, + BasketLibP1, BrokerP1, ComptrollerMock, CTokenFiatCollateral, @@ -507,6 +508,10 @@ export const defaultFixture: Fixture = async function (): Promis await TradingLibFactory.deploy() ) + // Deploy BasketLib external library + const BasketLibFactory: ContractFactory = await ethers.getContractFactory('BasketLibP1') + const basketLib: BasketLibP1 = await BasketLibFactory.deploy() + const AssetRegImplFactory: ContractFactory = await ethers.getContractFactory('AssetRegistryP1') const assetRegImpl: AssetRegistryP1 = await AssetRegImplFactory.deploy() @@ -521,7 +526,8 @@ export const defaultFixture: Fixture = async function (): Promis const backingMgrImpl: BackingManagerP1 = await BackingMgrImplFactory.deploy() const BskHandlerImplFactory: ContractFactory = await ethers.getContractFactory( - 'BasketHandlerP1' + 'BasketHandlerP1', + { libraries: { BasketLibP1: basketLib.address } } ) const bskHndlrImpl: BasketHandlerP1 = await BskHandlerImplFactory.deploy() diff --git a/test/integration/AssetPlugins.test.ts b/test/integration/AssetPlugins.test.ts index 71e1f99f1e..60748e1b6d 100644 --- a/test/integration/AssetPlugins.test.ts +++ b/test/integration/AssetPlugins.test.ts @@ -1793,10 +1793,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, ) // approx 10K in value // Redeem Rtokens - await expect(rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce())).to.emit( - rToken, - 'Redemption' - ) + await expect(rToken.connect(addr1).redeem(issueAmount)).to.emit(rToken, 'Redemption') // Check funds were transferred expect(await rToken.balanceOf(addr1.address)).to.equal(0) @@ -1963,10 +1960,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(totalAssetValue3).to.be.gt(totalAssetValue2) // Redeem Rtokens with the udpated rates - await expect(rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce())).to.emit( - rToken, - 'Redemption' - ) + await expect(rToken.connect(addr1).redeem(issueAmount)).to.emit(rToken, 'Redemption') // Check funds were transferred expect(await rToken.balanceOf(addr1.address)).to.equal(0) @@ -2212,9 +2206,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, ) // Redeem Rtokens - await expect( - rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) - ).to.emit(rToken, 'Redemption') + await expect(rToken.connect(addr1).redeem(issueAmount)).to.emit(rToken, 'Redemption') // Check funds were transferred expect(await rToken.balanceOf(addr1.address)).to.equal(0) @@ -2385,9 +2377,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, ) // Redeem Rtokens - await expect( - rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) - ).to.emit(rToken, 'Redemption') + await expect(rToken.connect(addr1).redeem(issueAmount)).to.emit(rToken, 'Redemption') // Check funds were transferred expect(await rToken.balanceOf(addr1.address)).to.equal(0) diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index 72a82d89d3..469b91e96f 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -14,6 +14,7 @@ import { ATokenMock, BackingManagerP1, BasketHandlerP1, + BasketLibP1, BrokerP1, ComptrollerMock, CTokenFiatCollateral, @@ -670,6 +671,10 @@ export const defaultFixture: Fixture = async function (): Promis await TradingLibFactory.deploy() ) + // Deploy BasketLib external library + const BasketLibFactory: ContractFactory = await ethers.getContractFactory('BasketLibP1') + const basketLib: BasketLibP1 = await BasketLibFactory.deploy() + // Deploy RSR Asset const AssetFactory: ContractFactory = await ethers.getContractFactory('Asset') const rsrAsset: Asset = ( @@ -711,7 +716,8 @@ export const defaultFixture: Fixture = async function (): Promis const backingMgrImpl: BackingManagerP1 = await BackingMgrImplFactory.deploy() const BskHandlerImplFactory: ContractFactory = await ethers.getContractFactory( - 'BasketHandlerP1' + 'BasketHandlerP1', + { libraries: { BasketLibP1: basketLib.address } } ) const bskHndlrImpl: BasketHandlerP1 = await BskHandlerImplFactory.deploy() diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index b90f1be880..e1ab308702 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -27,7 +27,6 @@ import { RTokenAsset, StaticATokenMock, TestIBackingManager, - TestIBasketHandler, TestIRToken, USDCMock, UnpricedAssetMock, @@ -74,7 +73,6 @@ describe('Assets contracts #fast', () => { let wallet: Wallet let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager - let basketHandler: TestIBasketHandler // Factory let AssetFactory: ContractFactory @@ -101,7 +99,6 @@ describe('Assets contracts #fast', () => { config, rToken, rTokenAsset, - basketHandler, } = await loadFixture(defaultFixture)) // Get collateral tokens @@ -295,7 +292,7 @@ describe('Assets contracts #fast', () => { it('Should not revert RToken price if supply is zero', async () => { // Redeem RToken to make price function revert // Note: To get RToken price to 0, a full basket refresh needs to occur (covered in RToken tests) - await rToken.connect(wallet).redeem(amt, await basketHandler.nonce()) + await rToken.connect(wallet).redeem(amt) await expectRTokenPrice( rTokenAsset.address, fp('1'), diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index d7c7701077..4042c8283b 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -541,10 +541,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(totalAssetValue3).to.be.gt(totalAssetValue2) // Redeem Rtokens with the updated rates - await expect(rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce())).to.emit( - rToken, - 'Redemption' - ) + await expect(rToken.connect(addr1).redeem(issueAmount)).to.emit(rToken, 'Redemption') // Check funds were transferred expect(await rToken.balanceOf(addr1.address)).to.equal(0) diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 1984a0d5d4..e7b1ef6868 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -522,10 +522,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(totalAssetValue3).to.be.gt(totalAssetValue2) // Redeem Rtokens with the updated rates - await expect(rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce())).to.emit( - rToken, - 'Redemption' - ) + await expect(rToken.connect(addr1).redeem(issueAmount)).to.emit(rToken, 'Redemption') // Check funds were transferred expect(await rToken.balanceOf(addr1.address)).to.equal(0) diff --git a/test/plugins/individual-collateral/fixtures.ts b/test/plugins/individual-collateral/fixtures.ts index c44b23b67c..b1b5e9e1fa 100644 --- a/test/plugins/individual-collateral/fixtures.ts +++ b/test/plugins/individual-collateral/fixtures.ts @@ -9,6 +9,7 @@ import { AssetRegistryP1, BackingManagerP1, BasketHandlerP1, + BasketLibP1, BrokerP1, DeployerP0, DeployerP1, @@ -98,6 +99,10 @@ export const getDefaultFixture = async function (salt: string) { await TradingLibFactory.deploy() ) + // Deploy BasketLib external library + const BasketLibFactory: ContractFactory = await ethers.getContractFactory('BasketLibP1') + const basketLib: BasketLibP1 = await BasketLibFactory.deploy() + // Deploy FacadeWriteLib external library const facadeWriteLib = await (await ethers.getContractFactory('FacadeWriteLib')).deploy() @@ -143,7 +148,8 @@ export const getDefaultFixture = async function (salt: string) { ) const BskHandlerImplFactory: ContractFactory = await ethers.getContractFactory( - 'BasketHandlerP1' + 'BasketHandlerP1', + { libraries: { BasketLibP1: basketLib.address } } ) const bskHndlrImpl: BasketHandlerP1 = await BskHandlerImplFactory.deploy() diff --git a/test/scenario/BadCollateralPlugin.test.ts b/test/scenario/BadCollateralPlugin.test.ts index 01892c2429..2134c37f59 100644 --- a/test/scenario/BadCollateralPlugin.test.ts +++ b/test/scenario/BadCollateralPlugin.test.ts @@ -159,7 +159,7 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { it('should keep a constant redemption basket as collateral loses value', async () => { // Redemption should be restrained to be prorata expect(await token0.balanceOf(addr1.address)).to.equal(0) - await rToken.connect(addr1).redeem(initialBal.div(2), await basketHandler.nonce()) + await rToken.connect(addr1).redeem(initialBal.div(2)) expect(await rToken.totalSupply()).to.equal(initialBal.div(2)) expect(await token0.balanceOf(addr1.address)).to.equal(initialBal.div(2)) await expectRTokenPrice( @@ -173,7 +173,7 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { it('should increase the issuance basket as collateral loses value', async () => { // Should be able to redeem half the RToken at-par - await rToken.connect(addr1).redeem(initialBal.div(2), await basketHandler.nonce()) + await rToken.connect(addr1).redeem(initialBal.div(2)) expect(await rToken.totalSupply()).to.equal(initialBal.div(2)) expect(await rToken.balanceOf(addr1.address)).to.equal(initialBal.div(2)) @@ -214,7 +214,7 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { it('should not change the redemption basket', async () => { // Should be able to redeem half the RToken at-par - await rToken.connect(addr1).redeem(initialBal.div(2), await basketHandler.nonce()) + await rToken.connect(addr1).redeem(initialBal.div(2)) expect(await rToken.totalSupply()).to.equal(initialBal.div(2)) expect(await rToken.balanceOf(addr1.address)).to.equal(initialBal.div(2)) @@ -241,14 +241,14 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { expect(await collateral0.status()).to.equal(CollateralStatus.SOUND) // RToken redemption should ignore depegging - await rToken.connect(addr1).redeem(initialBal.div(4), await basketHandler.nonce()) + await rToken.connect(addr1).redeem(initialBal.div(4)) expect(await rToken.totalSupply()).to.equal(initialBal.div(4)) expect(await token0.balanceOf(addr1.address)).to.equal(initialBal.mul(3).div(4)) }) it('should not change the issuance basket', async () => { // Should be able to redeem half the RToken at-par - await rToken.connect(addr1).redeem(initialBal.div(2), await basketHandler.nonce()) + await rToken.connect(addr1).redeem(initialBal.div(2)) expect(await rToken.totalSupply()).to.equal(initialBal.div(2)) expect(await rToken.balanceOf(addr1.address)).to.equal(initialBal.div(2)) diff --git a/test/scenario/BadERC20.test.ts b/test/scenario/BadERC20.test.ts index 0b1b604252..c4a96e63f5 100644 --- a/test/scenario/BadERC20.test.ts +++ b/test/scenario/BadERC20.test.ts @@ -201,13 +201,11 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { }) it('should revert during redemption', async () => { - await expect( - rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce()) - ).to.be.revertedWith('No Decimals') + await expect(rToken.connect(addr1).redeem(issueAmt)).to.be.revertedWith('No Decimals') // Should work now await token0.setRevertDecimals(false) - await rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmt) }) it('should revert during trading', async () => { @@ -302,14 +300,12 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { }) it('should revert during redemption', async () => { - await expect( - rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce()) - ).to.be.revertedWith('censored') + await expect(rToken.connect(addr1).redeem(issueAmt)).to.be.revertedWith('censored') // Should work now await token0.setCensored(backingManager.address, false) await token0.setCensored(rToken.address, false) - await rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmt) }) it('should revert during trading', async () => { diff --git a/test/scenario/ComplexBasket.test.ts b/test/scenario/ComplexBasket.test.ts index 0dfc4a3b1c..b1eb4c7f84 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -500,7 +500,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { expect(expectedTkn7).to.equal(quotes[7]) // Redeem - await rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmt) expect(await rToken.balanceOf(addr1.address)).to.equal(0) expect(await rToken.totalSupply()).to.equal(0) diff --git a/test/scenario/EURT.test.ts b/test/scenario/EURT.test.ts index 2da3a3c75d..4062eea2a3 100644 --- a/test/scenario/EURT.test.ts +++ b/test/scenario/EURT.test.ts @@ -200,7 +200,7 @@ describe(`EUR fiatcoins (eg EURT) - P${IMPLEMENTATION}`, () => { await targetUnitOracle.updateAnswer(bn('1e8')) // Price change should not impact share of redemption tokens - expect(await rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce())) + expect(await rToken.connect(addr1).redeem(issueAmt)) expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) expect(await eurt.balanceOf(addr1.address)).to.equal(initialBal) }) diff --git a/test/scenario/MaxBasketSize.test.ts b/test/scenario/MaxBasketSize.test.ts index 8ee85a3c8d..82b1a9713d 100644 --- a/test/scenario/MaxBasketSize.test.ts +++ b/test/scenario/MaxBasketSize.test.ts @@ -330,9 +330,9 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { // Redemption if (REPORT_GAS) { - await snapshotGasCost(rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce())) + await snapshotGasCost(rToken.connect(addr1).redeem(issueAmt)) } else { - await rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmt) } expect(await rToken.balanceOf(addr1.address)).to.equal(0) }) @@ -467,9 +467,9 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { // Redemption if (REPORT_GAS) { - await snapshotGasCost(rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce())) + await snapshotGasCost(rToken.connect(addr1).redeem(issueAmt)) } else { - await rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmt) } expect(await rToken.balanceOf(addr1.address)).to.equal(0) }) diff --git a/test/scenario/NestedRTokens.test.ts b/test/scenario/NestedRTokens.test.ts index 1645114118..59590ed1a7 100644 --- a/test/scenario/NestedRTokens.test.ts +++ b/test/scenario/NestedRTokens.test.ts @@ -180,10 +180,10 @@ describe(`Nested RTokens - P${IMPLEMENTATION}`, () => { expect(await staticATokenERC20.balanceOf(addr1.address)).to.equal(0) expect(await one.rToken.balanceOf(addr1.address)).to.equal(0) expect(await two.rToken.balanceOf(addr1.address)).to.equal(issueAmt) - await two.rToken.connect(addr1).redeem(issueAmt, await two.basketHandler.nonce()) + await two.rToken.connect(addr1).redeem(issueAmt) expect(await one.rToken.balanceOf(addr1.address)).to.equal(issueAmt) expect(await two.rToken.balanceOf(addr1.address)).to.equal(0) - await one.rToken.connect(addr1).redeem(issueAmt, await one.basketHandler.nonce()) + await one.rToken.connect(addr1).redeem(issueAmt) expect(await one.rToken.balanceOf(addr1.address)).to.equal(0) expect(await two.rToken.balanceOf(addr1.address)).to.equal(0) expect(await one.rToken.totalSupply()).to.equal(0) diff --git a/test/scenario/NontrivialPeg.test.ts b/test/scenario/NontrivialPeg.test.ts index 2cc2d1ab1b..dfeeac9d0d 100644 --- a/test/scenario/NontrivialPeg.test.ts +++ b/test/scenario/NontrivialPeg.test.ts @@ -155,7 +155,7 @@ describe(`The peg (target/ref) should be arbitrary - P${IMPLEMENTATION}`, () => }) it('should respect differing scales during redemption', async () => { - await rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmt) expect(await token0.balanceOf(backingManager.address)).to.equal(0) expect(await token1.balanceOf(backingManager.address)).to.equal(0) }) diff --git a/test/scenario/RevenueHiding.test.ts b/test/scenario/RevenueHiding.test.ts index 21f46eb30f..3fbe8e9855 100644 --- a/test/scenario/RevenueHiding.test.ts +++ b/test/scenario/RevenueHiding.test.ts @@ -190,7 +190,7 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME // Redeem half const balBefore = await cDAI.balanceOf(addr1.address) const redeemAmt = issueAmt.div(2) - await rToken.connect(addr1).redeem(redeemAmt, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(redeemAmt) const balAfter = await cDAI.balanceOf(addr1.address) const cTokenRedeemAmt = q2.mul(redeemAmt.div(bn('1e10'))).div(fp('1')) expect(balAfter).to.equal(balBefore.add(cTokenRedeemAmt)) diff --git a/test/scenario/SetProtocol.test.ts b/test/scenario/SetProtocol.test.ts index e0d944e3e1..b044b2490a 100644 --- a/test/scenario/SetProtocol.test.ts +++ b/test/scenario/SetProtocol.test.ts @@ -229,7 +229,7 @@ describe(`Linear combination of self-referential collateral - P${IMPLEMENTATION} await expectPrice(basketHandler.address, price.div(2), ORACLE_ERROR, true) // Redeem - await rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce()) + await rToken.connect(addr1).redeem(issueAmt) expect(await token0.balanceOf(backingManager.address)).to.equal(0) expect(await token1.balanceOf(backingManager.address)).to.equal(0) expect(await token2.balanceOf(backingManager.address)).to.equal(0) diff --git a/test/scenario/WBTC.test.ts b/test/scenario/WBTC.test.ts index da26e50680..d85b667745 100644 --- a/test/scenario/WBTC.test.ts +++ b/test/scenario/WBTC.test.ts @@ -236,7 +236,7 @@ describe(`Non-fiat collateral (eg WBTC) - P${IMPLEMENTATION}`, () => { await targetUnitOracle.updateAnswer(bn('40000e8')) // Price change should not impact share of redemption tokens - expect(await rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce())) + expect(await rToken.connect(addr1).redeem(issueAmt)) expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) expect(await wbtc.balanceOf(addr1.address)).to.equal(initialBal) }) diff --git a/test/scenario/WETH.test.ts b/test/scenario/WETH.test.ts index 0e7015cedd..c005db577f 100644 --- a/test/scenario/WETH.test.ts +++ b/test/scenario/WETH.test.ts @@ -225,7 +225,7 @@ describe(`Self-referential collateral (eg ETH via WETH) - P${IMPLEMENTATION}`, ( await setOraclePrice(wethCollateral.address, bn('2e8')) // doubling of price // Price change should not impact share of redemption tokens - expect(await rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce())) + expect(await rToken.connect(addr1).redeem(issueAmt)) expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) expect(await weth.balanceOf(addr1.address)).to.equal(ethBal) }) diff --git a/test/scenario/cETH.test.ts b/test/scenario/cETH.test.ts index be02aef4d3..bd54680c0a 100644 --- a/test/scenario/cETH.test.ts +++ b/test/scenario/cETH.test.ts @@ -262,7 +262,7 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, await setOraclePrice(wethCollateral.address, bn('2e8')) // doubling of price // Price change should not impact share of redemption tokens - expect(await rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce())) + expect(await rToken.connect(addr1).redeem(issueAmt)) expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) expect(await cETH.balanceOf(addr1.address)).to.equal(initialBal) }) @@ -271,7 +271,7 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, await cETH.setExchangeRate(fp('2')) // doubling of price // Compound Redemption rate should result in fewer tokens - expect(await rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce())) + expect(await rToken.connect(addr1).redeem(issueAmt)) expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) expect(await cETH.balanceOf(addr1.address)).to.equal( initialBal.sub(cTokenAmt.div(1000).div(2)) diff --git a/test/scenario/cWBTC.test.ts b/test/scenario/cWBTC.test.ts index 56092f98ea..d5b2a955c9 100644 --- a/test/scenario/cWBTC.test.ts +++ b/test/scenario/cWBTC.test.ts @@ -285,7 +285,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => await targetUnitOracle.updateAnswer(bn('10000e8')) // Price change should not impact share of redemption tokens - expect(await rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce())) + expect(await rToken.connect(addr1).redeem(issueAmt)) expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) expect(await cWBTC.balanceOf(addr1.address)).to.equal(initialBal) }) @@ -294,7 +294,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => await cWBTC.setExchangeRate(fp('2')) // doubling of price // Compound Redemption rate should result in fewer tokens - expect(await rToken.connect(addr1).redeem(issueAmt, await basketHandler.nonce())) + expect(await rToken.connect(addr1).redeem(issueAmt)) expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) expect(await cWBTC.balanceOf(addr1.address)).to.equal( initialBal.sub(cTokenAmt.div(1000).div(2)) From 35002a01a52cf597d58afc70cdd65794121dfcd6 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 14:04:29 -0400 Subject: [PATCH 167/462] scripts --- scripts/deployment/common.ts | 1 + .../deployment/phase1-common/1_deploy_libraries.ts | 11 ++++++++++- .../phase1-common/2_deploy_implementations.ts | 6 +++++- scripts/verification/0_verify_libraries.ts | 8 ++++++++ tasks/testing/upgrade-checker.ts | 2 +- 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/scripts/deployment/common.ts b/scripts/deployment/common.ts index 1132fb8816..0464368c1f 100644 --- a/scripts/deployment/common.ts +++ b/scripts/deployment/common.ts @@ -12,6 +12,7 @@ export interface IPrerequisites { export interface IDeployments { prerequisites: IPrerequisites tradingLib: string + basketLib: string facadeRead: string facadeWriteLib: string cvxMiningLib: string diff --git a/scripts/deployment/phase1-common/1_deploy_libraries.ts b/scripts/deployment/phase1-common/1_deploy_libraries.ts index 27a7d0b935..ad67a1e4e4 100644 --- a/scripts/deployment/phase1-common/1_deploy_libraries.ts +++ b/scripts/deployment/phase1-common/1_deploy_libraries.ts @@ -4,9 +4,10 @@ import { getChainId } from '../../../common/blockchain-utils' import { networkConfig } from '../../../common/configuration' import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../common' import { validatePrerequisites } from '../utils' -import { CvxMining, RecollateralizationLibP1 } from '../../../typechain' +import { BasketLibP1, CvxMining, RecollateralizationLibP1 } from '../../../typechain' let tradingLib: RecollateralizationLibP1 +let basketLib: BasketLibP1 let cvxMiningLib: CvxMining async function main() { @@ -37,6 +38,14 @@ async function main() { fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) + // Deploy BasketLib external library + const BasketLibFactory = await ethers.getContractFactory('BasketLibP1') + basketLib = await BasketLibFactory.connect(burner).deploy() + await basketLib.deployed() + deployments.basketLib = basketLib.address + + fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) + // Deploy CvxMining external library const CvxMiningFactory = await ethers.getContractFactory('CvxMining') cvxMiningLib = await CvxMiningFactory.connect(burner).deploy() diff --git a/scripts/deployment/phase1-common/2_deploy_implementations.ts b/scripts/deployment/phase1-common/2_deploy_implementations.ts index 311ac237a3..055c522497 100644 --- a/scripts/deployment/phase1-common/2_deploy_implementations.ts +++ b/scripts/deployment/phase1-common/2_deploy_implementations.ts @@ -65,6 +65,8 @@ async function main() { if (!deployments.tradingLib) { throw new Error(`Missing pre-requisite addresses in network ${hre.network.name}`) + } else if (!deployments.basketLib) { + throw new Error(`Missing pre-requisite addresses in network ${hre.network.name}`) } else if (!(await isValidContract(hre, deployments.tradingLib))) { throw new Error(`TradingLib contract not found in network ${hre.network.name}`) } @@ -139,7 +141,9 @@ async function main() { ) // 3. ********* Basket Handler *************/ - const BskHandlerImplFactory = await ethers.getContractFactory('BasketHandlerP1') + const BskHandlerImplFactory = await ethers.getContractFactory('BasketHandlerP1', { + libraries: { BasketLibP1: deployments.basketLib }, + }) bskHndlrImpl = await BskHandlerImplFactory.connect(burner).deploy() await bskHndlrImpl.deployed() diff --git a/scripts/verification/0_verify_libraries.ts b/scripts/verification/0_verify_libraries.ts index b74a593d0f..7cc93761ae 100644 --- a/scripts/verification/0_verify_libraries.ts +++ b/scripts/verification/0_verify_libraries.ts @@ -27,6 +27,14 @@ async function main() { [], 'contracts/p1/mixins/RecollateralizationLib.sol:RecollateralizationLibP1' ) + + /** ******************** Verify Basket Library ****************************************/ + await verifyContract( + chainId, + deployments.basketLib, + [], + 'contracts/p1/mixins/BasketLib.sol:BasketLibP1' + ) } main().catch((error) => { diff --git a/tasks/testing/upgrade-checker.ts b/tasks/testing/upgrade-checker.ts index f0274cfbeb..282308d493 100644 --- a/tasks/testing/upgrade-checker.ts +++ b/tasks/testing/upgrade-checker.ts @@ -298,7 +298,7 @@ const redeemRTokens = async ( const preRedeemRTokenBal = await rToken.balanceOf(user.address) const preRedeemErc20Bals = await getAccountBalances(hre, user.address, expectedTokens) - await rToken.connect(user).redeem(redeemAmount, await basketHandler.nonce()) + await rToken.connect(user).redeem(redeemAmount) const postRedeemRTokenBal = await rToken.balanceOf(user.address) const postRedeemErc20Bals = await getAccountBalances(hre, user.address, expectedTokens) From e73033c7b951847e54dd358b036d56078078bc12 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 15:06:04 -0400 Subject: [PATCH 168/462] add safe mulDiv to custom redemption in case of hyperinflated refPerTok --- contracts/interfaces/ITrading.sol | 5 +++-- contracts/p0/BasketHandler.sol | 22 +++++++++++++++++++- contracts/p0/mixins/Trading.sol | 7 ++++--- contracts/p0/mixins/TradingLib.sol | 2 +- contracts/p1/BasketHandler.sol | 33 ++++++++++++++++++++++-------- contracts/p1/mixins/TradeLib.sol | 2 +- contracts/p1/mixins/Trading.sol | 7 ++++--- 7 files changed, 59 insertions(+), 19 deletions(-) diff --git a/contracts/interfaces/ITrading.sol b/contracts/interfaces/ITrading.sol index 4f76877f07..fd3801855b 100644 --- a/contracts/interfaces/ITrading.sol +++ b/contracts/interfaces/ITrading.sol @@ -63,10 +63,11 @@ interface ITrading is IComponent, IRewardableComponent { function tradesOpen() external view returns (uint48); /// Light wrapper around FixLib.mulDiv to support try-catch - function mulDivCeil( + function mulDiv( uint192 x, uint192 y, - uint192 z + uint192 z, + RoundingMode rounding ) external pure returns (uint192); } diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index fd1fcc2cdf..acdb9cf1c1 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -509,7 +509,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { if (refPerTok == 0) continue; // quantities[i] = 0; // {tok} = {BU} * {ref/BU} / {ref/tok} - quantities[i] = amount.mulDiv(refAmtsAll[i], refPerTok, FLOOR).shiftl_toUint( + quantities[i] = safeMulDivFloor(amount, refAmtsAll[i], refPerTok).shiftl_toUint( int8(asset.erc20Decimals()), FLOOR ); @@ -772,4 +772,24 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { return false; } } + + // === Private === + + /// @return The floored result of FixLib.mulDiv + function safeMulDivFloor( + uint192 x, + uint192 y, + uint192 z + ) internal view returns (uint192) { + try main.backingManager().mulDiv(x, y, z, FLOOR) returns (uint192 result) { + return result; + } catch Panic(uint256 errorCode) { + // 0x11: overflow + // 0x12: div-by-zero + assert(errorCode == 0x11 || errorCode == 0x12); + } catch (bytes memory reason) { + assert(keccak256(reason) == UIntOutofBoundsHash); + } + return FIX_MAX; + } } diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index af0938158d..832561274d 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -101,11 +101,12 @@ abstract contract TradingP0 is RewardableP0, ITrading { // === FixLib Helper === /// Light wrapper around FixLib.mulDiv to support try-catch - function mulDivCeil( + function mulDiv( uint192 x, uint192 y, - uint192 z + uint192 z, + RoundingMode rounding ) external pure returns (uint192) { - return x.mulDiv(y, z, CEIL); + return x.mulDiv(y, z, rounding); } } diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index 0254cffd16..bd3043887e 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -554,7 +554,7 @@ library TradingLibP0 { uint192 y, uint192 z ) internal pure returns (uint192) { - try trader.mulDivCeil(x, y, z) returns (uint192 result) { + try trader.mulDiv(x, y, z, CEIL) returns (uint192 result) { return result; } catch Panic(uint256 errorCode) { // 0x11: overflow diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index b7ccbbbe2f..6479deb5c3 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -445,15 +445,12 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { IAsset asset = assetRegistry.toAsset(IERC20(erc20s[i])); if (!asset.isCollateral()) continue; // skip token if no longer registered - // prevent div-by-zero - uint192 refPerTok = ICollateral(address(asset)).refPerTok(); - if (refPerTok == 0) continue; // quantities[i] = 0; - // {tok} = {BU} * {ref/BU} / {ref/tok} - quantities[i] = amount.mulDiv(refAmtsAll[i], refPerTok, FLOOR).shiftl_toUint( - int8(asset.erc20Decimals()), - FLOOR - ); + quantities[i] = safeMulDivFloor( + amount, + refAmtsAll[i], + ICollateral(address(asset)).refPerTok() + ).shiftl_toUint(int8(asset.erc20Decimals()), FLOOR); // marginally more penalizing than its sibling calculation that uses _quantity() // because does not intermediately CEIL as part of the division } @@ -614,6 +611,26 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { max = backup.max; } + // === Private === + + /// @return The floored result of FixLib.mulDiv + function safeMulDivFloor( + uint192 x, + uint192 y, + uint192 z + ) internal view returns (uint192) { + try backingManager.mulDiv(x, y, z, FLOOR) returns (uint192 result) { + return result; + } catch Panic(uint256 errorCode) { + // 0x11: overflow + // 0x12: div-by-zero + assert(errorCode == 0x11 || errorCode == 0x12); + } catch (bytes memory reason) { + assert(keccak256(reason) == UIntOutofBoundsHash); + } + return FIX_MAX; + } + // ==== Storage Gap ==== /** diff --git a/contracts/p1/mixins/TradeLib.sol b/contracts/p1/mixins/TradeLib.sol index fe225d6be4..7893cda7cb 100644 --- a/contracts/p1/mixins/TradeLib.sol +++ b/contracts/p1/mixins/TradeLib.sol @@ -159,7 +159,7 @@ library TradeLib { uint192 y, uint192 z ) internal pure returns (uint192) { - try trader.mulDivCeil(x, y, z) returns (uint192 result) { + try trader.mulDiv(x, y, z, CEIL) returns (uint192 result) { return result; } catch Panic(uint256 errorCode) { // 0x11: overflow diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 2461bd15dc..90ab1cbfe3 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -155,12 +155,13 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // === FixLib Helper === /// Light wrapper around FixLib.mulDiv to support try-catch - function mulDivCeil( + function mulDiv( uint192 x, uint192 y, - uint192 z + uint192 z, + RoundingMode round ) external pure returns (uint192) { - return x.mulDiv(y, z, CEIL); + return x.mulDiv(y, z, round); } /** From 89916aadea849079ae44db648d683533b2ae326f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 15:23:40 -0400 Subject: [PATCH 169/462] try-catch to handle unregistered assets --- contracts/p0/BasketHandler.sol | 31 ++++++++++++++++++------------- contracts/p1/BasketHandler.sol | 26 +++++++++++++++----------- contracts/p1/RToken.sol | 4 +++- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index acdb9cf1c1..e1ff9405ab 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -501,20 +501,25 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // Calculate quantities for (uint256 i = 0; i < len; ++i) { erc20s[i] = address(erc20sAll[i]); - IAsset asset = main.assetRegistry().toAsset(IERC20(erc20s[i])); - if (!asset.isCollateral()) continue; // skip token if no longer registered - // prevent div-by-zero - uint192 refPerTok = ICollateral(address(asset)).refPerTok(); - if (refPerTok == 0) continue; // quantities[i] = 0; - - // {tok} = {BU} * {ref/BU} / {ref/tok} - quantities[i] = safeMulDivFloor(amount, refAmtsAll[i], refPerTok).shiftl_toUint( - int8(asset.erc20Decimals()), - FLOOR - ); - // marginally more penalizing than its sibling calculation that uses _quantity() - // because does not intermediately CEIL as part of the division + try main.assetRegistry().toAsset(IERC20(erc20s[i])) returns (IAsset asset) { + if (!asset.isCollateral()) continue; // skip token if no longer registered + + // prevent div-by-zero + uint192 refPerTok = ICollateral(address(asset)).refPerTok(); + if (refPerTok == 0) continue; // quantities[i] = 0; + + // {tok} = {BU} * {ref/BU} / {ref/tok} + quantities[i] = safeMulDivFloor(amount, refAmtsAll[i], refPerTok).shiftl_toUint( + int8(asset.erc20Decimals()), + FLOOR + ); + // marginally more penalizing than its sibling calculation that uses _quantity() + // because does not intermediately CEIL as part of the division + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + } } } diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 6479deb5c3..bdbad0fec6 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -442,17 +442,21 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // Calculate quantities for (uint256 i = 0; i < len; ++i) { erc20s[i] = address(erc20sAll[i]); - IAsset asset = assetRegistry.toAsset(IERC20(erc20s[i])); - if (!asset.isCollateral()) continue; // skip token if no longer registered - - // {tok} = {BU} * {ref/BU} / {ref/tok} - quantities[i] = safeMulDivFloor( - amount, - refAmtsAll[i], - ICollateral(address(asset)).refPerTok() - ).shiftl_toUint(int8(asset.erc20Decimals()), FLOOR); - // marginally more penalizing than its sibling calculation that uses _quantity() - // because does not intermediately CEIL as part of the division + try assetRegistry.toAsset(IERC20(erc20s[i])) returns (IAsset asset) { + if (!asset.isCollateral()) continue; // skip token if no longer registered + + // {tok} = {BU} * {ref/BU} / {ref/tok} + quantities[i] = safeMulDivFloor( + amount, + refAmtsAll[i], + ICollateral(address(asset)).refPerTok() + ).shiftl_toUint(int8(asset.erc20Decimals()), FLOOR); + // marginally more penalizing than its sibling calculation that uses _quantity() + // because does not intermediately CEIL as part of the division + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + } } } diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 048bd69f64..b61c7abd07 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -351,6 +351,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { uint256[] memory erc20sOutBalances = new uint256[](erc20sOut.length); for (uint256 i = 0; i < erc20sOut.length; ++i) { erc20sOutBalances[i] = erc20sOut[i].balanceOf(recipient); + // we haven't verified this ERC20 is registered but this is always a staticcall } // === Interactions === @@ -359,7 +360,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { { bool allZero = true; for (uint256 i = 0; i < erc20s.length; ++i) { - if (amounts[i] == 0) continue; + if (amounts[i] == 0) continue; // unregistered ERC20s will have 0 amount if (allZero) allZero = false; // Send withdrawal @@ -377,6 +378,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // Check post-balances for (uint256 i = 0; i < erc20sOut.length; ++i) { uint256 bal = erc20sOut[i].balanceOf(recipient); + // we haven't verified this ERC20 is registered but this is always a staticcall require(bal - erc20sOutBalances[i] >= minAmounts[i], "redemption below minimum"); } } From 0f27c08ace8e0b31309d74b7314dac12254e139b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 15:29:47 -0400 Subject: [PATCH 170/462] RToken + Main tests --- test/Main.test.ts | 4 +- test/RToken.test.ts | 254 ++++++++++++++++++++++++-------------------- 2 files changed, 138 insertions(+), 120 deletions(-) diff --git a/test/Main.test.ts b/test/Main.test.ts index 73b3bc5c37..2c16aeda59 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1736,7 +1736,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) // TODO - describe.skip('Historical Redemptions', () => { + describe('Custom Redemption', () => { const issueAmount = fp('10000') let usdcChainlink: MockV3Aggregator let daiChainlink: MockV3Aggregator @@ -1766,7 +1766,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await rToken.connect(addr1).issue(issueAmount) }) - const expectEqualArrays = (arr1: Array, arr2: Array) => { + const expectEqualArrays = (arr1: Array, arr2: Array) => { expect(arr1.length).equal(arr2.length) for (let i = 0; i < arr1.length; i++) { expect(arr1[i]).equal(arr2[i]) diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 2821e773ac..e79c6ebea6 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -1048,75 +1048,32 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.totalSupply()).to.equal(issueAmount) }) - // TODO - it.skip('Should revert if empty redemption #fast', async function () { - // Eliminate most token balances - const bal = issueAmount.div(4) - await token0.connect(owner).burn(backingManager.address, bal) - await token1.connect(owner).burn(backingManager.address, toBNDecimals(bal, 6)) - await token2.connect(owner).burn(backingManager.address, bal) - - // Should not revert with empty redemption yet - await rToken.connect(addr1).redeem(issueAmount.div(2)) - expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) - - // Burn the rest - await token3 - .connect(owner) - .burn(backingManager.address, await token3.balanceOf(backingManager.address)) - - // Now it should revert - await expect(rToken.connect(addr1).redeem(issueAmount.div(2))).to.be.revertedWith( - 'empty redemption' - ) - - // Check values - expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) - }) - - it('Should revert if undercollateralized #fast', async function () { + it('Should revert if undercollateralized from missing balance #fast', async function () { await token0.connect(owner).burn(backingManager.address, issueAmount.div(4)) await expect(rToken.connect(addr1).redeem(issueAmount.div(2))).to.be.revertedWith( 'partial redemption; use redeemToCustom' ) }) - it('Should prorate redemption if basket is DISABLED from fallen refPerTok() #fast', async function () { + it('Should revert if undercollateralized from refPerTok decrease #fast', async function () { // Default immediately await token2.setExchangeRate(fp('0.1')) // 90% decrease // Even though a single BU requires 10x token2 as before, it should still hand out evenly // 1st redemption - await expect(rToken.connect(addr1).redeem(issueAmount.div(2))).to.emit(rToken, 'Redemption') - expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) - expect(await token0.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(8))) - expect(await token2.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(8))) - - // 2nd redemption - await expect(rToken.connect(addr1).redeem(issueAmount.div(2))).to.emit(rToken, 'Redemption') - expect(await token0.balanceOf(addr1.address)).to.equal(initialBal) - expect(await token2.balanceOf(addr1.address)).to.equal(initialBal) + await expect(rToken.connect(addr1).redeem(issueAmount.div(2))).be.revertedWith( + 'partial redemption; use redeemToCustom' + ) }) it('Should not redeem() unregistered collateral #fast', async function () { // Unregister collateral2 await assetRegistry.connect(owner).unregister(collateral2.address) - await expect(rToken.connect(addr1).redeem(issueAmount)).revertedWith('erc20 unregistered') - }) - - it('Should redeem prorata when refPerTok() is 0 #fast', async function () { - // Set refPerTok to 0 - await token2.setExchangeRate(fp('0')) - - // Redemption - await expect(rToken.connect(addr1).redeem(issueAmount)).to.emit(rToken, 'Redemption') - expect(await rToken.totalSupply()).to.equal(0) - expect(await token0.balanceOf(addr1.address)).to.be.equal(initialBal) - expect(await token1.balanceOf(addr1.address)).to.be.equal(initialBal) - expect(await token2.balanceOf(addr1.address)).to.be.equal(initialBal) - expect(await token3.balanceOf(addr1.address)).to.be.equal(initialBal) + await expect(rToken.connect(addr1).redeem(issueAmount)).revertedWith( + 'partial redemption; use redeemToCustom' + ) }) it('Should not overflow BU exchange rate above 1e9 on redeem', async function () { @@ -1130,6 +1087,24 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await rToken.connect(signer).setBasketsNeeded(fp('1e9')) }) + // Add extra backing + await token0.mint( + backingManager.address, + fp('1e9').mul(await token0.balanceOf(backingManager.address)) + ) + await token1.mint( + backingManager.address, + fp('1e9').mul(await token1.balanceOf(backingManager.address)) + ) + await token2.mint( + backingManager.address, + fp('1e9').mul(await token2.balanceOf(backingManager.address)) + ) + await token3.mint( + backingManager.address, + fp('1e9').mul(await token3.balanceOf(backingManager.address)) + ) + const redeemAmount: BigNumber = bn('1.5e9') // Redeem rTokens successfully @@ -1390,7 +1365,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) }) - context.only('Historical redemptions with issued RTokens', function () { + context('Custom redemption', function () { let issueAmount: BigNumber beforeEach(async function () { @@ -1403,6 +1378,27 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await rToken.connect(addr1).issue(issueAmount) }) + it('Should revert if zero amount #fast', async function () { + const zero: BigNumber = bn('0') + await expect(rToken.connect(addr1).redeem(zero)).to.be.revertedWith('Cannot redeem zero') + }) + + it('Should revert if no balance of RToken #fast', async function () { + const redeemAmount: BigNumber = bn('20000e18') + await expect( + rToken + .connect(addr2) + .redeemToCustom( + addr2.address, + redeemAmount, + [await basketHandler.nonce()], + [fp('1')], + [], + [] + ) + ).to.be.revertedWith('insufficient balance') + }) + it('Should redeem RTokens correctly', async function () { const redeemAmount = bn('100e18') @@ -1637,21 +1633,12 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Should not revert with empty redemption yet const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteCustomRedemption( - basketNonces, - portions, - issueAmount.div(2) - ) + await expect( + basketHandler.quoteCustomRedemption(basketNonces, portions, issueAmount.div(2)) + ).to.not.be.reverted await rToken .connect(addr1) - .redeemToCustom( - addr1.address, - issueAmount.div(2), - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) + .redeemToCustom(addr1.address, issueAmount.div(2), basketNonces, portions, [], []) expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) // Burn the rest @@ -1663,41 +1650,43 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom( - addr1.address, - issueAmount.div(2), - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) + .redeemToCustom(addr1.address, issueAmount.div(2), basketNonces, portions, [], []) ).to.be.revertedWith('empty redemption') // Check values expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) }) - it('Should revert if different basketNonce #fast', async function () { - // Should fail if revertOnPartialRedemption is true - const basketNonces = [1] + it('Should enforce primeNonce <-> reference nonce relationship #fast', async function () { const portions = [fp('1')] - const quote = await basketHandler.quoteCustomRedemption( - basketNonces, - portions, - issueAmount.div(2) - ) await expect( rToken .connect(addr1) - .redeemToCustom( - addr1.address, - issueAmount.div(2), - [2], - portions, - quote.erc20s, - quote.quantities - ) + .redeemToCustom(addr1.address, issueAmount.div(2), [2], portions, [], []) + ).to.be.revertedWith('invalid basketNonce') + + // Bump primeNonce + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + + // Old basket is no longer redeemable + await expect( + rToken + .connect(addr1) + .redeemToCustom(addr1.address, issueAmount.div(2), [1], portions, [], []) ).to.be.revertedWith('invalid basketNonce') + + // Nonce 2 still doesn't have a reference basket yet + await expect( + rToken + .connect(addr1) + .redeemToCustom(addr1.address, issueAmount.div(2), [2], portions, [], []) + ).to.be.revertedWith('invalid basketNonce') + + // Refresh reference basket + await basketHandler.connect(owner).refreshBasket() + await rToken + .connect(addr1) + .redeemToCustom(addr1.address, issueAmount.div(2), [2], portions, [], []) }) it('Should prorate redemption if basket is DISABLED from fallen refPerTok() #fast', async function () { @@ -1747,25 +1736,44 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await token2.balanceOf(addr1.address)).to.equal(initialBal) }) - it('Should not redeem() unregistered collateral #fast', async function () { + it('Should not revert when redeeming unregistered collateral #fast', async function () { // Unregister collateral2 const basketNonces = [1] const portions = [fp('1')] - const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, issueAmount) await assetRegistry.connect(owner).unregister(collateral2.address) - await expect( - rToken - .connect(addr1) - .redeemToCustom( - addr1.address, - issueAmount, - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) - ).revertedWith('erc20 unregistered') + const quote = await basketHandler.quoteCustomRedemption( + basketNonces, + portions, + issueAmount.div(2) + ) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + issueAmount.div(2), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + + // Adding an unregistered ERC20 to the erc20sOut should not break anything + expect(await assetRegistry.isRegistered(token2.address)).to.equal(false) + const neweRC20s = JSON.parse(JSON.stringify(quote.erc20s)) + const newQuantities = JSON.parse(JSON.stringify(quote.quantities)) + neweRC20s.push(token2.address) + newQuantities.push(fp('0')) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + issueAmount.div(2), + basketNonces, + portions, + neweRC20s, + newQuantities + ) }) it('Should redeem prorata when refPerTok() is 0 #fast', async function () { @@ -1938,7 +1946,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { .connect(addr1) .redeemToCustom( addr1.address, - redeemAmount.add(1), + redeemAmount, basketNonces, portions, quote2.erc20s, @@ -2012,22 +2020,32 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { portions, redeemAmount.add(1) ) - await rToken - .connect(addr1) - .redeemToCustom( - addr1.address, - redeemAmount.add(1), - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) - await expect(rToken.connect(addr1).redeem(redeemAmount.add(1))).to.be.revertedWith( - 'supply change throttled' - ) + await expect( + rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + redeemAmount.add(1), + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + ).to.be.revertedWith('supply change throttled') // amtRate redemption should succeed - await expect(rToken.connect(addr1).redeem(redeemAmount)).to.emit(rToken, 'Redemption') + await expect( + rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + redeemAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + ).to.emit(rToken, 'Redemption') // Check redemption throttle is 0 expect(await rToken.redemptionAvailable()).to.equal(bn(0)) From 7c5dafcaf2ea9817fdc6529663763bf89d94f5f2 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 15:49:49 -0400 Subject: [PATCH 171/462] add test for splitting portions and test portions sum to 1 --- test/Main.test.ts | 80 ++++++++++++++++++--------------------------- test/RToken.test.ts | 26 +++++++++++---- 2 files changed, 52 insertions(+), 54 deletions(-) diff --git a/test/Main.test.ts b/test/Main.test.ts index 2c16aeda59..a5e4cebb7d 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -16,6 +16,7 @@ import { } from '../common/configuration' import { CollateralStatus, + RoundingMode, ZERO_ADDRESS, ONE_ADDRESS, MAX_UINT256, @@ -1735,7 +1736,6 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { .withArgs(2, [token0.address], [fp('1')], [ethers.utils.formatBytes32String('USD')]) }) - // TODO describe('Custom Redemption', () => { const issueAmount = fp('10000') let usdcChainlink: MockV3Aggregator @@ -1787,14 +1787,17 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { } } - it('Should correctly quote a historical redemption (current)', async () => { + it('Should correctly quote the current basket, same as quote()', async () => { /* Test Quote */ const basketNonces = [1] const portions = [fp('1')] const amount = fp('10000') + const baseline = await basketHandler.quote(amount, RoundingMode.FLOOR) const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, amount) + expectEqualArrays(quote.erc20s, baseline.erc20s) + expectEqualArrays(quote.quantities, baseline.quantities) expect(quote.erc20s.length).equal(4) expect(quote.quantities.length).equal(4) @@ -1823,7 +1826,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expectEqualArrays(quote.quantities, expectedQuantities) /* - Test Historical Redemption + Test Custom Redemption */ const balsBefore = await getBalances(addr1.address, expectedTokens) await rToken @@ -1837,10 +1840,10 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { quote.quantities ) const balsAfter = await getBalances(addr1.address, expectedTokens) - expectDelta(balsBefore, quote.quantities, balsAfter) + expectDelta(balsBefore, baseline.quantities, balsAfter) }) - it('Should correctly quote a historical redemption [single-asset default & replacement]', async () => { + it('Should correctly quote a custom redemption across 2 baskets after default', async () => { /* Setup */ @@ -1890,7 +1893,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expectEqualArrays(quote.quantities, expectedQuantities) /* - Test Historical Redemption + Test Custom Redemption */ const balsBefore = await getBalances(addr1.address, expectedTokens) @@ -1925,54 +1928,35 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expectDelta(balsBefore, quote.quantities, balsAfter) }) - it('Should correctly quote a historical redemption [single-asset refPerTok appreciation]', async () => { - /* - Setup - */ - // appreciate aDai and refresh basket - await token2.setExchangeRate(fp('2')) - await basketHandler.refreshBasket() - await advanceTime(Number(config.warmupPeriod) + 1) - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - + it('Repeating basket nonces should not be exploitable', async () => { /* Test Quote */ - const basketNonces = [1, 2] - const portions = [fp('0.5'), fp('0.5')] + const basketNonces = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + const portions = [ + fp('0.1'), + fp('0.1'), + fp('0.1'), + fp('0.1'), + fp('0.1'), + fp('0.1'), + fp('0.1'), + fp('0.1'), + fp('0.1'), + fp('0.1'), + ] const amount = fp('10000') + const baseline = await basketHandler.quote(amount, RoundingMode.FLOOR) const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, amount) - - expect(quote.erc20s.length).equal(4) - expect(quote.quantities.length).equal(4) - - const expectedTokens = [token0, token1, token2, token3] - const expectedAddresses = expectedTokens.map((t) => t.address) - const expectedQuantities = [ - fp('0.25') - .mul(issueAmount) - .div(await collateral0.refPerTok()) - .div(bn(`1e${18 - (await token0.decimals())}`)), - fp('0.25') - .mul(issueAmount) - .div(await collateral1.refPerTok()) - .div(bn(`1e${18 - (await token1.decimals())}`)), - fp('0.25') - .mul(issueAmount) - .div(await collateral2.refPerTok()) - .div(bn(`1e${18 - (await token2.decimals())}`)), - fp('0.25') - .mul(issueAmount) - .div(await collateral3.refPerTok()) - .div(bn(`1e${18 - (await token3.decimals())}`)), - ] - expectEqualArrays(quote.erc20s, expectedAddresses) - expectEqualArrays(quote.quantities, expectedQuantities) + expectEqualArrays(quote.erc20s, baseline.erc20s) + expectEqualArrays(quote.quantities, baseline.quantities) /* - Test Historical Redemption + Test Custom Redemption */ + const expectedTokens = await Promise.all( + quote.erc20s.map(async (e) => ethers.getContractAt('ERC20Mock', e)) + ) const balsBefore = await getBalances(addr1.address, expectedTokens) await rToken .connect(addr1) @@ -1985,7 +1969,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { quote.quantities ) const balsAfter = await getBalances(addr1.address, expectedTokens) - expectDelta(balsBefore, quote.quantities, balsAfter) + expectDelta(balsBefore, baseline.quantities, balsAfter) }) it('Should correctly quote a historical redemption [full basket default, multi-token-backup]', async () => { @@ -2050,7 +2034,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expectEqualArrays(quote.quantities, expectedQuantities) /* - Test Historical Redemption + Test Custom Redemption */ const balsBefore = await getBalances(addr1.address, expectedTokens) await backupToken1.mint(backingManager.address, issueAmount) diff --git a/test/RToken.test.ts b/test/RToken.test.ts index e79c6ebea6..69f16fb752 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -1380,23 +1380,37 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { it('Should revert if zero amount #fast', async function () { const zero: BigNumber = bn('0') - await expect(rToken.connect(addr1).redeem(zero)).to.be.revertedWith('Cannot redeem zero') + await expect( + rToken + .connect(addr1) + .redeemToCustom(addr1.address, zero, [await basketHandler.nonce()], [fp('1')], [], []) + ).to.be.revertedWith('Cannot redeem zero') }) it('Should revert if no balance of RToken #fast', async function () { const redeemAmount: BigNumber = bn('20000e18') + const nonce = await basketHandler.nonce() await expect( rToken .connect(addr2) + .redeemToCustom(addr2.address, redeemAmount, [nonce], [fp('1')], [], []) + ).to.be.revertedWith('insufficient balance') + }) + + it('Should revert if portions do not sum to FIX_ONE #fast', async function () { + const nonce = await basketHandler.nonce() + await expect( + rToken + .connect(addr1) .redeemToCustom( - addr2.address, - redeemAmount, - [await basketHandler.nonce()], - [fp('1')], + addr1.address, + fp('1'), + [nonce, nonce], + [fp('0.5'), fp('0.5').add(1)], [], [] ) - ).to.be.revertedWith('insufficient balance') + ).to.be.revertedWith('portions do not add up to FIX_ONE') }) it('Should redeem RTokens correctly', async function () { From 5e5e9ccc8ca5d0917946c4413330bf5e1c65812d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 16:04:34 -0400 Subject: [PATCH 172/462] add simulation help by returning actual amounts transferred --- contracts/interfaces/IRToken.sol | 6 ++--- contracts/p0/RToken.sol | 41 +++++++++++++++++++------------- contracts/p1/RToken.sol | 41 ++++++++++++++++++-------------- test/Main.test.ts | 8 +------ test/RToken.test.ts | 18 +++++++++++++- test/utils/matchers.ts | 8 +++++++ 6 files changed, 76 insertions(+), 46 deletions(-) diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index d8c8341fa2..9239ca01d6 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -98,7 +98,7 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @param basketNonces An array of basket nonces to do redemption from /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE - /// @param erc20sOut An array of ERC20s expected out + /// @param expectedERC20sOut An array of ERC20s expected out /// @param minAmounts {qTok} The minimum ERC20 quantities the caller should receive /// @custom:interaction function redeemToCustom( @@ -106,9 +106,9 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, - IERC20[] memory erc20sOut, + address[] memory expectedERC20sOut, uint256[] memory minAmounts - ) external; + ) external returns (address[] memory erc20sOut, uint256[] memory amountsOut); /// Mints a quantity of RToken to the `recipient`, callable only by the BackingManager /// @param recipient The recipient of the newly minted RToken diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 1629e05171..7d19e5fc62 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -189,7 +189,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @param basketNonces An array of basket nonces to do redemption from /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE - /// @param erc20sOut An array of ERC20s expected out + /// @param expectedERC20sOut An array of ERC20s expected out /// @param minAmounts {qTok} The minimum ERC20 quantities the caller should receive /// @custom:interaction function redeemToCustom( @@ -197,9 +197,14 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, - IERC20[] memory erc20sOut, + address[] memory expectedERC20sOut, uint256[] memory minAmounts - ) external notFrozen exchangeRateIsValidAfter { + ) + external + notFrozen + exchangeRateIsValidAfter + returns (address[] memory erc20sOut, uint256[] memory amountsOut) + { require(amount > 0, "Cannot redeem zero"); require(amount <= balanceOf(_msgSender()), "insufficient balance"); @@ -225,39 +230,41 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); } - (address[] memory erc20s, uint256[] memory amounts) = main - .basketHandler() - .quoteCustomRedemption(basketNonces, portions, basketsRedeemed); + (erc20sOut, amountsOut) = main.basketHandler().quoteCustomRedemption( + basketNonces, + portions, + basketsRedeemed + ); emit BasketsNeededChanged(basketsNeeded, basketsNeeded.minus(basketsRedeemed)); basketsNeeded = basketsNeeded.minus(basketsRedeemed); // === Save initial recipient balances === - uint256[] memory erc20sOutBalances = new uint256[](erc20sOut.length); - for (uint256 i = 0; i < erc20sOut.length; ++i) { - erc20sOutBalances[i] = erc20sOut[i].balanceOf(recipient); + uint256[] memory pastBals = new uint256[](expectedERC20sOut.length); + for (uint256 i = 0; i < expectedERC20sOut.length; ++i) { + pastBals[i] = IERC20(expectedERC20sOut[i]).balanceOf(recipient); } // ==== Prorate redemption + send out balances ==== { bool allZero = true; // Bound each withdrawal by the prorata share, in case currently under-collateralized - for (uint256 i = 0; i < erc20s.length; i++) { - uint256 bal = IERC20Upgradeable(erc20s[i]).balanceOf( + for (uint256 i = 0; i < erc20sOut.length; i++) { + uint256 bal = IERC20Upgradeable(erc20sOut[i]).balanceOf( address(main.backingManager()) ); // {qTok} // {qTok} = {qTok} * {qRTok} / {qRTok} uint256 prorata = mulDiv256(bal, amount, totalSupply()); // FLOOR - if (prorata < amounts[i]) amounts[i] = prorata; + if (prorata < amountsOut[i]) amountsOut[i] = prorata; // Send withdrawal - if (amounts[i] > 0) { - IERC20(erc20s[i]).safeTransferFrom( + if (amountsOut[i] > 0) { + IERC20(erc20sOut[i]).safeTransferFrom( address(main.backingManager()), recipient, - amounts[i] + amountsOut[i] ); allZero = false; } @@ -271,9 +278,9 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { // === Post-checks === // Check post-balances - for (uint256 i = 0; i < erc20sOut.length; ++i) { + for (uint256 i = 0; i < expectedERC20sOut.length; ++i) { require( - erc20sOut[i].balanceOf(recipient) - erc20sOutBalances[i] >= minAmounts[i], + IERC20(expectedERC20sOut[i]).balanceOf(recipient) - pastBals[i] >= minAmounts[i], "redemption below minimum" ); } diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index b61c7abd07..920f30589d 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -280,7 +280,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @param basketNonces An array of basket nonces to do redemption from /// @param portions {1} An array of Fix quantities that must add up to FIX_ONE - /// @param erc20sOut An array of ERC20s expected out + /// @param expectedERC20sOut An array of ERC20s expected out /// @param minAmounts {qTok} The minimum ERC20 quantities the caller should receive /// @custom:interaction RCEI function redeemToCustom( @@ -288,9 +288,14 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, - IERC20[] memory erc20sOut, + address[] memory expectedERC20sOut, uint256[] memory minAmounts - ) external notFrozen exchangeRateIsValidAfter { + ) + external + notFrozen + exchangeRateIsValidAfter + returns (address[] memory erc20sOut, uint256[] memory amountsOut) + { // == Refresh == assetRegistry.refresh(); @@ -324,7 +329,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // === Get basket redemption amounts === - (address[] memory erc20s, uint256[] memory amounts) = basketHandler.quoteCustomRedemption( + (erc20sOut, amountsOut) = basketHandler.quoteCustomRedemption( basketNonces, portions, basketsRedeemed @@ -332,25 +337,25 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // ==== Prorate redemption ==== // i.e, set amounts = min(amounts, balances * amount / totalSupply) - // where balances[i] = erc20s[i].balanceOf(backingManager) + // where balances[i] = erc20sOut[i].balanceOf(backingManager) // Bound each withdrawal by the prorata share, in case we're currently under-collateralized - for (uint256 i = 0; i < erc20s.length; ++i) { + for (uint256 i = 0; i < erc20sOut.length; ++i) { // {qTok} = {qTok} * {qRTok} / {qRTok} uint256 prorata = mulDiv256( - IERC20Upgradeable(erc20s[i]).balanceOf(address(backingManager)), + IERC20(erc20sOut[i]).balanceOf(address(backingManager)), amount, supply ); // FLOOR - if (prorata < amounts[i]) amounts[i] = prorata; + if (prorata < amountsOut[i]) amountsOut[i] = prorata; } // === Save initial recipient balances === - uint256[] memory erc20sOutBalances = new uint256[](erc20sOut.length); - for (uint256 i = 0; i < erc20sOut.length; ++i) { - erc20sOutBalances[i] = erc20sOut[i].balanceOf(recipient); + uint256[] memory pastBals = new uint256[](expectedERC20sOut.length); + for (uint256 i = 0; i < expectedERC20sOut.length; ++i) { + pastBals[i] = IERC20(expectedERC20sOut[i]).balanceOf(recipient); // we haven't verified this ERC20 is registered but this is always a staticcall } @@ -359,15 +364,15 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // Distribute tokens; revert if empty redemption { bool allZero = true; - for (uint256 i = 0; i < erc20s.length; ++i) { - if (amounts[i] == 0) continue; // unregistered ERC20s will have 0 amount + for (uint256 i = 0; i < erc20sOut.length; ++i) { + if (amountsOut[i] == 0) continue; // unregistered ERC20s will have 0 amount if (allZero) allZero = false; // Send withdrawal - IERC20Upgradeable(erc20s[i]).safeTransferFrom( + IERC20Upgradeable(erc20sOut[i]).safeTransferFrom( address(backingManager), recipient, - amounts[i] + amountsOut[i] ); } if (allZero) revert("empty redemption"); @@ -376,10 +381,10 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // === Post-checks === // Check post-balances - for (uint256 i = 0; i < erc20sOut.length; ++i) { - uint256 bal = erc20sOut[i].balanceOf(recipient); + for (uint256 i = 0; i < expectedERC20sOut.length; ++i) { + uint256 bal = IERC20(expectedERC20sOut[i]).balanceOf(recipient); // we haven't verified this ERC20 is registered but this is always a staticcall - require(bal - erc20sOutBalances[i] >= minAmounts[i], "redemption below minimum"); + require(bal - pastBals[i] >= minAmounts[i], "redemption below minimum"); } } diff --git a/test/Main.test.ts b/test/Main.test.ts index a5e4cebb7d..89abc2d449 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -26,6 +26,7 @@ import { PAUSER, MAX_UINT192, } from '../common/constants' +import { expectEqualArrays } from './utils/matchers' import { expectInIndirectReceipt, expectInReceipt, expectEvents } from '../common/events' import { expectPrice, expectUnpriced, setOraclePrice } from './utils/oracles' import { bn, fp } from '../common/numbers' @@ -1766,13 +1767,6 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await rToken.connect(addr1).issue(issueAmount) }) - const expectEqualArrays = (arr1: Array, arr2: Array) => { - expect(arr1.length).equal(arr2.length) - for (let i = 0; i < arr1.length; i++) { - expect(arr1[i]).equal(arr2[i]) - } - } - const getBalances = async (account: string, tokens: Array) => { const bals: Array = [] for (const token of tokens) { diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 69f16fb752..fa752f9a6c 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -53,6 +53,7 @@ import { PRICE_TIMEOUT, VERSION, } from './fixtures' +import { expectEqualArrays } from './utils/matchers' import { cartesianProduct } from './utils/cases' import { useEnv } from '#/utils/env' import { mintCollaterals } from './utils/tokens' @@ -1413,7 +1414,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ).to.be.revertedWith('portions do not add up to FIX_ONE') }) - it('Should redeem RTokens correctly', async function () { + it('Should redeemToCustom RTokens correctly', async function () { const redeemAmount = bn('100e18') // Check balances @@ -1429,6 +1430,21 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { portions, redeemAmount ) + + // Check simulation result + const [actualERC20s, actualQuantities] = await rToken + .connect(addr1) + .callStatic.redeemToCustom( + addr1.address, + redeemAmount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + expectEqualArrays(actualERC20s, quote.erc20s) + expectEqualArrays(actualQuantities, quote.quantities) + await rToken .connect(addr1) .redeemToCustom( diff --git a/test/utils/matchers.ts b/test/utils/matchers.ts index c126583816..51fe4a97e8 100644 --- a/test/utils/matchers.ts +++ b/test/utils/matchers.ts @@ -1,4 +1,5 @@ import { BigNumber } from 'ethers' +import { expect } from 'chai' import { bn } from '../../common/numbers' // Creates a chai matcher that returns true if y is within a quadrillion of x @@ -10,3 +11,10 @@ export const withinQuad = (x: BigNumber): ((y: BigNumber) => boolean) => { return y.gte(lower) && y.lte(higher) } } + +export const expectEqualArrays = (arr1: Array, arr2: Array) => { + expect(arr1.length).equal(arr2.length) + for (let i = 0; i < arr1.length; i++) { + expect(arr1[i]).equal(arr2[i]) + } +} From 870e86bd7393a65cd303226cbd85c1d048a33b59 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 16:15:42 -0400 Subject: [PATCH 173/462] BasketLib proofreading --- contracts/p1/mixins/BasketLib.sol | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/contracts/p1/mixins/BasketLib.sol b/contracts/p1/mixins/BasketLib.sol index 8e6f933587..f4dba9282a 100644 --- a/contracts/p1/mixins/BasketLib.sol +++ b/contracts/p1/mixins/BasketLib.sol @@ -7,7 +7,7 @@ import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "../../interfaces/IAssetRegistry.sol"; import "../../libraries/Fixed.sol"; -// A "valid collateral array" is a an IERC20[] value without rtoken, rsr, or any duplicate values +// A "valid collateral array" is a IERC20[] array without rtoken/rsr/stRSR/zero address/duplicates // A BackupConfig value is valid if erc20s is a valid collateral array struct BackupConfig { @@ -42,7 +42,6 @@ struct BasketConfig { /// The type of BasketHandler.basket. /// Defines a basket unit (BU) in terms of reference amounts of underlying tokens // Logically, basket is just a mapping of erc20 addresses to ref-unit amounts. -// In the analytical comments I'll just refer to it that way. // // A Basket is valid if erc20s is a valid collateral array and erc20s == keys(refAmts) struct Basket { @@ -50,6 +49,11 @@ struct Basket { mapping(IERC20 => uint192) refAmts; // {ref/BU} } +/** + * @title BasketLibP1 + * @notice A helper library that implements a `nextBasket()` function for selecting a reference + * basket from the current basket config in combination with collateral statuses/exchange rates. + */ library BasketLibP1 { using BasketLibP1 for Basket; using EnumerableSet for EnumerableSet.AddressSet; @@ -153,7 +157,7 @@ library BasketLibP1 { /// @param targetNames Scratch space for computation; initial value unused /// @param newBasket Scratch space for computation; initial value unused /// @param config The current basket configuration - /// @return If successful; i.e the next argument contains a SOUND non-empty basket + /// @return success result; i.e newBasket can be expected to contain a valid reference basket function nextBasket( Basket storage newBasket, EnumerableSet.Bytes32Set storage targetNames, @@ -286,7 +290,7 @@ library BasketLibP1 { ) private view returns (bool) { if (address(erc20) == address(0)) return false; // P1 gas optimization - // We do not need to check that the token is not a system token + // We do not need to check that the ERC20 is not a system token // BasketHandlerP1.requireValidCollArray() has been run on all ERC20s already try assetRegistry.toColl(erc20) returns (ICollateral coll) { From 689fc316777d4d362bd44b7c70e0d8097895a1c5 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 16:54:55 -0400 Subject: [PATCH 174/462] update to 3.0.0 BackingManager/RevenueTrader behavior --- contracts/facade/FacadeRead.sol | 54 ++++++++++++++------------------- test/FacadeAct.test.ts | 2 +- test/FacadeRead.test.ts | 7 ++--- 3 files changed, 27 insertions(+), 36 deletions(-) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index cbf6d746d0..8443c16945 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "../plugins/trading/DutchTrade.sol"; import "../interfaces/IAsset.sol"; import "../interfaces/IAssetRegistry.sol"; import "../interfaces/IFacadeRead.sol"; @@ -226,33 +227,22 @@ contract FacadeRead is IFacadeRead { uint256 sellAmount ) { - bm.main().assetRegistry().refresh(); - bm.main().furnace().melt(); - - IERC20[] memory erc20s = bm.main().assetRegistry().erc20s(); - - // Settle all backingManager auctions - for (uint256 i = 0; i < erc20s.length; ++i) { - ITrade trade = bm.trades(erc20s[i]); - if (address(trade) != address(0) && trade.canSettle()) { - bm.settleTrade(erc20s[i]); - } - } - if (bm.tradesOpen() == 0) { + IERC20[] memory erc20s = bm.main().assetRegistry().erc20s(); + // Try to launch auctions - bm.manageTokensSortedOrder(new IERC20[](0)); - canStart = bm.tradesOpen() > 0; - - // Find the started auction - for (uint256 i = 0; i < erc20s.length; ++i) { - ITrade trade = bm.trades(erc20s[i]); - if (address(trade) != address(0)) { - sell = trade.sell(); - buy = trade.buy(); - sellAmount = trade.initBal(); + try bm.rebalance(TradeKind.DUTCH_AUCTION) { + // Find the started auction + for (uint256 i = 0; i < erc20s.length; ++i) { + DutchTrade trade = DutchTrade(address(bm.trades(erc20s[i]))); + if (address(trade) != address(0)) { + canStart = true; + sell = trade.sell(); + buy = trade.buy(); + sellAmount = trade.sellAmount(); + } } - } + } catch {} } } @@ -275,7 +265,7 @@ contract FacadeRead is IFacadeRead { Registry memory reg = revenueTrader.main().assetRegistry().getRegistry(); // Forward ALL revenue - revenueTrader.main().backingManager().manageTokens(reg.erc20s); + revenueTrader.main().backingManager().forwardRevenue(reg.erc20s); erc20s = new IERC20[](reg.erc20s.length); canStart = new bool[](reg.erc20s.length); @@ -300,12 +290,14 @@ contract FacadeRead is IFacadeRead { int8(reg.assets[i].erc20Decimals()) ); - try revenueTrader.manageToken(reg.erc20s[i]) { - if (revenueTrader.tradesOpen() - tradesOpen > 0) { - canStart[i] = true; - } - // solhint-disable-next-line no-empty-blocks - } catch {} + if (reg.erc20s[i].balanceOf(address(revenueTrader)) >= minTradeAmounts[i]) { + try revenueTrader.manageToken(reg.erc20s[i], TradeKind.DUTCH_AUCTION) { + if (revenueTrader.tradesOpen() - tradesOpen > 0) { + canStart[i] = true; + } + // solhint-disable-next-line no-empty-blocks + } catch {} + } } } diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts index 73bc35b826..8f1e522545 100644 --- a/test/FacadeAct.test.ts +++ b/test/FacadeAct.test.ts @@ -248,7 +248,7 @@ describe('FacadeAct contract', () => { it('Basket - Should handle no valid basket after refresh', async () => { // Redeem all RTokens - await rToken.connect(addr1).redeem(issueAmount) + await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) // Set simple basket with only one collateral await basketHandler.connect(owner).setPrimeBasket([aToken.address], [fp('1')]) diff --git a/test/FacadeRead.test.ts b/test/FacadeRead.test.ts index 268a549af6..993e64eb55 100644 --- a/test/FacadeRead.test.ts +++ b/test/FacadeRead.test.ts @@ -167,7 +167,7 @@ describe('FacadeRead contract', () => { expect(await facade.callStatic.maxIssuable(rToken.address, other.address)).to.equal(0) // Redeem all RTokens - await rToken.connect(addr1).redeem(issueAmount) + await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) // With 0 baskets needed - Returns correct value expect(await facade.callStatic.maxIssuable(rToken.address, addr2.address)).to.equal( @@ -258,7 +258,7 @@ describe('FacadeRead contract', () => { expect(overCollateralization).to.equal(fp('1')) // Redeem all RTokens - await rToken.connect(addr1).redeem(issueAmount) + await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) // Check values = 0 (no supply) ;[backing, overCollateralization] = await facade.callStatic.backingOverview(rToken.address) @@ -391,7 +391,6 @@ describe('FacadeRead contract', () => { if (erc20s[i] == usdc.address) bal = issueAmount.div(4).div(bn('1e12')) if (erc20s[i] == aToken.address) bal = issueAmount.div(4) if (erc20s[i] == cTokenVault.address) bal = issueAmount.div(4).mul(50).div(bn('1e10')) - expect(balances[i]).to.equal(bal) if ( [token.address, usdc.address, aToken.address, cTokenVault.address].indexOf(erc20s[i]) >= 0 @@ -482,7 +481,7 @@ describe('FacadeRead contract', () => { it('Should return basketBreakdown correctly when RToken supply = 0', async () => { // Redeem all RTokens - await rToken.connect(addr1).redeem(issueAmount) + await rToken.connect(addr1).redeem(issueAmount, await basketHandler.nonce()) expect(await rToken.totalSupply()).to.equal(bn(0)) From 8ce7274871d5a05f6a5b3a599f87a437063c835b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 17:27:45 -0400 Subject: [PATCH 175/462] draft 1-function approach --- contracts/p0/RevenueTrader.sol | 6 +- contracts/p0/mixins/TradingLib.sol | 5 +- contracts/p1/RevenueTrader.sol | 75 ++------- contracts/p1/mixins/TradeLib.sol | 10 +- test/Revenues.test.ts | 238 +++-------------------------- 5 files changed, 41 insertions(+), 293 deletions(-) diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index fec1c0233c..c46cf44826 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -86,15 +86,15 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { buyPrice: buyPrice }); - // If not dust, trade the non-target asset for the target asset + // Whether dust or not, trade the non-target asset for the target asset // Any asset with a broken price feed will trigger a revert here - (bool launch, TradeRequest memory req) = TradingLibP0.prepareTradeSell( + (, TradeRequest memory req) = TradingLibP0.prepareTradeSell( trade, minTradeVolume, maxTradeSlippage ); + require(req.sellAmount > 1, "sell amount too low"); - require(launch, "trade not worth launching"); tryTrade(kind, req); } } diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index 0254cffd16..302568671f 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -50,10 +50,7 @@ library TradingLibP0 { (uint192 lotLow, uint192 lotHigh) = trade.sell.lotPrice(); - // Don't sell dust - if (!isEnoughToSell(trade.sell, trade.sellAmount, lotLow, minTradeVolume)) { - return (false, req); - } + notDust = !isEnoughToSell(trade.sell, trade.sellAmount, lotLow, minTradeVolume); // Cap sell amount uint192 maxSell = maxTradeSize(trade.sell, trade.buy, lotHigh); // {sellTok} diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index acd165e57f..eca0b0dc7f 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -56,49 +56,6 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { distributor.distribute(tokenToBuy, bal); } - /// If erc20 is tokenToBuy, do nothing; otherwise, sell it for tokenToBuy - /// @dev Intended to be used to manually start an auction for some token that the - /// RevenueTrader has a balance of but cannot be sold via a normal manageTokens(...) call - /// it will allow user to start trade even if trade size is < minTradeVolume - /// OR a BATCH_AUCTION trade in case the price feed is broken. - /// Works identially to manageToken(...) otherwise - /// @param erc20 The token the revenue trader has a balance of but cannot - /// be sold via a normal manageTokens(...) call - /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION. - /// DUTCH_AUCTION can only be used if oracles are working - /// @custom:interaction RCEI - // - function startDustAuction(IERC20 erc20, TradeKind kind) external notTradingPausedOrFrozen { - require(erc20 != tokenToBuy, "invalid token"); - // == Refresh + basic Checks/Effects == - TradeInfo memory trade = getTrade(erc20); - require(trade.sellAmount != 0, "zero balance"); - - uint192 minBuyAmount = (trade.sellPrice != 0 && trade.sellPrice != FIX_MAX) - ? trade.buyPrice.div(trade.sellPrice, CEIL).mul(trade.sellAmount) - : 0; - - // == Checks/Effects == - // Specific to starting dust auctions - if (minBuyAmount == 0) { - require(kind != TradeKind.DUTCH_AUCTION, "infinite slippage DUTCH_AUCTION not allowed"); - } - uint192 maxSellTradeSize = trade.sell.maxTradeVolume(); - uint192 maxBuyTradeSize = trade.buy.maxTradeVolume(); - require(minBuyAmount <= maxBuyTradeSize, "buy amount too large"); - require(trade.sellAmount <= maxSellTradeSize, "sell amount too large"); - - TradeRequest memory req = TradeRequest({ - sellAmount: trade.sellAmount.shiftl_toUint(int8(trade.sell.erc20Decimals()), FLOOR), - minBuyAmount: minBuyAmount.shiftl_toUint(int8(trade.buy.erc20Decimals()), CEIL), - sell: trade.sell, - buy: trade.buy - }); - - // == Interactions == - tryTrade(kind, req); - } - /// If erc20 is tokenToBuy, distribute it; else, sell it for tokenToBuy /// @dev Intended to be used with multicall /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION @@ -122,22 +79,6 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { return; } - TradeInfo memory trade = getTrade(erc20); - - // If not dust, trade the non-target asset for the target asset - // Any asset with a broken price feed will trigger a revert here - (bool launch, TradeRequest memory req) = TradeLib.prepareTradeSell( - trade, - minTradeVolume, - maxTradeSlippage - ); - require(launch, "trade not worth launching"); - - // == Interactions == - tryTrade(kind, req); - } - - function getTrade(IERC20 erc20) internal returns (TradeInfo memory trade) { IAsset sell; IAsset buy; @@ -164,11 +105,13 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { require(address(trades[erc20]) == address(0), "trade open"); require(erc20.balanceOf(address(this)) > 0, "0 balance"); + (uint192 sellPrice, ) = sell.price(); // {UoA/tok} (, uint192 buyPrice) = buy.price(); // {UoA/tok} + require(buyPrice > 0 && buyPrice < FIX_MAX, "buy asset price unknown"); - trade = TradeInfo({ + TradeInfo memory trade = TradeInfo({ sell: sell, buy: buy, sellAmount: sell.bal(address(this)), @@ -176,6 +119,18 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { sellPrice: sellPrice, buyPrice: buyPrice }); + + // Whether dust or not, trade the non-target asset for the target asset + // Any asset with a broken price feed will trigger a revert here + (, TradeRequest memory req) = TradeLib.prepareTradeSell( + trade, + minTradeVolume, + maxTradeSlippage + ); + require(req.sellAmount > 1, "sell amount too low"); + + // == Interactions == + tryTrade(kind, req); } /// Call after upgrade to >= 3.0.0 diff --git a/contracts/p1/mixins/TradeLib.sol b/contracts/p1/mixins/TradeLib.sol index fe225d6be4..01cfb8a18b 100644 --- a/contracts/p1/mixins/TradeLib.sol +++ b/contracts/p1/mixins/TradeLib.sol @@ -8,6 +8,8 @@ import "../../interfaces/ITrading.sol"; import "../../libraries/Fixed.sol"; import "./RecollateralizationLib.sol"; +import "hardhat/console.sol"; + struct TradeInfo { IAsset sell; IAsset buy; @@ -53,10 +55,7 @@ library TradeLib { (uint192 lotLow, uint192 lotHigh) = trade.sell.lotPrice(); - // Don't sell dust - if (!isEnoughToSell(trade.sell, trade.sellAmount, lotLow, minTradeVolume)) { - return (false, req); - } + notDust = isEnoughToSell(trade.sell, trade.sellAmount, lotLow, minTradeVolume); // Cap sell amount uint192 maxSell = maxTradeSize(trade.sell, trade.buy, lotHigh); // {sellTok} @@ -77,7 +76,8 @@ library TradeLib { req.sell = trade.sell; req.buy = trade.buy; - return (true, req); + console.log("prepareTradeSell", notDust, trade.sellAmount, req.sellAmount); + return (notDust, req); } /// Assuming we have `trade.sellAmount` sell tokens available, prepare a trade to cover as diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 98f1fbdfac..fd391d2c8e 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -40,7 +40,6 @@ import { TestIStRSR, USDCMock, FiatCollateral, - RevenueTraderP1__factory, } from '../typechain' import { whileImpersonating } from './utils/impersonation' import snapshotGasCost from './utils/snapshotGasCost' @@ -626,13 +625,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const dustAmount = bn('1e17') await token0.connect(addr1).transfer(rTokenTrader.address, dustAmount) - await expect( - rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) - ).to.revertedWith('trade not worth launching') - const p1RevenueTrader = await ethers.getContractAt('RevenueTraderP1', rTokenTrader.address) await expect( - await p1RevenueTrader.startDustAuction(token0.address, TradeKind.BATCH_AUCTION) + await p1RevenueTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) ).to.emit(rTokenTrader, 'TradeStarted') }) @@ -648,17 +643,15 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const p1RevenueTrader = await ethers.getContractAt('RevenueTraderP1', rTokenTrader.address) await expect( - await p1RevenueTrader.startDustAuction(token0.address, TradeKind.DUTCH_AUCTION) + await p1RevenueTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) ).to.emit(rTokenTrader, 'TradeStarted') }) - it('Should only be able to start a dust auction BATCH_AUCTION if oracle has failed', async () => { + it('Should only be able to start a dust auction BATCH_AUCTION (and not DUTCH_AUCTION) if oracle has failed', async () => { const minTrade = bn('1e18') await rTokenTrader.connect(owner).setMinTradeVolume(minTrade) - - const dustAmount = bn('1e17') await token0.connect(addr1).transfer(rTokenTrader.address, dustAmount) @@ -666,160 +659,21 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await setOraclePrice(collateral0.address, bn(0)) await collateral0.refresh() await expect( - p1RevenueTrader.startDustAuction(token0.address, TradeKind.DUTCH_AUCTION) - ).to.revertedWith('infinite slippage DUTCH_AUCTION not allowed') + p1RevenueTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + ).to.revertedWith('bad sell pricing') await expect( - await p1RevenueTrader.startDustAuction(token0.address, TradeKind.BATCH_AUCTION) + await p1RevenueTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) ).to.emit(rTokenTrader, 'TradeStarted') }) - it('Should not auction 1qTok - Amount too small', async () => { - // Set min trade volume for COMP to 1 qtok - await backingManager.connect(owner).setMinTradeVolume(0) - await rsrTrader.connect(owner).setMinTradeVolume(0) - await rTokenTrader.connect(owner).setMinTradeVolume(0) - - // Set f = 1 - await expect( - distributor - .connect(owner) - .setDistribution(FURNACE_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) - ) - .to.emit(distributor, 'DistributionSet') - .withArgs(FURNACE_DEST, bn(0), bn(0)) - - // Avoid dropping 20 qCOMP by making there be exactly 1 distribution share. - await expect( - distributor - .connect(owner) - .setDistribution(STRSR_DEST, { rTokenDist: bn(0), rsrDist: bn(1) }) - ) - .to.emit(distributor, 'DistributionSet') - .withArgs(STRSR_DEST, bn(0), bn(1)) - - // Set COMP tokens as reward -1 qtok - rewardAmountAAVE = bn('1') - - // COMP Rewards - 1 qTok - await token2.setRewards(backingManager.address, rewardAmountAAVE) - - // Collect revenue - await expectEvents(backingManager.claimRewards(), [ - { - contract: token3, - name: 'RewardsClaimed', - args: [compToken.address, bn(0)], - emitted: true, - }, - { - contract: token2, - name: 'RewardsClaimed', - args: [aaveToken.address, rewardAmountAAVE], - emitted: true, - }, - ]) - expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmountAAVE) - - // Check status of destinations at this point - expect(await rsr.balanceOf(stRSR.address)).to.equal(0) - expect(await rToken.balanceOf(furnace.address)).to.equal(0) - - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: rsrTrader, - name: 'TradeStarted', - emitted: false, - }, - { - contract: rTokenTrader, - name: 'TradeStarted', - emitted: false, - }, - ]) - - // Check no funds in Market, now in trader - expect(await aaveToken.balanceOf(gnosis.address)).to.equal(bn(0)) - expect(await aaveToken.balanceOf(backingManager.address)).to.equal(bn(0)) - expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(rewardAmountAAVE) - - // Check destinations, nothing changed - expect(await rsr.balanceOf(stRSR.address)).to.equal(0) - expect(await rToken.balanceOf(furnace.address)).to.equal(0) - }) - - it('Should not sell an asset with 0 price', async () => { - // Set f = 1 + it('Should not launch an auction for 1 qTok', async () => { + await token0.connect(addr1).transfer(rTokenTrader.address, 1) await expect( - distributor - .connect(owner) - .setDistribution(FURNACE_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) - ) - .to.emit(distributor, 'DistributionSet') - .withArgs(FURNACE_DEST, bn(0), bn(0)) - - // Avoid dropping 20 qCOMP by making there be exactly 1 distribution share. + rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + ).to.be.revertedWith('sell amount too low') await expect( - distributor - .connect(owner) - .setDistribution(STRSR_DEST, { rTokenDist: bn(0), rsrDist: bn(1) }) - ) - .to.emit(distributor, 'DistributionSet') - .withArgs(STRSR_DEST, bn(0), bn(1)) - - // Set COMP tokens as reward - rewardAmountAAVE = bn('1000e18') - - // COMP Rewards - await token2.setRewards(backingManager.address, rewardAmountAAVE) - - // Set COMP price to 0 - await setOraclePrice(aaveAsset.address, bn(0)) - - // Refresh asset so lot price also = 0 - await aaveAsset.refresh() - - // Collect revenue - await expectEvents(backingManager.claimRewards(), [ - { - contract: token3, - name: 'RewardsClaimed', - args: [compToken.address, bn(0)], - emitted: true, - }, - { - contract: token2, - name: 'RewardsClaimed', - args: [aaveToken.address, rewardAmountAAVE], - emitted: true, - }, - ]) - expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmountAAVE) - - // Check status of destinations at this point - expect(await rsr.balanceOf(stRSR.address)).to.equal(0) - expect(await rToken.balanceOf(furnace.address)).to.equal(0) - - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: rsrTrader, - name: 'TradeStarted', - emitted: false, - }, - { - contract: rTokenTrader, - name: 'TradeStarted', - emitted: false, - }, - ]) - - // Check no funds in Market, now in trader - expect(await aaveToken.balanceOf(gnosis.address)).to.equal(bn(0)) - expect(await aaveToken.balanceOf(backingManager.address)).to.equal(bn(0)) - expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(rewardAmountAAVE) - - // Check destinations, nothing changed - expect(await rsr.balanceOf(stRSR.address)).to.equal(0) - expect(await rToken.balanceOf(furnace.address)).to.equal(0) + rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('sell amount too low') }) it('Should handle properly an asset with low maxTradeVolume', async () => { @@ -853,7 +707,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await aaveAsset.chainlinkFeed(), ORACLE_ERROR, aaveToken.address, - bn(1), // very low + bn(606), // 2 qTok auction at $300 (after accounting for price.high) ORACLE_TIMEOUT ) @@ -899,9 +753,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { anyValue, aaveToken.address, rsr.address, - bn(1), + bn(2), // the 1% increase here offsets the 1% decrease that would normally be applied to the sellAmt, but since 1 is the floor, isn't - await toMinBuyAmt(bn(1), fp('303'), fp('1')), + await toMinBuyAmt(bn(2), fp('303'), fp('1')), ], }, { @@ -912,9 +766,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ]) // Check funds now in Market - expect(await aaveToken.balanceOf(gnosis.address)).to.equal(bn(1)) + expect(await aaveToken.balanceOf(gnosis.address)).to.equal(bn(2)) expect(await aaveToken.balanceOf(backingManager.address)).to.equal(bn(0)) - expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(rewardAmountAAVE.sub(bn(1))) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(rewardAmountAAVE.sub(bn(2))) // Check destinations, nothing still - Auctions need to be completed expect(await rsr.balanceOf(stRSR.address)).to.equal(0) @@ -1764,64 +1618,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(furnace.address)).to.equal(0) }) - it('Should not trade dust when claiming rewards', async () => { - // Set AAVE tokens as reward - both halves are < dust - rewardAmountAAVE = bn('0.01e18') - - // AAVE Rewards - await token2.setRewards(backingManager.address, rewardAmountAAVE) - - // Collect revenue - await expectEvents(backingManager.claimRewards(), [ - { - contract: token3, - name: 'RewardsClaimed', - args: [compToken.address, bn(0)], - emitted: true, - }, - { - contract: token2, - name: 'RewardsClaimed', - args: [aaveToken.address, rewardAmountAAVE], - emitted: true, - }, - ]) - - expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmountAAVE) - - // Set expected values, based on f = 0.6 - const expectedToTrader = rewardAmountAAVE.mul(60).div(100) - const expectedToFurnace = rewardAmountAAVE.sub(expectedToTrader) - - // Check status of traders and destinations at this point - expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(0) - expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) - expect(await rsr.balanceOf(stRSR.address)).to.equal(0) - expect(await rToken.balanceOf(furnace.address)).to.equal(0) - - // Run auctions - should not start any auctions - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: rsrTrader, - name: 'TradeStarted', - emitted: false, - }, - { - contract: rTokenTrader, - name: 'TradeStarted', - emitted: false, - }, - ]) - - // Check funds sent to traders - expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader) - expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(expectedToFurnace) - - expect(await rsr.balanceOf(stRSR.address)).to.equal(0) - expect(await rToken.balanceOf(furnace.address)).to.equal(0) - expect(await aaveToken.balanceOf(backingManager.address)).to.equal(0) - }) - it('Should not trade if price for buy token = 0', async () => { // Set AAVE tokens as reward rewardAmountAAVE = bn('1e18') From 507f7e313f74bc38072a3060f2bd3d5fe8373324 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 17:28:25 -0400 Subject: [PATCH 176/462] remove console.log --- contracts/p1/mixins/TradeLib.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/contracts/p1/mixins/TradeLib.sol b/contracts/p1/mixins/TradeLib.sol index 01cfb8a18b..74ee66e1ea 100644 --- a/contracts/p1/mixins/TradeLib.sol +++ b/contracts/p1/mixins/TradeLib.sol @@ -8,8 +8,6 @@ import "../../interfaces/ITrading.sol"; import "../../libraries/Fixed.sol"; import "./RecollateralizationLib.sol"; -import "hardhat/console.sol"; - struct TradeInfo { IAsset sell; IAsset buy; @@ -76,7 +74,6 @@ library TradeLib { req.sell = trade.sell; req.buy = trade.buy; - console.log("prepareTradeSell", notDust, trade.sellAmount, req.sellAmount); return (notDust, req); } From 8c26706b4ab95322b00a9070225c9d6de6902d31 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 17:32:30 -0400 Subject: [PATCH 177/462] fix p1 tests --- test/Main.test.ts | 4 ++-- test/Upgradeability.test.ts | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/test/Main.test.ts b/test/Main.test.ts index 89abc2d449..c2c0ea14f9 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1721,13 +1721,13 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { it('Should not allow to set prime Basket with RSR/RToken', async () => { await expect( basketHandler.connect(owner).setPrimeBasket([rsr.address], [fp('1')]) - ).to.be.revertedWith('invalid collateral') + ).to.be.revertedWith('RSR is not valid collateral') await expect( basketHandler .connect(owner) .setPrimeBasket([token0.address, rToken.address], [fp('0.5'), fp('0.5')]) - ).to.be.revertedWith('invalid collateral') + ).to.be.revertedWith('RToken is not valid collateral') }) it('Should allow to set prime Basket if OWNER', async () => { diff --git a/test/Upgradeability.test.ts b/test/Upgradeability.test.ts index d681e0413d..e2e6e801aa 100644 --- a/test/Upgradeability.test.ts +++ b/test/Upgradeability.test.ts @@ -511,8 +511,12 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { 'BasketHandlerP1V2', { libraries: { BasketLibP1: basketLib.address } } ) - const bskHndlrV2: BasketHandlerP1V2 = ( - await upgrades.upgradeProxy(basketHandler.address, BasketHandlerV2Factory) + const bskHndlrV2: BasketHandlerP1V2 = await upgrades.upgradeProxy( + basketHandler.address, + BasketHandlerV2Factory, + { + unsafeAllow: ['external-library-linking'], // BasketLibP1 + } ) // Check address is maintained From 7e641988f37d60de41ac7e0c981f94ecf965ffce Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 17:47:56 -0400 Subject: [PATCH 178/462] fix p0 tests --- contracts/p0/BasketHandler.sol | 19 +++++++++---------- contracts/p0/RToken.sol | 6 ++---- contracts/p1/BasketHandler.sol | 2 ++ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index e1ff9405ab..63d89371a4 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -482,15 +482,16 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } // Add new ERC20 entry if not found + uint192 amt = portions[i].mul(b.refAmts[erc20], FLOOR); if (erc20Index == type(uint256).max) { erc20sAll[len] = erc20; // {ref} = {1} * {ref} - refAmtsAll[len] = portions[j].mul(b.refAmts[erc20], FLOOR); + refAmtsAll[len] = amt; ++len; } else { // {ref} = {1} * {ref} - refAmtsAll[erc20Index] += portions[j].mul(b.refAmts[erc20], FLOOR); + refAmtsAll[erc20Index] += amt; } } } @@ -504,10 +505,11 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { try main.assetRegistry().toAsset(IERC20(erc20s[i])) returns (IAsset asset) { if (!asset.isCollateral()) continue; // skip token if no longer registered + quantities[i] = FIX_MAX; // prevent div-by-zero uint192 refPerTok = ICollateral(address(asset)).refPerTok(); - if (refPerTok == 0) continue; // quantities[i] = 0; + if (refPerTok == 0) continue; // {tok} = {BU} * {ref/BU} / {ref/tok} quantities[i] = safeMulDivFloor(amount, refAmtsAll[i], refPerTok).shiftl_toUint( @@ -745,15 +747,12 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { /// Require that erc20s is a valid collateral array function requireValidCollArray(IERC20[] calldata erc20s) internal view { IERC20 zero = IERC20(address(0)); - IERC20 rsr = main.rsr(); - IERC20 rToken = IERC20(address(main.rToken())); - IERC20 stRSR = IERC20(address(main.stRSR())); for (uint256 i = 0; i < erc20s.length; i++) { - require( - erc20s[i] != zero && erc20s[i] != rsr && erc20s[i] != rToken && erc20s[i] != stRSR, - "invalid collateral" - ); + require(erc20s[i] != main.rsr(), "RSR is not valid collateral"); + require(erc20s[i] != IERC20(address(main.rToken())), "RToken is not valid collateral"); + require(erc20s[i] != IERC20(address(main.stRSR())), "stRSR is not valid collateral"); + require(erc20s[i] != zero, "address zero is not valid collateral"); } require(ArrayLib.allUnique(erc20s), "contains duplicates"); diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 7d19e5fc62..5384ad62ab 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -279,10 +279,8 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { // Check post-balances for (uint256 i = 0; i < expectedERC20sOut.length; ++i) { - require( - IERC20(expectedERC20sOut[i]).balanceOf(recipient) - pastBals[i] >= minAmounts[i], - "redemption below minimum" - ); + uint256 bal = IERC20(expectedERC20sOut[i]).balanceOf(recipient); + require(bal - pastBals[i] >= minAmounts[i], "redemption below minimum"); } } diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index bdbad0fec6..ddcd67cdc2 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -407,6 +407,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { "invalid basketNonce" ); // will always revert directly after setPrimeBasket() Basket storage b = basketHistory[basketNonces[i]]; + // Add-in refAmts contribution from historical basket for (uint256 j = 0; j < b.erc20s.length; ++j) { IERC20 erc20 = b.erc20s[j]; @@ -442,6 +443,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // Calculate quantities for (uint256 i = 0; i < len; ++i) { erc20s[i] = address(erc20sAll[i]); + try assetRegistry.toAsset(IERC20(erc20s[i])) returns (IAsset asset) { if (!asset.isCollateral()) continue; // skip token if no longer registered From 3d6e2b244a1442d9a1c25fe5463a0870c102ee28 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 18:30:54 -0400 Subject: [PATCH 179/462] fix typo in p0 --- contracts/p0/mixins/TradingLib.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index 302568671f..7262799ad5 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -70,7 +70,7 @@ library TradingLibP0 { req.minBuyAmount = b.shiftl_toUint(int8(trade.buy.erc20Decimals()), CEIL); req.sell = trade.sell; req.buy = trade.buy; - return (true, req); + return (notDust, req); } /// Assuming we have `trade.sellAmount` sell tokens available, prepare a trade to cover as From 9ea2468982940e8bc8b8ffed519f5b8cd3d8e139 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 18:31:16 -0400 Subject: [PATCH 180/462] fix typo in p0 --- contracts/p0/mixins/TradingLib.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index 7262799ad5..26ebdfcd47 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -50,7 +50,7 @@ library TradingLibP0 { (uint192 lotLow, uint192 lotHigh) = trade.sell.lotPrice(); - notDust = !isEnoughToSell(trade.sell, trade.sellAmount, lotLow, minTradeVolume); + notDust = isEnoughToSell(trade.sell, trade.sellAmount, lotLow, minTradeVolume); // Cap sell amount uint192 maxSell = maxTradeSize(trade.sell, trade.buy, lotHigh); // {sellTok} From 01a874b555370488859cf6a2b26ca4f629c6fe21 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 5 May 2023 21:04:22 -0400 Subject: [PATCH 181/462] RTokenP0 RCEI --- contracts/p0/RToken.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 5384ad62ab..7a782e6fce 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -140,6 +140,9 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @custom:interaction function redeemTo(address recipient, uint256 amount) public notFrozen exchangeRateIsValidAfter { + // Call collective state keepers. + main.poke(); + require(amount > 0, "Cannot redeem zero"); require(amount <= balanceOf(_msgSender()), "insufficient balance"); require( @@ -148,9 +151,6 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { ); // redemption while IFFY/DISABLED allowed - // Call collective state keepers. - main.poke(); - // Revert if redemption exceeds either supply throttle issuanceThrottle.useAvailable(totalSupply(), -int256(amount)); redemptionThrottle.useAvailable(totalSupply(), int256(amount)); // reverts on overuse From 57388becfd2c1373c488280a1c5d1dfb0440abf9 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sun, 7 May 2023 19:14:39 -0400 Subject: [PATCH 182/462] test redemption after every recollateralization --- test/Recollateralization.test.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index b030eb1a05..7d18b6a82c 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -809,6 +809,12 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await stRSR.connect(addr1).stake(stakeAmount) }) + afterEach(async () => { + // Every test in this section should end with full collateralization + expect(await basketHandler.fullyCollateralized()).to.equal(true) + await rToken.connect(addr1).redeem(1) + }) + it('Should not trade if trading paused', async () => { await main.connect(owner).pauseTrading() await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( @@ -821,6 +827,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'frozen or trading paused' ) + + // Unfreeze for afterEach + await main.connect(owner).unfreeze() }) it('Should trade if issuance paused', async () => { @@ -829,10 +838,16 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await basketHandler.refreshBasket() await main.connect(owner).pauseIssuance() - await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.emit( backingManager, 'TradeStarted' ) + + // Complete recollateralization + const tradeAddr = await backingManager.trades(token0.address) + const trade = await ethers.getContractAt('DutchTrade', tradeAddr) + await token1.connect(addr1).approve(trade.address, initialBal) + await trade.connect(addr1).bid() }) it('Should not trade if UNPRICED', async () => { From 5532124254849287781414bc74ba5570fdbc317e Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sun, 7 May 2023 19:52:57 -0400 Subject: [PATCH 183/462] test redeem() behavior after governance basket change --- test/RToken.test.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/RToken.test.ts b/test/RToken.test.ts index fa752f9a6c..bd4d6d7311 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -1112,6 +1112,39 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect(rToken.connect(addr1).redeem(bn(redeemAmount))).to.not.be.reverted }) + it('Should revert while rebalancing after prime basket change', async function () { + // New prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + expect(await basketHandler.fullyCollateralized()).to.equal(true) // ref basket hasn't changed + + // Should still redeem through the main flow; not custom flow + await rToken.connect(addr1).redeem(1) + await expect( + rToken + .connect(addr1) + .redeemToCustom(addr1.address, 1, [await basketHandler.nonce()], [fp('1')], [], []) + ).to.be.revertedWith('invalid basketNonce') + + // New reference basket + await basketHandler.refreshBasket() + expect(await basketHandler.fullyCollateralized()).to.equal(false) + + // Before the first auction is completed, BOTH redemption methods are bricked + await expect(rToken.connect(addr1).redeem(1)).to.be.revertedWith( + 'partial redemption; use redeemToCustom' + ) + const nonce = await basketHandler.nonce() + await expect( + rToken.connect(addr1).redeemToCustom(addr1.address, 1, [nonce], [fp('1')], [], []) + ).to.be.revertedWith('empty redemption') + await expect( + rToken.connect(addr1).redeemToCustom(addr1.address, 1, [nonce - 1], [fp('1')], [], []) + ).to.be.revertedWith('invalid basketNonce') + await expect( + rToken.connect(addr1).redeemToCustom(addr1.address, 1, [nonce + 1], [fp('1')], [], []) + ).to.be.revertedWith('invalid basketNonce') + }) + context('And redemption throttling', function () { // the fixture-configured redemption throttle uses 5% let redemptionThrottleParams: ThrottleParams From 6a11079643d28680bdcc33b4c4a69eb9b1d38e08 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 8 May 2023 17:42:25 -0400 Subject: [PATCH 184/462] implement constant target weights --- contracts/p0/BasketHandler.sol | 43 +++++++++++++++++++++++++++-- contracts/p1/BasketHandler.sol | 50 ++++++++++++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 63d89371a4..fb265d8d84 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "../interfaces/IAsset.sol"; import "../interfaces/IAssetRegistry.sol"; @@ -107,6 +108,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { using CollateralStatusComparator for CollateralStatus; using EnumerableSet for EnumerableSet.AddressSet; using EnumerableSet for EnumerableSet.Bytes32Set; + using EnumerableMap for EnumerableMap.Bytes32ToUintMap; using FixLib for uint192; uint48 public constant MIN_WARMUP_PERIOD = 60; // {s} 1 minute @@ -133,6 +135,9 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { EnumerableSet.Bytes32Set private targetNames; Basket private newBasket; + // Effectively local variable of `_switchConfig`. + EnumerableMap.Bytes32ToUintMap private _targetAmts; // targetName -> {target/BU} + uint48 public warmupPeriod; // {s} how long to wait until issuance/trading after regaining SOUND // basket status changes, mainly set when `trackStatus()` is called @@ -241,6 +246,9 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { require(erc20s.length == targetAmts.length, "must be same length"); requireValidCollArray(erc20s); + // If this isn't initial setup, require targets remain constant + if (config.erc20s.length > 0) requireConstantConfigTargets(erc20s, targetAmts); + // Clean up previous basket config for (uint256 i = 0; i < config.erc20s.length; ++i) { delete config.targetAmts[config.erc20s[i]]; @@ -745,7 +753,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } /// Require that erc20s is a valid collateral array - function requireValidCollArray(IERC20[] calldata erc20s) internal view { + function requireValidCollArray(IERC20[] calldata erc20s) private view { IERC20 zero = IERC20(address(0)); for (uint256 i = 0; i < erc20s.length; i++) { @@ -758,6 +766,37 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { require(ArrayLib.allUnique(erc20s), "contains duplicates"); } + /// Require that newERC20s and newTargetAmts preserve the current config targets + function requireConstantConfigTargets( + IERC20[] calldata newERC20s, + uint192[] calldata newTargetAmts + ) private { + // Empty _targetAmts mapping + while (_targetAmts.length() > 0) { + (bytes32 key, ) = _targetAmts.at(0); + _targetAmts.remove(key); + } + + // Populate _targetAmts mapping with old basket config + for (uint256 i = 0; i < config.erc20s.length; i++) { + IERC20 erc20 = config.erc20s[i]; + bytes32 targetName = config.targetNames[erc20]; + uint192 targetAmt = config.targetAmts[erc20]; + (bool contains, uint256 amt) = _targetAmts.tryGet(targetName); + _targetAmts.set(targetName, contains ? amt + targetAmt : targetAmt); + } + + // Require new basket is exactly equal to old basket, in terms of targetAmts by targetName + for (uint256 i = 0; i < newERC20s.length; i++) { + bytes32 targetName = main.assetRegistry().toColl(newERC20s[i]).targetName(); + (bool contains, uint256 amt) = _targetAmts.tryGet(targetName); + require(contains && amt >= newTargetAmts[i], "new basket adds target weights"); + if (amt == newTargetAmts[i]) _targetAmts.remove(targetName); + else _targetAmts.set(targetName, amt - newTargetAmts[i]); + } + require(_targetAmts.length() == 0, "new basket missing target weights"); + } + /// Good collateral is registered, collateral, SOUND, has the expected targetName, /// and not a system token or 0 addr function goodCollateral(bytes32 targetName, IERC20 erc20) private view returns (bool) { @@ -777,7 +816,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } } - // === Private === + // === Internal === /// @return The floored result of FixLib.mulDiv function safeMulDivFloor( diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index ddcd67cdc2..0254d815b6 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - import "../interfaces/IAssetRegistry.sol"; import "../interfaces/IBasketHandler.sol"; import "../interfaces/IMain.sol"; @@ -19,6 +19,7 @@ import "./mixins/Component.sol"; */ contract BasketHandlerP1 is ComponentP1, IBasketHandler { using BasketLibP1 for Basket; + using EnumerableMap for EnumerableMap.Bytes32ToUintMap; using CollateralStatusComparator for CollateralStatus; using FixLib for uint192; @@ -76,6 +77,9 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // A history of baskets by basket nonce; includes current basket mapping(uint48 => Basket) private basketHistory; + // Effectively local variable of `_switchConfig`. + EnumerableMap.Bytes32ToUintMap private _targetAmts; // targetName -> {target/BU} + // === // ==== Invariants ==== @@ -177,6 +181,9 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { require(erc20s.length == targetAmts.length, "must be same length"); requireValidCollArray(erc20s); + // If this isn't initial setup, require targets remain constant + if (config.erc20s.length > 0) requireConstantConfigTargets(erc20s, targetAmts); + // Clean up previous basket config for (uint256 i = 0; i < config.erc20s.length; ++i) { delete config.targetAmts[config.erc20s[i]]; @@ -534,8 +541,45 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { emit BasketSet(nonce, basket.erc20s, refAmts, disabled); } + /// Require that newERC20s and newTargetAmts preserve the current config targets + function requireConstantConfigTargets( + IERC20[] calldata newERC20s, + uint192[] calldata newTargetAmts + ) private { + // Empty _targetAmts mapping + while (_targetAmts.length() > 0) { + (bytes32 key, ) = _targetAmts.at(0); + _targetAmts.remove(key); + } + + // Populate _targetAmts mapping with old basket config + uint256 len = config.erc20s.length; + for (uint256 i = 0; i < len; ++i) { + IERC20 erc20 = config.erc20s[i]; + bytes32 targetName = config.targetNames[erc20]; + (bool contains, uint256 amt) = _targetAmts.tryGet(targetName); + _targetAmts.set( + targetName, + contains ? amt + config.targetAmts[erc20] : config.targetAmts[erc20] + ); + } + + // Require new basket is exactly equal to old basket, in terms of targetAmts by targetName + len = newERC20s.length; + for (uint256 i = 0; i < len; ++i) { + bytes32 targetName = assetRegistry.toColl(newERC20s[i]).targetName(); + // if the asset registry has a new registered asset targetName, this always reverts + + (bool contains, uint256 amt) = _targetAmts.tryGet(targetName); + require(contains && amt >= newTargetAmts[i], "new basket adds target weights"); + if (amt > newTargetAmts[i]) _targetAmts.set(targetName, amt - newTargetAmts[i]); + else _targetAmts.remove(targetName); + } + require(_targetAmts.length() == 0, "new basket missing target weights"); + } + /// Require that erc20s is a valid collateral array - function requireValidCollArray(IERC20[] calldata erc20s) internal view { + function requireValidCollArray(IERC20[] calldata erc20s) private view { IERC20 zero = IERC20(address(0)); for (uint256 i = 0; i < erc20s.length; i++) { @@ -644,5 +688,5 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[40] private __gap; + uint256[38] private __gap; } From 583e667fd8cfb89674e46c4b84509e61cfac68d1 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 8 May 2023 17:43:54 -0400 Subject: [PATCH 185/462] comment visibility nits --- contracts/p0/BasketHandler.sol | 4 ++-- contracts/p1/BasketHandler.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index fb265d8d84..7e29734250 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -816,14 +816,14 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } } - // === Internal === + // === Private === /// @return The floored result of FixLib.mulDiv function safeMulDivFloor( uint192 x, uint192 y, uint192 z - ) internal view returns (uint192) { + ) private view returns (uint192) { try main.backingManager().mulDiv(x, y, z, FLOOR) returns (uint192 result) { return result; } catch Panic(uint256 errorCode) { diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 0254d815b6..c777eea14d 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -668,7 +668,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { uint192 x, uint192 y, uint192 z - ) internal view returns (uint192) { + ) private view returns (uint192) { try backingManager.mulDiv(x, y, z, FLOOR) returns (uint192 result) { return result; } catch Panic(uint256 errorCode) { From dfc5c4b2697a087ff5383dfa06d9f113f9c099a7 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 8 May 2023 18:32:55 -0400 Subject: [PATCH 186/462] update Main tests --- contracts/p0/BasketHandler.sol | 4 +- contracts/p1/BasketHandler.sol | 4 +- test/Main.test.ts | 109 +++++++++++++++++++++++++++------ 3 files changed, 94 insertions(+), 23 deletions(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 7e29734250..eab8a8ddce 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -263,7 +263,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { for (uint256 i = 0; i < erc20s.length; ++i) { // This is a nice catch to have, but in general it is possible for // an ERC20 in the prime basket to have its asset unregistered. - require(reg.toAsset(erc20s[i]).isCollateral(), "token is not collateral"); + require(reg.toAsset(erc20s[i]).isCollateral(), "erc20 is not collateral"); require(0 < targetAmts[i], "invalid target amount; must be nonzero"); require(targetAmts[i] <= MAX_TARGET_AMT, "invalid target amount; too large"); @@ -300,7 +300,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { for (uint256 i = 0; i < erc20s.length; ++i) { // This is a nice catch to have, but in general it is possible for // an ERC20 in the backup config to have its asset altered. - require(reg.toAsset(erc20s[i]).isCollateral(), "token is not collateral"); + require(reg.toAsset(erc20s[i]).isCollateral(), "erc20 is not collateral"); conf.erc20s.push(erc20s[i]); } emit BackupConfigSet(targetName, max, erc20s); diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index c777eea14d..c2fdb7bec9 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -197,7 +197,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { for (uint256 i = 0; i < erc20s.length; ++i) { // This is a nice catch to have, but in general it is possible for // an ERC20 in the prime basket to have its asset unregistered. - require(assetRegistry.toAsset(erc20s[i]).isCollateral(), "token is not collateral"); + require(assetRegistry.toAsset(erc20s[i]).isCollateral(), "erc20 is not collateral"); require(0 < targetAmts[i], "invalid target amount; must be nonzero"); require(targetAmts[i] <= MAX_TARGET_AMT, "invalid target amount; too large"); @@ -233,7 +233,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { for (uint256 i = 0; i < erc20s.length; ++i) { // This is a nice catch to have, but in general it is possible for // an ERC20 in the backup config to have its asset altered. - require(assetRegistry.toAsset(erc20s[i]).isCollateral(), "token is not collateral"); + require(assetRegistry.toAsset(erc20s[i]).isCollateral(), "erc20 is not collateral"); conf.erc20s.push(erc20s[i]); } emit BackupConfigSet(targetName, max, erc20s); diff --git a/test/Main.test.ts b/test/Main.test.ts index c2c0ea14f9..26ced13d67 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1203,9 +1203,9 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { // Put all 128 coll in the basket const erc20s = await Promise.all(coll.map(async (c) => await c.erc20())) - const refAmts = erc20s.map(() => fp('1')) + const targetAmts = erc20s.map(() => fp('1').div(128)) expect(erc20s.length).to.equal(128) - await basketHandler.setPrimeBasket(erc20s, refAmts) + await basketHandler.setPrimeBasket(erc20s, targetAmts) await basketHandler.refreshBasket() expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) const [quoteERC20s, tokAmts] = await basketHandler.quote(fp('1'), 0) @@ -1662,13 +1662,45 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) describe('Basket Handling', () => { + let freshBasketHandler: TestIBasketHandler // need to be able to execute first setPrimeBasket + + beforeEach(async () => { + if (IMPLEMENTATION == Implementation.P0) { + const BasketHandlerFactory = await ethers.getContractFactory('BasketHandlerP0') + freshBasketHandler = ((await BasketHandlerFactory.deploy()) as unknown) + } else if (IMPLEMENTATION == Implementation.P1) { + const basketLib = await (await ethers.getContractFactory('BasketLibP1')).deploy() + const BasketHandlerFactory = await ethers.getContractFactory('BasketHandlerP1', { + libraries: { BasketLibP1: basketLib.address }, + }) + freshBasketHandler = await upgrades.deployProxy( + BasketHandlerFactory, + [], + { + kind: 'uups', + unsafeAllow: ['external-library-linking'], // BasketLibP1 + } + ) + } else { + throw new Error('PROTO_IMPL must be set to either `0` or `1`') + } + + await freshBasketHandler.init(main.address, config.warmupPeriod) + }) + it('Should not allow to set prime Basket if not OWNER', async () => { + await expect( + freshBasketHandler.connect(other).setPrimeBasket([token0.address], [fp('1')]) + ).to.be.revertedWith('governance only') await expect( basketHandler.connect(other).setPrimeBasket([token0.address], [fp('1')]) ).to.be.revertedWith('governance only') }) it('Should not allow to set prime Basket with invalid length', async () => { + await expect( + freshBasketHandler.connect(owner).setPrimeBasket([token0.address], []) + ).to.be.revertedWith('must be same length') await expect( basketHandler.connect(owner).setPrimeBasket([token0.address], []) ).to.be.revertedWith('must be same length') @@ -1677,10 +1709,18 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { it('Should not allow to set prime Basket with non-collateral tokens', async () => { await expect( basketHandler.connect(owner).setPrimeBasket([compToken.address], [fp('1')]) - ).to.be.revertedWith('token is not collateral') + ).to.be.revertedWith('erc20 is not collateral') + await expect( + freshBasketHandler.connect(owner).setPrimeBasket([compToken.address], [fp('1')]) + ).to.be.revertedWith('erc20 is not collateral') }) it('Should not allow to set prime Basket with duplicate ERC20s', async () => { + await expect( + freshBasketHandler + .connect(owner) + .setPrimeBasket([token0.address, token0.address], [fp('1'), fp('1')]) + ).to.be.revertedWith('contains duplicates') await expect( basketHandler .connect(owner) @@ -1689,24 +1729,48 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) it('Should not allow to set prime Basket with 0 address tokens', async () => { + await expect( + freshBasketHandler.connect(owner).setPrimeBasket([ZERO_ADDRESS], [fp('1')]) + ).to.be.revertedWith('address zero is not valid collateral') await expect( basketHandler.connect(owner).setPrimeBasket([ZERO_ADDRESS], [fp('1')]) ).to.be.revertedWith('address zero is not valid collateral') }) it('Should not allow to set prime Basket with stRSR', async () => { + await expect( + freshBasketHandler.connect(owner).setPrimeBasket([stRSR.address], [fp('1')]) + ).to.be.revertedWith('stRSR is not valid collateral') await expect( basketHandler.connect(owner).setPrimeBasket([stRSR.address], [fp('1')]) ).to.be.revertedWith('stRSR is not valid collateral') }) - it('Should not allow to set prime Basket with invalid target amounts', async () => { + it('Should not allow to bypass MAX_TARGET_AMT', async () => { + // not possible on non-fresh basketHandler await expect( - basketHandler.connect(owner).setPrimeBasket([token0.address], [MAX_TARGET_AMT.add(1)]) + freshBasketHandler.connect(owner).setPrimeBasket([token0.address], [MAX_TARGET_AMT.add(1)]) ).to.be.revertedWith('invalid target amount; too large') }) + it('Should not allow to increase prime Basket weights', async () => { + // not possible on freshBasketHandler + await expect( + basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1').add(1)]) + ).to.be.revertedWith('new basket adds target weights') + }) + + it('Should not allow to decrease prime Basket weights', async () => { + // not possible on freshBasketHandler + await expect( + basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1').sub(1)]) + ).to.be.revertedWith('new basket missing target weights') + }) + it('Should not allow to set prime Basket with an empty basket', async () => { + await expect(freshBasketHandler.connect(owner).setPrimeBasket([], [])).to.be.revertedWith( + 'cannot empty basket' + ) await expect(basketHandler.connect(owner).setPrimeBasket([], [])).to.be.revertedWith( 'cannot empty basket' ) @@ -1714,15 +1778,26 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { it('Should not allow to set prime Basket with a zero amount', async () => { await expect( - basketHandler.connect(owner).setPrimeBasket([token0.address], [0]) + freshBasketHandler.connect(owner).setPrimeBasket([token0.address], [0]) ).to.be.revertedWith('invalid target amount; must be nonzero') + await expect( + basketHandler.connect(owner).setPrimeBasket([token0.address], [0]) + ).to.be.revertedWith('new basket missing target weights') }) it('Should not allow to set prime Basket with RSR/RToken', async () => { + await expect( + freshBasketHandler.connect(owner).setPrimeBasket([rsr.address], [fp('1')]) + ).to.be.revertedWith('RSR is not valid collateral') await expect( basketHandler.connect(owner).setPrimeBasket([rsr.address], [fp('1')]) ).to.be.revertedWith('RSR is not valid collateral') + await expect( + freshBasketHandler + .connect(owner) + .setPrimeBasket([token0.address, rToken.address], [fp('0.5'), fp('0.5')]) + ).to.be.revertedWith('RToken is not valid collateral') await expect( basketHandler .connect(owner) @@ -2099,7 +2174,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { basketHandler .connect(owner) .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [compToken.address]) - ).to.be.revertedWith('token is not collateral') + ).to.be.revertedWith('erc20 is not collateral') }) it('Should not allow to set backup Config with RSR/RToken', async () => { @@ -2337,10 +2412,6 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) it('Should handle a collateral (price * quantity) overflow', async () => { - // Check status and price - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - await expectPrice(basketHandler.address, fp('1'), ORACLE_ERROR, true) - // Swap in mock collateral with overflowing price const MockableCollateralFactory: ContractFactory = await ethers.getContractFactory( 'MockableCollateral' @@ -2363,8 +2434,8 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await setOraclePrice(newColl.address, MAX_UINT192) // overflow await expectUnpriced(newColl.address) await newColl.setTargetPerRef(1) - await basketHandler.setPrimeBasket([await newColl.erc20()], [fp('1000')]) - await basketHandler.refreshBasket() + await freshBasketHandler.setPrimeBasket([await newColl.erc20()], [fp('1000')]) + await freshBasketHandler.refreshBasket() // Expect [something > 0, FIX_MAX] const bh = await ethers.getContractAt('Asset', basketHandler.address) @@ -2398,10 +2469,10 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await newColl.refresh() // Set basket with single collateral - await basketHandler.connect(owner).setPrimeBasket([token2.address], [fp('1000')]) + await freshBasketHandler.connect(owner).setPrimeBasket([token2.address], [fp('1000')]) // Change basket - valid at this point - await basketHandler.connect(owner).refreshBasket() + await freshBasketHandler.connect(owner).refreshBasket() // Set refPerTok = 1 await newColl.setRate(bn(1)) @@ -2409,20 +2480,20 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const newPrice: BigNumber = MAX_UINT192.div(bn('1e10')) await setOraclePrice(collateral2.address, newPrice.sub(newPrice.div(100))) // oracle error - const [lowPrice, highPrice] = await basketHandler.price() + const [lowPrice, highPrice] = await freshBasketHandler.price() expect(lowPrice).to.equal(MAX_UINT192) expect(highPrice).to.equal(MAX_UINT192) }) it('Should handle overflow in price calculation and return [FIX_MAX, FIX_MAX] - case 2', async () => { // Set basket with single collateral - await basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1.1')]) - await basketHandler.refreshBasket() + await freshBasketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1.1')]) + await freshBasketHandler.refreshBasket() const newPrice: BigNumber = MAX_UINT192.div(bn('1e10')) await setOraclePrice(collateral0.address, newPrice.sub(newPrice.div(100))) // oracle error - const [lowPrice, highPrice] = await basketHandler.price() + const [lowPrice, highPrice] = await freshBasketHandler.price() expect(lowPrice).to.equal(MAX_UINT192) expect(highPrice).to.equal(MAX_UINT192) }) From 187f9705a1f4afcea84aaae47c1df9ed0070b85b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 8 May 2023 19:38:18 -0400 Subject: [PATCH 187/462] update recollat tests --- test/Recollateralization.test.ts | 36 +++++++++++++------------------- test/fixtures.ts | 30 +++++++++++++++++++------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 7d18b6a82c..3845fb76e2 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -41,6 +41,7 @@ import { advanceTime, advanceToTimestamp, getLatestBlockTimestamp } from './util import { Collateral, defaultFixture, + defaultFixtureNoBasket, Implementation, IMPLEMENTATION, ORACLE_ERROR, @@ -144,8 +145,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { return divCeil(divCeil(product, highBuyPrice), BN_SCALE_FACTOR) // (c) } - beforeEach(async () => { - ;[owner, addr1, addr2] = await ethers.getSigners() + const doFixtureSetup = async (setBasket: boolean) => { let erc20s: ERC20Mock[] // Deploy fixture @@ -169,7 +169,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { main, rTokenAsset, broker, - } = await loadFixture(defaultFixture)) + } = await loadFixture(setBasket ? defaultFixture : defaultFixtureNoBasket)) token0 = erc20s[collateral.indexOf(basket[0])] token1 = erc20s[collateral.indexOf(basket[1])] token2 = erc20s[collateral.indexOf(basket[2])] @@ -201,6 +201,11 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await backupToken1.connect(owner).mint(addr2.address, initialBal) await backupToken2.connect(owner).mint(addr2.address, initialBal) + } + + beforeEach(async () => { + ;[owner, addr1, addr2] = await ethers.getSigners() + await doFixtureSetup(true) }) describe('Default Handling - Basket Selection', function () { @@ -599,7 +604,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) }) - context('With multiple targets', function () { + context('With multiple targets -- USD + EUR', function () { let issueAmount: BigNumber let newEURCollateral: FiatCollateral let backupEURCollateral: Collateral @@ -609,6 +614,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { let quotes: BigNumber[] beforeEach(async function () { + await doFixtureSetup(false) // don't set an initial prime basket + // Issue some RTokens to user issueAmount = bn('100e18') @@ -715,7 +722,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { const newRefAmounts = [fp('0.5'), fp('0.5')] await expect(basketHandler.refreshBasket()) .to.emit(basketHandler, 'BasketSet') - .withArgs(3, newTokens, newRefAmounts, false) + .withArgs(2, newTokens, newRefAmounts, false) // Check state - Basket switch in EUR targets expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) @@ -771,7 +778,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Basket should switch to empty and defaulted await expect(basketHandler.refreshBasket()) .to.emit(basketHandler, 'BasketSet') - .withArgs(2, [], [], true) + .withArgs(1, [], [], true) // Check state - Basket is disabled expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) @@ -809,12 +816,6 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await stRSR.connect(addr1).stake(stakeAmount) }) - afterEach(async () => { - // Every test in this section should end with full collateralization - expect(await basketHandler.fullyCollateralized()).to.equal(true) - await rToken.connect(addr1).redeem(1) - }) - it('Should not trade if trading paused', async () => { await main.connect(owner).pauseTrading() await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( @@ -827,9 +828,6 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'frozen or trading paused' ) - - // Unfreeze for afterEach - await main.connect(owner).unfreeze() }) it('Should trade if issuance paused', async () => { @@ -842,12 +840,6 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { backingManager, 'TradeStarted' ) - - // Complete recollateralization - const tradeAddr = await backingManager.trades(token0.address) - const trade = await ethers.getContractAt('DutchTrade', tradeAddr) - await token1.connect(addr1).approve(trade.address, initialBal) - await trade.connect(addr1).bid() }) it('Should not trade if UNPRICED', async () => { @@ -1017,8 +1009,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Check initial state expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await basketHandler.fullyCollateralized()).to.equal(true) expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) expect(await token1.balanceOf(backingManager.address)).to.equal(0) expect(await rToken.totalSupply()).to.equal(issueAmount) diff --git a/test/fixtures.ts b/test/fixtures.ts index 7f89fa2d41..08f9061acd 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -6,6 +6,7 @@ import { IConfig, IImplementations, IRevenueShare, networkConfig } from '../comm import { expectInReceipt } from '../common/events' import { bn, fp } from '../common/numbers' import { CollateralStatus, PAUSER, LONG_FREEZER, SHORT_FREEZER } from '../common/constants' +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' import { Asset, AssetRegistryP1, @@ -408,7 +409,18 @@ export interface DefaultFixture extends RSRAndCompAaveAndCollateralAndModuleFixt type Fixture = () => Promise +// Use this fixture when the prime basket will be constant at 1 USD export const defaultFixture: Fixture = async function (): Promise { + return await loadFixture(makeFixture.bind(null, true)) +} + +// Use this fixture when the prime basket needs to be set away from 1 USD +export const defaultFixtureNoBasket: Fixture = + async function (): Promise { + return await loadFixture(makeFixture.bind(null, false)) + } + +const makeFixture = async (setBasket: boolean): Promise => { const signers = await ethers.getSigners() const owner = signers[0] const { rsr } = await rsrFixture() @@ -679,21 +691,23 @@ export const defaultFixture: Fixture = async function (): Promis // Basket should begin disabled at 0 len expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) - // Set non-empty basket - await basketHandler.connect(owner).setPrimeBasket(basketERC20s, basketsNeededAmts) - await basketHandler.connect(owner).refreshBasket() + if (setBasket) { + // Set non-empty basket + await basketHandler.connect(owner).setPrimeBasket(basketERC20s, basketsNeededAmts) + await basketHandler.connect(owner).refreshBasket() - // Advance time post warmup period - await advanceTime(Number(config.warmupPeriod) + 1) + // Advance time post warmup period + await advanceTime(Number(config.warmupPeriod) + 1) + + // Charge throttle + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 3600) + } // Set up allowances for (let i = 0; i < basket.length; i++) { await backingManager.grantRTokenAllowance(await basket[i].erc20()) } - // Charge throttle - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 3600) - // Set Owner as Pauser/Freezer for tests await main.connect(owner).grantRole(PAUSER, owner.address) await main.connect(owner).grantRole(SHORT_FREEZER, owner.address) From 6e84cfefe680c0bdefd6f4577174f8af80239a1d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 8 May 2023 19:52:41 -0400 Subject: [PATCH 188/462] update trading extremes tests --- test/ZTradingExtremes.test.ts | 10 +++++++--- test/fixtures.ts | 6 +++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/test/ZTradingExtremes.test.ts b/test/ZTradingExtremes.test.ts index 4a22c27806..9088b9fe2d 100644 --- a/test/ZTradingExtremes.test.ts +++ b/test/ZTradingExtremes.test.ts @@ -29,7 +29,7 @@ import { } from '../typechain' import { advanceTime } from './utils/time' import { - defaultFixture, + defaultFixtureNoBasket, ORACLE_ERROR, PRICE_TIMEOUT, REVENUE_HIDING, @@ -106,7 +106,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, rsrAsset, aaveAsset, compAsset, - } = await loadFixture(defaultFixture)) + } = await loadFixture(defaultFixtureNoBasket)) ERC20Mock = await ethers.getContractFactory('ERC20Mock') ATokenMockFactory = await ethers.getContractFactory('StaticATokenMock') @@ -336,6 +336,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, targetAmts ) await basketHandler.connect(owner).refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) // Issue rTokens const noThrottle = { amtRate: MAX_THROTTLE_AMT_RATE, pctRate: 0 } @@ -473,6 +474,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, targetAmts ) await expect(basketHandler.connect(owner).refreshBasket()).to.emit(basketHandler, 'BasketSet') + await advanceTime(Number(config.warmupPeriod) + 1) // Issue rTokens const noThrottle = { amtRate: MAX_THROTTLE_AMT_RATE, pctRate: 0 } @@ -638,6 +640,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, primeBasket.map((c) => c.address) ) await basketHandler.connect(owner).refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) // Over-collateralize with RSR await rsr.connect(owner).mint(addr1.address, fp('1e29')) @@ -756,7 +759,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, return erc20 } - ;({ assetRegistry, basketHandler, compoundMock } = await loadFixture(defaultFixture)) + ;({ assetRegistry, basketHandler, compoundMock } = await loadFixture(defaultFixtureNoBasket)) const primeERC20s = [] const targetAmts = [] @@ -792,6 +795,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, // Set prime basket with all collateral await basketHandler.setPrimeBasket(primeERC20s, targetAmts) await basketHandler.connect(owner).refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) // Unregister collateral and switch basket if (firstCollateral !== undefined) { diff --git a/test/fixtures.ts b/test/fixtures.ts index 08f9061acd..6361e909db 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -698,9 +698,6 @@ const makeFixture = async (setBasket: boolean): Promise => { // Advance time post warmup period await advanceTime(Number(config.warmupPeriod) + 1) - - // Charge throttle - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 3600) } // Set up allowances @@ -708,6 +705,9 @@ const makeFixture = async (setBasket: boolean): Promise => { await backingManager.grantRTokenAllowance(await basket[i].erc20()) } + // Charge throttle + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 3600) + // Set Owner as Pauser/Freezer for tests await main.connect(owner).grantRole(PAUSER, owner.address) await main.connect(owner).grantRole(SHORT_FREEZER, owner.address) From 5b90db7b6d06b066f773d09cb0ef8d31cf5a066f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 8 May 2023 20:02:09 -0400 Subject: [PATCH 189/462] give scenario tests in CI more memory --- .github/workflows/tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c1a945e598..de7b65169a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -76,6 +76,8 @@ jobs: - run: yarn install --immutable - run: yarn compile - run: yarn test:p0 + env: + NODE_OPTIONS: '--max-old-space-size=4096' p1-tests: name: 'P1 Tests' @@ -89,6 +91,8 @@ jobs: - run: yarn install --immutable - run: yarn compile - run: yarn test:p1 + env: + NODE_OPTIONS: '--max-old-space-size=4096' scenario-tests: name: 'Scenario Tests' @@ -102,6 +106,8 @@ jobs: - run: yarn install --immutable - run: yarn compile - run: yarn test:scenario + env: + NODE_OPTIONS: '--max-old-space-size=4096' extreme-tests: name: 'Extreme Tests' From a1a7376b3f1da5d5d73daf9159807b9267c51c1c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 8 May 2023 20:26:18 -0400 Subject: [PATCH 190/462] fix scenario tests (were broken in CI for a while) --- test/scenario/BadCollateralPlugin.test.ts | 39 ++++++++++++++++++++--- test/scenario/BadERC20.test.ts | 14 ++++---- test/scenario/ComplexBasket.test.ts | 4 +-- test/scenario/EURT.test.ts | 5 +-- test/scenario/MaxBasketSize.test.ts | 4 +-- test/scenario/NestedRTokens.test.ts | 12 +++++-- test/scenario/NontrivialPeg.test.ts | 6 ++-- test/scenario/RevenueHiding.test.ts | 4 +-- test/scenario/SetProtocol.test.ts | 6 ++-- test/scenario/WBTC.test.ts | 5 +-- test/scenario/WETH.test.ts | 5 +-- test/scenario/cETH.test.ts | 6 ++-- test/scenario/cWBTC.test.ts | 6 ++-- 13 files changed, 82 insertions(+), 34 deletions(-) diff --git a/test/scenario/BadCollateralPlugin.test.ts b/test/scenario/BadCollateralPlugin.test.ts index 2134c37f59..d0198ee97d 100644 --- a/test/scenario/BadCollateralPlugin.test.ts +++ b/test/scenario/BadCollateralPlugin.test.ts @@ -20,10 +20,11 @@ import { TestIRToken, } from '../../typechain' import { expectRTokenPrice, setOraclePrice } from '../utils/oracles' +import { advanceTime } from '../utils/time' import { getTrade } from '../utils/trades' import { Collateral, - defaultFixture, + defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, @@ -79,7 +80,7 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { basketHandler, aaveToken, rTokenAsset, - } = await loadFixture(defaultFixture)) + } = await loadFixture(defaultFixtureNoBasket)) // Token0 const nonStaticERC20 = await ( @@ -124,6 +125,7 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { backupToken.address, ]) await basketHandler.refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) await backingManager.grantRTokenAllowance(token0.address) await backingManager.grantRTokenAllowance(backupToken.address) @@ -159,7 +161,16 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { it('should keep a constant redemption basket as collateral loses value', async () => { // Redemption should be restrained to be prorata expect(await token0.balanceOf(addr1.address)).to.equal(0) - await rToken.connect(addr1).redeem(initialBal.div(2)) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + initialBal.div(2), + [await basketHandler.nonce()], + [fp('1')], + [], + [] + ) expect(await rToken.totalSupply()).to.equal(initialBal.div(2)) expect(await token0.balanceOf(addr1.address)).to.equal(initialBal.div(2)) await expectRTokenPrice( @@ -173,7 +184,16 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { it('should increase the issuance basket as collateral loses value', async () => { // Should be able to redeem half the RToken at-par - await rToken.connect(addr1).redeem(initialBal.div(2)) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + initialBal.div(2), + [await basketHandler.nonce()], + [fp('1')], + [], + [] + ) expect(await rToken.totalSupply()).to.equal(initialBal.div(2)) expect(await rToken.balanceOf(addr1.address)).to.equal(initialBal.div(2)) @@ -214,7 +234,16 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { it('should not change the redemption basket', async () => { // Should be able to redeem half the RToken at-par - await rToken.connect(addr1).redeem(initialBal.div(2)) + await rToken + .connect(addr1) + .redeemToCustom( + addr1.address, + initialBal.div(2), + [await basketHandler.nonce()], + [fp('1')], + [], + [] + ) expect(await rToken.totalSupply()).to.equal(initialBal.div(2)) expect(await rToken.balanceOf(addr1.address)).to.equal(initialBal.div(2)) diff --git a/test/scenario/BadERC20.test.ts b/test/scenario/BadERC20.test.ts index c4a96e63f5..1b1dfde490 100644 --- a/test/scenario/BadERC20.test.ts +++ b/test/scenario/BadERC20.test.ts @@ -25,7 +25,7 @@ import { getTrade } from '../utils/trades' import { advanceTime } from '../utils/time' import { Collateral, - defaultFixture, + defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, @@ -108,7 +108,7 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { rTokenTrader, rsrTrader, rTokenAsset, - } = await loadFixture(defaultFixture)) + } = await loadFixture(defaultFixtureNoBasket)) // Main ERC20 token0 = await (await ethers.getContractFactory('BadERC20')).deploy('Bad ERC20', 'BERC20') @@ -142,6 +142,7 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { backupToken.address, ]) await basketHandler.refreshBasket() + await advanceTime(config.warmupPeriod.toNumber() + 1) await backingManager.grantRTokenAllowance(token0.address) await backingManager.grantRTokenAllowance(backupToken.address) @@ -214,7 +215,8 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { await advanceTime(DELAY_UNTIL_DEFAULT.toString()) await expect(basketHandler.refreshBasket()) .to.emit(basketHandler, 'BasketSet') - .withArgs(3, [backupToken.address], [fp('1')], false) + .withArgs(2, [backupToken.address], [fp('1')], false) + await advanceTime(config.warmupPeriod.toNumber() + 1) await expect(backingManager.forwardRevenue([])).to.be.reverted // can't catch No Decimals await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.reverted // can't catch No Decimals }) @@ -249,7 +251,7 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { expect(await assetRegistry.isRegistered(collateral0.address)).to.equal(false) await expect(basketHandler.refreshBasket()) .to.emit(basketHandler, 'BasketSet') - .withArgs(3, [backupToken.address], [fp('1')], false) + .withArgs(2, [backupToken.address], [fp('1')], false) // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) @@ -314,7 +316,7 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { await advanceTime(DELAY_UNTIL_DEFAULT.toString()) await expect(basketHandler.refreshBasket()) .to.emit(basketHandler, 'BasketSet') - .withArgs(3, [backupToken.address], [fp('1')], false) + .withArgs(2, [backupToken.address], [fp('1')], false) // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) @@ -360,7 +362,7 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { expect(await assetRegistry.isRegistered(collateral0.address)).to.equal(false) await expect(basketHandler.refreshBasket()) .to.emit(basketHandler, 'BasketSet') - .withArgs(3, [backupToken.address], [fp('1')], false) + .withArgs(2, [backupToken.address], [fp('1')], false) // Advance time post warmup period - SOUND just regained await advanceTime(Number(config.warmupPeriod) + 1) diff --git a/test/scenario/ComplexBasket.test.ts b/test/scenario/ComplexBasket.test.ts index b1eb4c7f84..50fbae5bff 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -31,7 +31,7 @@ import { withinQuad } from '../utils/matchers' import { advanceTime, getLatestBlockTimestamp } from '../utils/time' import { Collateral, - defaultFixture, + defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, @@ -142,7 +142,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { rsrTrader, gnosis, rTokenAsset, - } = await loadFixture(defaultFixture)) + } = await loadFixture(defaultFixtureNoBasket)) // Mint initial balances initialBal = bn('100000000e18') diff --git a/test/scenario/EURT.test.ts b/test/scenario/EURT.test.ts index 4062eea2a3..bc49ffcfb6 100644 --- a/test/scenario/EURT.test.ts +++ b/test/scenario/EURT.test.ts @@ -23,7 +23,7 @@ import { import { getTrade } from '../utils/trades' import { Collateral, - defaultFixture, + defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, @@ -86,7 +86,7 @@ describe(`EUR fiatcoins (eg EURT) - P${IMPLEMENTATION}`, () => { rsrTrader, rTokenTrader, facadeTest, - } = await loadFixture(defaultFixture)) + } = await loadFixture(defaultFixtureNoBasket)) // Main ERC20 token0 = erc20s[7] // aDAI @@ -122,6 +122,7 @@ describe(`EUR fiatcoins (eg EURT) - P${IMPLEMENTATION}`, () => { await assetRegistry.connect(owner).register(eurtCollateral.address) await basketHandler.setPrimeBasket([token0.address, eurt.address], [fp('0.5'), fp('0.5')]) await basketHandler.refreshBasket() + await advanceTime(config.warmupPeriod.toNumber() + 1) await backingManager.grantRTokenAllowance(token0.address) await backingManager.grantRTokenAllowance(eurt.address) diff --git a/test/scenario/MaxBasketSize.test.ts b/test/scenario/MaxBasketSize.test.ts index 82b1a9713d..79c5c1e3b3 100644 --- a/test/scenario/MaxBasketSize.test.ts +++ b/test/scenario/MaxBasketSize.test.ts @@ -25,7 +25,7 @@ import { } from '../../typechain' import { advanceTime, getLatestBlockTimestamp } from '../utils/time' import { - defaultFixture, + defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, @@ -287,7 +287,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { basketHandler, facade, facadeTest, - } = await loadFixture(defaultFixture)) + } = await loadFixture(defaultFixtureNoBasket)) // Mint initial balances initialBal = bn('10000e18') diff --git a/test/scenario/NestedRTokens.test.ts b/test/scenario/NestedRTokens.test.ts index 59590ed1a7..6386b158fd 100644 --- a/test/scenario/NestedRTokens.test.ts +++ b/test/scenario/NestedRTokens.test.ts @@ -3,6 +3,7 @@ import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { ethers } from 'hardhat' +import { BigNumber } from 'ethers' import { ONE_PERIOD, ZERO_ADDRESS, CollateralStatus, TradeKind } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { withinQuad } from '../utils/matchers' @@ -17,7 +18,7 @@ import { RTokenCollateral, } from '../../typechain' import { - defaultFixture, + defaultFixtureNoBasket, DefaultFixture, IMPLEMENTATION, ORACLE_ERROR, @@ -55,8 +56,8 @@ Function.prototype.clone = function () { const dualFixture: Fixture = async function (): Promise { return { - one: await loadFixture(defaultFixture), - two: await loadFixture(defaultFixture.clone()), + one: await loadFixture(defaultFixtureNoBasket), + two: await loadFixture(defaultFixtureNoBasket.clone()), } } @@ -77,9 +78,12 @@ describe(`Nested RTokens - P${IMPLEMENTATION}`, () => { let one: DefaultFixture let two: DefaultFixture + let warmupPeriod: BigNumber + beforeEach(async () => { ;[owner, addr1] = await ethers.getSigners() ;({ one, two } = await loadFixture(dualFixture)) + warmupPeriod = one.config.warmupPeriod }) // this is mostly a check on our testing suite @@ -134,6 +138,7 @@ describe(`Nested RTokens - P${IMPLEMENTATION}`, () => { await one.assetRegistry.connect(owner).register(aTokenCollateral.address) await one.basketHandler.connect(owner).setPrimeBasket([staticATokenERC20.address], [fp('1')]) await one.basketHandler.refreshBasket() + await advanceTime(warmupPeriod.toNumber() + 1) await staticATokenERC20.connect(owner).mint(addr1.address, issueAmt) await staticATokenERC20.connect(addr1).approve(one.rToken.address, issueAmt) await one.rToken.connect(addr1).issue(issueAmt) @@ -143,6 +148,7 @@ describe(`Nested RTokens - P${IMPLEMENTATION}`, () => { await two.assetRegistry.connect(owner).register(rTokenCollateral.address) await two.basketHandler.connect(owner).setPrimeBasket([one.rToken.address], [fp('1')]) await two.basketHandler.refreshBasket() + await advanceTime(warmupPeriod.toNumber() + 1) await one.rToken.connect(addr1).approve(two.rToken.address, issueAmt) await two.rToken.connect(addr1).issue(issueAmt) expect(await two.rToken.balanceOf(addr1.address)).to.equal(issueAmt) diff --git a/test/scenario/NontrivialPeg.test.ts b/test/scenario/NontrivialPeg.test.ts index dfeeac9d0d..70e0fa263f 100644 --- a/test/scenario/NontrivialPeg.test.ts +++ b/test/scenario/NontrivialPeg.test.ts @@ -7,6 +7,7 @@ import { CollateralStatus, TradeKind } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { expectRTokenPrice, setOraclePrice } from '../utils/oracles' import { expectEvents } from '../../common/events' +import { advanceTime } from '../utils/time' import { ERC20Mock, IAssetRegistry, @@ -19,7 +20,7 @@ import { TestIRToken, } from '../../typechain' import { - defaultFixture, + defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, @@ -63,7 +64,7 @@ describe(`The peg (target/ref) should be arbitrary - P${IMPLEMENTATION}`, () => // Deploy fixture ;({ rsr, stRSR, config, rToken, assetRegistry, backingManager, basketHandler, rTokenAsset } = - await loadFixture(defaultFixture)) + await loadFixture(defaultFixtureNoBasket)) // Variable-peg ERC20 token0 = await (await ethers.getContractFactory('ERC20Mock')).deploy('Peg ERC20', 'PERC20') @@ -136,6 +137,7 @@ describe(`The peg (target/ref) should be arbitrary - P${IMPLEMENTATION}`, () => await basketHandler.setPrimeBasket([token0.address, token1.address], [fp('1'), fp('1')]) await basketHandler.refreshBasket() + await advanceTime(config.warmupPeriod.toNumber() + 1) // Issue await token0.connect(addr1).approve(rToken.address, initialBal) diff --git a/test/scenario/RevenueHiding.test.ts b/test/scenario/RevenueHiding.test.ts index 3fbe8e9855..6035603056 100644 --- a/test/scenario/RevenueHiding.test.ts +++ b/test/scenario/RevenueHiding.test.ts @@ -22,7 +22,7 @@ import { advanceTime } from '../utils/time' import { getTrade } from '../utils/trades' import { Collateral, - defaultFixture, + defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, @@ -101,7 +101,7 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME basketHandler, rsrTrader, rTokenTrader, - } = await loadFixture(defaultFixture)) + } = await loadFixture(defaultFixtureNoBasket)) // Main ERC20 dai = erc20s[0] diff --git a/test/scenario/SetProtocol.test.ts b/test/scenario/SetProtocol.test.ts index b044b2490a..a2a67dd94a 100644 --- a/test/scenario/SetProtocol.test.ts +++ b/test/scenario/SetProtocol.test.ts @@ -8,6 +8,7 @@ import { CollateralStatus, TradeKind } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { expectPrice, setOraclePrice } from '../utils/oracles' import { expectEvents } from '../../common/events' +import { advanceTime } from '../utils/time' import { ERC20Mock, IAssetRegistry, @@ -21,7 +22,7 @@ import { WETH9, } from '../../typechain' import { - defaultFixture, + defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, @@ -74,7 +75,7 @@ describe(`Linear combination of self-referential collateral - P${IMPLEMENTATION} basketHandler, rTokenTrader, rsrTrader, - } = await loadFixture(defaultFixture)) + } = await loadFixture(defaultFixtureNoBasket)) await backingManager.connect(owner).setBackingBuffer(0) @@ -138,6 +139,7 @@ describe(`Linear combination of self-referential collateral - P${IMPLEMENTATION} [fp('1'), fp('3'), fp('9')] // powers of 3 ) await basketHandler.refreshBasket() + await advanceTime(config.warmupPeriod.toNumber() + 1) // Mint initial balances await token1.connect(owner).mint(addr1.address, initialBal) diff --git a/test/scenario/WBTC.test.ts b/test/scenario/WBTC.test.ts index d85b667745..64f1766949 100644 --- a/test/scenario/WBTC.test.ts +++ b/test/scenario/WBTC.test.ts @@ -23,7 +23,7 @@ import { import { getTrade } from '../utils/trades' import { Collateral, - defaultFixture, + defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, @@ -88,7 +88,7 @@ describe(`Non-fiat collateral (eg WBTC) - P${IMPLEMENTATION}`, () => { rsrTrader, rTokenTrader, facadeTest, - } = await loadFixture(defaultFixture)) + } = await loadFixture(defaultFixtureNoBasket)) // Main ERC20 token0 = erc20s[7] // aDAI @@ -133,6 +133,7 @@ describe(`Non-fiat collateral (eg WBTC) - P${IMPLEMENTATION}`, () => { backupToken.address, ]) await basketHandler.refreshBasket() + await advanceTime(config.warmupPeriod.toNumber() + 1) await backingManager.grantRTokenAllowance(token0.address) await backingManager.grantRTokenAllowance(wbtc.address) diff --git a/test/scenario/WETH.test.ts b/test/scenario/WETH.test.ts index c005db577f..89bd6f4f6e 100644 --- a/test/scenario/WETH.test.ts +++ b/test/scenario/WETH.test.ts @@ -25,7 +25,7 @@ import { advanceTime } from '../utils/time' import { getTrade } from '../utils/trades' import { Collateral, - defaultFixture, + defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, @@ -85,7 +85,7 @@ describe(`Self-referential collateral (eg ETH via WETH) - P${IMPLEMENTATION}`, ( rsrTrader, rTokenTrader, facadeTest, - } = await loadFixture(defaultFixture)) + } = await loadFixture(defaultFixtureNoBasket)) // Main ERC20 token0 = erc20s[4] // cDai @@ -123,6 +123,7 @@ describe(`Self-referential collateral (eg ETH via WETH) - P${IMPLEMENTATION}`, ( backupToken.address, ]) await basketHandler.refreshBasket() + await advanceTime(config.warmupPeriod.toNumber() + 1) await backingManager.grantRTokenAllowance(token0.address) await backingManager.grantRTokenAllowance(weth.address) diff --git a/test/scenario/cETH.test.ts b/test/scenario/cETH.test.ts index bd54680c0a..164109c2a0 100644 --- a/test/scenario/cETH.test.ts +++ b/test/scenario/cETH.test.ts @@ -28,7 +28,7 @@ import { getTrade } from '../utils/trades' import { setOraclePrice } from '../utils/oracles' import { Collateral, - defaultFixture, + defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, @@ -96,7 +96,7 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, rsrTrader, rTokenTrader, facadeTest, - } = await loadFixture(defaultFixture)) + } = await loadFixture(defaultFixtureNoBasket)) // Main ERC20 token0 = erc20s[4] // cDai @@ -163,6 +163,7 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, weth.address, ]) await basketHandler.refreshBasket() + await advanceTime(config.warmupPeriod.toNumber() + 1) await backingManager.grantRTokenAllowance(token0.address) await backingManager.grantRTokenAllowance(cETH.address) @@ -281,6 +282,7 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, it('should sell cETH for RToken after redemption rate increase', async () => { await cETH.setExchangeRate(fp('2')) // doubling of price await basketHandler.refreshBasket() + await advanceTime(config.warmupPeriod.toNumber() + 1) await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'already collateralized' ) diff --git a/test/scenario/cWBTC.test.ts b/test/scenario/cWBTC.test.ts index d5b2a955c9..2754cdce81 100644 --- a/test/scenario/cWBTC.test.ts +++ b/test/scenario/cWBTC.test.ts @@ -25,7 +25,7 @@ import { import { getTrade } from '../utils/trades' import { Collateral, - defaultFixture, + defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, @@ -98,7 +98,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => rsrTrader, rTokenTrader, facadeTest, - } = await loadFixture(defaultFixture)) + } = await loadFixture(defaultFixtureNoBasket)) // Main ERC20 token0 = erc20s[4] // cDai @@ -171,6 +171,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => wbtc.address, ]) await basketHandler.refreshBasket() + await advanceTime(config.warmupPeriod.toNumber() + 1) await backingManager.grantRTokenAllowance(token0.address) await backingManager.grantRTokenAllowance(cWBTC.address) @@ -304,6 +305,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => it('should sell cWBTC for RToken after redemption rate increase', async () => { await cWBTC.setExchangeRate(fp('2')) // doubling of price await basketHandler.refreshBasket() + await advanceTime(config.warmupPeriod.toNumber() + 1) await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( 'already collateralized' ) From c1ffa0cfdda5b0cb75cd0395c8ecf6cbe99238fe Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 8 May 2023 21:02:45 -0400 Subject: [PATCH 191/462] update integration tests --- test/integration/AssetPlugins.test.ts | 2989 +++++++++++++------------ test/integration/fixtures.ts | 24 +- 2 files changed, 1536 insertions(+), 1477 deletions(-) diff --git a/test/integration/AssetPlugins.test.ts b/test/integration/AssetPlugins.test.ts index 60748e1b6d..9638125005 100644 --- a/test/integration/AssetPlugins.test.ts +++ b/test/integration/AssetPlugins.test.ts @@ -11,7 +11,7 @@ import { PRICE_TIMEOUT, REVENUE_HIDING, } from '../fixtures' -import { defaultFixture } from './fixtures' +import { defaultFixtureNoBasket } from './fixtures' import { getChainId } from '../../common/blockchain-utils' import { IConfig, MAX_ORACLE_TIMEOUT, networkConfig } from '../../common/configuration' import { CollateralStatus, BN_SCALE_FACTOR } from '../../common/constants' @@ -219,7 +219,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, facade, facadeTest, config, - } = await loadFixture(defaultFixture)) + } = await loadFixture(defaultFixtureNoBasket)) // Get tokens dai = erc20s[0] // DAI @@ -379,1663 +379,1706 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, mockChainlinkFeed = await MockV3AggregatorFactory.deploy(8, bn('1e10')) }) - it('Should setup assets correctly', async () => { - // COMP Token - expect(await compAsset.isCollateral()).to.equal(false) - expect(await compAsset.erc20()).to.equal(compToken.address) - expect(await compAsset.erc20()).to.equal(networkConfig[chainId].tokens.COMP) - expect(await compToken.decimals()).to.equal(18) - await expectPrice(compAsset.address, fp('58.28'), ORACLE_ERROR, true) // Close to $58 USD - June 2022 - await expect(compAsset.claimRewards()).to.not.emit(compAsset, 'RewardsClaimed') - expect(await compAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) - - // stkAAVE Token - expect(await aaveAsset.isCollateral()).to.equal(false) - expect(await aaveAsset.erc20()).to.equal(aaveToken.address) - expect(await aaveAsset.erc20()).to.equal(networkConfig[chainId].tokens.stkAAVE) - expect(await aaveToken.decimals()).to.equal(18) - await expectPrice(aaveAsset.address, fp('104.88183739'), ORACLE_ERROR, true) // Close to $104.8 USD - July 2022 - Uses AAVE price - await expect(aaveAsset.claimRewards()).to.not.emit(aaveAsset, 'RewardsClaimed') - expect(await aaveAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) - - // RSR Token - expect(await rsrAsset.isCollateral()).to.equal(false) - expect(await rsrAsset.erc20()).to.equal(rsr.address) - expect(await rsrAsset.erc20()).to.equal(networkConfig[chainId].tokens.RSR) - expect(rsr.address).to.equal(networkConfig[chainId].tokens.RSR) - expect(await rsr.decimals()).to.equal(18) - await expectPrice(rsrAsset.address, fp('0.0069934'), ORACLE_ERROR, true) // Close to $0.00699 - await expect(rsrAsset.claimRewards()).to.not.emit(rsrAsset, 'RewardsClaimed') - expect(await rsrAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) - }) + context('Setup and validation', () => { + beforeEach(async () => { + // Setup basket + await basketHandler + .connect(owner) + .setPrimeBasket( + [dai.address, stataDai.address, cDaiVault.address], + [fp('0.25'), fp('0.25'), fp('0.5')] + ) + await basketHandler.connect(owner).refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) + }) - it('Should setup collateral correctly - Fiatcoins', async () => { - // Define interface required for each fiat coin - interface TokenInfo { - token: ERC20Mock - tokenDecimals: number - tokenAddress: string - tokenCollateral: FiatCollateral - price: BigNumber - } + it('Should setup assets correctly', async () => { + // COMP Token + expect(await compAsset.isCollateral()).to.equal(false) + expect(await compAsset.erc20()).to.equal(compToken.address) + expect(await compAsset.erc20()).to.equal(networkConfig[chainId].tokens.COMP) + expect(await compToken.decimals()).to.equal(18) + await expectPrice(compAsset.address, fp('58.28'), ORACLE_ERROR, true) // Close to $58 USD - June 2022 + await expect(compAsset.claimRewards()).to.not.emit(compAsset, 'RewardsClaimed') + expect(await compAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) + + // stkAAVE Token + expect(await aaveAsset.isCollateral()).to.equal(false) + expect(await aaveAsset.erc20()).to.equal(aaveToken.address) + expect(await aaveAsset.erc20()).to.equal(networkConfig[chainId].tokens.stkAAVE) + expect(await aaveToken.decimals()).to.equal(18) + await expectPrice(aaveAsset.address, fp('104.88183739'), ORACLE_ERROR, true) // Close to $104.8 USD - July 2022 - Uses AAVE price + await expect(aaveAsset.claimRewards()).to.not.emit(aaveAsset, 'RewardsClaimed') + expect(await aaveAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) + + // RSR Token + expect(await rsrAsset.isCollateral()).to.equal(false) + expect(await rsrAsset.erc20()).to.equal(rsr.address) + expect(await rsrAsset.erc20()).to.equal(networkConfig[chainId].tokens.RSR) + expect(rsr.address).to.equal(networkConfig[chainId].tokens.RSR) + expect(await rsr.decimals()).to.equal(18) + await expectPrice(rsrAsset.address, fp('0.0069934'), ORACLE_ERROR, true) // Close to $0.00699 + await expect(rsrAsset.claimRewards()).to.not.emit(rsrAsset, 'RewardsClaimed') + expect(await rsrAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) + }) - // DAI - USDC - USDT - BUSD - const tokenInfos: TokenInfo[] = [ - { - token: dai, - tokenDecimals: 18, - tokenAddress: networkConfig[chainId].tokens.DAI || '', - tokenCollateral: daiCollateral, - price: fp('1'), - }, - { - token: usdc, - tokenDecimals: 6, - tokenAddress: networkConfig[chainId].tokens.USDC || '', - tokenCollateral: usdcCollateral, - price: fp('1.0003994'), - }, - { - token: usdt, - tokenDecimals: 6, - tokenAddress: networkConfig[chainId].tokens.USDT || '', - tokenCollateral: usdtCollateral, - price: fp('0.99934692'), - }, - { - token: busd, - tokenDecimals: 18, - tokenAddress: networkConfig[chainId].tokens.BUSD || '', - tokenCollateral: busdCollateral, - price: fp('1.00030972'), - }, - { - token: usdp, - tokenDecimals: 18, - tokenAddress: networkConfig[chainId].tokens.USDP || '', - tokenCollateral: usdpCollateral, - price: fp('0.99995491'), - }, - { - token: tusd, - tokenDecimals: 18, - tokenAddress: networkConfig[chainId].tokens.TUSD || '', - tokenCollateral: tusdCollateral, - price: fp('1.00022194'), - }, - ] - - for (const tkInf of tokenInfos) { - // Fiat Token Assets - expect(await tkInf.tokenCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await tkInf.tokenCollateral.isCollateral()).to.equal(true) - expect(await tkInf.tokenCollateral.erc20()).to.equal(tkInf.token.address) - expect(await tkInf.tokenCollateral.erc20()).to.equal(tkInf.tokenAddress) - expect(await tkInf.token.decimals()).to.equal(tkInf.tokenDecimals) - expect(await tkInf.tokenCollateral.targetName()).to.equal( - ethers.utils.formatBytes32String('USD') - ) - expect(await tkInf.tokenCollateral.refPerTok()).to.equal(fp('1')) - expect(await tkInf.tokenCollateral.targetPerRef()).to.equal(fp('1')) + it('Should setup collateral correctly - Fiatcoins', async () => { + // Define interface required for each fiat coin + interface TokenInfo { + token: ERC20Mock + tokenDecimals: number + tokenAddress: string + tokenCollateral: FiatCollateral + price: BigNumber + } - await expectPrice(tkInf.tokenCollateral.address, tkInf.price, ORACLE_ERROR, true, bn('1e5')) + // DAI - USDC - USDT - BUSD + const tokenInfos: TokenInfo[] = [ + { + token: dai, + tokenDecimals: 18, + tokenAddress: networkConfig[chainId].tokens.DAI || '', + tokenCollateral: daiCollateral, + price: fp('1'), + }, + { + token: usdc, + tokenDecimals: 6, + tokenAddress: networkConfig[chainId].tokens.USDC || '', + tokenCollateral: usdcCollateral, + price: fp('1.0003994'), + }, + { + token: usdt, + tokenDecimals: 6, + tokenAddress: networkConfig[chainId].tokens.USDT || '', + tokenCollateral: usdtCollateral, + price: fp('0.99934692'), + }, + { + token: busd, + tokenDecimals: 18, + tokenAddress: networkConfig[chainId].tokens.BUSD || '', + tokenCollateral: busdCollateral, + price: fp('1.00030972'), + }, + { + token: usdp, + tokenDecimals: 18, + tokenAddress: networkConfig[chainId].tokens.USDP || '', + tokenCollateral: usdpCollateral, + price: fp('0.99995491'), + }, + { + token: tusd, + tokenDecimals: 18, + tokenAddress: networkConfig[chainId].tokens.TUSD || '', + tokenCollateral: tusdCollateral, + price: fp('1.00022194'), + }, + ] - await expect(tkInf.tokenCollateral.claimRewards()).to.not.emit( - tkInf.tokenCollateral, - 'RewardsClaimed' - ) - expect(await tkInf.tokenCollateral.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) - } - }) + for (const tkInf of tokenInfos) { + // Fiat Token Assets + expect(await tkInf.tokenCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await tkInf.tokenCollateral.isCollateral()).to.equal(true) + expect(await tkInf.tokenCollateral.erc20()).to.equal(tkInf.token.address) + expect(await tkInf.tokenCollateral.erc20()).to.equal(tkInf.tokenAddress) + expect(await tkInf.token.decimals()).to.equal(tkInf.tokenDecimals) + expect(await tkInf.tokenCollateral.targetName()).to.equal( + ethers.utils.formatBytes32String('USD') + ) + expect(await tkInf.tokenCollateral.refPerTok()).to.equal(fp('1')) + expect(await tkInf.tokenCollateral.targetPerRef()).to.equal(fp('1')) + + await expectPrice( + tkInf.tokenCollateral.address, + tkInf.price, + ORACLE_ERROR, + true, + bn('1e5') + ) + + await expect(tkInf.tokenCollateral.claimRewards()).to.not.emit( + tkInf.tokenCollateral, + 'RewardsClaimed' + ) + expect(await tkInf.tokenCollateral.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) + } + }) - it('Should setup collateral correctly - CTokens Fiat', async () => { - // Define interface required for each ctoken - interface CTokenInfo { - token: ERC20Mock - tokenAddress: string - cToken: CTokenMock - cTokenVault: CTokenVault - cTokenAddress: string - cTokenCollateral: CTokenFiatCollateral - pegPrice: BigNumber - refPerTok: BigNumber - } + it('Should setup collateral correctly - CTokens Fiat', async () => { + // Define interface required for each ctoken + interface CTokenInfo { + token: ERC20Mock + tokenAddress: string + cToken: CTokenMock + cTokenVault: CTokenVault + cTokenAddress: string + cTokenCollateral: CTokenFiatCollateral + pegPrice: BigNumber + refPerTok: BigNumber + } - // Compound - cUSDC and cUSDT - const cTokenInfos: CTokenInfo[] = [ - { - token: dai, - tokenAddress: networkConfig[chainId].tokens.DAI || '', - cToken: cDai, - cTokenVault: cDaiVault, - cTokenAddress: networkConfig[chainId].tokens.cDAI || '', - cTokenCollateral: cDaiCollateral, - pegPrice: fp('1'), - refPerTok: fp('0.022015108677007985'), - }, - { - token: usdc, - tokenAddress: networkConfig[chainId].tokens.USDC || '', - cToken: cUsdc, - cTokenVault: cUsdcVault, - cTokenAddress: networkConfig[chainId].tokens.cUSDC || '', - cTokenCollateral: cUsdcCollateral, - pegPrice: fp('1.0003994'), - refPerTok: fp('0.022611941829792900'), - }, - { - token: usdt, - tokenAddress: networkConfig[chainId].tokens.USDT || '', - cToken: cUsdt, - cTokenVault: cUsdtVault, - cTokenAddress: networkConfig[chainId].tokens.cUSDT || '', - cTokenCollateral: cUsdtCollateral, - pegPrice: fp('0.99934692'), - refPerTok: fp('0.021859813029312800'), - }, - { - token: usdp, - tokenAddress: networkConfig[chainId].tokens.USDP || '', - cToken: cUsdp, - cTokenVault: cUsdpVault, - cTokenAddress: networkConfig[chainId].tokens.cUSDP || '', - cTokenCollateral: cUsdpCollateral, - pegPrice: fp('0.99995491'), - refPerTok: fp('0.020090037479321573'), - }, - ] - - for (const ctkInf of cTokenInfos) { - // CToken - expect(await ctkInf.cTokenCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await ctkInf.cTokenCollateral.isCollateral()).to.equal(true) - expect(await ctkInf.cTokenCollateral.referenceERC20Decimals()).to.equal( - await ctkInf.token.decimals() - ) - expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) - expect(await ctkInf.cTokenVault.asset()).to.equal(ctkInf.cTokenAddress) - expect(await ctkInf.cToken.decimals()).to.equal(8) - expect(await ctkInf.cTokenVault.decimals()).to.equal(17) - expect(await ctkInf.cTokenCollateral.targetName()).to.equal( - ethers.utils.formatBytes32String('USD') - ) - expect(await ctkInf.cTokenCollateral.refPerTok()).to.be.closeTo( - ctkInf.refPerTok, - fp('0.001') - ) - expect(await ctkInf.cTokenCollateral.targetPerRef()).to.equal(fp('1')) - expect(await ctkInf.cTokenCollateral.exposedReferencePrice()).to.equal( - await ctkInf.cTokenCollateral.refPerTok() - ) + // Compound - cUSDC and cUSDT + const cTokenInfos: CTokenInfo[] = [ + { + token: dai, + tokenAddress: networkConfig[chainId].tokens.DAI || '', + cToken: cDai, + cTokenVault: cDaiVault, + cTokenAddress: networkConfig[chainId].tokens.cDAI || '', + cTokenCollateral: cDaiCollateral, + pegPrice: fp('1'), + refPerTok: fp('0.022015108677007985'), + }, + { + token: usdc, + tokenAddress: networkConfig[chainId].tokens.USDC || '', + cToken: cUsdc, + cTokenVault: cUsdcVault, + cTokenAddress: networkConfig[chainId].tokens.cUSDC || '', + cTokenCollateral: cUsdcCollateral, + pegPrice: fp('1.0003994'), + refPerTok: fp('0.022611941829792900'), + }, + { + token: usdt, + tokenAddress: networkConfig[chainId].tokens.USDT || '', + cToken: cUsdt, + cTokenVault: cUsdtVault, + cTokenAddress: networkConfig[chainId].tokens.cUSDT || '', + cTokenCollateral: cUsdtCollateral, + pegPrice: fp('0.99934692'), + refPerTok: fp('0.021859813029312800'), + }, + { + token: usdp, + tokenAddress: networkConfig[chainId].tokens.USDP || '', + cToken: cUsdp, + cTokenVault: cUsdpVault, + cTokenAddress: networkConfig[chainId].tokens.cUSDP || '', + cTokenCollateral: cUsdpCollateral, + pegPrice: fp('0.99995491'), + refPerTok: fp('0.020090037479321573'), + }, + ] - await expectPrice( - ctkInf.cTokenCollateral.address, - ctkInf.pegPrice.mul(ctkInf.refPerTok).div(BN_SCALE_FACTOR), - ORACLE_ERROR, - true, - bn('1e4') - ) + for (const ctkInf of cTokenInfos) { + // CToken + expect(await ctkInf.cTokenCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await ctkInf.cTokenCollateral.isCollateral()).to.equal(true) + expect(await ctkInf.cTokenCollateral.referenceERC20Decimals()).to.equal( + await ctkInf.token.decimals() + ) + expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) + expect(await ctkInf.cTokenVault.asset()).to.equal(ctkInf.cTokenAddress) + expect(await ctkInf.cToken.decimals()).to.equal(8) + expect(await ctkInf.cTokenVault.decimals()).to.equal(17) + expect(await ctkInf.cTokenCollateral.targetName()).to.equal( + ethers.utils.formatBytes32String('USD') + ) + expect(await ctkInf.cTokenCollateral.refPerTok()).to.be.closeTo( + ctkInf.refPerTok, + fp('0.001') + ) + expect(await ctkInf.cTokenCollateral.targetPerRef()).to.equal(fp('1')) + expect(await ctkInf.cTokenCollateral.exposedReferencePrice()).to.equal( + await ctkInf.cTokenCollateral.refPerTok() + ) + + await expectPrice( + ctkInf.cTokenCollateral.address, + ctkInf.pegPrice.mul(ctkInf.refPerTok).div(BN_SCALE_FACTOR), + ORACLE_ERROR, + true, + bn('1e4') + ) + + // TODO: deprecate + await expect(ctkInf.cTokenCollateral.claimRewards()) + .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') + .withArgs(compToken.address, 0) + + await expect(ctkInf.cTokenVault.claimRewards()) + .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') + .withArgs(compToken.address, 0) + + expect(await ctkInf.cTokenCollateral.maxTradeVolume()).to.equal( + config.rTokenMaxTradeVolume + ) + } + }) - // TODO: deprecate - await expect(ctkInf.cTokenCollateral.claimRewards()) - .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') - .withArgs(compToken.address, 0) + it('Should setup collateral correctly - ATokens Fiat', async () => { + // Define interface required for each aToken + interface ATokenInfo { + token: ERC20Mock + tokenAddress: string + stataToken: StaticATokenLM + aToken: IAToken + aTokenAddress: string + aTokenCollateral: ATokenFiatCollateral + pegPrice: BigNumber + refPerTok: BigNumber + } - await expect(ctkInf.cTokenVault.claimRewards()) - .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') - .withArgs(compToken.address, 0) + // aUSDC, aUSDT, and aBUSD + const aTokenInfos: ATokenInfo[] = [ + { + token: dai, + tokenAddress: networkConfig[chainId].tokens.DAI || '', + stataToken: stataDai, + aToken: aDai, + aTokenAddress: networkConfig[chainId].tokens.aDAI || '', + aTokenCollateral: aDaiCollateral, + pegPrice: fp('1'), + refPerTok: fp('1.072871692909066736'), + }, + { + token: usdc, + tokenAddress: networkConfig[chainId].tokens.USDC || '', + stataToken: stataUsdc, + aToken: aUsdc, + aTokenAddress: networkConfig[chainId].tokens.aUSDC || '', + aTokenCollateral: aUsdcCollateral, + pegPrice: fp('1.0003994'), + refPerTok: fp('1.075820226287820705'), + }, + { + token: usdt, + tokenAddress: networkConfig[chainId].tokens.USDT || '', + stataToken: stataUsdt, + aToken: aUsdt, + aTokenAddress: networkConfig[chainId].tokens.aUSDT || '', + aTokenCollateral: aUsdtCollateral, + pegPrice: fp('0.99934692'), + refPerTok: fp('1.088178891886696259'), + }, + { + token: busd, + tokenAddress: networkConfig[chainId].tokens.BUSD || '', + stataToken: stataBusd, + aToken: aBusd, + aTokenAddress: networkConfig[chainId].tokens.aBUSD || '', + aTokenCollateral: aBusdCollateral, + pegPrice: fp('1.00030972'), + refPerTok: fp('1.093996241277203301'), + }, + { + token: usdp, + tokenAddress: networkConfig[chainId].tokens.USDP || '', + stataToken: stataUsdp, + aToken: aUsdp, + aTokenAddress: networkConfig[chainId].tokens.aUSDP || '', + aTokenCollateral: aUsdpCollateral, + pegPrice: fp('0.99995491'), + refPerTok: fp('1.019878722522085537'), + }, + ] - expect(await ctkInf.cTokenCollateral.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) - } - }) + for (const atkInf of aTokenInfos) { + // AToken + expect(await atkInf.aTokenCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await atkInf.aTokenCollateral.isCollateral()).to.equal(true) + expect(await atkInf.aTokenCollateral.erc20()).to.equal(atkInf.stataToken.address) + expect(await atkInf.stataToken.decimals()).to.equal(await atkInf.token.decimals()) + expect(await atkInf.aTokenCollateral.targetName()).to.equal( + ethers.utils.formatBytes32String('USD') + ) + expect(await atkInf.aTokenCollateral.refPerTok()).to.be.closeTo(fp('1'), fp('0.095')) + + expect(await atkInf.aTokenCollateral.targetPerRef()).to.equal(fp('1')) + expect(await atkInf.aTokenCollateral.exposedReferencePrice()).to.be.closeTo( + await atkInf.aTokenCollateral.refPerTok(), + fp('0.000005') + ) + + await expectPrice( + atkInf.aTokenCollateral.address, + atkInf.pegPrice.mul(atkInf.refPerTok).div(BN_SCALE_FACTOR), + ORACLE_ERROR, + true, + bn('1e5') + ) + + // TODO: deprecate + await expect(atkInf.aTokenCollateral.claimRewards()) + .to.emit(atkInf.stataToken, 'RewardsClaimed') + .withArgs(aaveToken.address, 0) + + await expect(atkInf.stataToken['claimRewards()']()) + .to.emit(atkInf.stataToken, 'RewardsClaimed') + .withArgs(aaveToken.address, 0) + + // Check StaticAToken + expect(await atkInf.stataToken.name()).to.equal( + 'Static Aave interest bearing ' + (await atkInf.token.symbol()) + ) + expect(await atkInf.stataToken.symbol()).to.equal('stata' + (await atkInf.token.symbol())) + expect(await atkInf.stataToken.decimals()).to.equal(await atkInf.token.decimals()) + expect(await atkInf.stataToken.LENDING_POOL()).to.equal( + networkConfig[chainId].AAVE_LENDING_POOL + ) + expect(await atkInf.stataToken.INCENTIVES_CONTROLLER()).to.equal( + networkConfig[chainId].AAVE_INCENTIVES + ) + expect(await atkInf.stataToken.ATOKEN()).to.equal(atkInf.aToken.address) + expect(await atkInf.stataToken.ATOKEN()).to.equal(atkInf.aTokenAddress) + expect(await atkInf.stataToken.ASSET()).to.equal(atkInf.token.address) + expect(await atkInf.stataToken.ASSET()).to.equal(atkInf.tokenAddress) + expect(await atkInf.stataToken.REWARD_TOKEN()).to.equal(aaveToken.address) + + expect(await atkInf.aTokenCollateral.maxTradeVolume()).to.equal( + config.rTokenMaxTradeVolume + ) + } + }) - it('Should setup collateral correctly - ATokens Fiat', async () => { - // Define interface required for each aToken - interface ATokenInfo { - token: ERC20Mock - tokenAddress: string - stataToken: StaticATokenLM - aToken: IAToken - aTokenAddress: string - aTokenCollateral: ATokenFiatCollateral - pegPrice: BigNumber - refPerTok: BigNumber - } + it('Should setup collateral correctly - Non-Fiatcoins', async () => { + // Define interface required for each non-fiat coin + interface TokenInfo { + nonFiatToken: ERC20Mock + nonFiatTokenDecimals: number + nonFiatTokenAddress: string + nonFiatTokenCollateral: NonFiatCollateral + targetPrice: BigNumber + refPrice: BigNumber + targetName: string + } - // aUSDC, aUSDT, and aBUSD - const aTokenInfos: ATokenInfo[] = [ - { - token: dai, - tokenAddress: networkConfig[chainId].tokens.DAI || '', - stataToken: stataDai, - aToken: aDai, - aTokenAddress: networkConfig[chainId].tokens.aDAI || '', - aTokenCollateral: aDaiCollateral, - pegPrice: fp('1'), - refPerTok: fp('1.072871692909066736'), - }, - { - token: usdc, - tokenAddress: networkConfig[chainId].tokens.USDC || '', - stataToken: stataUsdc, - aToken: aUsdc, - aTokenAddress: networkConfig[chainId].tokens.aUSDC || '', - aTokenCollateral: aUsdcCollateral, - pegPrice: fp('1.0003994'), - refPerTok: fp('1.075820226287820705'), - }, - { - token: usdt, - tokenAddress: networkConfig[chainId].tokens.USDT || '', - stataToken: stataUsdt, - aToken: aUsdt, - aTokenAddress: networkConfig[chainId].tokens.aUSDT || '', - aTokenCollateral: aUsdtCollateral, - pegPrice: fp('0.99934692'), - refPerTok: fp('1.088178891886696259'), - }, - { - token: busd, - tokenAddress: networkConfig[chainId].tokens.BUSD || '', - stataToken: stataBusd, - aToken: aBusd, - aTokenAddress: networkConfig[chainId].tokens.aBUSD || '', - aTokenCollateral: aBusdCollateral, - pegPrice: fp('1.00030972'), - refPerTok: fp('1.093996241277203301'), - }, - { - token: usdp, - tokenAddress: networkConfig[chainId].tokens.USDP || '', - stataToken: stataUsdp, - aToken: aUsdp, - aTokenAddress: networkConfig[chainId].tokens.aUSDP || '', - aTokenCollateral: aUsdpCollateral, - pegPrice: fp('0.99995491'), - refPerTok: fp('1.019878722522085537'), - }, - ] - - for (const atkInf of aTokenInfos) { - // AToken - expect(await atkInf.aTokenCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await atkInf.aTokenCollateral.isCollateral()).to.equal(true) - expect(await atkInf.aTokenCollateral.erc20()).to.equal(atkInf.stataToken.address) - expect(await atkInf.stataToken.decimals()).to.equal(await atkInf.token.decimals()) - expect(await atkInf.aTokenCollateral.targetName()).to.equal( - ethers.utils.formatBytes32String('USD') - ) - expect(await atkInf.aTokenCollateral.refPerTok()).to.be.closeTo(fp('1'), fp('0.095')) + // WBTC + const tokenInfos: TokenInfo[] = [ + { + nonFiatToken: wbtc, + nonFiatTokenDecimals: 8, + nonFiatTokenAddress: networkConfig[chainId].tokens.WBTC || '', + nonFiatTokenCollateral: wbtcCollateral, + targetPrice: fp('31311.5'), // approx price June 6, 2022 + refPrice: fp('1.00062735'), // approx price wbtc-btc + targetName: 'BTC', + }, + ] - expect(await atkInf.aTokenCollateral.targetPerRef()).to.equal(fp('1')) - expect(await atkInf.aTokenCollateral.exposedReferencePrice()).to.be.closeTo( - await atkInf.aTokenCollateral.refPerTok(), - fp('0.000005') - ) + for (const tkInf of tokenInfos) { + // Non-Fiat Token Assets + expect(await tkInf.nonFiatTokenCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await tkInf.nonFiatTokenCollateral.isCollateral()).to.equal(true) + expect(await tkInf.nonFiatTokenCollateral.erc20()).to.equal(tkInf.nonFiatToken.address) + expect(await tkInf.nonFiatTokenCollateral.erc20()).to.equal(tkInf.nonFiatTokenAddress) + expect(await tkInf.nonFiatToken.decimals()).to.equal(tkInf.nonFiatTokenDecimals) + expect(await tkInf.nonFiatTokenCollateral.targetName()).to.equal( + ethers.utils.formatBytes32String(tkInf.targetName) + ) + + // Get priceable info + await tkInf.nonFiatTokenCollateral.refresh() + expect(await tkInf.nonFiatTokenCollateral.refPerTok()).to.equal(fp('1')) + expect(await tkInf.nonFiatTokenCollateral.targetPerRef()).to.equal(fp('1')) + + // ref price approx 1.00062 + await expectPrice( + tkInf.nonFiatTokenCollateral.address, + tkInf.targetPrice.mul(tkInf.refPrice).div(BN_SCALE_FACTOR), + ORACLE_ERROR, + true, + bn('1e10') + ) + + await expect(tkInf.nonFiatTokenCollateral.claimRewards()).to.not.emit( + tkInf.nonFiatTokenCollateral, + 'RewardsClaimed' + ) + + expect(await tkInf.nonFiatTokenCollateral.maxTradeVolume()).to.equal( + config.rTokenMaxTradeVolume + ) + } + }) - await expectPrice( - atkInf.aTokenCollateral.address, - atkInf.pegPrice.mul(atkInf.refPerTok).div(BN_SCALE_FACTOR), - ORACLE_ERROR, - true, - bn('1e5') - ) + it('Should setup collateral correctly - CTokens Non-Fiat', async () => { + // Define interface required for each ctoken + interface CTokenInfo { + token: ERC20Mock + tokenAddress: string + cToken: CTokenMock + cTokenVault: CTokenVault + cTokenAddress: string + cTokenCollateral: CTokenNonFiatCollateral + targetPrice: BigNumber + refPrice: BigNumber + refPerTok: BigNumber + targetName: string + } - // TODO: deprecate - await expect(atkInf.aTokenCollateral.claimRewards()) - .to.emit(atkInf.stataToken, 'RewardsClaimed') - .withArgs(aaveToken.address, 0) + // Compound - cWBTC + const cTokenInfos: CTokenInfo[] = [ + { + token: wbtc, + tokenAddress: networkConfig[chainId].tokens.WBTC || '', + cToken: cWBTC, + cTokenVault: cWBTCVault, + cTokenAddress: networkConfig[chainId].tokens.cWBTC || '', + cTokenCollateral: cWBTCCollateral, + targetPrice: fp('31311.5'), // approx price June 6, 2022 + refPrice: fp('1.00062735'), // approx price wbtc-btc + refPerTok: fp('0.020065932066404677'), // for wbtc on June 2022 + targetName: 'BTC', + }, + ] - await expect(atkInf.stataToken['claimRewards()']()) - .to.emit(atkInf.stataToken, 'RewardsClaimed') - .withArgs(aaveToken.address, 0) + for (const ctkInf of cTokenInfos) { + // CToken + expect(await ctkInf.cTokenCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await ctkInf.cTokenCollateral.isCollateral()).to.equal(true) + expect(await ctkInf.cTokenCollateral.referenceERC20Decimals()).to.equal( + await ctkInf.token.decimals() + ) + expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) + expect(await ctkInf.cTokenVault.asset()).to.equal(ctkInf.cTokenAddress) + expect(await ctkInf.cToken.decimals()).to.equal(8) + expect(await ctkInf.cTokenVault.decimals()).to.equal(17) + expect(await ctkInf.cTokenCollateral.targetName()).to.equal( + ethers.utils.formatBytes32String(ctkInf.targetName) + ) + expect(await ctkInf.cTokenCollateral.refPerTok()).to.be.closeTo( + ctkInf.refPerTok, + fp('0.001') + ) + expect(await ctkInf.cTokenCollateral.targetPerRef()).to.equal(fp('1')) + + expect(await ctkInf.cTokenCollateral.exposedReferencePrice()).to.equal( + await ctkInf.cTokenCollateral.refPerTok() + ) + + // close to $633 usd + await expectPrice( + ctkInf.cTokenCollateral.address, + ctkInf.targetPrice + .mul(ctkInf.refPrice) + .mul(await ctkInf.cTokenCollateral.refPerTok()) + .div(BN_SCALE_FACTOR.pow(2)), + ORACLE_ERROR, + true + ) + + // TODO: deprecate + await expect(ctkInf.cTokenCollateral.claimRewards()) + .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') + .withArgs(compToken.address, 0) + + await expect(ctkInf.cTokenVault.claimRewards()) + .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') + .withArgs(compToken.address, 0) + + expect(await ctkInf.cTokenCollateral.maxTradeVolume()).to.equal( + config.rTokenMaxTradeVolume + ) + } + }) - // Check StaticAToken - expect(await atkInf.stataToken.name()).to.equal( - 'Static Aave interest bearing ' + (await atkInf.token.symbol()) - ) - expect(await atkInf.stataToken.symbol()).to.equal('stata' + (await atkInf.token.symbol())) - expect(await atkInf.stataToken.decimals()).to.equal(await atkInf.token.decimals()) - expect(await atkInf.stataToken.LENDING_POOL()).to.equal( - networkConfig[chainId].AAVE_LENDING_POOL - ) - expect(await atkInf.stataToken.INCENTIVES_CONTROLLER()).to.equal( - networkConfig[chainId].AAVE_INCENTIVES - ) - expect(await atkInf.stataToken.ATOKEN()).to.equal(atkInf.aToken.address) - expect(await atkInf.stataToken.ATOKEN()).to.equal(atkInf.aTokenAddress) - expect(await atkInf.stataToken.ASSET()).to.equal(atkInf.token.address) - expect(await atkInf.stataToken.ASSET()).to.equal(atkInf.tokenAddress) - expect(await atkInf.stataToken.REWARD_TOKEN()).to.equal(aaveToken.address) + it('Should setup collateral correctly - Self-Referential', async () => { + // Define interface required for each self-referential coin + interface TokenInfo { + selfRefToken: ERC20Mock | WETH9 + selfRefTokenDecimals: number + selfRefTokenAddress: string + selfRefTokenCollateral: FiatCollateral + price: BigNumber + targetName: string + } - expect(await atkInf.aTokenCollateral.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) - } - }) + // WBTC + const tokenInfos: TokenInfo[] = [ + { + selfRefToken: weth, + selfRefTokenDecimals: 18, + selfRefTokenAddress: networkConfig[chainId].tokens.WETH || '', + selfRefTokenCollateral: wethCollateral, + price: fp('1859.17'), //approx price June 2022 + targetName: 'ETH', + }, + ] - it('Should setup collateral correctly - Non-Fiatcoins', async () => { - // Define interface required for each non-fiat coin - interface TokenInfo { - nonFiatToken: ERC20Mock - nonFiatTokenDecimals: number - nonFiatTokenAddress: string - nonFiatTokenCollateral: NonFiatCollateral - targetPrice: BigNumber - refPrice: BigNumber - targetName: string - } + for (const tkInf of tokenInfos) { + // Non-Fiat Token Assets + expect(await tkInf.selfRefTokenCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await tkInf.selfRefTokenCollateral.isCollateral()).to.equal(true) + expect(await tkInf.selfRefTokenCollateral.erc20()).to.equal(tkInf.selfRefToken.address) + expect(await tkInf.selfRefTokenCollateral.erc20()).to.equal(tkInf.selfRefTokenAddress) + expect(await tkInf.selfRefToken.decimals()).to.equal(tkInf.selfRefTokenDecimals) + expect(await tkInf.selfRefTokenCollateral.targetName()).to.equal( + ethers.utils.formatBytes32String(tkInf.targetName) + ) + // Get priceable info + await tkInf.selfRefTokenCollateral.refresh() + expect(await tkInf.selfRefTokenCollateral.refPerTok()).to.equal(fp('1')) + expect(await tkInf.selfRefTokenCollateral.targetPerRef()).to.equal(fp('1')) + + await expectPrice(tkInf.selfRefTokenCollateral.address, tkInf.price, ORACLE_ERROR, true) + + await expect(tkInf.selfRefTokenCollateral.claimRewards()).to.not.emit( + tkInf.selfRefTokenCollateral, + 'RewardsClaimed' + ) + expect(await tkInf.selfRefTokenCollateral.maxTradeVolume()).to.equal( + config.rTokenMaxTradeVolume + ) + } + }) - // WBTC - const tokenInfos: TokenInfo[] = [ - { - nonFiatToken: wbtc, - nonFiatTokenDecimals: 8, - nonFiatTokenAddress: networkConfig[chainId].tokens.WBTC || '', - nonFiatTokenCollateral: wbtcCollateral, - targetPrice: fp('31311.5'), // approx price June 6, 2022 - refPrice: fp('1.00062735'), // approx price wbtc-btc - targetName: 'BTC', - }, - ] - - for (const tkInf of tokenInfos) { - // Non-Fiat Token Assets - expect(await tkInf.nonFiatTokenCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await tkInf.nonFiatTokenCollateral.isCollateral()).to.equal(true) - expect(await tkInf.nonFiatTokenCollateral.erc20()).to.equal(tkInf.nonFiatToken.address) - expect(await tkInf.nonFiatTokenCollateral.erc20()).to.equal(tkInf.nonFiatTokenAddress) - expect(await tkInf.nonFiatToken.decimals()).to.equal(tkInf.nonFiatTokenDecimals) - expect(await tkInf.nonFiatTokenCollateral.targetName()).to.equal( - ethers.utils.formatBytes32String(tkInf.targetName) - ) + it('Should setup collateral correctly - CTokens Self-Referential', async () => { + // Define interface required for each ctoken + interface CTokenInfo { + token: ERC20Mock + tokenAddress: string + cToken: CTokenMock + cTokenVault: CTokenVault + cTokenAddress: string + cTokenCollateral: CTokenSelfReferentialCollateral + price: BigNumber + refPerTok: BigNumber + targetName: string + } - // Get priceable info - await tkInf.nonFiatTokenCollateral.refresh() - expect(await tkInf.nonFiatTokenCollateral.refPerTok()).to.equal(fp('1')) - expect(await tkInf.nonFiatTokenCollateral.targetPerRef()).to.equal(fp('1')) + // Compound - cUSDC and cUSDT + const cTokenInfos: CTokenInfo[] = [ + { + token: weth, + tokenAddress: networkConfig[chainId].tokens.WETH || '', + cToken: cETH, + cTokenVault: cETHVault, + cTokenAddress: networkConfig[chainId].tokens.cETH || '', + cTokenCollateral: cETHCollateral, + price: fp('1859.17'), // approx price June 6, 2022 + refPerTok: fp('0.020064224962890636'), // for weth on June 2022 + targetName: 'ETH', + }, + ] - // ref price approx 1.00062 - await expectPrice( - tkInf.nonFiatTokenCollateral.address, - tkInf.targetPrice.mul(tkInf.refPrice).div(BN_SCALE_FACTOR), - ORACLE_ERROR, - true, - bn('1e10') - ) + for (const ctkInf of cTokenInfos) { + // CToken + expect(await ctkInf.cTokenCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await ctkInf.cTokenCollateral.isCollateral()).to.equal(true) + expect(await ctkInf.cTokenCollateral.referenceERC20Decimals()).to.equal( + await ctkInf.token.decimals() + ) + expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) + expect(await ctkInf.cTokenVault.asset()).to.equal(ctkInf.cTokenAddress) + expect(await ctkInf.cToken.decimals()).to.equal(8) + expect(await ctkInf.cTokenVault.decimals()).to.equal(17) + expect(await ctkInf.cTokenCollateral.targetName()).to.equal( + ethers.utils.formatBytes32String(ctkInf.targetName) + ) + + expect(await ctkInf.cTokenCollateral.refPerTok()).to.be.closeTo( + ctkInf.refPerTok, + fp('0.001') + ) + expect(await ctkInf.cTokenCollateral.targetPerRef()).to.equal(fp('1')) + expect(await ctkInf.cTokenCollateral.exposedReferencePrice()).to.equal( + await ctkInf.cTokenCollateral.refPerTok() + ) + + await expectPrice( + ctkInf.cTokenCollateral.address, + ctkInf.price.mul(ctkInf.refPerTok).div(BN_SCALE_FACTOR), + ORACLE_ERROR, + true, + bn('1e5') + ) + + // TODO: deprecate + await expect(ctkInf.cTokenCollateral.claimRewards()) + .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') + .withArgs(compToken.address, 0) + + await expect(ctkInf.cTokenVault.claimRewards()) + .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') + .withArgs(compToken.address, 0) + + expect(await ctkInf.cTokenCollateral.maxTradeVolume()).to.equal( + config.rTokenMaxTradeVolume + ) + } + }) - await expect(tkInf.nonFiatTokenCollateral.claimRewards()).to.not.emit( - tkInf.nonFiatTokenCollateral, - 'RewardsClaimed' - ) + it('Should setup collateral correctly - EUR Fiatcoins', async () => { + // Define interface required for each Eur-fiat coin + interface TokenInfo { + eurFiatToken: ERC20Mock + eurFiatTokenDecimals: number + eurFiatTokenAddress: string + eurFiatTokenCollateral: EURFiatCollateral + targetPrice: BigNumber + refPrice: BigNumber + targetName: string + } - expect(await tkInf.nonFiatTokenCollateral.maxTradeVolume()).to.equal( - config.rTokenMaxTradeVolume - ) - } - }) + // EURT + const tokenInfos: TokenInfo[] = [ + { + eurFiatToken: eurt, + eurFiatTokenDecimals: 6, + eurFiatTokenAddress: networkConfig[chainId].tokens.EURT || '', + eurFiatTokenCollateral: eurtCollateral, + targetPrice: fp('1.07025'), // approx price EUR-USD June 6, 2022 + refPrice: fp('1.073'), // approx price EURT-USD June 6, 2022 + targetName: 'EUR', + }, + ] - it('Should setup collateral correctly - CTokens Non-Fiat', async () => { - // Define interface required for each ctoken - interface CTokenInfo { - token: ERC20Mock - tokenAddress: string - cToken: CTokenMock - cTokenVault: CTokenVault - cTokenAddress: string - cTokenCollateral: CTokenNonFiatCollateral - targetPrice: BigNumber - refPrice: BigNumber - refPerTok: BigNumber - targetName: string - } + for (const tkInf of tokenInfos) { + // Non-Fiat Token Assets + expect(await tkInf.eurFiatTokenCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await tkInf.eurFiatTokenCollateral.isCollateral()).to.equal(true) + expect(await tkInf.eurFiatTokenCollateral.erc20()).to.equal(tkInf.eurFiatToken.address) + expect(await tkInf.eurFiatTokenCollateral.erc20()).to.equal(tkInf.eurFiatTokenAddress) + expect(await tkInf.eurFiatToken.decimals()).to.equal(tkInf.eurFiatTokenDecimals) + expect(await tkInf.eurFiatTokenCollateral.targetName()).to.equal( + ethers.utils.formatBytes32String(tkInf.targetName) + ) + + // Get priceable info + await tkInf.eurFiatTokenCollateral.refresh() + expect(await tkInf.eurFiatTokenCollateral.refPerTok()).to.equal(fp('1')) + expect(await tkInf.eurFiatTokenCollateral.targetPerRef()).to.equal(fp('1')) + + // ref price approx 1.07 + await expectPrice( + tkInf.eurFiatTokenCollateral.address, + tkInf.refPrice, + ORACLE_ERROR, + true + ) + + await expect(tkInf.eurFiatTokenCollateral.claimRewards()).to.not.emit( + tkInf.eurFiatTokenCollateral, + 'RewardsClaimed' + ) + + expect(await tkInf.eurFiatTokenCollateral.maxTradeVolume()).to.equal( + config.rTokenMaxTradeVolume + ) + } + }) - // Compound - cWBTC - const cTokenInfos: CTokenInfo[] = [ - { - token: wbtc, - tokenAddress: networkConfig[chainId].tokens.WBTC || '', - cToken: cWBTC, - cTokenVault: cWBTCVault, - cTokenAddress: networkConfig[chainId].tokens.cWBTC || '', - cTokenCollateral: cWBTCCollateral, - targetPrice: fp('31311.5'), // approx price June 6, 2022 - refPrice: fp('1.00062735'), // approx price wbtc-btc - refPerTok: fp('0.020065932066404677'), // for wbtc on June 2022 - targetName: 'BTC', - }, - ] - - for (const ctkInf of cTokenInfos) { - // CToken - expect(await ctkInf.cTokenCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await ctkInf.cTokenCollateral.isCollateral()).to.equal(true) - expect(await ctkInf.cTokenCollateral.referenceERC20Decimals()).to.equal( - await ctkInf.token.decimals() + it('Should handle invalid/stale Price - Assets', async () => { + await advanceTime(ORACLE_TIMEOUT.toString()) + + // Stale Oracle + await expectUnpriced(compAsset.address) + await expectUnpriced(aaveAsset.address) + + // Setup Assets with no price feed + const nonpriceAsset: Asset = ( + await ( + await ethers.getContractFactory('Asset') + ).deploy( + PRICE_TIMEOUT, + NO_PRICE_DATA_FEED, + ORACLE_ERROR, + networkConfig[chainId].tokens.stkAAVE || '', + config.rTokenMaxTradeVolume, + MAX_ORACLE_TIMEOUT + ) ) - expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) - expect(await ctkInf.cTokenVault.asset()).to.equal(ctkInf.cTokenAddress) - expect(await ctkInf.cToken.decimals()).to.equal(8) - expect(await ctkInf.cTokenVault.decimals()).to.equal(17) - expect(await ctkInf.cTokenCollateral.targetName()).to.equal( - ethers.utils.formatBytes32String(ctkInf.targetName) + // Assets with invalid feed - revert + await expect(nonpriceAsset.price()).to.be.reverted + + // With a feed with zero price + const zeroPriceAsset: Asset = ( + await ( + await ethers.getContractFactory('Asset') + ).deploy( + PRICE_TIMEOUT, + mockChainlinkFeed.address, + ORACLE_ERROR, + networkConfig[chainId].tokens.stkAAVE || '', + config.rTokenMaxTradeVolume, + MAX_ORACLE_TIMEOUT + ) ) - expect(await ctkInf.cTokenCollateral.refPerTok()).to.be.closeTo( - ctkInf.refPerTok, - fp('0.001') - ) - expect(await ctkInf.cTokenCollateral.targetPerRef()).to.equal(fp('1')) - expect(await ctkInf.cTokenCollateral.exposedReferencePrice()).to.equal( - await ctkInf.cTokenCollateral.refPerTok() - ) + await setOraclePrice(zeroPriceAsset.address, bn(0)) - // close to $633 usd - await expectPrice( - ctkInf.cTokenCollateral.address, - ctkInf.targetPrice - .mul(ctkInf.refPrice) - .mul(await ctkInf.cTokenCollateral.refPerTok()) - .div(BN_SCALE_FACTOR.pow(2)), - ORACLE_ERROR, - true - ) + // Zero price + await expectPrice(zeroPriceAsset.address, bn('0'), bn('0'), false) + }) - // TODO: deprecate - await expect(ctkInf.cTokenCollateral.claimRewards()) - .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') - .withArgs(compToken.address, 0) + it('Should handle invalid/stale Price - Collateral - Fiat', async () => { + // Does not revert with stale price + await advanceTime(ORACLE_TIMEOUT.toString()) + + await expectUnpriced(daiCollateral.address) + await expectUnpriced(usdcCollateral.address) + await expectUnpriced(usdtCollateral.address) + await expectUnpriced(busdCollateral.address) + await expectUnpriced(usdpCollateral.address) + await expectUnpriced(tusdCollateral.address) + + // Refresh should mark status IFFY + await daiCollateral.refresh() + await usdcCollateral.refresh() + await usdtCollateral.refresh() + await busdCollateral.refresh() + await usdpCollateral.refresh() + await tusdCollateral.refresh() + expect(await daiCollateral.status()).to.equal(CollateralStatus.IFFY) + expect(await usdcCollateral.status()).to.equal(CollateralStatus.IFFY) + expect(await usdtCollateral.status()).to.equal(CollateralStatus.IFFY) + expect(await busdCollateral.status()).to.equal(CollateralStatus.IFFY) + expect(await usdpCollateral.status()).to.equal(CollateralStatus.IFFY) + expect(await tusdCollateral.status()).to.equal(CollateralStatus.IFFY) + + const defaultThreshold = fp('0.01') // 1% + const delayUntilDefault = bn('86400') // 24h + + // Non price Fiat collateral + const nonPriceCollateral: FiatCollateral = await ( + await ethers.getContractFactory('FiatCollateral') + ).deploy({ + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: NO_PRICE_DATA_FEED, + oracleError: ORACLE_ERROR, + erc20: dai.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: MAX_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold, + delayUntilDefault, + }) - await expect(ctkInf.cTokenVault.claimRewards()) - .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') - .withArgs(compToken.address, 0) + // Collateral with no price should revert + await expect(nonPriceCollateral.price()).to.be.reverted - expect(await ctkInf.cTokenCollateral.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) - } - }) + // Refresh should also revert - status is not modified + await expect(nonPriceCollateral.refresh()).to.be.reverted + expect(await nonPriceCollateral.status()).to.equal(CollateralStatus.SOUND) - it('Should setup collateral correctly - Self-Referential', async () => { - // Define interface required for each self-referential coin - interface TokenInfo { - selfRefToken: ERC20Mock | WETH9 - selfRefTokenDecimals: number - selfRefTokenAddress: string - selfRefTokenCollateral: FiatCollateral - price: BigNumber - targetName: string - } + // feed with zero price - does not revert + const zeroFiatCollateral: FiatCollateral = await ( + await ethers.getContractFactory('FiatCollateral') + ).deploy({ + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: dai.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: MAX_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold, + delayUntilDefault, + }) + await zeroFiatCollateral.refresh() - // WBTC - const tokenInfos: TokenInfo[] = [ - { - selfRefToken: weth, - selfRefTokenDecimals: 18, - selfRefTokenAddress: networkConfig[chainId].tokens.WETH || '', - selfRefTokenCollateral: wethCollateral, - price: fp('1859.17'), //approx price June 2022 - targetName: 'ETH', - }, - ] - - for (const tkInf of tokenInfos) { - // Non-Fiat Token Assets - expect(await tkInf.selfRefTokenCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await tkInf.selfRefTokenCollateral.isCollateral()).to.equal(true) - expect(await tkInf.selfRefTokenCollateral.erc20()).to.equal(tkInf.selfRefToken.address) - expect(await tkInf.selfRefTokenCollateral.erc20()).to.equal(tkInf.selfRefTokenAddress) - expect(await tkInf.selfRefToken.decimals()).to.equal(tkInf.selfRefTokenDecimals) - expect(await tkInf.selfRefTokenCollateral.targetName()).to.equal( - ethers.utils.formatBytes32String(tkInf.targetName) - ) - // Get priceable info - await tkInf.selfRefTokenCollateral.refresh() - expect(await tkInf.selfRefTokenCollateral.refPerTok()).to.equal(fp('1')) - expect(await tkInf.selfRefTokenCollateral.targetPerRef()).to.equal(fp('1')) + await setOraclePrice(zeroFiatCollateral.address, bn(0)) - await expectPrice(tkInf.selfRefTokenCollateral.address, tkInf.price, ORACLE_ERROR, true) + // With zero price + await expectPrice(zeroFiatCollateral.address, bn('0'), bn('0'), false) - await expect(tkInf.selfRefTokenCollateral.claimRewards()).to.not.emit( - tkInf.selfRefTokenCollateral, - 'RewardsClaimed' - ) - expect(await tkInf.selfRefTokenCollateral.maxTradeVolume()).to.equal( - config.rTokenMaxTradeVolume + // Refresh should mark status IFFY + await zeroFiatCollateral.refresh() + expect(await zeroFiatCollateral.status()).to.equal(CollateralStatus.IFFY) + }) + + it('Should handle invalid/stale Price - Collateral - CTokens Fiat', async () => { + expect(await cDaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await cUsdcCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await cUsdtCollateral.status()).to.equal(CollateralStatus.SOUND) + + // Does not revert with stale price + await advanceTime(ORACLE_TIMEOUT.toString()) + + // Compound + await expectUnpriced(cDaiCollateral.address) + await expectUnpriced(cUsdcCollateral.address) + await expectUnpriced(cUsdtCollateral.address) + + // Refresh should mark status IFFY + await cDaiCollateral.refresh() + await cUsdcCollateral.refresh() + await cUsdtCollateral.refresh() + expect(await cDaiCollateral.status()).to.equal(CollateralStatus.IFFY) + expect(await cUsdcCollateral.status()).to.equal(CollateralStatus.IFFY) + expect(await cUsdtCollateral.status()).to.equal(CollateralStatus.IFFY) + + const defaultThreshold = fp('0.01') // 1% + const delayUntilDefault = bn('86400') // 24h + + // CTokens Collateral with no price + const nonpriceCtokenCollateral: CTokenFiatCollateral = await ( + await ethers.getContractFactory('CTokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: NO_PRICE_DATA_FEED, + oracleError: ORACLE_ERROR, + erc20: cDaiVault.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: MAX_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold, + delayUntilDefault, + }, + REVENUE_HIDING ) - } - }) + // CTokens - Collateral with no price info should revert + await expect(nonpriceCtokenCollateral.price()).to.be.reverted - it('Should setup collateral correctly - CTokens Self-Referential', async () => { - // Define interface required for each ctoken - interface CTokenInfo { - token: ERC20Mock - tokenAddress: string - cToken: CTokenMock - cTokenVault: CTokenVault - cTokenAddress: string - cTokenCollateral: CTokenSelfReferentialCollateral - price: BigNumber - refPerTok: BigNumber - targetName: string - } + // Refresh should also revert - status is not modified + await expect(nonpriceCtokenCollateral.refresh()).to.be.reverted + expect(await nonpriceCtokenCollateral.status()).to.equal(CollateralStatus.SOUND) - // Compound - cUSDC and cUSDT - const cTokenInfos: CTokenInfo[] = [ - { - token: weth, - tokenAddress: networkConfig[chainId].tokens.WETH || '', - cToken: cETH, - cTokenVault: cETHVault, - cTokenAddress: networkConfig[chainId].tokens.cETH || '', - cTokenCollateral: cETHCollateral, - price: fp('1859.17'), // approx price June 6, 2022 - refPerTok: fp('0.020064224962890636'), // for weth on June 2022 - targetName: 'ETH', - }, - ] - - for (const ctkInf of cTokenInfos) { - // CToken - expect(await ctkInf.cTokenCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await ctkInf.cTokenCollateral.isCollateral()).to.equal(true) - expect(await ctkInf.cTokenCollateral.referenceERC20Decimals()).to.equal( - await ctkInf.token.decimals() - ) - expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) - expect(await ctkInf.cTokenVault.asset()).to.equal(ctkInf.cTokenAddress) - expect(await ctkInf.cToken.decimals()).to.equal(8) - expect(await ctkInf.cTokenVault.decimals()).to.equal(17) - expect(await ctkInf.cTokenCollateral.targetName()).to.equal( - ethers.utils.formatBytes32String(ctkInf.targetName) + // Does not revert with a feed with zero price + const zeropriceCtokenCollateral: CTokenFiatCollateral = await ( + await ethers.getContractFactory('CTokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: cDaiVault.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: MAX_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold, + delayUntilDefault, + }, + REVENUE_HIDING ) + await zeropriceCtokenCollateral.refresh() - expect(await ctkInf.cTokenCollateral.refPerTok()).to.be.closeTo( - ctkInf.refPerTok, - fp('0.001') - ) - expect(await ctkInf.cTokenCollateral.targetPerRef()).to.equal(fp('1')) - expect(await ctkInf.cTokenCollateral.exposedReferencePrice()).to.equal( - await ctkInf.cTokenCollateral.refPerTok() - ) + await setOraclePrice(zeropriceCtokenCollateral.address, bn(0)) - await expectPrice( - ctkInf.cTokenCollateral.address, - ctkInf.price.mul(ctkInf.refPerTok).div(BN_SCALE_FACTOR), - ORACLE_ERROR, - true, - bn('1e5') - ) + // With zero price + await expectPrice(zeropriceCtokenCollateral.address, bn('0'), bn('0'), false) - // TODO: deprecate - await expect(ctkInf.cTokenCollateral.claimRewards()) - .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') - .withArgs(compToken.address, 0) + // Refresh should mark status IFFY + await zeropriceCtokenCollateral.refresh() + expect(await zeropriceCtokenCollateral.status()).to.equal(CollateralStatus.IFFY) + }) - await expect(ctkInf.cTokenVault.claimRewards()) - .to.emit(ctkInf.cTokenVault, 'RewardsClaimed') - .withArgs(compToken.address, 0) + it('Should handle invalid/stale Price - Collateral - ATokens Fiat', async () => { + // Does not revert with stale price + await advanceTime(ORACLE_TIMEOUT.toString()) + + // Aave + await expectUnpriced(aDaiCollateral.address) + await expectUnpriced(aUsdcCollateral.address) + await expectUnpriced(aUsdtCollateral.address) + await expectUnpriced(aBusdCollateral.address) + + // Refresh should mark status IFFY + await aDaiCollateral.refresh() + await aUsdcCollateral.refresh() + await aUsdtCollateral.refresh() + await aBusdCollateral.refresh() + expect(await aDaiCollateral.status()).to.equal(CollateralStatus.IFFY) + expect(await aUsdcCollateral.status()).to.equal(CollateralStatus.IFFY) + expect(await aUsdtCollateral.status()).to.equal(CollateralStatus.IFFY) + expect(await aBusdCollateral.status()).to.equal(CollateralStatus.IFFY) + + const defaultThreshold = fp('0.01') // 1% + const delayUntilDefault = bn('86400') // 24h + + // AToken collateral with no price + const nonpriceAtokenCollateral: ATokenFiatCollateral = await ( + await ethers.getContractFactory('ATokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: NO_PRICE_DATA_FEED, + oracleError: ORACLE_ERROR, + erc20: stataDai.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: MAX_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold, + delayUntilDefault, + }, + REVENUE_HIDING + ) - expect(await ctkInf.cTokenCollateral.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) - } - }) + // ATokens - Collateral with no price info should revert + await expect(nonpriceAtokenCollateral.price()).to.be.reverted - it('Should setup collateral correctly - EUR Fiatcoins', async () => { - // Define interface required for each Eur-fiat coin - interface TokenInfo { - eurFiatToken: ERC20Mock - eurFiatTokenDecimals: number - eurFiatTokenAddress: string - eurFiatTokenCollateral: EURFiatCollateral - targetPrice: BigNumber - refPrice: BigNumber - targetName: string - } + // Refresh should also revert - status is not modified + await expect(nonpriceAtokenCollateral.refresh()).to.be.reverted + expect(await nonpriceAtokenCollateral.status()).to.equal(CollateralStatus.SOUND) - // EURT - const tokenInfos: TokenInfo[] = [ - { - eurFiatToken: eurt, - eurFiatTokenDecimals: 6, - eurFiatTokenAddress: networkConfig[chainId].tokens.EURT || '', - eurFiatTokenCollateral: eurtCollateral, - targetPrice: fp('1.07025'), // approx price EUR-USD June 6, 2022 - refPrice: fp('1.073'), // approx price EURT-USD June 6, 2022 - targetName: 'EUR', - }, - ] - - for (const tkInf of tokenInfos) { - // Non-Fiat Token Assets - expect(await tkInf.eurFiatTokenCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await tkInf.eurFiatTokenCollateral.isCollateral()).to.equal(true) - expect(await tkInf.eurFiatTokenCollateral.erc20()).to.equal(tkInf.eurFiatToken.address) - expect(await tkInf.eurFiatTokenCollateral.erc20()).to.equal(tkInf.eurFiatTokenAddress) - expect(await tkInf.eurFiatToken.decimals()).to.equal(tkInf.eurFiatTokenDecimals) - expect(await tkInf.eurFiatTokenCollateral.targetName()).to.equal( - ethers.utils.formatBytes32String(tkInf.targetName) + // Does not revert with a feed with zero price + const zeroPriceAtokenCollateral: ATokenFiatCollateral = await ( + await ethers.getContractFactory('ATokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: stataDai.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: MAX_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold, + delayUntilDefault, + }, + REVENUE_HIDING ) + await zeroPriceAtokenCollateral.refresh() - // Get priceable info - await tkInf.eurFiatTokenCollateral.refresh() - expect(await tkInf.eurFiatTokenCollateral.refPerTok()).to.equal(fp('1')) - expect(await tkInf.eurFiatTokenCollateral.targetPerRef()).to.equal(fp('1')) + await setOraclePrice(zeroPriceAtokenCollateral.address, bn(0)) - // ref price approx 1.07 - await expectPrice(tkInf.eurFiatTokenCollateral.address, tkInf.refPrice, ORACLE_ERROR, true) + // With zero price + await expectPrice(zeroPriceAtokenCollateral.address, bn('0'), bn('0'), false) - await expect(tkInf.eurFiatTokenCollateral.claimRewards()).to.not.emit( - tkInf.eurFiatTokenCollateral, - 'RewardsClaimed' - ) + // Refresh should mark status IFFY + await zeroPriceAtokenCollateral.refresh() + expect(await zeroPriceAtokenCollateral.status()).to.equal(CollateralStatus.IFFY) + }) - expect(await tkInf.eurFiatTokenCollateral.maxTradeVolume()).to.equal( - config.rTokenMaxTradeVolume - ) - } - }) + it('Should handle invalid/stale Price - Collateral - Non-Fiatcoins', async () => { + // Does not revert with stale price + await advanceTime(ORACLE_TIMEOUT.toString()) - it('Should handle invalid/stale Price - Assets', async () => { - await advanceTime(ORACLE_TIMEOUT.toString()) + // Aave + await expectUnpriced(wbtcCollateral.address) - // Stale Oracle - await expectUnpriced(compAsset.address) - await expectUnpriced(aaveAsset.address) + await wbtcCollateral.refresh() + expect(await wbtcCollateral.status()).to.equal(CollateralStatus.IFFY) - // Setup Assets with no price feed - const nonpriceAsset: Asset = ( - await ( - await ethers.getContractFactory('Asset') + const defaultThreshold = fp('0.01') // 1% + const delayUntilDefault = bn('86400') // 24h + + // Non-Fiat collateral with no price + const nonpriceNonFiatCollateral: NonFiatCollateral = await ( + await ethers.getContractFactory('NonFiatCollateral') ).deploy( - PRICE_TIMEOUT, + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: NO_PRICE_DATA_FEED, + oracleError: ORACLE_ERROR, + erc20: wbtc.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: MAX_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('BTC'), + defaultThreshold, + delayUntilDefault, + }, NO_PRICE_DATA_FEED, - ORACLE_ERROR, - networkConfig[chainId].tokens.stkAAVE || '', - config.rTokenMaxTradeVolume, MAX_ORACLE_TIMEOUT ) - ) - // Assets with invalid feed - revert - await expect(nonpriceAsset.price()).to.be.reverted - // With a feed with zero price - const zeroPriceAsset: Asset = ( - await ( - await ethers.getContractFactory('Asset') + // Non-fiat Collateral with no price should revert + await expect(nonpriceNonFiatCollateral.price()).to.be.reverted + + // Refresh should also revert - status is not modified + await expect(nonpriceNonFiatCollateral.refresh()).to.be.reverted + expect(await nonpriceNonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) + + // Non-Fiat collateral with zero price + const zeroPriceNonFiatCollateral: NonFiatCollateral = await ( + await ethers.getContractFactory('NonFiatCollateral') ).deploy( - PRICE_TIMEOUT, + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: wbtc.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: MAX_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('BTC'), + defaultThreshold, + delayUntilDefault, + }, mockChainlinkFeed.address, - ORACLE_ERROR, - networkConfig[chainId].tokens.stkAAVE || '', - config.rTokenMaxTradeVolume, MAX_ORACLE_TIMEOUT ) - ) - - await setOraclePrice(zeroPriceAsset.address, bn(0)) + await zeroPriceNonFiatCollateral.refresh() - // Zero price - await expectPrice(zeroPriceAsset.address, bn('0'), bn('0'), false) - }) + // Set price = 0 + const chainlinkFeedAddr = await zeroPriceNonFiatCollateral.chainlinkFeed() + const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) + await v3Aggregator.updateAnswer(bn(0)) - it('Should handle invalid/stale Price - Collateral - Fiat', async () => { - // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) - - await expectUnpriced(daiCollateral.address) - await expectUnpriced(usdcCollateral.address) - await expectUnpriced(usdtCollateral.address) - await expectUnpriced(busdCollateral.address) - await expectUnpriced(usdpCollateral.address) - await expectUnpriced(tusdCollateral.address) - - // Refresh should mark status IFFY - await daiCollateral.refresh() - await usdcCollateral.refresh() - await usdtCollateral.refresh() - await busdCollateral.refresh() - await usdpCollateral.refresh() - await tusdCollateral.refresh() - expect(await daiCollateral.status()).to.equal(CollateralStatus.IFFY) - expect(await usdcCollateral.status()).to.equal(CollateralStatus.IFFY) - expect(await usdtCollateral.status()).to.equal(CollateralStatus.IFFY) - expect(await busdCollateral.status()).to.equal(CollateralStatus.IFFY) - expect(await usdpCollateral.status()).to.equal(CollateralStatus.IFFY) - expect(await tusdCollateral.status()).to.equal(CollateralStatus.IFFY) - - const defaultThreshold = fp('0.01') // 1% - const delayUntilDefault = bn('86400') // 24h - - // Non price Fiat collateral - const nonPriceCollateral: FiatCollateral = await ( - await ethers.getContractFactory('FiatCollateral') - ).deploy({ - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: NO_PRICE_DATA_FEED, - oracleError: ORACLE_ERROR, - erc20: dai.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold, - delayUntilDefault, - }) + // Does not revert with zero price + await expectPrice(zeroPriceNonFiatCollateral.address, bn('0'), bn('0'), false) - // Collateral with no price should revert - await expect(nonPriceCollateral.price()).to.be.reverted - - // Refresh should also revert - status is not modified - await expect(nonPriceCollateral.refresh()).to.be.reverted - expect(await nonPriceCollateral.status()).to.equal(CollateralStatus.SOUND) - - // feed with zero price - does not revert - const zeroFiatCollateral: FiatCollateral = await ( - await ethers.getContractFactory('FiatCollateral') - ).deploy({ - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: mockChainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: dai.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold, - delayUntilDefault, + // Refresh should mark status IFFY + await zeroPriceNonFiatCollateral.refresh() + expect(await zeroPriceNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) }) - await zeroFiatCollateral.refresh() - - await setOraclePrice(zeroFiatCollateral.address, bn(0)) - - // With zero price - await expectPrice(zeroFiatCollateral.address, bn('0'), bn('0'), false) - - // Refresh should mark status IFFY - await zeroFiatCollateral.refresh() - expect(await zeroFiatCollateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('Should handle invalid/stale Price - Collateral - CTokens Fiat', async () => { - expect(await cDaiCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await cUsdcCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await cUsdtCollateral.status()).to.equal(CollateralStatus.SOUND) - - // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) - - // Compound - await expectUnpriced(cDaiCollateral.address) - await expectUnpriced(cUsdcCollateral.address) - await expectUnpriced(cUsdtCollateral.address) - - // Refresh should mark status IFFY - await cDaiCollateral.refresh() - await cUsdcCollateral.refresh() - await cUsdtCollateral.refresh() - expect(await cDaiCollateral.status()).to.equal(CollateralStatus.IFFY) - expect(await cUsdcCollateral.status()).to.equal(CollateralStatus.IFFY) - expect(await cUsdtCollateral.status()).to.equal(CollateralStatus.IFFY) - - const defaultThreshold = fp('0.01') // 1% - const delayUntilDefault = bn('86400') // 24h - - // CTokens Collateral with no price - const nonpriceCtokenCollateral: CTokenFiatCollateral = await ( - await ethers.getContractFactory('CTokenFiatCollateral') - ).deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: NO_PRICE_DATA_FEED, - oracleError: ORACLE_ERROR, - erc20: cDaiVault.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold, - delayUntilDefault, - }, - REVENUE_HIDING - ) - // CTokens - Collateral with no price info should revert - await expect(nonpriceCtokenCollateral.price()).to.be.reverted - - // Refresh should also revert - status is not modified - await expect(nonpriceCtokenCollateral.refresh()).to.be.reverted - expect(await nonpriceCtokenCollateral.status()).to.equal(CollateralStatus.SOUND) - - // Does not revert with a feed with zero price - const zeropriceCtokenCollateral: CTokenFiatCollateral = await ( - await ethers.getContractFactory('CTokenFiatCollateral') - ).deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: mockChainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: cDaiVault.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold, - delayUntilDefault, - }, - REVENUE_HIDING - ) - await zeropriceCtokenCollateral.refresh() - - await setOraclePrice(zeropriceCtokenCollateral.address, bn(0)) - // With zero price - await expectPrice(zeropriceCtokenCollateral.address, bn('0'), bn('0'), false) - - // Refresh should mark status IFFY - await zeropriceCtokenCollateral.refresh() - expect(await zeropriceCtokenCollateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('Should handle invalid/stale Price - Collateral - ATokens Fiat', async () => { - // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) - - // Aave - await expectUnpriced(aDaiCollateral.address) - await expectUnpriced(aUsdcCollateral.address) - await expectUnpriced(aUsdtCollateral.address) - await expectUnpriced(aBusdCollateral.address) - - // Refresh should mark status IFFY - await aDaiCollateral.refresh() - await aUsdcCollateral.refresh() - await aUsdtCollateral.refresh() - await aBusdCollateral.refresh() - expect(await aDaiCollateral.status()).to.equal(CollateralStatus.IFFY) - expect(await aUsdcCollateral.status()).to.equal(CollateralStatus.IFFY) - expect(await aUsdtCollateral.status()).to.equal(CollateralStatus.IFFY) - expect(await aBusdCollateral.status()).to.equal(CollateralStatus.IFFY) - - const defaultThreshold = fp('0.01') // 1% - const delayUntilDefault = bn('86400') // 24h - - // AToken collateral with no price - const nonpriceAtokenCollateral: ATokenFiatCollateral = await ( - await ethers.getContractFactory('ATokenFiatCollateral') - ).deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: NO_PRICE_DATA_FEED, - oracleError: ORACLE_ERROR, - erc20: stataDai.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold, - delayUntilDefault, - }, - REVENUE_HIDING - ) - - // ATokens - Collateral with no price info should revert - await expect(nonpriceAtokenCollateral.price()).to.be.reverted - - // Refresh should also revert - status is not modified - await expect(nonpriceAtokenCollateral.refresh()).to.be.reverted - expect(await nonpriceAtokenCollateral.status()).to.equal(CollateralStatus.SOUND) + it('Should handle invalid/stale Price - Collateral - CTokens Non-Fiat', async () => { + // Does not revert with stale price + await advanceTime(ORACLE_TIMEOUT.toString()) + + // Compound + await expectUnpriced(cWBTCCollateral.address) + + // Refresh should mark status IFFY + await cWBTCCollateral.refresh() + expect(await cWBTCCollateral.status()).to.equal(CollateralStatus.IFFY) + + const defaultThreshold = fp('0.01') // 1% + const delayUntilDefault = bn('86400') // 24h + + // CTokens Collateral with no price + const nonpriceCtokenNonFiatCollateral: CTokenNonFiatCollateral = ( + await ( + await ethers.getContractFactory('CTokenNonFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: NO_PRICE_DATA_FEED, + oracleError: ORACLE_ERROR, + erc20: cWBTCVault.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: MAX_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('BTC'), + defaultThreshold, + delayUntilDefault, + }, + NO_PRICE_DATA_FEED, + MAX_ORACLE_TIMEOUT, + REVENUE_HIDING + ) + ) - // Does not revert with a feed with zero price - const zeroPriceAtokenCollateral: ATokenFiatCollateral = await ( - await ethers.getContractFactory('ATokenFiatCollateral') - ).deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: mockChainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: stataDai.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold, - delayUntilDefault, - }, - REVENUE_HIDING - ) - await zeroPriceAtokenCollateral.refresh() + // CTokens - Collateral with no price info should revert + await expect(nonpriceCtokenNonFiatCollateral.price()).to.be.reverted + + // Refresh should also revert - status is not modified + await expect(nonpriceCtokenNonFiatCollateral.refresh()).to.be.reverted + expect(await nonpriceCtokenNonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) + + // Does not revert with a feed with zero price + const zeropriceCtokenNonFiatCollateral: CTokenNonFiatCollateral = ( + await ( + await ethers.getContractFactory('CTokenNonFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: cWBTCVault.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: MAX_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('BTC'), + defaultThreshold, + delayUntilDefault, + }, + mockChainlinkFeed.address, + MAX_ORACLE_TIMEOUT, + REVENUE_HIDING + ) + ) + await zeropriceCtokenNonFiatCollateral.refresh() - await setOraclePrice(zeroPriceAtokenCollateral.address, bn(0)) + // Set price = 0 + const chainlinkFeedAddr = await zeropriceCtokenNonFiatCollateral.targetUnitChainlinkFeed() + const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) + await v3Aggregator.updateAnswer(bn(0)) - // With zero price - await expectPrice(zeroPriceAtokenCollateral.address, bn('0'), bn('0'), false) + // With zero price + await expectPrice(zeropriceCtokenNonFiatCollateral.address, bn('0'), bn('0'), false) - // Refresh should mark status IFFY - await zeroPriceAtokenCollateral.refresh() - expect(await zeroPriceAtokenCollateral.status()).to.equal(CollateralStatus.IFFY) - }) + // Refresh should mark status IFFY + await zeropriceCtokenNonFiatCollateral.refresh() + expect(await zeropriceCtokenNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) + }) - it('Should handle invalid/stale Price - Collateral - Non-Fiatcoins', async () => { - // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + it('Should handle invalid/stale Price - Collateral - Self-Referential', async () => { + const delayUntilDefault = bn('86400') // 24h - // Aave - await expectUnpriced(wbtcCollateral.address) + // Dows not revert with stale price + await advanceTime(ORACLE_TIMEOUT.toString()) - await wbtcCollateral.refresh() - expect(await wbtcCollateral.status()).to.equal(CollateralStatus.IFFY) + // Aave + await expectUnpriced(wethCollateral.address) - const defaultThreshold = fp('0.01') // 1% - const delayUntilDefault = bn('86400') // 24h + await wethCollateral.refresh() + expect(await wethCollateral.status()).to.equal(CollateralStatus.IFFY) - // Non-Fiat collateral with no price - const nonpriceNonFiatCollateral: NonFiatCollateral = await ( - await ethers.getContractFactory('NonFiatCollateral') - ).deploy( - { + // Self referential collateral with no price + const nonpriceSelfReferentialCollateral: FiatCollateral = await ( + await ethers.getContractFactory('FiatCollateral') + ).deploy({ priceTimeout: PRICE_TIMEOUT, chainlinkFeed: NO_PRICE_DATA_FEED, oracleError: ORACLE_ERROR, - erc20: wbtc.address, + erc20: weth.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('BTC'), - defaultThreshold, + targetName: ethers.utils.formatBytes32String('ETH'), + defaultThreshold: bn('0'), delayUntilDefault, - }, - NO_PRICE_DATA_FEED, - MAX_ORACLE_TIMEOUT - ) + }) - // Non-fiat Collateral with no price should revert - await expect(nonpriceNonFiatCollateral.price()).to.be.reverted + // Non-fiat Collateral with no price should revert + await expect(nonpriceSelfReferentialCollateral.price()).to.be.reverted - // Refresh should also revert - status is not modified - await expect(nonpriceNonFiatCollateral.refresh()).to.be.reverted - expect(await nonpriceNonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) + // Refresh should also revert - status is not modified + await expect(nonpriceSelfReferentialCollateral.refresh()).to.be.reverted + expect(await nonpriceSelfReferentialCollateral.status()).to.equal(CollateralStatus.SOUND) - // Non-Fiat collateral with zero price - const zeroPriceNonFiatCollateral: NonFiatCollateral = await ( - await ethers.getContractFactory('NonFiatCollateral') - ).deploy( - { + // Self referential collateral with zero price + const zeroPriceSelfReferentialCollateral: FiatCollateral = await ( + await ethers.getContractFactory('FiatCollateral') + ).deploy({ priceTimeout: PRICE_TIMEOUT, chainlinkFeed: mockChainlinkFeed.address, oracleError: ORACLE_ERROR, - erc20: wbtc.address, + erc20: weth.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('BTC'), - defaultThreshold, + targetName: ethers.utils.formatBytes32String('ETH'), + defaultThreshold: bn('0'), delayUntilDefault, - }, - mockChainlinkFeed.address, - MAX_ORACLE_TIMEOUT - ) - await zeroPriceNonFiatCollateral.refresh() + }) + await zeroPriceSelfReferentialCollateral.refresh() - // Set price = 0 - const chainlinkFeedAddr = await zeroPriceNonFiatCollateral.chainlinkFeed() - const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) - await v3Aggregator.updateAnswer(bn(0)) + // Set price = 0 + await setOraclePrice(zeroPriceSelfReferentialCollateral.address, bn(0)) - // Does not revert with zero price - await expectPrice(zeroPriceNonFiatCollateral.address, bn('0'), bn('0'), false) + // Does not revert with zero price + await expectPrice(zeroPriceSelfReferentialCollateral.address, bn('0'), bn('0'), false) - // Refresh should mark status IFFY - await zeroPriceNonFiatCollateral.refresh() - expect(await zeroPriceNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) - }) + // Refresh should mark status IFFY + await zeroPriceSelfReferentialCollateral.refresh() + expect(await zeroPriceSelfReferentialCollateral.status()).to.equal(CollateralStatus.IFFY) + }) - it('Should handle invalid/stale Price - Collateral - CTokens Non-Fiat', async () => { - // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + it('Should handle invalid/stale Price - Collateral - CTokens Self-Referential', async () => { + const delayUntilDefault = bn('86400') // 24h - // Compound - await expectUnpriced(cWBTCCollateral.address) + // Does not revert with stale price + await advanceTime(ORACLE_TIMEOUT.toString()) - // Refresh should mark status IFFY - await cWBTCCollateral.refresh() - expect(await cWBTCCollateral.status()).to.equal(CollateralStatus.IFFY) + // Compound + await expectUnpriced(cETHCollateral.address) - const defaultThreshold = fp('0.01') // 1% - const delayUntilDefault = bn('86400') // 24h + // Refresh should mark status IFFY + await cETHCollateral.refresh() + expect(await cETHCollateral.status()).to.equal(CollateralStatus.IFFY) - // CTokens Collateral with no price - const nonpriceCtokenNonFiatCollateral: CTokenNonFiatCollateral = ( - await ( - await ethers.getContractFactory('CTokenNonFiatCollateral') + // CTokens Collateral with no price + const nonpriceCtokenSelfReferentialCollateral: CTokenSelfReferentialCollateral = < + CTokenSelfReferentialCollateral + >await ( + await ethers.getContractFactory('CTokenSelfReferentialCollateral') ).deploy( { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: NO_PRICE_DATA_FEED, oracleError: ORACLE_ERROR, - erc20: cWBTCVault.address, + erc20: cETHVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('BTC'), - defaultThreshold, + targetName: ethers.utils.formatBytes32String('ETH'), + defaultThreshold: bn('0'), delayUntilDefault, }, - NO_PRICE_DATA_FEED, - MAX_ORACLE_TIMEOUT, - REVENUE_HIDING + REVENUE_HIDING, + await weth.decimals() ) - ) - // CTokens - Collateral with no price info should revert - await expect(nonpriceCtokenNonFiatCollateral.price()).to.be.reverted + // CTokens - Collateral with no price info should revert + await expect(nonpriceCtokenSelfReferentialCollateral.price()).to.be.reverted - // Refresh should also revert - status is not modified - await expect(nonpriceCtokenNonFiatCollateral.refresh()).to.be.reverted - expect(await nonpriceCtokenNonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) + // Refresh should also revert - status is not modified + await expect(nonpriceCtokenSelfReferentialCollateral.refresh()).to.be.reverted + expect(await nonpriceCtokenSelfReferentialCollateral.status()).to.equal( + CollateralStatus.SOUND + ) - // Does not revert with a feed with zero price - const zeropriceCtokenNonFiatCollateral: CTokenNonFiatCollateral = ( - await ( - await ethers.getContractFactory('CTokenNonFiatCollateral') + // Does not revert with a feed with zero price + const zeroPriceCtokenSelfReferentialCollateral: CTokenSelfReferentialCollateral = < + CTokenSelfReferentialCollateral + >await ( + await ethers.getContractFactory('CTokenSelfReferentialCollateral') ).deploy( { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: mockChainlinkFeed.address, oracleError: ORACLE_ERROR, - erc20: cWBTCVault.address, + erc20: cETHVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('BTC'), - defaultThreshold, + targetName: ethers.utils.formatBytes32String('ETH'), + defaultThreshold: bn('0'), delayUntilDefault, }, - mockChainlinkFeed.address, - MAX_ORACLE_TIMEOUT, - REVENUE_HIDING + REVENUE_HIDING, + await weth.decimals() ) - ) - await zeropriceCtokenNonFiatCollateral.refresh() - - // Set price = 0 - const chainlinkFeedAddr = await zeropriceCtokenNonFiatCollateral.targetUnitChainlinkFeed() - const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) - await v3Aggregator.updateAnswer(bn(0)) + await zeroPriceCtokenSelfReferentialCollateral.refresh() - // With zero price - await expectPrice(zeropriceCtokenNonFiatCollateral.address, bn('0'), bn('0'), false) + // Set price = 0 + await setOraclePrice(zeroPriceCtokenSelfReferentialCollateral.address, bn(0)) - // Refresh should mark status IFFY - await zeropriceCtokenNonFiatCollateral.refresh() - expect(await zeropriceCtokenNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) - }) + // With zero price + await expectPrice(zeroPriceCtokenSelfReferentialCollateral.address, bn('0'), bn('0'), false) - it('Should handle invalid/stale Price - Collateral - Self-Referential', async () => { - const delayUntilDefault = bn('86400') // 24h - - // Dows not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) - - // Aave - await expectUnpriced(wethCollateral.address) - - await wethCollateral.refresh() - expect(await wethCollateral.status()).to.equal(CollateralStatus.IFFY) - - // Self referential collateral with no price - const nonpriceSelfReferentialCollateral: FiatCollateral = await ( - await ethers.getContractFactory('FiatCollateral') - ).deploy({ - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: NO_PRICE_DATA_FEED, - oracleError: ORACLE_ERROR, - erc20: weth.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('ETH'), - defaultThreshold: bn('0'), - delayUntilDefault, + // Refresh should mark status IFFY + await zeroPriceCtokenSelfReferentialCollateral.refresh() + expect(await zeroPriceCtokenSelfReferentialCollateral.status()).to.equal( + CollateralStatus.IFFY + ) }) - // Non-fiat Collateral with no price should revert - await expect(nonpriceSelfReferentialCollateral.price()).to.be.reverted - - // Refresh should also revert - status is not modified - await expect(nonpriceSelfReferentialCollateral.refresh()).to.be.reverted - expect(await nonpriceSelfReferentialCollateral.status()).to.equal(CollateralStatus.SOUND) - - // Self referential collateral with zero price - const zeroPriceSelfReferentialCollateral: FiatCollateral = await ( - await ethers.getContractFactory('FiatCollateral') - ).deploy({ - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: mockChainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: weth.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('ETH'), - defaultThreshold: bn('0'), - delayUntilDefault, - }) - await zeroPriceSelfReferentialCollateral.refresh() + it('Should handle invalid/stale Price - Collateral - EUR Fiat', async () => { + // Does not revert with stale price + await advanceTime(ORACLE_TIMEOUT.toString()) - // Set price = 0 - await setOraclePrice(zeroPriceSelfReferentialCollateral.address, bn(0)) + await expectUnpriced(eurtCollateral.address) - // Does not revert with zero price - await expectPrice(zeroPriceSelfReferentialCollateral.address, bn('0'), bn('0'), false) + // Refresh should mark status IFFY + await eurtCollateral.refresh() - // Refresh should mark status IFFY - await zeroPriceSelfReferentialCollateral.refresh() - expect(await zeroPriceSelfReferentialCollateral.status()).to.equal(CollateralStatus.IFFY) - }) + const defaultThreshold = fp('0.01') // 1% + const delayUntilDefault = bn('86400') // 24h - it('Should handle invalid/stale Price - Collateral - CTokens Self-Referential', async () => { - const delayUntilDefault = bn('86400') // 24h + // Non price EUR Fiat collateral + const nonPriceEURCollateral: EURFiatCollateral = await ( + await ethers.getContractFactory('EURFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: NO_PRICE_DATA_FEED, + oracleError: ORACLE_ERROR, + erc20: eurt.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: MAX_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('EUR'), + defaultThreshold, + delayUntilDefault, + }, + NO_PRICE_DATA_FEED, + MAX_ORACLE_TIMEOUT + ) - // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + // Collateral with no price should revert + await expect(nonPriceEURCollateral.price()).to.be.reverted - // Compound - await expectUnpriced(cETHCollateral.address) + // Refresh should also revert - status is not modified + await expect(nonPriceEURCollateral.refresh()).to.be.reverted + expect(await nonPriceEURCollateral.status()).to.equal(CollateralStatus.SOUND) - // Refresh should mark status IFFY - await cETHCollateral.refresh() - expect(await cETHCollateral.status()).to.equal(CollateralStatus.IFFY) + // Does not revert with a feed with zero price + const invalidPriceEURCollateral: EURFiatCollateral = await ( + await ethers.getContractFactory('EURFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: eurt.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: MAX_ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('EUR'), + defaultThreshold, + delayUntilDefault, + }, + mockChainlinkFeed.address, + MAX_ORACLE_TIMEOUT + ) + await invalidPriceEURCollateral.refresh() - // CTokens Collateral with no price - const nonpriceCtokenSelfReferentialCollateral: CTokenSelfReferentialCollateral = < - CTokenSelfReferentialCollateral - >await ( - await ethers.getContractFactory('CTokenSelfReferentialCollateral') - ).deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: NO_PRICE_DATA_FEED, - oracleError: ORACLE_ERROR, - erc20: cETHVault.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('ETH'), - defaultThreshold: bn('0'), - delayUntilDefault, - }, - REVENUE_HIDING, - await weth.decimals() - ) + // Set price = 0 + const chainlinkFeedAddr = await invalidPriceEURCollateral.targetUnitChainlinkFeed() + const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) + await v3Aggregator.updateAnswer(bn(0)) - // CTokens - Collateral with no price info should revert - await expect(nonpriceCtokenSelfReferentialCollateral.price()).to.be.reverted + // With zero price + await expectUnpriced(invalidPriceEURCollateral.address) - // Refresh should also revert - status is not modified - await expect(nonpriceCtokenSelfReferentialCollateral.refresh()).to.be.reverted - expect(await nonpriceCtokenSelfReferentialCollateral.status()).to.equal( - CollateralStatus.SOUND - ) + // Refresh should mark status IFFY + await invalidPriceEURCollateral.refresh() + expect(await invalidPriceEURCollateral.status()).to.equal(CollateralStatus.IFFY) + }) - // Does not revert with a feed with zero price - const zeroPriceCtokenSelfReferentialCollateral: CTokenSelfReferentialCollateral = < - CTokenSelfReferentialCollateral - >await ( - await ethers.getContractFactory('CTokenSelfReferentialCollateral') - ).deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: mockChainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: cETHVault.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('ETH'), - defaultThreshold: bn('0'), - delayUntilDefault, - }, - REVENUE_HIDING, - await weth.decimals() - ) - await zeroPriceCtokenSelfReferentialCollateral.refresh() + it('Should register ERC20s and Assets/Collateral correctly', async () => { + // Check assets/collateral + const ERC20s = await assetRegistry.erc20s() + expect(ERC20s[0]).to.equal(rToken.address) + expect(ERC20s[1]).to.equal(rsr.address) + expect(ERC20s[2]).to.equal(aaveToken.address) + expect(ERC20s[3]).to.equal(compToken.address) + + const initialTokens: string[] = await Promise.all( + basket.map(async (c): Promise => { + return await c.erc20() + }) + ) + expect(ERC20s.slice(4)).to.eql(initialTokens) + expect(ERC20s.length).to.eql((await facade.basketTokens(rToken.address)).length + 4) + + // Assets + expect(await assetRegistry.toAsset(ERC20s[0])).to.equal(rTokenAsset.address) + expect(await assetRegistry.toAsset(ERC20s[1])).to.equal(rsrAsset.address) + expect(await assetRegistry.toAsset(ERC20s[2])).to.equal(aaveAsset.address) + expect(await assetRegistry.toAsset(ERC20s[3])).to.equal(compAsset.address) + expect(await assetRegistry.toAsset(ERC20s[4])).to.equal(daiCollateral.address) + expect(await assetRegistry.toAsset(ERC20s[5])).to.equal(aDaiCollateral.address) + expect(await assetRegistry.toAsset(ERC20s[6])).to.equal(cDaiCollateral.address) + + // Collaterals + expect(await assetRegistry.toColl(ERC20s[4])).to.equal(daiCollateral.address) + expect(await assetRegistry.toColl(ERC20s[5])).to.equal(aDaiCollateral.address) + expect(await assetRegistry.toColl(ERC20s[6])).to.equal(cDaiCollateral.address) + }) - // Set price = 0 - await setOraclePrice(zeroPriceCtokenSelfReferentialCollateral.address, bn(0)) + it('Should register simple Basket correctly', async () => { + // Basket + expect(await basketHandler.fullyCollateralized()).to.equal(true) + const backing = await facade.basketTokens(rToken.address) + expect(backing[0]).to.equal(dai.address) + expect(backing[1]).to.equal(stataDai.address) + expect(backing[2]).to.equal(cDaiVault.address) - // With zero price - await expectPrice(zeroPriceCtokenSelfReferentialCollateral.address, bn('0'), bn('0'), false) + expect(backing.length).to.equal(3) - // Refresh should mark status IFFY - await zeroPriceCtokenSelfReferentialCollateral.refresh() - expect(await zeroPriceCtokenSelfReferentialCollateral.status()).to.equal( - CollateralStatus.IFFY - ) - }) + // Check other values + expect(await basketHandler.timestamp()).to.be.gt(bn(0)) + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) + await expectPrice(basketHandler.address, fp('1'), ORACLE_ERROR, true, bn('1e5')) - it('Should handle invalid/stale Price - Collateral - EUR Fiat', async () => { - // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + // Check RToken price + const issueAmount: BigNumber = bn('10000e18') + await dai.connect(addr1).approve(rToken.address, issueAmount) + await stataDai.connect(addr1).approve(rToken.address, issueAmount) + await cDaiVault + .connect(addr1) + .approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) + await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') - await expectUnpriced(eurtCollateral.address) + await expectRTokenPrice( + rTokenAsset.address, + fp('1'), + ORACLE_ERROR, + await backingManager.maxTradeSlippage(), + config.minTradeVolume.mul((await assetRegistry.erc20s()).length) + ) + }) - // Refresh should mark status IFFY - await eurtCollateral.refresh() + it('Should issue/reedem correctly with simple basket ', async function () { + const MIN_ISSUANCE_PER_BLOCK = bn('10000e18') + const issueAmount: BigNumber = MIN_ISSUANCE_PER_BLOCK - const defaultThreshold = fp('0.01') // 1% - const delayUntilDefault = bn('86400') // 24h + // Check balances before + expect(await dai.balanceOf(backingManager.address)).to.equal(0) + expect(await stataDai.balanceOf(backingManager.address)).to.equal(0) + expect(await cDaiVault.balanceOf(backingManager.address)).to.equal(0) + expect(await dai.balanceOf(addr1.address)).to.equal(initialBal) + + // Balance for Static a Token is about 18641.55e18, about 93.21% of the provided amount (20K) + const initialBalAToken = initialBal.mul(9321).div(10000) + expect(await stataDai.balanceOf(addr1.address)).to.be.closeTo(initialBalAToken, fp('1.5')) + expect(await cDaiVault.balanceOf(addr1.address)).to.equal( + toBNDecimals(initialBal, 17).mul(100) + ) - // Non price EUR Fiat collateral - const nonPriceEURCollateral: EURFiatCollateral = await ( - await ethers.getContractFactory('EURFiatCollateral') - ).deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: NO_PRICE_DATA_FEED, - oracleError: ORACLE_ERROR, - erc20: eurt.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('EUR'), - defaultThreshold, - delayUntilDefault, - }, - NO_PRICE_DATA_FEED, - MAX_ORACLE_TIMEOUT - ) + // Provide approvals + await dai.connect(addr1).approve(rToken.address, issueAmount) + await stataDai.connect(addr1).approve(rToken.address, issueAmount) + await cDaiVault + .connect(addr1) + .approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) - // Collateral with no price should revert - await expect(nonPriceEURCollateral.price()).to.be.reverted + // Check rToken balance + expect(await rToken.balanceOf(addr1.address)).to.equal(0) + expect(await rToken.balanceOf(main.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(0) - // Refresh should also revert - status is not modified - await expect(nonPriceEURCollateral.refresh()).to.be.reverted - expect(await nonPriceEURCollateral.status()).to.equal(CollateralStatus.SOUND) + // Issue rTokens + await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') - // Does not revert with a feed with zero price - const invalidPriceEURCollateral: EURFiatCollateral = await ( - await ethers.getContractFactory('EURFiatCollateral') - ).deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: mockChainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: eurt.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('EUR'), - defaultThreshold, - delayUntilDefault, - }, - mockChainlinkFeed.address, - MAX_ORACLE_TIMEOUT - ) - await invalidPriceEURCollateral.refresh() + // Check Balances after + expect(await dai.balanceOf(backingManager.address)).to.equal(issueAmount.div(4)) // 2.5K needed (25% of basket) + const issueAmtAToken = issueAmount.div(4).mul(9321).div(10000) // approx 93.21% of 2.5K needed (25% of basket) + expect(await stataDai.balanceOf(backingManager.address)).to.be.closeTo( + issueAmtAToken, + fp('1') + ) + const requiredCTokens: BigNumber = bn('227116e17') // approx 227K needed (~5K, 50% of basket) - Price: ~0.022 + expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo( + requiredCTokens, + bn('1e17') + ) - // Set price = 0 - const chainlinkFeedAddr = await invalidPriceEURCollateral.targetUnitChainlinkFeed() - const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) - await v3Aggregator.updateAnswer(bn(0)) + // Balances for user + expect(await dai.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(4))) + expect(await stataDai.balanceOf(addr1.address)).to.be.closeTo( + initialBalAToken.sub(issueAmtAToken), + fp('1.5') + ) + expect(await cDaiVault.balanceOf(addr1.address)).to.be.closeTo( + toBNDecimals(initialBal, 17).mul(100).sub(requiredCTokens), + bn('1e17') + ) + // Check RTokens issued to user + expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) + expect(await rToken.balanceOf(main.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) - // With zero price - await expectUnpriced(invalidPriceEURCollateral.address) + // Check asset value + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( + issueAmount, + fp('150') + ) // approx 10K in value - // Refresh should mark status IFFY - await invalidPriceEURCollateral.refresh() - expect(await invalidPriceEURCollateral.status()).to.equal(CollateralStatus.IFFY) - }) + // Redeem Rtokens + await expect(rToken.connect(addr1).redeem(issueAmount)).to.emit(rToken, 'Redemption') - it('Should register ERC20s and Assets/Collateral correctly', async () => { - // Check assets/collateral - const ERC20s = await assetRegistry.erc20s() - expect(ERC20s[0]).to.equal(rToken.address) - expect(ERC20s[1]).to.equal(rsr.address) - expect(ERC20s[2]).to.equal(aaveToken.address) - expect(ERC20s[3]).to.equal(compToken.address) - - const initialTokens: string[] = await Promise.all( - basket.map(async (c): Promise => { - return await c.erc20() - }) - ) - expect(ERC20s.slice(4)).to.eql(initialTokens) - expect(ERC20s.length).to.eql((await facade.basketTokens(rToken.address)).length + 4) - - // Assets - expect(await assetRegistry.toAsset(ERC20s[0])).to.equal(rTokenAsset.address) - expect(await assetRegistry.toAsset(ERC20s[1])).to.equal(rsrAsset.address) - expect(await assetRegistry.toAsset(ERC20s[2])).to.equal(aaveAsset.address) - expect(await assetRegistry.toAsset(ERC20s[3])).to.equal(compAsset.address) - expect(await assetRegistry.toAsset(ERC20s[4])).to.equal(daiCollateral.address) - expect(await assetRegistry.toAsset(ERC20s[5])).to.equal(aDaiCollateral.address) - expect(await assetRegistry.toAsset(ERC20s[6])).to.equal(cDaiCollateral.address) - - // Collaterals - expect(await assetRegistry.toColl(ERC20s[4])).to.equal(daiCollateral.address) - expect(await assetRegistry.toColl(ERC20s[5])).to.equal(aDaiCollateral.address) - expect(await assetRegistry.toColl(ERC20s[6])).to.equal(cDaiCollateral.address) - }) + // Check funds were transferred + expect(await rToken.balanceOf(addr1.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(0) - it('Should register simple Basket correctly', async () => { - // Basket - expect(await basketHandler.fullyCollateralized()).to.equal(true) - const backing = await facade.basketTokens(rToken.address) - expect(backing[0]).to.equal(dai.address) - expect(backing[1]).to.equal(stataDai.address) - expect(backing[2]).to.equal(cDaiVault.address) + // Check balances after - Backing Manager is empty + expect(await dai.balanceOf(backingManager.address)).to.equal(0) + expect(await stataDai.balanceOf(backingManager.address)).to.be.closeTo(bn(0), fp('0.01')) + expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo(bn(0), bn('1e15')) - expect(backing.length).to.equal(3) + // Check funds returned to user + expect(await dai.balanceOf(addr1.address)).to.equal(initialBal) + expect(await stataDai.balanceOf(addr1.address)).to.be.closeTo(initialBalAToken, fp('1.5')) + expect(await cDaiVault.balanceOf(addr1.address)).to.be.closeTo( + toBNDecimals(initialBal, 17).mul(100), + bn('1e16') + ) - // Check other values - expect(await basketHandler.timestamp()).to.be.gt(bn(0)) - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) - await expectPrice(basketHandler.address, fp('1'), ORACLE_ERROR, true, bn('1e5')) + // Check asset value left + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( + bn(0), + fp('0.001') + ) // Near zero + }) - // Check RToken price - const issueAmount: BigNumber = bn('10000e18') - await dai.connect(addr1).approve(rToken.address, issueAmount) - await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) - await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') + it('Should handle rates correctly on Issue/Redeem', async function () { + const MIN_ISSUANCE_PER_BLOCK = bn('10000e18') + const issueAmount: BigNumber = MIN_ISSUANCE_PER_BLOCK - await expectRTokenPrice( - rTokenAsset.address, - fp('1'), - ORACLE_ERROR, - await backingManager.maxTradeSlippage(), - config.minTradeVolume.mul((await assetRegistry.erc20s()).length) - ) - }) + // Provide approvals for issuances + await dai.connect(addr1).approve(rToken.address, issueAmount) + await stataDai.connect(addr1).approve(rToken.address, issueAmount) + await cDaiVault + .connect(addr1) + .approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) - it('Should issue/reedem correctly with simple basket ', async function () { - const MIN_ISSUANCE_PER_BLOCK = bn('10000e18') - const issueAmount: BigNumber = MIN_ISSUANCE_PER_BLOCK + // Issue rTokens + await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') - // Check balances before - expect(await dai.balanceOf(backingManager.address)).to.equal(0) - expect(await stataDai.balanceOf(backingManager.address)).to.equal(0) - expect(await cDaiVault.balanceOf(backingManager.address)).to.equal(0) - expect(await dai.balanceOf(addr1.address)).to.equal(initialBal) - - // Balance for Static a Token is about 18641.55e18, about 93.21% of the provided amount (20K) - const initialBalAToken = initialBal.mul(9321).div(10000) - expect(await stataDai.balanceOf(addr1.address)).to.be.closeTo(initialBalAToken, fp('1.5')) - expect(await cDaiVault.balanceOf(addr1.address)).to.equal( - toBNDecimals(initialBal, 17).mul(100) - ) + // Check RTokens issued to user + expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) - // Provide approvals - await dai.connect(addr1).approve(rToken.address, issueAmount) - await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) + // Store Balances after issuance + const balanceAddr1Dai: BigNumber = await dai.balanceOf(addr1.address) + const balanceAddr1aDai: BigNumber = await stataDai.balanceOf(addr1.address) + const balanceAddr1cDai: BigNumber = await cDaiVault.balanceOf(addr1.address) - // Check rToken balance - expect(await rToken.balanceOf(addr1.address)).to.equal(0) - expect(await rToken.balanceOf(main.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(0) + // Check rates and prices + const [aDaiPriceLow1, aDaiPriceHigh1] = await aDaiCollateral.price() // ~1.07546 + const aDaiRefPerTok1: BigNumber = await aDaiCollateral.refPerTok() // ~ 1.07287 + const [cDaiPriceLow1, cDaiPriceHigh1] = await cDaiCollateral.price() // ~ 0.022015 cents + const cDaiRefPerTok1: BigNumber = await cDaiCollateral.refPerTok() // ~ 0.022015 cents - // Issue rTokens - await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') + await expectPrice( + aDaiCollateral.address, + fp('1.072871695141967225'), + ORACLE_ERROR, + true, + bn('1e5') + ) + expect(aDaiRefPerTok1).to.be.closeTo(fp('1'), fp('0.095')) - // Check Balances after - expect(await dai.balanceOf(backingManager.address)).to.equal(issueAmount.div(4)) // 2.5K needed (25% of basket) - const issueAmtAToken = issueAmount.div(4).mul(9321).div(10000) // approx 93.21% of 2.5K needed (25% of basket) - expect(await stataDai.balanceOf(backingManager.address)).to.be.closeTo( - issueAmtAToken, - fp('1') - ) - const requiredCTokens: BigNumber = bn('227116e17') // approx 227K needed (~5K, 50% of basket) - Price: ~0.022 - expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo( - requiredCTokens, - bn('1e17') - ) + await expectPrice( + cDaiCollateral.address, + fp('0.022015110752383443'), + ORACLE_ERROR, + true, + bn('1e5') + ) + expect(cDaiRefPerTok1).to.be.closeTo(fp('0.022'), fp('0.001')) - // Balances for user - expect(await dai.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(4))) - expect(await stataDai.balanceOf(addr1.address)).to.be.closeTo( - initialBalAToken.sub(issueAmtAToken), - fp('1.5') - ) - expect(await cDaiVault.balanceOf(addr1.address)).to.be.closeTo( - toBNDecimals(initialBal, 17).mul(100).sub(requiredCTokens), - bn('1e17') - ) - // Check RTokens issued to user - expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) - expect(await rToken.balanceOf(main.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check asset value - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( - issueAmount, - fp('150') - ) // approx 10K in value - - // Redeem Rtokens - await expect(rToken.connect(addr1).redeem(issueAmount)).to.emit(rToken, 'Redemption') - - // Check funds were transferred - expect(await rToken.balanceOf(addr1.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(0) - - // Check balances after - Backing Manager is empty - expect(await dai.balanceOf(backingManager.address)).to.equal(0) - expect(await stataDai.balanceOf(backingManager.address)).to.be.closeTo(bn(0), fp('0.01')) - expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo(bn(0), bn('1e15')) - - // Check funds returned to user - expect(await dai.balanceOf(addr1.address)).to.equal(initialBal) - expect(await stataDai.balanceOf(addr1.address)).to.be.closeTo(initialBalAToken, fp('1.5')) - expect(await cDaiVault.balanceOf(addr1.address)).to.be.closeTo( - toBNDecimals(initialBal, 17).mul(100), - bn('1e16') - ) + // Check total asset value + const totalAssetValue1: BigNumber = await facadeTest.callStatic.totalAssetValue( + rToken.address + ) + expect(totalAssetValue1).to.be.closeTo(issueAmount, fp('150')) // approx 10K in value + + // Advance time and blocks slightly + await advanceTime(10000) + await advanceBlocks(10000) + + // Refresh assets + await assetRegistry.refresh() + + // Check rates and prices - Have changed, slight inrease + const [aDaiPriceLow2, aDaiPriceHigh2] = await aDaiCollateral.price() // ~1.07548 + const aDaiRefPerTok2: BigNumber = await aDaiCollateral.refPerTok() // ~1.07288 + const [cDaiPriceLow2, cDaiPriceHigh2] = await cDaiCollateral.price() // ~0.022016 + const cDaiRefPerTok2: BigNumber = await cDaiCollateral.refPerTok() // ~0.022016 + + // Check rates and price increase + expect(aDaiPriceLow2).to.be.gt(aDaiPriceLow1) + expect(aDaiPriceHigh2).to.be.gt(aDaiPriceHigh1) + expect(aDaiRefPerTok2).to.be.gt(aDaiRefPerTok1) + expect(cDaiPriceLow2).to.be.gt(cDaiPriceLow1) + expect(cDaiPriceHigh2).to.be.gt(cDaiPriceHigh1) + expect(cDaiRefPerTok2).to.be.gt(cDaiRefPerTok1) + + // Still close to the original values + await expectPrice( + aDaiCollateral.address, + fp('1.072882861877314264'), + ORACLE_ERROR, + true, + bn('1e5') + ) + expect(aDaiRefPerTok2).to.be.closeTo(fp('1'), fp('0.095')) - // Check asset value left - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( - bn(0), - fp('0.001') - ) // Near zero - }) + await expectPrice( + cDaiCollateral.address, + fp('0.022016203274102888'), + ORACLE_ERROR, + true, + bn('1e5') + ) + expect(cDaiRefPerTok2).to.be.closeTo(fp('0.022'), fp('0.001')) - it('Should handle rates correctly on Issue/Redeem', async function () { - const MIN_ISSUANCE_PER_BLOCK = bn('10000e18') - const issueAmount: BigNumber = MIN_ISSUANCE_PER_BLOCK + // Check total asset value increased + const totalAssetValue2: BigNumber = await facadeTest.callStatic.totalAssetValue( + rToken.address + ) + expect(totalAssetValue2).to.be.gt(totalAssetValue1) - // Provide approvals for issuances - await dai.connect(addr1).approve(rToken.address, issueAmount) - await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) + // Advance time and blocks significantly + await advanceTime(100000000) + await advanceBlocks(100000000) - // Issue rTokens - await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') + // Refresh cToken manually (required) + await assetRegistry.refresh() - // Check RTokens issued to user - expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) + // Check rates and prices - Have changed significantly + const [aDaiPriceLow3, aDaiPriceHigh3] = await aDaiCollateral.price() // ~1.1873 + const aDaiRefPerTok3: BigNumber = await aDaiCollateral.refPerTok() // ~1.1845 + const [cDaiPriceLow3, cDaiPriceHigh3] = await cDaiCollateral.price() // ~0.03294 + const cDaiRefPerTok3: BigNumber = await cDaiCollateral.refPerTok() // ~0.03294 - // Store Balances after issuance - const balanceAddr1Dai: BigNumber = await dai.balanceOf(addr1.address) - const balanceAddr1aDai: BigNumber = await stataDai.balanceOf(addr1.address) - const balanceAddr1cDai: BigNumber = await cDaiVault.balanceOf(addr1.address) - - // Check rates and prices - const [aDaiPriceLow1, aDaiPriceHigh1] = await aDaiCollateral.price() // ~1.07546 - const aDaiRefPerTok1: BigNumber = await aDaiCollateral.refPerTok() // ~ 1.07287 - const [cDaiPriceLow1, cDaiPriceHigh1] = await cDaiCollateral.price() // ~ 0.022015 cents - const cDaiRefPerTok1: BigNumber = await cDaiCollateral.refPerTok() // ~ 0.022015 cents - - await expectPrice( - aDaiCollateral.address, - fp('1.072871695141967225'), - ORACLE_ERROR, - true, - bn('1e5') - ) - expect(aDaiRefPerTok1).to.be.closeTo(fp('1'), fp('0.095')) - - await expectPrice( - cDaiCollateral.address, - fp('0.022015110752383443'), - ORACLE_ERROR, - true, - bn('1e5') - ) - expect(cDaiRefPerTok1).to.be.closeTo(fp('0.022'), fp('0.001')) + // Check rates and price increase + expect(aDaiPriceLow3).to.be.gt(aDaiPriceLow2) + expect(aDaiPriceHigh3).to.be.gt(aDaiPriceHigh2) + expect(aDaiRefPerTok3).to.be.gt(aDaiRefPerTok2) + expect(cDaiPriceLow3).to.be.gt(cDaiPriceLow2) + expect(cDaiPriceHigh3).to.be.gt(cDaiPriceHigh2) + expect(cDaiRefPerTok3).to.be.gt(cDaiRefPerTok2) - // Check total asset value - const totalAssetValue1: BigNumber = await facadeTest.callStatic.totalAssetValue( - rToken.address - ) - expect(totalAssetValue1).to.be.closeTo(issueAmount, fp('150')) // approx 10K in value - - // Advance time and blocks slightly - await advanceTime(10000) - await advanceBlocks(10000) - - // Refresh assets - await assetRegistry.refresh() - - // Check rates and prices - Have changed, slight inrease - const [aDaiPriceLow2, aDaiPriceHigh2] = await aDaiCollateral.price() // ~1.07548 - const aDaiRefPerTok2: BigNumber = await aDaiCollateral.refPerTok() // ~1.07288 - const [cDaiPriceLow2, cDaiPriceHigh2] = await cDaiCollateral.price() // ~0.022016 - const cDaiRefPerTok2: BigNumber = await cDaiCollateral.refPerTok() // ~0.022016 - - // Check rates and price increase - expect(aDaiPriceLow2).to.be.gt(aDaiPriceLow1) - expect(aDaiPriceHigh2).to.be.gt(aDaiPriceHigh1) - expect(aDaiRefPerTok2).to.be.gt(aDaiRefPerTok1) - expect(cDaiPriceLow2).to.be.gt(cDaiPriceLow1) - expect(cDaiPriceHigh2).to.be.gt(cDaiPriceHigh1) - expect(cDaiRefPerTok2).to.be.gt(cDaiRefPerTok1) - - // Still close to the original values - await expectPrice( - aDaiCollateral.address, - fp('1.072882861877314264'), - ORACLE_ERROR, - true, - bn('1e5') - ) - expect(aDaiRefPerTok2).to.be.closeTo(fp('1'), fp('0.095')) - - await expectPrice( - cDaiCollateral.address, - fp('0.022016203274102888'), - ORACLE_ERROR, - true, - bn('1e5') - ) - expect(cDaiRefPerTok2).to.be.closeTo(fp('0.022'), fp('0.001')) + await expectPrice( + aDaiCollateral.address, + fp('1.184527887459258141'), + ORACLE_ERROR, + true, + bn('1e5') + ) + expect(aDaiRefPerTok3).to.be.closeTo(fp('1.1'), fp('0.095')) + await expectPrice( + cDaiCollateral.address, + fp('0.032941268543431921'), + ORACLE_ERROR, + true, + bn('1e5') + ) + expect(cDaiRefPerTok3).to.be.closeTo(fp('0.032'), fp('0.001')) - // Check total asset value increased - const totalAssetValue2: BigNumber = await facadeTest.callStatic.totalAssetValue( - rToken.address - ) - expect(totalAssetValue2).to.be.gt(totalAssetValue1) - - // Advance time and blocks significantly - await advanceTime(100000000) - await advanceBlocks(100000000) - - // Refresh cToken manually (required) - await assetRegistry.refresh() - - // Check rates and prices - Have changed significantly - const [aDaiPriceLow3, aDaiPriceHigh3] = await aDaiCollateral.price() // ~1.1873 - const aDaiRefPerTok3: BigNumber = await aDaiCollateral.refPerTok() // ~1.1845 - const [cDaiPriceLow3, cDaiPriceHigh3] = await cDaiCollateral.price() // ~0.03294 - const cDaiRefPerTok3: BigNumber = await cDaiCollateral.refPerTok() // ~0.03294 - - // Check rates and price increase - expect(aDaiPriceLow3).to.be.gt(aDaiPriceLow2) - expect(aDaiPriceHigh3).to.be.gt(aDaiPriceHigh2) - expect(aDaiRefPerTok3).to.be.gt(aDaiRefPerTok2) - expect(cDaiPriceLow3).to.be.gt(cDaiPriceLow2) - expect(cDaiPriceHigh3).to.be.gt(cDaiPriceHigh2) - expect(cDaiRefPerTok3).to.be.gt(cDaiRefPerTok2) - - await expectPrice( - aDaiCollateral.address, - fp('1.184527887459258141'), - ORACLE_ERROR, - true, - bn('1e5') - ) - expect(aDaiRefPerTok3).to.be.closeTo(fp('1.1'), fp('0.095')) - await expectPrice( - cDaiCollateral.address, - fp('0.032941268543431921'), - ORACLE_ERROR, - true, - bn('1e5') - ) - expect(cDaiRefPerTok3).to.be.closeTo(fp('0.032'), fp('0.001')) + // Check total asset value increased + const totalAssetValue3: BigNumber = await facadeTest.callStatic.totalAssetValue( + rToken.address + ) + expect(totalAssetValue3).to.be.gt(totalAssetValue2) - // Check total asset value increased - const totalAssetValue3: BigNumber = await facadeTest.callStatic.totalAssetValue( - rToken.address - ) - expect(totalAssetValue3).to.be.gt(totalAssetValue2) - - // Redeem Rtokens with the udpated rates - await expect(rToken.connect(addr1).redeem(issueAmount)).to.emit(rToken, 'Redemption') - - // Check funds were transferred - expect(await rToken.balanceOf(addr1.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(0) - - // Check balances - Fewer ATokens and cTokens should have been sent to the user - const newBalanceAddr1Dai: BigNumber = await dai.balanceOf(addr1.address) - const newBalanceAddr1aDai: BigNumber = await stataDai.balanceOf(addr1.address) - const newBalanceAddr1cDai: BigNumber = await cDaiVault.balanceOf(addr1.address) - - // Check received tokens represent ~10K in value at current prices - expect(newBalanceAddr1Dai.sub(balanceAddr1Dai)).to.equal(issueAmount.div(4)) // = 2.5K (25% of basket) - expect(newBalanceAddr1aDai.sub(balanceAddr1aDai)).to.be.closeTo(fp('2110.5'), fp('0.5')) // ~1.1873 * 2110.5 ~= 2.5K (25% of basket) - expect(newBalanceAddr1cDai.sub(balanceAddr1cDai)).to.be.closeTo(bn('151785e17'), bn('5e16')) // ~0.03294 * 151785.3 ~= 5K (50% of basket) - - // Check remainders in Backing Manager - expect(await dai.balanceOf(backingManager.address)).to.equal(0) - expect(await stataDai.balanceOf(backingManager.address)).to.be.closeTo( - fp('219.64'), // ~= 260 usd in value - fp('0.01') - ) - expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo( - bn('75331e17'), - bn('5e16') - ) // ~= 2481 usd in value - - // Check total asset value (remainder) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( - fp('2742'), // ~= 260usd + 2481 usd (from above) - fp('1') - ) - }) + // Redeem Rtokens with the udpated rates + await expect(rToken.connect(addr1).redeem(issueAmount)).to.emit(rToken, 'Redemption') - it('Should also support StaticAToken from underlying', async () => { - // Transfer out all existing stataDai - empty balance - await stataDai.connect(addr1).transfer(addr2.address, await stataDai.balanceOf(addr1.address)) - expect(await stataDai.balanceOf(addr1.address)).to.equal(bn(0)) + // Check funds were transferred + expect(await rToken.balanceOf(addr1.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(0) - const MIN_ISSUANCE_PER_BLOCK = bn('10000e18') - const issueAmount: BigNumber = MIN_ISSUANCE_PER_BLOCK + // Check balances - Fewer ATokens and cTokens should have been sent to the user + const newBalanceAddr1Dai: BigNumber = await dai.balanceOf(addr1.address) + const newBalanceAddr1aDai: BigNumber = await stataDai.balanceOf(addr1.address) + const newBalanceAddr1cDai: BigNumber = await cDaiVault.balanceOf(addr1.address) + + // Check received tokens represent ~10K in value at current prices + expect(newBalanceAddr1Dai.sub(balanceAddr1Dai)).to.equal(issueAmount.div(4)) // = 2.5K (25% of basket) + expect(newBalanceAddr1aDai.sub(balanceAddr1aDai)).to.be.closeTo(fp('2110.5'), fp('0.5')) // ~1.1873 * 2110.5 ~= 2.5K (25% of basket) + expect(newBalanceAddr1cDai.sub(balanceAddr1cDai)).to.be.closeTo(bn('151785e17'), bn('5e16')) // ~0.03294 * 151785.3 ~= 5K (50% of basket) + + // Check remainders in Backing Manager + expect(await dai.balanceOf(backingManager.address)).to.equal(0) + expect(await stataDai.balanceOf(backingManager.address)).to.be.closeTo( + fp('219.64'), // ~= 260 usd in value + fp('0.01') + ) + expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo( + bn('75331e17'), + bn('5e16') + ) // ~= 2481 usd in value - // Transfer plain DAI - await whileImpersonating(holderDAI, async (daiSigner) => { - await dai.connect(daiSigner).transfer(addr1.address, initialBal) + // Check total asset value (remainder) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( + fp('2742'), // ~= 260usd + 2481 usd (from above) + fp('1') + ) }) - // Wrap DAI into a staticaDAI - await dai.connect(addr1).approve(stataDai.address, initialBal) - await stataDai.connect(addr1).deposit(addr1.address, initialBal, 0, true) + it('Should also support StaticAToken from underlying', async () => { + // Transfer out all existing stataDai - empty balance + await stataDai + .connect(addr1) + .transfer(addr2.address, await stataDai.balanceOf(addr1.address)) + expect(await stataDai.balanceOf(addr1.address)).to.equal(bn(0)) - // Balance for Static a Token is about 18641.55e18, about 93.21% of the provided amount (20K) - const initialBalAToken = initialBal.mul(9321).div(10000) - expect(await stataDai.balanceOf(addr1.address)).to.be.closeTo(initialBalAToken, fp('1.5')) + const MIN_ISSUANCE_PER_BLOCK = bn('10000e18') + const issueAmount: BigNumber = MIN_ISSUANCE_PER_BLOCK - // Provide approvals - await dai.connect(addr1).approve(rToken.address, issueAmount) - await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) + // Transfer plain DAI + await whileImpersonating(holderDAI, async (daiSigner) => { + await dai.connect(daiSigner).transfer(addr1.address, initialBal) + }) - // Check rToken balance - expect(await rToken.balanceOf(addr1.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(0) + // Wrap DAI into a staticaDAI + await dai.connect(addr1).approve(stataDai.address, initialBal) + await stataDai.connect(addr1).deposit(addr1.address, initialBal, 0, true) - // Issue rTokens - await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') + // Balance for Static a Token is about 18641.55e18, about 93.21% of the provided amount (20K) + const initialBalAToken = initialBal.mul(9321).div(10000) + expect(await stataDai.balanceOf(addr1.address)).to.be.closeTo(initialBalAToken, fp('1.5')) - // Check RTokens issued to user - expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) - expect(await rToken.totalSupply()).to.equal(issueAmount) + // Provide approvals + await dai.connect(addr1).approve(rToken.address, issueAmount) + await stataDai.connect(addr1).approve(rToken.address, issueAmount) + await cDaiVault + .connect(addr1) + .approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) + + // Check rToken balance + expect(await rToken.balanceOf(addr1.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(0) + + // Issue rTokens + await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') + + // Check RTokens issued to user + expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) + expect(await rToken.totalSupply()).to.equal(issueAmount) - // Check asset value - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( - issueAmount, - fp('150') - ) // approx 10K in value + // Check asset value + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( + issueAmount, + fp('150') + ) // approx 10K in value + }) }) context('With Complex basket', function () { @@ -2064,6 +2107,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Set non-empty basket await basketHandler.connect(owner).setPrimeBasket(newBasketERC20s, newBasketsNeededAmts) await basketHandler.connect(owner).refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) // Approve all balances for user await wbtc.connect(addr1).approve(rToken.address, await wbtc.balanceOf(addr1.address)) @@ -2311,6 +2355,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Set non-empty basket await basketHandler.connect(owner).setPrimeBasket(newBasketERC20s, newBasketsNeededAmts) await basketHandler.connect(owner).refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) // Approve all balances for user await usdt.connect(addr1).approve(rToken.address, await usdt.balanceOf(addr1.address)) @@ -2437,7 +2482,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, rToken, rTokenAsset, facade, - } = await loadFixture(defaultFixture)) + } = await loadFixture(defaultFixtureNoBasket)) // Get assets and tokens for default basket daiCollateral = basket[0] diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index 469b91e96f..9cfabb27fc 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -2,6 +2,7 @@ import { BigNumber, ContractFactory } from 'ethers' import hre, { ethers } from 'hardhat' import { getChainId } from '../../common/blockchain-utils' import { IConfig, IImplementations, IRevenueShare, networkConfig } from '../../common/configuration' +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' import { PAUSER, SHORT_FREEZER, LONG_FREEZER } from '../../common/constants' import { expectInReceipt } from '../../common/events' import { advanceTime } from '../utils/time' @@ -610,7 +611,18 @@ interface DefaultFixture extends RSRAndCompAaveAndCollateralAndModuleFixture { type Fixture = () => Promise +// Use this fixture when the prime basket will be constant at 1 USD export const defaultFixture: Fixture = async function (): Promise { + return await loadFixture(makeFixture.bind(null, true)) +} + +// Use this fixture when the prime basket needs to be set away from 1 USD +export const defaultFixtureNoBasket: Fixture = + async function (): Promise { + return await loadFixture(makeFixture.bind(null, false)) + } + +const makeFixture = async (setBasket: boolean): Promise => { const signers = await ethers.getSigners() const owner = signers[0] const { rsr } = await rsrFixture() @@ -868,12 +880,14 @@ export const defaultFixture: Fixture = async function (): Promis basketERC20s.push(await basket[i].erc20()) } - // Set non-empty basket - await basketHandler.connect(owner).setPrimeBasket(basketERC20s, basketsNeededAmts) - await basketHandler.connect(owner).refreshBasket() + if (setBasket) { + // Set non-empty basket + await basketHandler.connect(owner).setPrimeBasket(basketERC20s, basketsNeededAmts) + await basketHandler.connect(owner).refreshBasket() - // Advance time post warmup period - await advanceTime(Number(config.warmupPeriod) + 1) + // Advance time post warmup period + await advanceTime(Number(config.warmupPeriod) + 1) + } // Set up allowances for (let i = 0; i < basket.length; i++) { From fe1bb6d88357195e363f550864a542dda04bdf11 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 8 May 2023 21:38:33 -0400 Subject: [PATCH 192/462] add manual gaslimit to all FacadeAct tests --- test/FacadeAct.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts index 73bc35b826..c4c1720a03 100644 --- a/test/FacadeAct.test.ts +++ b/test/FacadeAct.test.ts @@ -238,6 +238,7 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ).to.emit(basketHandler, 'BasketSet') @@ -291,6 +292,7 @@ describe('FacadeAct contract', () => { await owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) // Collect revenue @@ -310,6 +312,7 @@ describe('FacadeAct contract', () => { await owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) // Next call would start Revenue auction - RTokenTrader @@ -322,6 +325,7 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ) .to.emit(rTokenTrader, 'TradeStarted') @@ -343,6 +347,7 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ) .to.emit(rsrTrader, 'TradeStarted') @@ -378,6 +383,7 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ) .to.emit(rTokenTrader, 'TradeSettled') @@ -393,6 +399,7 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ) .to.emit(rsrTrader, 'TradeSettled') @@ -432,6 +439,7 @@ describe('FacadeAct contract', () => { await owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) // Next call would start Revenue auction - RSR Trader @@ -445,6 +453,7 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ) .to.emit(rsrTrader, 'TradeStarted') @@ -674,6 +683,7 @@ describe('FacadeAct contract', () => { await owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) // Check rewards collected @@ -689,6 +699,7 @@ describe('FacadeAct contract', () => { owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) ).to.emit(rsrTrader, 'TradeStarted') @@ -704,6 +715,7 @@ describe('FacadeAct contract', () => { await owner.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) // Check rewards collected - there are funds in the RTokenTrader now @@ -738,6 +750,7 @@ describe('FacadeAct contract', () => { await addr1.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) // RSR forwarded @@ -769,6 +782,7 @@ describe('FacadeAct contract', () => { await addr1.sendTransaction({ to: addr, data, + gasLimit: bn('10000000'), }) // RToken forwarded From 68ba06fdbb41f26556896174935542f690afcc45 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 8 May 2023 21:44:27 -0400 Subject: [PATCH 193/462] remove --parallel from p0/p1 tests, seems to be causing nondeterminism --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2e4d6c546c..ca12e80bf0 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "test:extreme:integration": "FORK=1 EXTREME=1 PROTO_IMPL=1 npx hardhat test test/integration/**/*.test.ts", "test:unit": "yarn test:plugins && yarn test:p0 && yarn test:p1", "test:fast": "bash tools/fast-test.sh", - "test:p0": "PROTO_IMPL=0 hardhat test test/*.test.ts --parallel", - "test:p1": "PROTO_IMPL=1 hardhat test test/*.test.ts --parallel", + "test:p0": "PROTO_IMPL=0 hardhat test test/*.test.ts", + "test:p1": "PROTO_IMPL=1 hardhat test test/*.test.ts", "test:plugins": "hardhat test test/{libraries,plugins}/*.test.ts --parallel", "test:plugins:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/plugins/individual-collateral/**/*.test.ts", "test:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/integration/**/*.test.ts", From 91a1ba2a965831c93ef714085316b36b4bde299f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 10:12:27 -0400 Subject: [PATCH 194/462] remove _switchConfig mentions --- contracts/p0/BasketHandler.sol | 2 +- contracts/p1/BasketHandler.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index eab8a8ddce..4b7f842988 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -135,7 +135,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { EnumerableSet.Bytes32Set private targetNames; Basket private newBasket; - // Effectively local variable of `_switchConfig`. + // Effectively local variable of `requireConstantConfigTargets()` EnumerableMap.Bytes32ToUintMap private _targetAmts; // targetName -> {target/BU} uint48 public warmupPeriod; // {s} how long to wait until issuance/trading after regaining SOUND diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index c2fdb7bec9..134606acb0 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -77,7 +77,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // A history of baskets by basket nonce; includes current basket mapping(uint48 => Basket) private basketHistory; - // Effectively local variable of `_switchConfig`. + // Effectively local variable of `requireConstantConfigTargets()` EnumerableMap.Bytes32ToUintMap private _targetAmts; // targetName -> {target/BU} // === From 82334f261116bbb3a9879b7a309a2e1c66486ce0 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 10:17:18 -0400 Subject: [PATCH 195/462] remove loadFixture --- test/fixtures.ts | 7 +++---- test/integration/fixtures.ts | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/test/fixtures.ts b/test/fixtures.ts index 6361e909db..51832b5888 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -6,7 +6,6 @@ import { IConfig, IImplementations, IRevenueShare, networkConfig } from '../comm import { expectInReceipt } from '../common/events' import { bn, fp } from '../common/numbers' import { CollateralStatus, PAUSER, LONG_FREEZER, SHORT_FREEZER } from '../common/constants' -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' import { Asset, AssetRegistryP1, @@ -411,16 +410,16 @@ type Fixture = () => Promise // Use this fixture when the prime basket will be constant at 1 USD export const defaultFixture: Fixture = async function (): Promise { - return await loadFixture(makeFixture.bind(null, true)) + return await makeDefaultFixture(true) } // Use this fixture when the prime basket needs to be set away from 1 USD export const defaultFixtureNoBasket: Fixture = async function (): Promise { - return await loadFixture(makeFixture.bind(null, false)) + return makeDefaultFixture(false) } -const makeFixture = async (setBasket: boolean): Promise => { +const makeDefaultFixture = async (setBasket: boolean): Promise => { const signers = await ethers.getSigners() const owner = signers[0] const { rsr } = await rsrFixture() diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index 9cfabb27fc..2d803d8e80 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -2,7 +2,6 @@ import { BigNumber, ContractFactory } from 'ethers' import hre, { ethers } from 'hardhat' import { getChainId } from '../../common/blockchain-utils' import { IConfig, IImplementations, IRevenueShare, networkConfig } from '../../common/configuration' -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' import { PAUSER, SHORT_FREEZER, LONG_FREEZER } from '../../common/constants' import { expectInReceipt } from '../../common/events' import { advanceTime } from '../utils/time' @@ -613,16 +612,16 @@ type Fixture = () => Promise // Use this fixture when the prime basket will be constant at 1 USD export const defaultFixture: Fixture = async function (): Promise { - return await loadFixture(makeFixture.bind(null, true)) + return await makeDefaultFixture(true) } // Use this fixture when the prime basket needs to be set away from 1 USD export const defaultFixtureNoBasket: Fixture = async function (): Promise { - return await loadFixture(makeFixture.bind(null, false)) + return await makeDefaultFixture(false) } -const makeFixture = async (setBasket: boolean): Promise => { +const makeDefaultFixture = async (setBasket: boolean): Promise => { const signers = await ethers.getSigners() const owner = signers[0] const { rsr } = await rsrFixture() From e5b9df59ff6f3b7f55c3017e87d5887722a5bfbf Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 10:29:30 -0400 Subject: [PATCH 196/462] remove confusing comment + add test case for it --- contracts/p1/BasketHandler.sol | 2 -- test/Main.test.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 134606acb0..e87369f264 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -568,8 +568,6 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { len = newERC20s.length; for (uint256 i = 0; i < len; ++i) { bytes32 targetName = assetRegistry.toColl(newERC20s[i]).targetName(); - // if the asset registry has a new registered asset targetName, this always reverts - (bool contains, uint256 amt) = _targetAmts.tryGet(targetName); require(contains && amt >= newTargetAmts[i], "new basket adds target weights"); if (amt > newTargetAmts[i]) _targetAmts.set(targetName, amt - newTargetAmts[i]); diff --git a/test/Main.test.ts b/test/Main.test.ts index 26ced13d67..26b68493c4 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1812,6 +1812,33 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { .withArgs(2, [token0.address], [fp('1')], [ethers.utils.formatBytes32String('USD')]) }) + it('Should revert if target has been changed in asset registry', async () => { + // Swap registered asset for NEW_TARGET target + const FiatCollateralFactory = await ethers.getContractFactory('FiatCollateral') + const coll = await FiatCollateralFactory.deploy({ + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await collateral0.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: await collateral0.erc20(), + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: await collateral0.oracleTimeout(), + targetName: ethers.utils.formatBytes32String('NEW_TARGET'), + defaultThreshold: fp('0.01'), + delayUntilDefault: await collateral0.delayUntilDefault(), + }) + await assetRegistry.connect(owner).swapRegistered(coll.address) + + // Should revert + await expect( + basketHandler + .connect(owner) + .setPrimeBasket( + [token0.address, token1.address, token2.address, token3.address], + [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.25')] + ) + ).to.be.revertedWith('new basket adds target weights') + }) + describe('Custom Redemption', () => { const issueAmount = fp('10000') let usdcChainlink: MockV3Aggregator From f9f9050dcba1215f512be38967f5d676a7c5a2d3 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 10:39:08 -0400 Subject: [PATCH 197/462] add back afterEach() for fully collateralized + redemption --- test/Recollateralization.test.ts | 3800 +++++++++++++++--------------- 1 file changed, 1908 insertions(+), 1892 deletions(-) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 3845fb76e2..48ab75a45f 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -1003,2125 +1003,2141 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) }) - it('Should recollateralize correctly when switching basket - Full amount covered', async () => { - // Setup prime basket - await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) - - // Check initial state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) + context('Should successfully recollateralize after governance basket switch', () => { + afterEach(async () => { + // Should be fully capitalized again + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await backingManager.tradesOpen()).to.equal(0) + }) - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + it('Should recollateralize correctly when switching basket - Full amount covered', async () => { + // Setup prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) - // Switch Basket - await expect(basketHandler.connect(owner).refreshBasket()) - .to.emit(basketHandler, 'BasketSet') - .withArgs(3, [token1.address], [fp('1')], false) + // Check initial state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(3, [token1.address], [fp('1')], false) + + // Check state remains SOUND + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + + // Check price in USD of the current redemption basket + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Trigger recollateralization + const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs( + anyValue, + token0.address, + token1.address, + sellAmt, + toBNDecimals(minBuyAmt, 6).add(1) + ) - // Check state remains SOUND - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) + const auctionTimestamp: number = await getLatestBlockTimestamp() - // Check price in USD of the current redemption basket - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // Check auction registered + // Token0 -> Token1 Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) - // Trigger recollateralization - const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) - const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + // Asset value is zero, everything was moved to the Market + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) - .to.emit(backingManager, 'TradeStarted') - .withArgs( - anyValue, - token0.address, - token1.address, - sellAmt, - toBNDecimals(minBuyAmt, 6).add(1) - ) + // Check price in USD of the current redemption basket + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - const auctionTimestamp: number = await getLatestBlockTimestamp() + // Check Gnosis + expect(await token0.balanceOf(gnosis.address)).to.equal(issueAmount) - // Check auction registered - // Token0 -> Token1 Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: token1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), - }) + // Another call should not create any new auctions if still ongoing + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.not.emit( + backingManager, + 'TradeStarted' + ) - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - // Asset value is zero, everything was moved to the Market - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) + // Perform Mock Bids for the new Token (addr1 has balance) + // Get fair price - all tokens + await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6).add(1)) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: toBNDecimals(sellAmt, 6).add(1), + }) - // Check price in USD of the current redemption basket - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // If we attempt to settle before auction ended it reverts + await expect(backingManager.settleTrade(token0.address)).to.be.revertedWith( + 'cannot settle yet' + ) - // Check Gnosis - expect(await token0.balanceOf(gnosis.address)).to.equal(issueAmount) + // Nothing occurs if we attempt to settle for a token that is not being traded + await expect(backingManager.settleTrade(token3Vault.address)).to.not.emit + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction, should not start any new auctions + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [ + anyValue, + token0.address, + token1.address, + sellAmt, + toBNDecimals(sellAmt, 6).add(1), + ], + emitted: true, + }, + { contract: backingManager, name: 'TradeStarted', emitted: false }, + ]) - // Another call should not create any new auctions if still ongoing - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.not.emit( - backingManager, - 'TradeStarted' - ) + // Check state - Order restablished + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + const totalValue = toBNDecimals(sellAmt, 6).add(1).mul(bn('1e12')) // small gain; decimal adjustment + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(totalValue) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal( + toBNDecimals(issueAmount, 6).add(1) + ) + expect(await rToken.totalSupply()).to.equal(issueAmount) // assets kept in backing buffer - // Perform Mock Bids for the new Token (addr1 has balance) - // Get fair price - all tokens - await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6).add(1)) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: toBNDecimals(sellAmt, 6).add(1), + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) }) - // If we attempt to settle before auction ended it reverts - await expect(backingManager.settleTrade(token0.address)).to.be.revertedWith( - 'cannot settle yet' - ) + it('Should recollateralize correctly when switching basket - Using lot price', async () => { + // Set price to unpriced (will use lotPrice to size trade) + await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) - // Nothing occurs if we attempt to settle for a token that is not being traded - await expect(backingManager.settleTrade(token3Vault.address)).to.not.emit + // Setup prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(3, [token1.address], [fp('1')], false) - // End current auction, should not start any new auctions - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [ - anyValue, - token0.address, - token1.address, - sellAmt, - toBNDecimals(sellAmt, 6).add(1), - ], - emitted: true, - }, - { contract: backingManager, name: 'TradeStarted', emitted: false }, - ]) + // Advance time post warmup period - temporary IFFY->SOUND + await advanceTime(Number(config.warmupPeriod) + 1) - // Check state - Order restablished - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - const totalValue = toBNDecimals(sellAmt, 6).add(1).mul(bn('1e12')) // small gain; decimal adjustment - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(totalValue) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal( - toBNDecimals(issueAmount, 6).add(1) - ) - expect(await rToken.totalSupply()).to.equal(issueAmount) // assets kept in backing buffer + // Check state remains SOUND + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - }) + // Check price in USD of the current redemption basket + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - it('Should recollateralize correctly when switching basket - Using lot price', async () => { - // Set price to unpriced (will use lotPrice to size trade) - await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) + // Trigger recollateralization + const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) - // Setup prime basket - await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs(anyValue, token0.address, token1.address, sellAmt, bn(0)) - // Switch Basket - await expect(basketHandler.connect(owner).refreshBasket()) - .to.emit(basketHandler, 'BasketSet') - .withArgs(3, [token1.address], [fp('1')], false) + const auctionTimestamp: number = await getLatestBlockTimestamp() - // Advance time post warmup period - temporary IFFY->SOUND - await advanceTime(Number(config.warmupPeriod) + 1) + // Check auction registered + // Token0 -> Token1 Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) - // Check state remains SOUND - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check Gnosis + expect(await token0.balanceOf(gnosis.address)).to.equal(issueAmount) + + // Perform Mock Bids for the new Token (addr1 has balance) + // Get all tokens for simplification + await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6)) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: toBNDecimals(sellAmt, 6), + }) - // Check price in USD of the current redemption basket - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction, should not start any new auctions + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, token0.address, token1.address, sellAmt, toBNDecimals(sellAmt, 6)], + emitted: true, + }, + { contract: backingManager, name: 'TradeStarted', emitted: false }, + ]) - // Trigger recollateralization - const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) + // Check state - Order restablished + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal( + toBNDecimals(issueAmount, 6) + ) + expect(await rToken.totalSupply()).to.equal(issueAmount) - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) - .to.emit(backingManager, 'TradeStarted') - .withArgs(anyValue, token0.address, token1.address, sellAmt, bn(0)) + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + }) - const auctionTimestamp: number = await getLatestBlockTimestamp() + it('Should recollateralize correctly when switching basket - Taking no Haircut - No RSR', async () => { + await backingManager.connect(owner).setBackingBuffer(0) - // Check auction registered - // Token0 -> Token1 Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: token1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), - }) + // Empty out the staking pool + await stRSR.connect(addr1).unstake(stakeAmount) + await advanceTime(config.unstakingDelay.toString()) + await stRSR.connect(addr1).withdraw(addr1.address, 1) - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) + // Set prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) - // Check Gnosis - expect(await token0.balanceOf(gnosis.address)).to.equal(issueAmount) + // Check initial state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(3, [token1.address], [fp('1')], false) + + // Check state remains SOUND + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + + // Check price in USD of the current RToken -- no backing swapped in yet + await expectRTokenPrice( + rTokenAsset.address, + fp('1'), + ORACLE_ERROR, + await backingManager.maxTradeSlippage(), + config.minTradeVolume.mul((await assetRegistry.erc20s()).length) + ) - // Perform Mock Bids for the new Token (addr1 has balance) - // Get all tokens for simplification - await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6)) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: toBNDecimals(sellAmt, 6), - }) + // Trigger recollateralization + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs(anyValue, token0.address, token1.address, anyValue, anyValue) - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) + const auctionTimestamp: number = await getLatestBlockTimestamp() - // End current auction, should not start any new auctions - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, token0.address, token1.address, sellAmt, toBNDecimals(sellAmt, 6)], - emitted: true, - }, - { contract: backingManager, name: 'TradeStarted', emitted: false }, - ]) + // Check auction registered + // Token0 -> Token1 Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) - // Check state - Order restablished - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal( - toBNDecimals(issueAmount, 6) - ) - expect(await rToken.totalSupply()).to.equal(issueAmount) + const t = await getTrade(backingManager, token0.address) + const sellAmt = await t.initBal() + expect(sellAmt).to.be.closeTo(issueAmount, issueAmount.mul(5).div(1000)) // within 0.5% + const remainder = issueAmount.sub(sellAmt) + + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(remainder) + expect(await token0.balanceOf(backingManager.address)).to.equal(remainder) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + + // Check price in USD of the current RToken -- no backing currently + const rTokenPrice = remainder.mul(BN_SCALE_FACTOR).div(issueAmount).add(2) // no RSR + await expectRTokenPrice( + rTokenAsset.address, + rTokenPrice, + ORACLE_ERROR, + await backingManager.maxTradeSlippage(), + config.minTradeVolume.mul((await assetRegistry.erc20s()).length) + ) - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - }) + // Check Gnosis + expect(await token0.balanceOf(gnosis.address)).to.equal(sellAmt) - it('Should recollateralize correctly when switching basket - Taking no Haircut - No RSR', async () => { - await backingManager.connect(owner).setBackingBuffer(0) + // Another call should not create any new auctions if still ongoing + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.not.emit( + backingManager, + 'TradeStarted' + ) - // Empty out the staking pool - await stRSR.connect(addr1).unstake(stakeAmount) - await advanceTime(config.unstakingDelay.toString()) - await stRSR.connect(addr1).withdraw(addr1.address, 1) + // Perform Mock Bids for the new Token (addr1 has balance) + const buyAmt = sellAmt.mul(2) // give free tokens + await token1.connect(addr1).approve(gnosis.address, toBNDecimals(buyAmt, 6)) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: toBNDecimals(buyAmt, 6), + }) - // Set prime basket - await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction, should not start any new auctions + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, token0.address, token1.address, sellAmt, toBNDecimals(buyAmt, 6)], + emitted: true, + }, + { contract: backingManager, name: 'TradeStarted', emitted: false }, + ]) - // Check initial state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) + // Check state - no Haircut taken; extra tokens sent to revenue traders + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.gt(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.be.closeTo(bn('0'), bn('100')) // up to 100 atto + expect(await token1.balanceOf(backingManager.address)).to.equal(toBNDecimals(buyAmt, 6)) + expect(await rToken.totalSupply()).to.be.gt(issueAmount) // New RToken minting - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // Check price in USD of the current RToken + expect(await rToken.basketsNeeded()).to.equal(await rToken.totalSupply()) // no haircut + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + }) - // Switch Basket - await expect(basketHandler.connect(owner).refreshBasket()) - .to.emit(basketHandler, 'BasketSet') - .withArgs(3, [token1.address], [fp('1')], false) + it('Should recollateralize correctly when switching basket - Taking average Haircut - No RSR', async () => { + // Empty out the staking pool + await stRSR.connect(addr1).unstake(stakeAmount) + await advanceTime(config.unstakingDelay.toString()) + await stRSR.connect(addr1).withdraw(addr1.address, 1) - // Check state remains SOUND - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) + // Set prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) - // Check price in USD of the current RToken -- no backing swapped in yet - await expectRTokenPrice( - rTokenAsset.address, - fp('1'), - ORACLE_ERROR, - await backingManager.maxTradeSlippage(), - config.minTradeVolume.mul((await assetRegistry.erc20s()).length) - ) + // Check initial state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(3, [token1.address], [fp('1')], false) + + // Check state remains SOUND + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + + // Check price in USD of the current RToken -- no backing swapped in yet + await expectRTokenPrice( + rTokenAsset.address, + fp('1'), + ORACLE_ERROR, + await backingManager.maxTradeSlippage(), + config.minTradeVolume.mul((await assetRegistry.erc20s()).length) + ) - // Trigger recollateralization - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) - .to.emit(backingManager, 'TradeStarted') - .withArgs(anyValue, token0.address, token1.address, anyValue, anyValue) + // Trigger recollateralization + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs(anyValue, token0.address, token1.address, anyValue, anyValue) - const auctionTimestamp: number = await getLatestBlockTimestamp() + const auctionTimestamp: number = await getLatestBlockTimestamp() - // Check auction registered - // Token0 -> Token1 Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: token1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), - }) + // Check auction registered + // Token0 -> Token1 Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) + + const t = await getTrade(backingManager, token0.address) + const sellAmt = await t.initBal() + expect(sellAmt).to.be.closeTo(issueAmount, issueAmount.mul(5).div(1000)) // within 0.5% + const remainder = issueAmount.sub(sellAmt) + + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(remainder) + expect(await token0.balanceOf(backingManager.address)).to.equal(remainder) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + + // Check price in USD of the current RToken -- no backing currently + const rTokenPrice = remainder.mul(BN_SCALE_FACTOR).div(issueAmount).add(2) // no RSR + await expectRTokenPrice( + rTokenAsset.address, + rTokenPrice, + ORACLE_ERROR, + await backingManager.maxTradeSlippage(), + config.minTradeVolume.mul((await assetRegistry.erc20s()).length) + ) - const t = await getTrade(backingManager, token0.address) - const sellAmt = await t.initBal() - expect(sellAmt).to.be.closeTo(issueAmount, issueAmount.mul(5).div(1000)) // within 0.5% - const remainder = issueAmount.sub(sellAmt) + // Check Gnosis + expect(await token0.balanceOf(gnosis.address)).to.equal(sellAmt) - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(remainder) - expect(await token0.balanceOf(backingManager.address)).to.equal(remainder) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) + // Another call should not create any new auctions if still ongoing + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.not.emit( + backingManager, + 'TradeStarted' + ) - // Check price in USD of the current RToken -- no backing currently - const rTokenPrice = remainder.mul(BN_SCALE_FACTOR).div(issueAmount).add(2) // no RSR - await expectRTokenPrice( - rTokenAsset.address, - rTokenPrice, - ORACLE_ERROR, - await backingManager.maxTradeSlippage(), - config.minTradeVolume.mul((await assetRegistry.erc20s()).length) - ) + // Perform Mock Bids for the new Token (addr1 has balance) + // Pay at market price + await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6)) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: toBNDecimals(sellAmt, 6), + }) - // Check Gnosis - expect(await token0.balanceOf(gnosis.address)).to.equal(sellAmt) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction, should not start any new auctions + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, token0.address, token1.address, sellAmt, toBNDecimals(sellAmt, 6)], + emitted: true, + }, + { contract: backingManager, name: 'TradeStarted', emitted: false }, + ]) - // Another call should not create any new auctions if still ongoing - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.not.emit( - backingManager, - 'TradeStarted' - ) + // Check state - Haircut taken, price of RToken has been reduced + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( + issueAmount, + issueAmount.mul(520).div(100000) // 520 parts in 1 miliion + ) + expect(await token0.balanceOf(backingManager.address)).to.be.closeTo(0, 10) + expect(await token1.balanceOf(backingManager.address)).to.be.closeTo( + 0, + toBNDecimals(sellAmt, 6) + ) + expect(await rToken.totalSupply()).to.equal(issueAmount) // Supply remains constant - // Perform Mock Bids for the new Token (addr1 has balance) - const buyAmt = sellAmt.mul(2) // give free tokens - await token1.connect(addr1).approve(gnosis.address, toBNDecimals(buyAmt, 6)) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: toBNDecimals(buyAmt, 6), + // Check price in USD of the current RToken + expect(await rToken.basketsNeeded()).to.be.closeTo( + issueAmount, + issueAmount.mul(520).div(1000000) // 520 parts in 1 million + ) + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) }) - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) + it('Should recollateralize correctly when switching basket - Taking maximum Haircut - No RSR', async () => { + // Empty out the staking pool + await stRSR.connect(addr1).unstake(stakeAmount) + await advanceTime(config.unstakingDelay.toString()) + await stRSR.connect(addr1).withdraw(addr1.address, 1) - // End current auction, should not start any new auctions - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, token0.address, token1.address, sellAmt, toBNDecimals(buyAmt, 6)], - emitted: true, - }, - { contract: backingManager, name: 'TradeStarted', emitted: false }, - ]) + // Set prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) - // Check state - no Haircut taken; extra tokens sent to revenue traders - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.gt(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.be.closeTo(bn('0'), bn('100')) // up to 100 atto - expect(await token1.balanceOf(backingManager.address)).to.equal(toBNDecimals(buyAmt, 6)) - expect(await rToken.totalSupply()).to.be.gt(issueAmount) // New RToken minting + // Check initial state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(3, [token1.address], [fp('1')], false) + + // Check state remains SOUND + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + + // Check price in USD of the current RToken -- no backing swapped in yet + await expectRTokenPrice( + rTokenAsset.address, + fp('1'), + ORACLE_ERROR, + await backingManager.maxTradeSlippage(), + config.minTradeVolume.mul((await assetRegistry.erc20s()).length) + ) - // Check price in USD of the current RToken - expect(await rToken.basketsNeeded()).to.equal(await rToken.totalSupply()) // no haircut - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - }) + // Trigger recollateralization + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs(anyValue, token0.address, token1.address, anyValue, anyValue) - it('Should recollateralize correctly when switching basket - Taking average Haircut - No RSR', async () => { - // Empty out the staking pool - await stRSR.connect(addr1).unstake(stakeAmount) - await advanceTime(config.unstakingDelay.toString()) - await stRSR.connect(addr1).withdraw(addr1.address, 1) + const auctionTimestamp: number = await getLatestBlockTimestamp() - // Set prime basket - await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + // Check auction registered + // Token0 -> Token1 Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) - // Check initial state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) + const t = await getTrade(backingManager, token0.address) + const sellAmt = await t.initBal() + const minBuyAmt = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + expect(sellAmt).to.be.closeTo(issueAmount, issueAmount.mul(5).div(1000)) // within 0.5% + const remainder = issueAmount.sub(sellAmt) + + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(remainder) + expect(await token0.balanceOf(backingManager.address)).to.equal(remainder) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + + // Check price in USD of the current RToken -- no backing currently + const rTokenPrice = remainder.mul(BN_SCALE_FACTOR).div(issueAmount).add(2) // no RSR + await expectRTokenPrice( + rTokenAsset.address, + rTokenPrice, + ORACLE_ERROR, + await backingManager.maxTradeSlippage(), + config.minTradeVolume.mul((await assetRegistry.erc20s()).length) + ) - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // Check Gnosis + expect(await token0.balanceOf(gnosis.address)).to.equal(sellAmt) - // Switch Basket - await expect(basketHandler.connect(owner).refreshBasket()) - .to.emit(basketHandler, 'BasketSet') - .withArgs(3, [token1.address], [fp('1')], false) + // Another call should not create any new auctions if still ongoing + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.not.emit( + backingManager, + 'TradeStarted' + ) - // Check state remains SOUND - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) + // Perform Mock Bids for the new Token (addr1 has balance) + // Pay at worst-case price + await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6)) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: toBNDecimals(minBuyAmt, 6), + }) - // Check price in USD of the current RToken -- no backing swapped in yet - await expectRTokenPrice( - rTokenAsset.address, - fp('1'), - ORACLE_ERROR, - await backingManager.maxTradeSlippage(), - config.minTradeVolume.mul((await assetRegistry.erc20s()).length) - ) - - // Trigger recollateralization - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) - .to.emit(backingManager, 'TradeStarted') - .withArgs(anyValue, token0.address, token1.address, anyValue, anyValue) - - const auctionTimestamp: number = await getLatestBlockTimestamp() - - // Check auction registered - // Token0 -> Token1 Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: token1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), - }) - - const t = await getTrade(backingManager, token0.address) - const sellAmt = await t.initBal() - expect(sellAmt).to.be.closeTo(issueAmount, issueAmount.mul(5).div(1000)) // within 0.5% - const remainder = issueAmount.sub(sellAmt) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(remainder) - expect(await token0.balanceOf(backingManager.address)).to.equal(remainder) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - - // Check price in USD of the current RToken -- no backing currently - const rTokenPrice = remainder.mul(BN_SCALE_FACTOR).div(issueAmount).add(2) // no RSR - await expectRTokenPrice( - rTokenAsset.address, - rTokenPrice, - ORACLE_ERROR, - await backingManager.maxTradeSlippage(), - config.minTradeVolume.mul((await assetRegistry.erc20s()).length) - ) - - // Check Gnosis - expect(await token0.balanceOf(gnosis.address)).to.equal(sellAmt) - - // Another call should not create any new auctions if still ongoing - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.not.emit( - backingManager, - 'TradeStarted' - ) - - // Perform Mock Bids for the new Token (addr1 has balance) - // Pay at market price - await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6)) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: toBNDecimals(sellAmt, 6), - }) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // End current auction, should not start any new auctions - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, token0.address, token1.address, sellAmt, toBNDecimals(sellAmt, 6)], - emitted: true, - }, - { contract: backingManager, name: 'TradeStarted', emitted: false }, - ]) - - // Check state - Haircut taken, price of RToken has been reduced - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( - issueAmount, - issueAmount.mul(520).div(100000) // 520 parts in 1 miliion - ) - expect(await token0.balanceOf(backingManager.address)).to.be.closeTo(0, 10) - expect(await token1.balanceOf(backingManager.address)).to.be.closeTo( - 0, - toBNDecimals(sellAmt, 6) - ) - expect(await rToken.totalSupply()).to.equal(issueAmount) // Supply remains constant - - // Check price in USD of the current RToken - expect(await rToken.basketsNeeded()).to.be.closeTo( - issueAmount, - issueAmount.mul(520).div(1000000) // 520 parts in 1 million - ) - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - }) - - it('Should recollateralize correctly when switching basket - Taking maximum Haircut - No RSR', async () => { - // Empty out the staking pool - await stRSR.connect(addr1).unstake(stakeAmount) - await advanceTime(config.unstakingDelay.toString()) - await stRSR.connect(addr1).withdraw(addr1.address, 1) - - // Set prime basket - await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) - - // Check initial state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - - // Switch Basket - await expect(basketHandler.connect(owner).refreshBasket()) - .to.emit(basketHandler, 'BasketSet') - .withArgs(3, [token1.address], [fp('1')], false) - - // Check state remains SOUND - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - - // Check price in USD of the current RToken -- no backing swapped in yet - await expectRTokenPrice( - rTokenAsset.address, - fp('1'), - ORACLE_ERROR, - await backingManager.maxTradeSlippage(), - config.minTradeVolume.mul((await assetRegistry.erc20s()).length) - ) - - // Trigger recollateralization - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) - .to.emit(backingManager, 'TradeStarted') - .withArgs(anyValue, token0.address, token1.address, anyValue, anyValue) - - const auctionTimestamp: number = await getLatestBlockTimestamp() - - // Check auction registered - // Token0 -> Token1 Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: token1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), - }) - - const t = await getTrade(backingManager, token0.address) - const sellAmt = await t.initBal() - const minBuyAmt = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) - expect(sellAmt).to.be.closeTo(issueAmount, issueAmount.mul(5).div(1000)) // within 0.5% - const remainder = issueAmount.sub(sellAmt) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(remainder) - expect(await token0.balanceOf(backingManager.address)).to.equal(remainder) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - - // Check price in USD of the current RToken -- no backing currently - const rTokenPrice = remainder.mul(BN_SCALE_FACTOR).div(issueAmount).add(2) // no RSR - await expectRTokenPrice( - rTokenAsset.address, - rTokenPrice, - ORACLE_ERROR, - await backingManager.maxTradeSlippage(), - config.minTradeVolume.mul((await assetRegistry.erc20s()).length) - ) - - // Check Gnosis - expect(await token0.balanceOf(gnosis.address)).to.equal(sellAmt) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction, should not start any new auctions + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, token0.address, token1.address, sellAmt, toBNDecimals(minBuyAmt, 6)], + emitted: true, + }, + { contract: backingManager, name: 'TradeStarted', emitted: false }, + ]) - // Another call should not create any new auctions if still ongoing - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.not.emit( - backingManager, - 'TradeStarted' - ) + // Check state - Haircut taken, price of RToken has been reduced + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + const remainingValue = toBNDecimals(minBuyAmt, 6).mul(bn('1e12')).add(remainder) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( + remainingValue, + remainingValue.div(bn('5e3')) + ) + expect(await token0.balanceOf(backingManager.address)).to.be.closeTo(0, 10) + expect(await token1.balanceOf(backingManager.address)).to.equal( + toBNDecimals(minBuyAmt, 6) + ) + expect(await rToken.totalSupply()).to.equal(issueAmount) // Supply remains constant + expect(await rToken.basketsNeeded()).to.be.closeTo( + issueAmount, + issueAmount.mul(31).div(1000) // within 3.1% + ) - // Perform Mock Bids for the new Token (addr1 has balance) - // Pay at worst-case price - await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6)) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: toBNDecimals(minBuyAmt, 6), + // Check price in USD of the current RToken + const rTokenPrice2 = remainingValue.mul(BN_SCALE_FACTOR).div(issueAmount) + expect(rTokenPrice2).to.be.gte(fp('0.97')) // less than 3% loss + await expectRTokenPrice(rTokenAsset.address, rTokenPrice2, ORACLE_ERROR) }) - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) + it('Should recollateralize correctly when switching basket - Necessary RSR overcollateralization', async () => { + // Eliminate minTradeVolume + await backingManager.connect(owner).setMinTradeVolume(0) - // End current auction, should not start any new auctions - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, token0.address, token1.address, sellAmt, toBNDecimals(minBuyAmt, 6)], - emitted: true, - }, - { contract: backingManager, name: 'TradeStarted', emitted: false }, - ]) + // Reduce stake to just necessary overcollateralization + const necessaryStake = issueAmount.mul(51).div(1000) // 5.1% of RToken supply + await stRSR.connect(addr1).unstake(stakeAmount.sub(necessaryStake)) + await advanceTime(config.unstakingDelay.toString()) + await stRSR.withdraw(addr1.address, 1) + expect(await rsr.balanceOf(stRSR.address)).to.equal(necessaryStake) - // Check state - Haircut taken, price of RToken has been reduced - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - const remainingValue = toBNDecimals(minBuyAmt, 6).mul(bn('1e12')).add(remainder) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.closeTo( - remainingValue, - remainingValue.div(bn('5e3')) - ) - expect(await token0.balanceOf(backingManager.address)).to.be.closeTo(0, 10) - expect(await token1.balanceOf(backingManager.address)).to.equal(toBNDecimals(minBuyAmt, 6)) - expect(await rToken.totalSupply()).to.equal(issueAmount) // Supply remains constant - expect(await rToken.basketsNeeded()).to.be.closeTo( - issueAmount, - issueAmount.mul(31).div(1000) // within 3.1% - ) + // Set prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) - // Check price in USD of the current RToken - const rTokenPrice2 = remainingValue.mul(BN_SCALE_FACTOR).div(issueAmount) - expect(rTokenPrice2).to.be.gte(fp('0.97')) // less than 3% loss - await expectRTokenPrice(rTokenAsset.address, rTokenPrice2, ORACLE_ERROR) - }) + // Check initial state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Check stakes + expect(await rsr.balanceOf(stRSR.address)).to.equal(necessaryStake) + expect(await stRSR.balanceOf(addr1.address)).to.equal(necessaryStake) + + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(3, [token1.address], [fp('1')], false) + + // Check state remains SOUND + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken -- retains price because of + // over-collateralization + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Trigger recollateralization + const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs( + anyValue, + token0.address, + token1.address, + sellAmt, + toBNDecimals(minBuyAmt, 6).add(1) + ) - it('Should recollateralize correctly when switching basket - Necessary RSR overcollateralization', async () => { - // Eliminate minTradeVolume - await backingManager.connect(owner).setMinTradeVolume(0) + let auctionTimestamp: number = await getLatestBlockTimestamp() - // Reduce stake to just necessary overcollateralization - const necessaryStake = issueAmount.mul(51).div(1000) // 5.1% of RToken supply - await stRSR.connect(addr1).unstake(stakeAmount.sub(necessaryStake)) - await advanceTime(config.unstakingDelay.toString()) - await stRSR.withdraw(addr1.address, 1) - expect(await rsr.balanceOf(stRSR.address)).to.equal(necessaryStake) + // Check auction registered + // Token0 -> Token1 Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) - // Set prime basket - await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + // Asset value is zero, everything was moved to the Market + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) - // Check initial state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) + // Check Gnosis + expect(await token0.balanceOf(gnosis.address)).to.equal(issueAmount) - // Check price in USD of the current - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // Another call should not create any new auctions if still ongoing + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.not.emit( + backingManager, + 'TradeStarted' + ) - // Check stakes - expect(await rsr.balanceOf(stRSR.address)).to.equal(necessaryStake) - expect(await stRSR.balanceOf(addr1.address)).to.equal(necessaryStake) + // Perform Mock Bids for the new Token (addr1 has balance) + // Pay at worst-case price + await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6).add(1)) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: toBNDecimals(minBuyAmt, 6).add(1), + }) - // Switch Basket - await expect(basketHandler.connect(owner).refreshBasket()) - .to.emit(basketHandler, 'BasketSet') - .withArgs(3, [token1.address], [fp('1')], false) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction, should start a new one to sell RSR for collateral + // ~3e18 Tokens left to buy - Sets Buy amount as independent value + const buyAmtBidRSR: BigNumber = sellAmt.sub(minBuyAmt) + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [ + anyValue, + token0.address, + token1.address, + sellAmt, + toBNDecimals(minBuyAmt, 6).add(1), + ], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + args: [ + anyValue, + rsr.address, + token1.address, + anyValue, + toBNDecimals(buyAmtBidRSR, 6).add(1), + ], + emitted: true, + }, + ]) - // Check state remains SOUND - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) + auctionTimestamp = await getLatestBlockTimestamp() - // Check price in USD of the current RToken -- retains price because of - // over-collateralization - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // RSR -> Token1 Auction + await expectTrade(backingManager, { + sell: rsr.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('1'), + }) - // Trigger recollateralization - const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) - const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + const t = await getTrade(backingManager, rsr.address) + const sellAmtRSR = await t.initBal() + expect(toBNDecimals(buyAmtBidRSR, 6)).to.equal( + toBNDecimals(await toMinBuyAmt(sellAmtRSR, fp('1'), fp('1')), 6) + ) - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) - .to.emit(backingManager, 'TradeStarted') - .withArgs( - anyValue, - token0.address, - token1.address, - sellAmt, + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal( toBNDecimals(minBuyAmt, 6).add(1) ) + expect(await rToken.totalSupply()).to.equal(issueAmount) - let auctionTimestamp: number = await getLatestBlockTimestamp() + // Check Gnosis + expect(await rsr.balanceOf(gnosis.address)).to.equal(sellAmtRSR) - // Check auction registered - // Token0 -> Token1 Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: token1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), - }) + // Another call should not create any new auctions if still ongoing + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.not.emit( + backingManager, + 'TradeStarted' + ) - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - // Asset value is zero, everything was moved to the Market - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) + // Perform Mock Bids for the new Token (addr1 has balance) + // Cover buyAmtBidRSR which is all the RSR required + await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmtRSR, 6)) + await gnosis.placeBid(1, { + bidder: addr1.address, + sellAmount: sellAmtRSR, + buyAmount: toBNDecimals(buyAmtBidRSR, 6), + }) - // Check Gnosis - expect(await token0.balanceOf(gnosis.address)).to.equal(issueAmount) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction, should start final dust auction + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [ + anyValue, + rsr.address, + token1.address, + sellAmtRSR, + toBNDecimals(buyAmtBidRSR, 6), + ], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + emitted: false, + }, + ]) - // Another call should not create any new auctions if still ongoing - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.not.emit( - backingManager, - 'TradeStarted' - ) + // Check state - Order restablished + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal( + toBNDecimals(issueAmount, 6) + ) + expect(await rToken.totalSupply()).to.equal(issueAmount) + expect(await rToken.basketsNeeded()).to.equal(issueAmount) // no haircut - // Perform Mock Bids for the new Token (addr1 has balance) - // Pay at worst-case price - await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6).add(1)) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: toBNDecimals(minBuyAmt, 6).add(1), + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) }) - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) + it('Should recollateralize correctly when switching basket - Using revenue asset token for remainder', async () => { + // Set prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) - // End current auction, should start a new one to sell RSR for collateral - // ~3e18 Tokens left to buy - Sets Buy amount as independent value - const buyAmtBidRSR: BigNumber = sellAmt.sub(minBuyAmt) - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [ + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(3, [token1.address], [fp('1')], false) + + // Check state remains SOUND + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken -- retains price because of + // over-collateralization + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Trigger recollateralization + const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs( anyValue, token0.address, token1.address, sellAmt, - toBNDecimals(minBuyAmt, 6).add(1), - ], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - args: [ - anyValue, - rsr.address, - token1.address, - anyValue, - toBNDecimals(buyAmtBidRSR, 6).add(1), - ], - emitted: true, - }, - ]) + toBNDecimals(minBuyAmt, 6).add(1) + ) - auctionTimestamp = await getLatestBlockTimestamp() + let auctionTimestamp: number = await getLatestBlockTimestamp() - // RSR -> Token1 Auction - await expectTrade(backingManager, { - sell: rsr.address, - buy: token1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('1'), - }) + // Check auction registered + // Token0 -> Token1 Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) - const t = await getTrade(backingManager, rsr.address) - const sellAmtRSR = await t.initBal() - expect(toBNDecimals(buyAmtBidRSR, 6)).to.equal( - toBNDecimals(await toMinBuyAmt(sellAmtRSR, fp('1'), fp('1')), 6) - ) + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + // Asset value is zero, everything was moved to the Market + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken -- retains price because of + // over-collateralization + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Check Gnosis + expect(await token0.balanceOf(gnosis.address)).to.equal(issueAmount) + + // Perform Mock Bids for the new Token (addr1 has balance) + // Get fair price - minBuyAmt + await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6).add(1)) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: toBNDecimals(minBuyAmt, 6).add(1), + }) - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal( - toBNDecimals(minBuyAmt, 6).add(1) - ) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check Gnosis - expect(await rsr.balanceOf(gnosis.address)).to.equal(sellAmtRSR) - - // Another call should not create any new auctions if still ongoing - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.not.emit( - backingManager, - 'TradeStarted' - ) - - // Perform Mock Bids for the new Token (addr1 has balance) - // Cover buyAmtBidRSR which is all the RSR required - await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmtRSR, 6)) - await gnosis.placeBid(1, { - bidder: addr1.address, - sellAmount: sellAmtRSR, - buyAmount: toBNDecimals(buyAmtBidRSR, 6), - }) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // End current auction, should start final dust auction - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [ - anyValue, - rsr.address, - token1.address, - sellAmtRSR, - toBNDecimals(buyAmtBidRSR, 6), - ], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - emitted: false, - }, - ]) - - // Check state - Order restablished - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal( - toBNDecimals(issueAmount, 6) - ) - expect(await rToken.totalSupply()).to.equal(issueAmount) - expect(await rToken.basketsNeeded()).to.equal(issueAmount) // no haircut - - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - }) - - it('Should recollateralize correctly when switching basket - Using revenue asset token for remainder', async () => { - // Set prime basket - await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) - - // Switch Basket - await expect(basketHandler.connect(owner).refreshBasket()) - .to.emit(basketHandler, 'BasketSet') - .withArgs(3, [token1.address], [fp('1')], false) - - // Check state remains SOUND - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check price in USD of the current RToken -- retains price because of - // over-collateralization - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - - // Trigger recollateralization - const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) - const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) - - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) - .to.emit(backingManager, 'TradeStarted') - .withArgs( - anyValue, - token0.address, - token1.address, - sellAmt, - toBNDecimals(minBuyAmt, 6).add(1) - ) - - let auctionTimestamp: number = await getLatestBlockTimestamp() - - // Check auction registered - // Token0 -> Token1 Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: token1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), - }) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - // Asset value is zero, everything was moved to the Market - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check price in USD of the current RToken -- retains price because of - // over-collateralization - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - - // Check Gnosis - expect(await token0.balanceOf(gnosis.address)).to.equal(issueAmount) - - // Perform Mock Bids for the new Token (addr1 has balance) - // Get fair price - minBuyAmt - await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6).add(1)) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: toBNDecimals(minBuyAmt, 6).add(1), - }) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // End current auction, should start a new one to sell a new revenue token instead of RSR - // About 3e18 Tokens left to buy - Sets Buy amount as independent value - const buyAmtBidRevToken: BigNumber = sellAmt.sub(minBuyAmt) - - // Send the excess revenue tokens to backing manager - should be used instead of RSR - // Set price = $1 as expected - await aaveToken.connect(owner).mint(backingManager.address, buyAmtBidRevToken.mul(2)) - await setOraclePrice(aaveAsset.address, bn('1e8')) - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [ - anyValue, - token0.address, - token1.address, - sellAmt, - toBNDecimals(minBuyAmt, 6).add(1), - ], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - args: [ - anyValue, - aaveToken.address, - token1.address, - anyValue, - toBNDecimals(buyAmtBidRevToken, 6).add(1), - ], - emitted: true, - }, - ]) - - auctionTimestamp = await getLatestBlockTimestamp() - - // Aave Token -> Token1 Auction - await expectTrade(backingManager, { - sell: aaveToken.address, - buy: token1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('1'), - }) - - const t = await getTrade(backingManager, aaveToken.address) - const sellAmtRevToken = await t.initBal() - expect(toBNDecimals(buyAmtBidRevToken, 6).add(1)).to.equal( - toBNDecimals(await toMinBuyAmt(sellAmtRevToken, fp('1'), fp('1')), 6).add(1) - ) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal( - toBNDecimals(minBuyAmt, 6).add(1) - ) - expect(await aaveToken.balanceOf(backingManager.address)).to.equal( - buyAmtBidRevToken.mul(2).sub(sellAmtRevToken) - ) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check Gnosis - expect(await aaveToken.balanceOf(gnosis.address)).to.equal(sellAmtRevToken) - - // Perform Mock Bids for the new Token (addr1 has balance) - // Cover buyAmtBidRevToken which is all the amount required - await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmtRevToken, 6)) - await gnosis.placeBid(1, { - bidder: addr1.address, - sellAmount: sellAmtRevToken, - buyAmount: toBNDecimals(buyAmtBidRevToken, 6).add(1), - }) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // End current auction - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [ - anyValue, - aaveToken.address, - token1.address, - sellAmtRevToken, - toBNDecimals(buyAmtBidRevToken, 6).add(1), - ], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - emitted: false, - }, - ]) - - // Check state - Order restablished - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal( - toBNDecimals(issueAmount, 6).add(1) - ) - expect(await aaveToken.balanceOf(backingManager.address)).to.be.closeTo(bn('0'), 100) // distributor leaves some - expect(await rToken.totalSupply()).to.be.closeTo(issueAmount, fp('0.000001')) // we have a bit more - - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - - // Stakes unchanged - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) - expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) - }) - - it('Should recollateralize correctly in case of default - Using RSR for remainder', async () => { - // Register Collateral - await assetRegistry.connect(owner).register(backupCollateral1.address) - - // Set backup configuration - USDT as backup - await basketHandler - .connect(owner) - .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [backupToken1.address]) - - // Set new max auction size for asset (will require 2 auctions) - const chainlinkFeed = ( - await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) - ) - const CollateralFactory: ContractFactory = await ethers.getContractFactory('FiatCollateral') - const newCollateral0: FiatCollateral = await CollateralFactory.deploy({ - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: token0.address, - maxTradeVolume: fp('25'), - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: await backupCollateral1.delayUntilDefault(), - }) - - // Perform swap - await assetRegistry.connect(owner).swapRegistered(newCollateral0.address) - expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) - await basketHandler.refreshBasket() - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - - // Advance time post warmup period - SOUND just regained - await advanceTime(Number(config.warmupPeriod) + 1) - - // Check initial state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - - // Check stakes - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) - expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) - - // Set Token0 to default - 50% price reduction - await setOraclePrice(newCollateral0.address, bn('0.5e8')) - - // Mark default as probable - await assetRegistry.refresh() - expect(await basketHandler.status()).to.equal(CollateralStatus.IFFY) - - // Advance time post collateral's default delay - await advanceTime((await newCollateral0.delayUntilDefault()).toString()) - - // Confirm default and trigger basket switch - await basketHandler.refreshBasket() - - // Advance time post warmup period - SOUND just regained - await advanceTime(Number(config.warmupPeriod) + 1) - - // Check new state after basket switch - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - issueAmount.div(2) - ) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - - // Running auctions will trigger recollateralization - skip half of the balance is available - // maxTradeVolume is 25 - const sellAmtBeforeSlippage: BigNumber = ( - await token0.balanceOf(backingManager.address) - ).div(2) - const sellAmt = sellAmtBeforeSlippage - .mul(BN_SCALE_FACTOR) - .div(BN_SCALE_FACTOR.add(ORACLE_ERROR)) - const minBuyAmt = await toMinBuyAmt(sellAmt, fp('0.5'), fp('1')) - - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) - .to.emit(backingManager, 'TradeStarted') - .withArgs(anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt) - - let auctionTimestamp = await getLatestBlockTimestamp() - - // Token0 -> Backup Token Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), - }) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - issueAmount.div(2).sub(sellAmt.div(2)) - ) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount.sub(sellAmt)) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - - // Perform Mock Bids (addr1 has balance) - // Pay at worst-case price - await backupToken1.connect(addr1).approve(gnosis.address, minBuyAmt) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: minBuyAmt, - }) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // Run auctions - will end current, and will open a new auction for the same amount - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - args: [anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt], - emitted: true, - }, - ]) - const leftoverSellAmt = issueAmount.sub(sellAmt.mul(2)) - - // Check new auction - // Token0 -> Backup Token Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: backupToken1.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), - externalId: bn('1'), - }) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - minBuyAmt.add(leftoverSellAmt.div(2)) - ) - expect(await token0.balanceOf(backingManager.address)).to.equal(leftoverSellAmt) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(minBuyAmt) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - - // Perform Mock Bids (addr1 has balance) - // Pay at worst-case price - await backupToken1.connect(addr1).approve(gnosis.address, minBuyAmt) - await gnosis.placeBid(1, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: minBuyAmt, - }) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // Check staking situation remains unchanged - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) - expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // Run auctions - will end current, and will open a new auction for the same amount - const leftoverMinBuyAmt = await toMinBuyAmt(leftoverSellAmt, fp('0.5'), fp('1')) - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - args: [ - anyValue, - token0.address, - backupToken1.address, - leftoverSellAmt, - leftoverMinBuyAmt, - ], - emitted: true, - }, - ]) - - // Check new auction - // Token0 -> Backup Token Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: backupToken1.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), - externalId: bn('2'), - }) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - minBuyAmt.mul(2) - ) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(minBuyAmt.mul(2)) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - - // Perform Mock Bids (addr1 has balance) - // Pay at worst-case price - await backupToken1.connect(addr1).approve(gnosis.address, minBuyAmt) - await gnosis.placeBid(2, { - bidder: addr1.address, - sellAmount: leftoverSellAmt, - buyAmount: leftoverMinBuyAmt, - }) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // Check staking situation remains unchanged - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) - expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) - - // End current auction, should start a new one to sell RSR for collateral - // ~51e18 Tokens left to buy - Sets Buy amount as independent value - const buyAmtBidRSR: BigNumber = issueAmount - .sub(minBuyAmt.mul(2).add(leftoverMinBuyAmt)) - .add(1) - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [ - anyValue, - token0.address, - backupToken1.address, - leftoverSellAmt, - leftoverMinBuyAmt, - ], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - args: [anyValue, rsr.address, backupToken1.address, anyValue, buyAmtBidRSR], - emitted: true, - }, - ]) - - auctionTimestamp = await getLatestBlockTimestamp() - - // Check new auction - // RSR -> Backup Token Auction - await expectTrade(backingManager, { - sell: rsr.address, - buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('3'), - }) - - const t = await getTrade(backingManager, rsr.address) - const sellAmtRSR = await t.initBal() - expect(buyAmtBidRSR).to.equal(await toMinBuyAmt(sellAmtRSR, fp('1'), fp('1'))) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - minBuyAmt.mul(2).add(leftoverMinBuyAmt) - ) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal( - minBuyAmt.mul(2).add(leftoverMinBuyAmt) - ) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check price in USD of the current RToken - half now - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - - // Should have seized RSR - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmtRSR)) // Sent to market (auction) - - // Perform Mock Bids for RSR (addr1 has balance) - // Pay at worst-case price - await backupToken1.connect(addr1).approve(gnosis.address, buyAmtBidRSR) - await gnosis.placeBid(3, { - bidder: addr1.address, - sellAmount: sellAmtRSR, - buyAmount: buyAmtBidRSR, - }) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // End current auction; should not start a new one - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, rsr.address, backupToken1.address, sellAmtRSR, buyAmtBidRSR], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - emitted: false, - }, - ]) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) // no dust - - // Should have small excess now - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - issueAmount.add(1) - ) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(issueAmount.add(1)) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check price in USD of the current RToken - Remains the same - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - }) - - it('Should overcollateralize when minTradeVolume is larger than the collateral deficit', async () => { - // Register Collateral - await assetRegistry.connect(owner).register(backupCollateral1.address) - - // Set backup configuration - USDT as backup - await basketHandler - .connect(owner) - .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [backupToken1.address]) - - // Check initial state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - - // Check stakes - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) - expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) - - // Set Token0 to default - 50% price reduction - await setOraclePrice(collateral0.address, bn('0.5e8')) - - // Mark default as probable - await assetRegistry.refresh() - expect(await basketHandler.status()).to.equal(CollateralStatus.IFFY) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction, should start a new one to sell a new revenue token instead of RSR + // About 3e18 Tokens left to buy - Sets Buy amount as independent value + const buyAmtBidRevToken: BigNumber = sellAmt.sub(minBuyAmt) + + // Send the excess revenue tokens to backing manager - should be used instead of RSR + // Set price = $1 as expected + await aaveToken.connect(owner).mint(backingManager.address, buyAmtBidRevToken.mul(2)) + await setOraclePrice(aaveAsset.address, bn('1e8')) + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [ + anyValue, + token0.address, + token1.address, + sellAmt, + toBNDecimals(minBuyAmt, 6).add(1), + ], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + args: [ + anyValue, + aaveToken.address, + token1.address, + anyValue, + toBNDecimals(buyAmtBidRevToken, 6).add(1), + ], + emitted: true, + }, + ]) - // Advance time post collateral's default delay - await advanceTime((await collateral0.delayUntilDefault()).toString()) + auctionTimestamp = await getLatestBlockTimestamp() - // Confirm default and trigger basket switch - await assetRegistry.refresh() - await basketHandler.refreshBasket() + // Aave Token -> Token1 Auction + await expectTrade(backingManager, { + sell: aaveToken.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('1'), + }) - // Advance time post warmup period - SOUND just regained - await advanceTime(Number(config.warmupPeriod) + 1) + const t = await getTrade(backingManager, aaveToken.address) + const sellAmtRevToken = await t.initBal() + expect(toBNDecimals(buyAmtBidRevToken, 6).add(1)).to.equal( + toBNDecimals(await toMinBuyAmt(sellAmtRevToken, fp('1'), fp('1')), 6).add(1) + ) - // Check new state after basket switch - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - // Asset value is half - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - issueAmount.div(2) - ) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) - // retains value because over-collateralization, true - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal( + toBNDecimals(minBuyAmt, 6).add(1) + ) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal( + buyAmtBidRevToken.mul(2).sub(sellAmtRevToken) + ) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check Gnosis + expect(await aaveToken.balanceOf(gnosis.address)).to.equal(sellAmtRevToken) + + // Perform Mock Bids for the new Token (addr1 has balance) + // Cover buyAmtBidRevToken which is all the amount required + await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmtRevToken, 6)) + await gnosis.placeBid(1, { + bidder: addr1.address, + sellAmount: sellAmtRevToken, + buyAmount: toBNDecimals(buyAmtBidRevToken, 6).add(1), + }) - // Running auctions will trigger recollateralization - const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) - const minBuyAmt = await toMinBuyAmt(sellAmt, fp('0.5'), fp('1')) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [ + anyValue, + aaveToken.address, + token1.address, + sellAmtRevToken, + toBNDecimals(buyAmtBidRevToken, 6).add(1), + ], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + emitted: false, + }, + ]) - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) - .to.emit(backingManager, 'TradeStarted') - .withArgs(anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt) + // Check state - Order restablished + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal( + toBNDecimals(issueAmount, 6).add(1) + ) + expect(await aaveToken.balanceOf(backingManager.address)).to.be.closeTo(bn('0'), 100) // distributor leaves some + expect(await rToken.totalSupply()).to.be.closeTo(issueAmount, fp('0.000001')) // we have a bit more - const auctionTimestamp = await getLatestBlockTimestamp() + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - // Token0 -> Backup Token Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), + // Stakes unchanged + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) }) - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - // Asset value is zero, the skip collateral held is defaulted - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount.sub(sellAmt)) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) + it('Should recollateralize correctly in case of default - Using RSR for remainder', async () => { + // Register Collateral + await assetRegistry.connect(owner).register(backupCollateral1.address) - // Check price in USD of the current RToken - retains value from over-collateralization - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // Set backup configuration - USDT as backup + await basketHandler + .connect(owner) + .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [backupToken1.address]) - // Perform Mock Bids (addr1 has balance) - // Assume fair price, get half of the tokens (because price reduction was 50%) - await backupToken1.connect(addr1).approve(gnosis.address, sellAmt.div(2)) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: sellAmt.div(2), - }) + // Set new max auction size for asset (will require 2 auctions) + const chainlinkFeed = ( + await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) + ) + const CollateralFactory: ContractFactory = await ethers.getContractFactory( + 'FiatCollateral' + ) + const newCollateral0: FiatCollateral = await CollateralFactory.deploy({ + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: chainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: token0.address, + maxTradeVolume: fp('25'), + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: await backupCollateral1.delayUntilDefault(), + }) - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) + // Perform swap + await assetRegistry.connect(owner).swapRegistered(newCollateral0.address) + expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) + await basketHandler.refreshBasket() + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - // Raise minTradeVolume to the whole issueAmount - await backingManager.connect(owner).setMinTradeVolume(issueAmount) + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) - // Run auctions - RSR Auction launched for minTradeVolume - const minBuyAmt2 = issueAmount.mul(100).div(101).add(3) - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, token0.address, backupToken1.address, sellAmt, sellAmt.div(2)], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - args: [anyValue, rsr.address, backupToken1.address, anyValue, minBuyAmt2], - emitted: true, - }, - ]) + // Check initial state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) - const t = await getTrade(backingManager, rsr.address) - const sellAmt2 = await t.initBal() - expect(minBuyAmt2).to.equal(await toMinBuyAmt(sellAmt2, fp('1'), fp('1'))) - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmt2)) + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) // no dust + // Check stakes + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) - // Should have half of the value - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - issueAmount.div(2) - ) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(sellAmt.div(2)) - expect(await rToken.totalSupply()).to.equal(issueAmount) + // Set Token0 to default - 50% price reduction + await setOraclePrice(newCollateral0.address, bn('0.5e8')) - // Check price in USD of the current RToken - overcollateralized and still targeting 1 - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // Mark default as probable + await assetRegistry.refresh() + expect(await basketHandler.status()).to.equal(CollateralStatus.IFFY) - // Perform Mock Bids (addr1 has balance) - // Assume fair price, get half of the tokens (because price reduction was 50%) - await backupToken1.connect(addr1).approve(gnosis.address, minBuyAmt2) - await gnosis.placeBid(1, { - bidder: addr1.address, - sellAmount: sellAmt2, - buyAmount: minBuyAmt2, - }) + // Advance time post collateral's default delay + await advanceTime((await newCollateral0.delayUntilDefault()).toString()) - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) + // Confirm default and trigger basket switch + await basketHandler.refreshBasket() - // Run auctions - NO RSR Auction launched but haircut taken - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, rsr.address, backupToken1.address, sellAmt2, minBuyAmt2], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - emitted: false, - }, - { - contract: rToken, - name: 'BasketsNeededChanged', - emitted: true, - }, - ]) + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) - // Check state - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmt2)) - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) // no dust + // Check new state after basket switch + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + issueAmount.div(2) + ) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Running auctions will trigger recollateralization - skip half of the balance is available + // maxTradeVolume is 25 + const sellAmtBeforeSlippage: BigNumber = ( + await token0.balanceOf(backingManager.address) + ).div(2) + const sellAmt = sellAmtBeforeSlippage + .mul(BN_SCALE_FACTOR) + .div(BN_SCALE_FACTOR.add(ORACLE_ERROR)) + const minBuyAmt = await toMinBuyAmt(sellAmt, fp('0.5'), fp('1')) + + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs(anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt) + + let auctionTimestamp = await getLatestBlockTimestamp() + + // Token0 -> Backup Token Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: backupToken1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) + + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + issueAmount.div(2).sub(sellAmt.div(2)) + ) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount.sub(sellAmt)) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Perform Mock Bids (addr1 has balance) + // Pay at worst-case price + await backupToken1.connect(addr1).approve(gnosis.address, minBuyAmt) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: minBuyAmt, + }) - // Should have 1.5x starting amounts due to minTradeVolume - const bonus = issueAmount.div(2).add(minBuyAmt2) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(bonus) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(bonus) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Run auctions - will end current, and will open a new auction for the same amount + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + args: [anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt], + emitted: true, + }, + ]) + const leftoverSellAmt = issueAmount.sub(sellAmt.mul(2)) + + // Check new auction + // Token0 -> Backup Token Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: backupToken1.address, + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), + externalId: bn('1'), + }) - const supply = bonus.sub(bonus.mul(config.backingBuffer).div(fp('1'))) - // Should mint the excess in order to re-handout to RToken holders and stakers - expect(await rToken.totalSupply()).to.be.closeTo(supply, supply.div(bn('1e6'))) - expect(await rToken.totalSupply()).to.be.gte(supply) + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + minBuyAmt.add(leftoverSellAmt.div(2)) + ) + expect(await token0.balanceOf(backingManager.address)).to.equal(leftoverSellAmt) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(minBuyAmt) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Perform Mock Bids (addr1 has balance) + // Pay at worst-case price + await backupToken1.connect(addr1).approve(gnosis.address, minBuyAmt) + await gnosis.placeBid(1, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: minBuyAmt, + }) - // Check price in USD of the current RToken - overcollateralized and still targeting 1 - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - }) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Check staking situation remains unchanged + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Run auctions - will end current, and will open a new auction for the same amount + const leftoverMinBuyAmt = await toMinBuyAmt(leftoverSellAmt, fp('0.5'), fp('1')) + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + args: [ + anyValue, + token0.address, + backupToken1.address, + leftoverSellAmt, + leftoverMinBuyAmt, + ], + emitted: true, + }, + ]) - it('Should recollateralize correctly in case of default - Using RSR for remainder - Multiple tokens and auctions - No overshoot', async () => { - // Set backing buffer to zero for simplification - await backingManager.connect(owner).setBackingBuffer(0) + // Check new auction + // Token0 -> Backup Token Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: backupToken1.address, + endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), + externalId: bn('2'), + }) - // Register Collateral - await assetRegistry.connect(owner).register(backupCollateral1.address) - await assetRegistry.connect(owner).register(backupCollateral2.address) + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + minBuyAmt.mul(2) + ) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(minBuyAmt.mul(2)) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Perform Mock Bids (addr1 has balance) + // Pay at worst-case price + await backupToken1.connect(addr1).approve(gnosis.address, minBuyAmt) + await gnosis.placeBid(2, { + bidder: addr1.address, + sellAmount: leftoverSellAmt, + buyAmount: leftoverMinBuyAmt, + }) - // Set backup configuration - USDT as backup - await basketHandler - .connect(owner) - .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(4), [ - backupToken1.address, - backupToken2.address, + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Check staking situation remains unchanged + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) + + // End current auction, should start a new one to sell RSR for collateral + // ~51e18 Tokens left to buy - Sets Buy amount as independent value + const buyAmtBidRSR: BigNumber = issueAmount + .sub(minBuyAmt.mul(2).add(leftoverMinBuyAmt)) + .add(1) + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [ + anyValue, + token0.address, + backupToken1.address, + leftoverSellAmt, + leftoverMinBuyAmt, + ], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + args: [anyValue, rsr.address, backupToken1.address, anyValue, buyAmtBidRSR], + emitted: true, + }, ]) - // Check initial state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) - expect(await backupToken2.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) + auctionTimestamp = await getLatestBlockTimestamp() - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // Check new auction + // RSR -> Backup Token Auction + await expectTrade(backingManager, { + sell: rsr.address, + buy: backupToken1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('3'), + }) - // Check stakes - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) - expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) + const t = await getTrade(backingManager, rsr.address) + const sellAmtRSR = await t.initBal() + expect(buyAmtBidRSR).to.equal(await toMinBuyAmt(sellAmtRSR, fp('1'), fp('1'))) - // Set Token0 to default - 50% price reduction in 100% of the basket - await setOraclePrice(collateral0.address, bn('0.5e8')) + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + minBuyAmt.mul(2).add(leftoverMinBuyAmt) + ) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal( + minBuyAmt.mul(2).add(leftoverMinBuyAmt) + ) + expect(await rToken.totalSupply()).to.equal(issueAmount) - // Mark default as probable - await assetRegistry.refresh() - expect(await basketHandler.status()).to.equal(CollateralStatus.IFFY) + // Check price in USD of the current RToken - half now + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - // Advance time post collateral's default delay - await advanceTime((await collateral0.delayUntilDefault()).toString()) + // Should have seized RSR + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmtRSR)) // Sent to market (auction) - // Confirm default and trigger basket switch - const newTokens = [backupToken1.address, backupToken2.address] - const bkpTokenRefAmt: BigNumber = fp('0.5') - const newRefAmounts = [bkpTokenRefAmt, bkpTokenRefAmt] + // Perform Mock Bids for RSR (addr1 has balance) + // Pay at worst-case price + await backupToken1.connect(addr1).approve(gnosis.address, buyAmtBidRSR) + await gnosis.placeBid(3, { + bidder: addr1.address, + sellAmount: sellAmtRSR, + buyAmount: buyAmtBidRSR, + }) - await assetRegistry.refresh() - await expect(basketHandler.refreshBasket()) - .to.emit(basketHandler, 'BasketSet') - .withArgs(3, newTokens, newRefAmounts, false) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction; should not start a new one + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, rsr.address, backupToken1.address, sellAmtRSR, buyAmtBidRSR], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + emitted: false, + }, + ]) - // Advance time post warmup period - SOUND just regained - await advanceTime(Number(config.warmupPeriod) + 1) + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) // no dust - // Check new state after basket switch - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - issueAmount.div(2) - ) // 50% loss - await expectCurrentBacking({ - tokens: newTokens, - quantities: [bn('0'), bn('0')], + // Should have small excess now + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + issueAmount.add(1) + ) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(issueAmount.add(1)) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken - Remains the same + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) }) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) + it('Should overcollateralize when minTradeVolume is larger than the collateral deficit', async () => { + // Register Collateral + await assetRegistry.connect(owner).register(backupCollateral1.address) - // Running auctions will trigger recollateralization - All token balance can be redeemed - const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) - const minBuyAmt = await toMinBuyAmt(sellAmt, fp('0.5'), fp('1')) - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) - .to.emit(backingManager, 'TradeStarted') - .withArgs(anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt) + // Set backup configuration - USDT as backup + await basketHandler + .connect(owner) + .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [backupToken1.address]) - let auctionTimestamp = await getLatestBlockTimestamp() + // Check initial state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) - // Token0 -> Backup Token Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), - }) + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - // Perform Mock Bids (addr1 has balance) - // Pay at fair price: 100 token0 -> 50 backupToken1 - await backupToken1.connect(addr1).approve(gnosis.address, sellAmt.div(2)) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: sellAmt.div(2), - }) + // Check stakes + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) + // Set Token0 to default - 50% price reduction + await setOraclePrice(collateral0.address, bn('0.5e8')) - // Run auctions - will end current, and will open a new auction to buy the remaining backup tokens - const buyAmtBidRSR: BigNumber = issueAmount.div(2).add(1) // other half to buy - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, token0.address, backupToken1.address, sellAmt, sellAmt.div(2)], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - args: [anyValue, rsr.address, backupToken2.address, anyValue, buyAmtBidRSR], - emitted: true, - }, - ]) + // Mark default as probable + await assetRegistry.refresh() + expect(await basketHandler.status()).to.equal(CollateralStatus.IFFY) - auctionTimestamp = await getLatestBlockTimestamp() + // Advance time post collateral's default delay + await advanceTime((await collateral0.delayUntilDefault()).toString()) - // Check new auction - // RSR -> Backup Token 2 - await expectTrade(backingManager, { - sell: rsr.address, - buy: backupToken2.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('1'), - }) + // Confirm default and trigger basket switch + await assetRegistry.refresh() + await basketHandler.refreshBasket() - // Check backing changed - await expectCurrentBacking({ - tokens: newTokens, - quantities: [sellAmt.div(2), bn('0')], - }) + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) - const t = await getTrade(backingManager, rsr.address) - const sellAmtRSR = await t.initBal() - expect(await toMinBuyAmt(sellAmtRSR, fp('1'), fp('1'))).to.equal(buyAmtBidRSR.add(1)) + // Check new state after basket switch + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + // Asset value is half + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + issueAmount.div(2) + ) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + // retains value because over-collateralization, true + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Running auctions will trigger recollateralization + const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) + const minBuyAmt = await toMinBuyAmt(sellAmt, fp('0.5'), fp('1')) + + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs(anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt) + + const auctionTimestamp = await getLatestBlockTimestamp() + + // Token0 -> Backup Token Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: backupToken1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) - // Should have seized RSR - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmtRSR)) // Sent to market (auction) + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + // Asset value is zero, the skip collateral held is defaulted + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount.sub(sellAmt)) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken - retains value from over-collateralization + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Perform Mock Bids (addr1 has balance) + // Assume fair price, get half of the tokens (because price reduction was 50%) + await backupToken1.connect(addr1).approve(gnosis.address, sellAmt.div(2)) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: sellAmt.div(2), + }) - // Perform Mock Bids for RSR (addr1 has balance) - // Assume fair price RSR = 1 get all of them - await backupToken2.connect(addr1).approve(gnosis.address, buyAmtBidRSR) - await gnosis.placeBid(1, { - bidder: addr1.address, - sellAmount: sellAmtRSR, - buyAmount: buyAmtBidRSR, - }) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Raise minTradeVolume to the whole issueAmount + await backingManager.connect(owner).setMinTradeVolume(issueAmount) + + // Run auctions - RSR Auction launched for minTradeVolume + const minBuyAmt2 = issueAmount.mul(100).div(101).add(3) + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, token0.address, backupToken1.address, sellAmt, sellAmt.div(2)], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + args: [anyValue, rsr.address, backupToken1.address, anyValue, minBuyAmt2], + emitted: true, + }, + ]) - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) + const t = await getTrade(backingManager, rsr.address) + const sellAmt2 = await t.initBal() + expect(minBuyAmt2).to.equal(await toMinBuyAmt(sellAmt2, fp('1'), fp('1'))) + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmt2)) - // End current auction - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, rsr.address, backupToken2.address, sellAmtRSR, buyAmtBidRSR], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - emitted: false, - }, - ]) + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) // no dust - // Check final state - All back to normal - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - issueAmount.add(1) - ) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(issueAmount.div(2)) - expect(await backupToken2.balanceOf(backingManager.address)).to.equal( - issueAmount.div(2).add(1) - ) - expect(await rToken.totalSupply()).to.equal(issueAmount) + // Should have half of the value + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + issueAmount.div(2) + ) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(sellAmt.div(2)) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken - overcollateralized and still targeting 1 + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Perform Mock Bids (addr1 has balance) + // Assume fair price, get half of the tokens (because price reduction was 50%) + await backupToken1.connect(addr1).approve(gnosis.address, minBuyAmt2) + await gnosis.placeBid(1, { + bidder: addr1.address, + sellAmount: sellAmt2, + buyAmount: minBuyAmt2, + }) - // Check backing changed - await expectCurrentBacking({ - tokens: newTokens, - quantities: [issueAmount.div(2), issueAmount.div(2).add(1)], - }) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Run auctions - NO RSR Auction launched but haircut taken + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, rsr.address, backupToken1.address, sellAmt2, minBuyAmt2], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + emitted: false, + }, + { + contract: rToken, + name: 'BasketsNeededChanged', + emitted: true, + }, + ]) - // Check price in USD of the current RToken - Remains the same - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - }) + // Check state + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmt2)) + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) // no dust - it('Should use exceeding RSR in Backing Manager before seizing - Using RSR', async () => { - // Set backing buffer to zero for simplification - await backingManager.connect(owner).setBackingBuffer(0) + // Should have 1.5x starting amounts due to minTradeVolume + const bonus = issueAmount.div(2).add(minBuyAmt2) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(bonus) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(bonus) - // Register Collateral - await assetRegistry.connect(owner).register(backupCollateral1.address) + const supply = bonus.sub(bonus.mul(config.backingBuffer).div(fp('1'))) + // Should mint the excess in order to re-handout to RToken holders and stakers + expect(await rToken.totalSupply()).to.be.closeTo(supply, supply.div(bn('1e6'))) + expect(await rToken.totalSupply()).to.be.gte(supply) - // Set backup configuration - USDT as backup - await basketHandler - .connect(owner) - .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [backupToken1.address]) + // Check price in USD of the current RToken - overcollateralized and still targeting 1 + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + }) - // Check initial state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) + it('Should recollateralize correctly in case of default - Using RSR for remainder - Multiple tokens and auctions - No overshoot', async () => { + // Set backing buffer to zero for simplification + await backingManager.connect(owner).setBackingBuffer(0) - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // Register Collateral + await assetRegistry.connect(owner).register(backupCollateral1.address) + await assetRegistry.connect(owner).register(backupCollateral2.address) - // Check stakes - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) - expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) + // Set backup configuration - USDT as backup + await basketHandler + .connect(owner) + .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(4), [ + backupToken1.address, + backupToken2.address, + ]) + + // Check initial state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) + expect(await backupToken2.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Check stakes + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) + + // Set Token0 to default - 50% price reduction in 100% of the basket + await setOraclePrice(collateral0.address, bn('0.5e8')) + + // Mark default as probable + await assetRegistry.refresh() + expect(await basketHandler.status()).to.equal(CollateralStatus.IFFY) + + // Advance time post collateral's default delay + await advanceTime((await collateral0.delayUntilDefault()).toString()) + + // Confirm default and trigger basket switch + const newTokens = [backupToken1.address, backupToken2.address] + const bkpTokenRefAmt: BigNumber = fp('0.5') + const newRefAmounts = [bkpTokenRefAmt, bkpTokenRefAmt] + + await assetRegistry.refresh() + await expect(basketHandler.refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(3, newTokens, newRefAmounts, false) + + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) + + // Check new state after basket switch + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + issueAmount.div(2) + ) // 50% loss + await expectCurrentBacking({ + tokens: newTokens, + quantities: [bn('0'), bn('0')], + }) - // Set Token0 to default - 50% price reduction - await setOraclePrice(collateral0.address, bn('0.5e8')) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Running auctions will trigger recollateralization - All token balance can be redeemed + const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) + const minBuyAmt = await toMinBuyAmt(sellAmt, fp('0.5'), fp('1')) + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs(anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt) + + let auctionTimestamp = await getLatestBlockTimestamp() + + // Token0 -> Backup Token Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: backupToken1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) - // Mark default as probable - await assetRegistry.refresh() - expect(await basketHandler.status()).to.equal(CollateralStatus.IFFY) + // Perform Mock Bids (addr1 has balance) + // Pay at fair price: 100 token0 -> 50 backupToken1 + await backupToken1.connect(addr1).approve(gnosis.address, sellAmt.div(2)) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: sellAmt.div(2), + }) - // Advance time post collateral's default delay - await advanceTime((await collateral0.delayUntilDefault()).toString()) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Run auctions - will end current, and will open a new auction to buy the remaining backup tokens + const buyAmtBidRSR: BigNumber = issueAmount.div(2).add(1) // other half to buy + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, token0.address, backupToken1.address, sellAmt, sellAmt.div(2)], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + args: [anyValue, rsr.address, backupToken2.address, anyValue, buyAmtBidRSR], + emitted: true, + }, + ]) - // Confirm default and trigger basket switch - await assetRegistry.refresh() - await basketHandler.refreshBasket() + auctionTimestamp = await getLatestBlockTimestamp() - // Advance time post warmup period - SOUND just regained - await advanceTime(Number(config.warmupPeriod) + 1) + // Check new auction + // RSR -> Backup Token 2 + await expectTrade(backingManager, { + sell: rsr.address, + buy: backupToken2.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('1'), + }) - // Running auctions will trigger recollateralization - All balance can be redeemed - const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) - const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('0.5'), fp('1')) - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) - .to.emit(backingManager, 'TradeStarted') - .withArgs(anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt) + // Check backing changed + await expectCurrentBacking({ + tokens: newTokens, + quantities: [sellAmt.div(2), bn('0')], + }) - let auctionTimestamp = await getLatestBlockTimestamp() + const t = await getTrade(backingManager, rsr.address) + const sellAmtRSR = await t.initBal() + expect(await toMinBuyAmt(sellAmtRSR, fp('1'), fp('1'))).to.equal(buyAmtBidRSR.add(1)) - // Token0 -> Backup Token Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), - }) + // Should have seized RSR + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmtRSR)) // Sent to market (auction) - // Perform Mock Bids (addr1 has balance) - // Pay at fair price: no slippage - await backupToken1.connect(addr1).approve(gnosis.address, sellAmt.div(2)) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: sellAmt.div(2), - }) + // Perform Mock Bids for RSR (addr1 has balance) + // Assume fair price RSR = 1 get all of them + await backupToken2.connect(addr1).approve(gnosis.address, buyAmtBidRSR) + await gnosis.placeBid(1, { + bidder: addr1.address, + sellAmount: sellAmtRSR, + buyAmount: buyAmtBidRSR, + }) - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, rsr.address, backupToken2.address, sellAmtRSR, buyAmtBidRSR], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + emitted: false, + }, + ]) - // Run auctions - will end current, and will open a new auction to sell RSR for collateral - // 50e18 Tokens left to buy - Sets Buy amount as independent value - const buyAmtBidRSR: BigNumber = sellAmt.div(2).add(1) - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, token0.address, backupToken1.address, sellAmt, sellAmt.div(2)], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - args: [anyValue, rsr.address, backupToken1.address, anyValue, buyAmtBidRSR], - emitted: true, - }, - ]) + // Check final state - All back to normal + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + issueAmount.add(1) + ) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(issueAmount.div(2)) + expect(await backupToken2.balanceOf(backingManager.address)).to.equal( + issueAmount.div(2).add(1) + ) + expect(await rToken.totalSupply()).to.equal(issueAmount) - auctionTimestamp = await getLatestBlockTimestamp() + // Check backing changed + await expectCurrentBacking({ + tokens: newTokens, + quantities: [issueAmount.div(2), issueAmount.div(2).add(1)], + }) - // Check new auction - // RSR -> Backup Token Auction - await expectTrade(backingManager, { - sell: rsr.address, - buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('1'), + // Check price in USD of the current RToken - Remains the same + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) }) - const t = await getTrade(backingManager, rsr.address) - const sellAmtRSR = await t.initBal() - expect(await toMinBuyAmt(sellAmtRSR, fp('1'), fp('1'))).to.equal(buyAmtBidRSR.add(1)) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(sellAmt.div(2)) // Reduced 50% - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(sellAmt.div(2)) - expect(await rToken.totalSupply()).to.equal(issueAmount) + it('Should use exceeding RSR in Backing Manager before seizing - Using RSR', async () => { + // Set backing buffer to zero for simplification + await backingManager.connect(owner).setBackingBuffer(0) - // Check price in USD of the current RToken - 1 with over-collateralization - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // Register Collateral + await assetRegistry.connect(owner).register(backupCollateral1.address) - // Should have seized RSR - Nothing in backing manager so far - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmtRSR)) // Sent to market (auction) - expect(await rsr.balanceOf(backingManager.address)).to.equal(0) + // Set backup configuration - USDT as backup + await basketHandler + .connect(owner) + .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [backupToken1.address]) - // Settle auction with no bids - will return RSR to Backing Manager - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) + // Check initial state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) - // End current auction - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, rsr.address, backupToken1.address, bn('0'), bn('0')], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - args: [anyValue, rsr.address, backupToken1.address, sellAmtRSR, buyAmtBidRSR], + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - emitted: true, - }, - ]) + // Check stakes + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) - auctionTimestamp = await getLatestBlockTimestamp() + // Set Token0 to default - 50% price reduction + await setOraclePrice(collateral0.address, bn('0.5e8')) - // Check new auction - // RSR -> Backup Token Auction - await expectTrade(backingManager, { - sell: rsr.address, - buy: backupToken1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('2'), - }) + // Mark default as probable + await assetRegistry.refresh() + expect(await basketHandler.status()).to.equal(CollateralStatus.IFFY) - // Funds were reused. No more seizures - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmtRSR)) // Sent to market (auction) + // Advance time post collateral's default delay + await advanceTime((await collateral0.delayUntilDefault()).toString()) - // Perform Mock Bids (addr1 has balance) - // Assume fair price, get all the RSR required - await backupToken1.connect(addr1).approve(gnosis.address, buyAmtBidRSR) - await gnosis.placeBid(2, { - bidder: addr1.address, - sellAmount: sellAmtRSR, - buyAmount: buyAmtBidRSR, - }) + // Confirm default and trigger basket switch + await assetRegistry.refresh() + await basketHandler.refreshBasket() - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) + // Advance time post warmup period - SOUND just regained + await advanceTime(Number(config.warmupPeriod) + 1) - // Run auctions again - Will close the pending auction - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, rsr.address, backupToken1.address, sellAmtRSR, buyAmtBidRSR], - emitted: true, - }, - { contract: backingManager, name: 'TradeStarted', emitted: false }, - ]) + // Running auctions will trigger recollateralization - All balance can be redeemed + const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('0.5'), fp('1')) + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs(anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt) - // Check final state - All back to normal - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - issueAmount.add(1) // 1 attoTokens accumulated - ) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(issueAmount.add(1)) - expect(await rToken.totalSupply()).to.equal(issueAmount.add(1)) // free minting + let auctionTimestamp = await getLatestBlockTimestamp() - // Check price in USD of the current RToken - Remains the same - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - }) + // Token0 -> Backup Token Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: backupToken1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) - it('Should not sell worthless asset when doing recollateralization- Use RSR directly for remainder', async () => { - // Set prime basket - await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + // Perform Mock Bids (addr1 has balance) + // Pay at fair price: no slippage + await backupToken1.connect(addr1).approve(gnosis.address, sellAmt.div(2)) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: sellAmt.div(2), + }) - // Switch Basket - await expect(basketHandler.connect(owner).refreshBasket()) - .to.emit(basketHandler, 'BasketSet') - .withArgs(3, [token1.address], [fp('1')], false) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Run auctions - will end current, and will open a new auction to sell RSR for collateral + // 50e18 Tokens left to buy - Sets Buy amount as independent value + const buyAmtBidRSR: BigNumber = sellAmt.div(2).add(1) + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, token0.address, backupToken1.address, sellAmt, sellAmt.div(2)], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + args: [anyValue, rsr.address, backupToken1.address, anyValue, buyAmtBidRSR], + emitted: true, + }, + ]) - // Check state remains SOUND - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) + auctionTimestamp = await getLatestBlockTimestamp() - // Check price in USD of the current RToken -- retains price because of over-collateralization - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // Check new auction + // RSR -> Backup Token Auction + await expectTrade(backingManager, { + sell: rsr.address, + buy: backupToken1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('1'), + }) - // Trigger recollateralization - const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) - const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + const t = await getTrade(backingManager, rsr.address) + const sellAmtRSR = await t.initBal() + expect(await toMinBuyAmt(sellAmtRSR, fp('1'), fp('1'))).to.equal(buyAmtBidRSR.add(1)) + + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + sellAmt.div(2) + ) // Reduced 50% + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(sellAmt.div(2)) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken - 1 with over-collateralization + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Should have seized RSR - Nothing in backing manager so far + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmtRSR)) // Sent to market (auction) + expect(await rsr.balanceOf(backingManager.address)).to.equal(0) + + // Settle auction with no bids - will return RSR to Backing Manager + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, rsr.address, backupToken1.address, bn('0'), bn('0')], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + args: [anyValue, rsr.address, backupToken1.address, sellAmtRSR, buyAmtBidRSR], + + emitted: true, + }, + ]) - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) - .to.emit(backingManager, 'TradeStarted') - .withArgs( - anyValue, - token0.address, - token1.address, - sellAmt, - toBNDecimals(minBuyAmt, 6).add(1) - ) + auctionTimestamp = await getLatestBlockTimestamp() - let auctionTimestamp: number = await getLatestBlockTimestamp() + // Check new auction + // RSR -> Backup Token Auction + await expectTrade(backingManager, { + sell: rsr.address, + buy: backupToken1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('2'), + }) - // Check auction registered - // Token0 -> Token1 Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: token1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), - }) + // Funds were reused. No more seizures + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmtRSR)) // Sent to market (auction) - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - // Asset value is zero, everything was moved to the Market - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) + // Perform Mock Bids (addr1 has balance) + // Assume fair price, get all the RSR required + await backupToken1.connect(addr1).approve(gnosis.address, buyAmtBidRSR) + await gnosis.placeBid(2, { + bidder: addr1.address, + sellAmount: sellAmtRSR, + buyAmount: buyAmtBidRSR, + }) - // Check price in USD of the current RToken -- retains price because of - // over-collateralization - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Run auctions again - Will close the pending auction + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [anyValue, rsr.address, backupToken1.address, sellAmtRSR, buyAmtBidRSR], + emitted: true, + }, + { contract: backingManager, name: 'TradeStarted', emitted: false }, + ]) - // Check Gnosis - expect(await token0.balanceOf(gnosis.address)).to.equal(issueAmount) + // Check final state - All back to normal + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + issueAmount.add(1) // 1 attoTokens accumulated + ) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(issueAmount.add(1)) + expect(await rToken.totalSupply()).to.equal(issueAmount.add(1)) // free minting - // Perform Mock Bids for the new Token (addr1 has balance) - // Get fair price - minBuyAmt - await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6).add(1)) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: toBNDecimals(minBuyAmt, 6).add(1), + // Check price in USD of the current RToken - Remains the same + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) }) - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // End current auction, should start a new one to sell a new revenue token instead of RSR - // But the revenue token will have price = 0 so it wont be sold, will use RSR - // About 3e18 Tokens left to buy - Sets Buy amount as independent value - const buyAmtBidRemToken: BigNumber = sellAmt.sub(minBuyAmt) + it('Should not sell worthless asset when doing recollateralization- Use RSR directly for remainder', async () => { + // Set prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) - // Send the excess revenue tokens to backing manager - should try to use it instead of RSR - // But we set price = $0, so it wont be sold -Will use RSR for remainder - await aaveToken.connect(owner).mint(backingManager.address, buyAmtBidRemToken.mul(2)) - await setOraclePrice(aaveAsset.address, bn('0')) - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [ + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(3, [token1.address], [fp('1')], false) + + // Check state remains SOUND + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken -- retains price because of over-collateralization + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Trigger recollateralization + const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs( anyValue, token0.address, token1.address, sellAmt, - toBNDecimals(minBuyAmt, 6).add(1), - ], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - args: [ - anyValue, - rsr.address, - token1.address, - anyValue, - toBNDecimals(buyAmtBidRemToken, 6).add(1), - ], - emitted: true, - }, - ]) + toBNDecimals(minBuyAmt, 6).add(1) + ) - auctionTimestamp = await getLatestBlockTimestamp() + let auctionTimestamp: number = await getLatestBlockTimestamp() - // RSR Token -> Token1 Auction - await expectTrade(backingManager, { - sell: rsr.address, - buy: token1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('1'), - }) + // Check auction registered + // Token0 -> Token1 Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) - const t = await getTrade(backingManager, rsr.address) - const sellAmtRemToken = await t.initBal() - expect(toBNDecimals(buyAmtBidRemToken, 6).add(1)).to.equal( - toBNDecimals(await toMinBuyAmt(sellAmtRemToken, fp('1'), fp('1')), 6).add(1) - ) + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + // Asset value is zero, everything was moved to the Market + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken -- retains price because of + // over-collateralization + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Check Gnosis + expect(await token0.balanceOf(gnosis.address)).to.equal(issueAmount) + + // Perform Mock Bids for the new Token (addr1 has balance) + // Get fair price - minBuyAmt + await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6).add(1)) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: toBNDecimals(minBuyAmt, 6).add(1), + }) - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal( - toBNDecimals(minBuyAmt, 6).add(1) - ) - // Aave token balance remains unchanged - expect(await aaveToken.balanceOf(backingManager.address)).to.equal(buyAmtBidRemToken.mul(2)) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction, should start a new one to sell a new revenue token instead of RSR + // But the revenue token will have price = 0 so it wont be sold, will use RSR + // About 3e18 Tokens left to buy - Sets Buy amount as independent value + const buyAmtBidRemToken: BigNumber = sellAmt.sub(minBuyAmt) + + // Send the excess revenue tokens to backing manager - should try to use it instead of RSR + // But we set price = $0, so it wont be sold -Will use RSR for remainder + await aaveToken.connect(owner).mint(backingManager.address, buyAmtBidRemToken.mul(2)) + await setOraclePrice(aaveAsset.address, bn('0')) + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [ + anyValue, + token0.address, + token1.address, + sellAmt, + toBNDecimals(minBuyAmt, 6).add(1), + ], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + args: [ + anyValue, + rsr.address, + token1.address, + anyValue, + toBNDecimals(buyAmtBidRemToken, 6).add(1), + ], + emitted: true, + }, + ]) - expect(await rToken.totalSupply()).to.equal(issueAmount) + auctionTimestamp = await getLatestBlockTimestamp() - // Check Gnosis- using RSR - expect(await rsr.balanceOf(gnosis.address)).to.equal(sellAmtRemToken) + // RSR Token -> Token1 Auction + await expectTrade(backingManager, { + sell: rsr.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('1'), + }) - // Perform Mock Bids for the new Token (addr1 has balance) - // Cover buyAmtBidRevToken which is all the amount required - await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmtRemToken, 6)) - await gnosis.placeBid(1, { - bidder: addr1.address, - sellAmount: sellAmtRemToken, - buyAmount: toBNDecimals(buyAmtBidRemToken, 6).add(1), - }) + const t = await getTrade(backingManager, rsr.address) + const sellAmtRemToken = await t.initBal() + expect(toBNDecimals(buyAmtBidRemToken, 6).add(1)).to.equal( + toBNDecimals(await toMinBuyAmt(sellAmtRemToken, fp('1'), fp('1')), 6).add(1) + ) - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal( + toBNDecimals(minBuyAmt, 6).add(1) + ) + // Aave token balance remains unchanged + expect(await aaveToken.balanceOf(backingManager.address)).to.equal( + buyAmtBidRemToken.mul(2) + ) - // End current auction - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [ - anyValue, - rsr.address, - token1.address, - sellAmtRemToken, - toBNDecimals(buyAmtBidRemToken, 6).add(1), - ], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - emitted: false, - }, - ]) + expect(await rToken.totalSupply()).to.equal(issueAmount) - // Check state - Order restablished - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal( - toBNDecimals(issueAmount, 6).add(1) - ) - expect(await aaveToken.balanceOf(backingManager.address)).to.be.closeTo(bn('0'), 100) // distributor leaves some - expect(await rToken.totalSupply()).to.be.closeTo(issueAmount, fp('0.000001')) // we have a bit more + // Check Gnosis- using RSR + expect(await rsr.balanceOf(gnosis.address)).to.equal(sellAmtRemToken) - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + // Perform Mock Bids for the new Token (addr1 has balance) + // Cover buyAmtBidRevToken which is all the amount required + await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmtRemToken, 6)) + await gnosis.placeBid(1, { + bidder: addr1.address, + sellAmount: sellAmtRemToken, + buyAmount: toBNDecimals(buyAmtBidRemToken, 6).add(1), + }) - // Stakes used in this case - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmtRemToken)) - expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [ + anyValue, + rsr.address, + token1.address, + sellAmtRemToken, + toBNDecimals(buyAmtBidRemToken, 6).add(1), + ], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + emitted: false, + }, + ]) + + // Check state - Order restablished + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal( + toBNDecimals(issueAmount, 6).add(1) + ) + expect(await aaveToken.balanceOf(backingManager.address)).to.be.closeTo(bn('0'), 100) // distributor leaves some + expect(await rToken.totalSupply()).to.be.closeTo(issueAmount, fp('0.000001')) // we have a bit more + + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Stakes used in this case + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmtRemToken)) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) + }) }) context('DutchTrade', () => { From 092e19b459a884d5958e864642c02b9cebd086f5 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 11:21:53 -0400 Subject: [PATCH 198/462] formalize storage layout yarn target --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ca12e80bf0..73f21024f0 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "prettier": "prettier --ignore-path .gitignore --loglevel warn --write \"./**/*.{js,ts,sol,json,md}\"", "size": "hardhat size-contracts", "slither": "python3 tools/slither.py", - "prepare": "husky install" + "prepare": "husky install", + "storage": "hardhat compile && hardhat check" }, "repository": { "type": "git", From 1aa7dbfd8ec808ad4351c334be191115fc54610b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 11:22:10 -0400 Subject: [PATCH 199/462] fix BasketHandlerP1 storage layout --- contracts/p1/BasketHandler.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index e87369f264..95a81f8faf 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -686,5 +686,5 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[38] private __gap; + uint256[37] private __gap; } From 72d017106cba62b07bbe0e29fd89e50ff816ce0b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 11:45:24 -0400 Subject: [PATCH 200/462] new basket handler tests for requireConstantConfigTargets --- test/Main.test.ts | 80 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/test/Main.test.ts b/test/Main.test.ts index 26b68493c4..3c7efd95b2 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1662,7 +1662,8 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) describe('Basket Handling', () => { - let freshBasketHandler: TestIBasketHandler // need to be able to execute first setPrimeBasket + let freshBasketHandler: TestIBasketHandler // need to have both this and regular basketHandler around + let eurToken: ERC20Mock beforeEach(async () => { if (IMPLEMENTATION == Implementation.P0) { @@ -1686,6 +1687,23 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { } await freshBasketHandler.init(main.address, config.warmupPeriod) + + eurToken = await (await ethers.getContractFactory('ERC20Mock')).deploy('EURO Token', 'EUR') + const FiatCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'FiatCollateral' + ) + const eurColl = await FiatCollateralFactory.deploy({ + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await collateral0.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: eurToken.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: await collateral0.oracleTimeout(), + targetName: ethers.utils.formatBytes32String('EUR'), + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: await collateral1.delayUntilDefault(), + }) + await assetRegistry.connect(owner).register(eurColl.address) }) it('Should not allow to set prime Basket if not OWNER', async () => { @@ -1785,6 +1803,66 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { ).to.be.revertedWith('new basket missing target weights') }) + it('Should be able to set exactly same basket', async () => { + await basketHandler + .connect(owner) + .setPrimeBasket( + [token0.address, token1.address, token2.address, token3.address], + [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.25')] + ) + }) + + it('Should not allow to set prime Basket as superset of old basket', async () => { + await assetRegistry.connect(owner).register(backupCollateral1.address) + await expect( + basketHandler + .connect(owner) + .setPrimeBasket( + [token0.address, token1.address, token2.address, token3.address, backupToken1.address], + [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.25'), fp('0.01')] + ) + ).to.be.revertedWith('new basket adds target weights') + + await expect( + basketHandler + .connect(owner) + .setPrimeBasket( + [token0.address, token1.address, token2.address, token3.address, eurToken.address], + [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.25'), fp('0.01')] + ) + ).to.be.revertedWith('new basket adds target weights') + }) + + it('Should not allow to set prime Basket as subset of old basket', async () => { + await expect( + basketHandler + .connect(owner) + .setPrimeBasket( + [token0.address, token1.address, token2.address, token3.address], + [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.24')] + ) + ).to.be.revertedWith('new basket missing target weights') + await expect( + basketHandler + .connect(owner) + .setPrimeBasket( + [token0.address, token1.address, token2.address], + [fp('0.25'), fp('0.25'), fp('0.25')] + ) + ).to.be.revertedWith('new basket missing target weights') + }) + + it('Should not allow to change target unit in old basket', async () => { + await expect( + basketHandler + .connect(owner) + .setPrimeBasket( + [token0.address, token1.address, token2.address, eurToken.address], + [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.25')] + ) + ).to.be.revertedWith('new basket adds target weights') + }) + it('Should not allow to set prime Basket with RSR/RToken', async () => { await expect( freshBasketHandler.connect(owner).setPrimeBasket([rsr.address], [fp('1')]) From eeddf1bf66e17dc7111862203b401a44f51e091c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 21:05:02 -0400 Subject: [PATCH 201/462] adjust FacadeAct.getRevenueAuctionERC20s to skip over dust auctions --- contracts/facade/FacadeAct.sol | 9 ++++++++- test/FacadeAct.test.ts | 19 +++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index f6c01f13cc..1025129f11 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -404,6 +404,7 @@ contract FacadeAct is IFacadeAct { returns (IERC20[] memory toStart) { Registry memory reg = revenueTrader.main().assetRegistry().getRegistry(); + uint192 minTradeVolume = revenueTrader.minTradeVolume(); // {UoA} // Forward ALL revenue revenueTrader.main().backingManager().forwardRevenue(reg.erc20s); @@ -418,8 +419,14 @@ contract FacadeAct is IFacadeAct { revenueTrader.settleTrade(reg.erc20s[i]); } - uint256 tradesOpen = revenueTrader.tradesOpen(); + // Skip over dust balances, even though the RevenueTrader will trade them + (uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/tok} + if (reg.assets[i].bal(address(revenueTrader)) < minTradeSize(minTradeVolume, lotLow)) { + continue; + } + // Include ERC20 if a trade is opened + uint256 tradesOpen = revenueTrader.tradesOpen(); try revenueTrader.manageToken(reg.erc20s[i], TradeKind.BATCH_AUCTION) { if (revenueTrader.tradesOpen() - tradesOpen > 0) { unfiltered[num] = reg.erc20s[i]; diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts index d5619e6a86..5c03c7ff5b 100644 --- a/test/FacadeAct.test.ts +++ b/test/FacadeAct.test.ts @@ -199,8 +199,13 @@ describe('FacadeAct contract', () => { context('getRevenueAuctionERC20s/runRevenueAuctions', () => { it('Revenues/Rewards', async () => { - const rewardAmountAAVE = bn('0.5e18') - const rewardAmountCOMP = bn('1e18') + const rewardAmountAAVE = fp('1') + const rewardAmountCOMP = fp('5') + + // Raise minTradeVolume to in-between the two amounts + await rTokenTrader.connect(owner).setMinTradeVolume(fp('0.5')) + // at 3/5 and 2/5 rev split, the RTokenTrader has dust and RSRTrader does not (of AAVE) + // both should have COMP in non-dust balances // Setup AAVE + COMP rewards await aToken.setRewards(backingManager.address, rewardAmountAAVE) @@ -211,9 +216,8 @@ describe('FacadeAct contract', () => { const rTokenERC20s = await facadeAct.callStatic.getRevenueAuctionERC20s( rTokenTrader.address ) - expect(rTokenERC20s.length).to.equal(2) - expect(rTokenERC20s[0]).to.equal(aaveToken.address) - expect(rTokenERC20s[1]).to.equal(compToken.address) + expect(rTokenERC20s.length).to.equal(1) + expect(rTokenERC20s[0]).to.equal(compToken.address) const rsrERC20s = await facadeAct.callStatic.getRevenueAuctionERC20s(rsrTrader.address) expect(rsrERC20s.length).to.equal(2) expect(rsrERC20s[0]).to.equal(aaveToken.address) @@ -232,9 +236,8 @@ describe('FacadeAct contract', () => { // Now both should be settleable const rTokenSettleable = await facade.auctionsSettleable(rTokenTrader.address) - expect(rTokenSettleable.length).to.equal(2) - expect(rTokenSettleable[0]).to.equal(aaveToken.address) - expect(rTokenSettleable[1]).to.equal(compToken.address) + expect(rTokenSettleable.length).to.equal(1) + expect(rTokenSettleable[0]).to.equal(compToken.address) const rsrSettleable = await facade.auctionsSettleable(rsrTrader.address) expect(rsrSettleable.length).to.equal(2) expect(rsrSettleable[0]).to.equal(aaveToken.address) From b147df3199a93a314242e7d81dee63d2f4f07df8 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 21:11:53 -0400 Subject: [PATCH 202/462] FacadeRead: use lotPrice when calculating minTradeAmounts --- contracts/facade/FacadeRead.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 8443c16945..b6cf37dc77 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -283,14 +283,14 @@ contract FacadeRead is IFacadeRead { erc20s[i] = reg.erc20s[i]; surpluses[i] = reg.erc20s[i].balanceOf(address(revenueTrader)); - (uint192 low, ) = reg.assets[i].price(); // {UoA/tok} + (uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/tok} // {qTok} = {UoA} / {UoA/tok} - minTradeAmounts[i] = minTradeVolume.div(low).shiftl_toUint( + minTradeAmounts[i] = minTradeVolume.div(lotLow).shiftl_toUint( int8(reg.assets[i].erc20Decimals()) ); - if (reg.erc20s[i].balanceOf(address(revenueTrader)) >= minTradeAmounts[i]) { + if (reg.erc20s[i].balanceOf(address(revenueTrader)) > minTradeAmounts[i]) { try revenueTrader.manageToken(reg.erc20s[i], TradeKind.DUTCH_AUCTION) { if (revenueTrader.tradesOpen() - tradesOpen > 0) { canStart[i] = true; From 1aa23fee1e9ae0fbb1b0d5e0a62c494a59adf311 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 21:31:35 -0400 Subject: [PATCH 203/462] cleanup FacadeRead/Act revenue/recollateralization helpers --- CHANGELOG.md | 3 +-- contracts/facade/FacadeAct.sol | 5 +++-- contracts/facade/FacadeRead.sol | 19 ++++++++++++++++--- contracts/interfaces/IFacadeAct.sol | 3 ++- contracts/interfaces/IFacadeRead.sol | 2 ++ test/FacadeRead.test.ts | 9 +++++++-- 6 files changed, 31 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8f4375eba..1f7269bc5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,10 +126,9 @@ Candidate release for the "all clear" milestone. There wasn't any real usage of - Extend `issue()` to return the estimated USD value of deposits as `depositsUoA` - Add `traderBalances()` - Add `auctionsSettleable()` + - Add `nextRecollateralizationAuction()` - Modify `backingOverview() to handle unpriced cases` - `FacadeAct` - - Add `canRunRecollateralizationAuctions()` - - Add `getRevenueAuctionERC20s()` - Add `runRevenueAuctions()` #### Assets diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index a62a8a6aea..7af37fe1af 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -380,7 +380,8 @@ contract FacadeAct is IFacadeAct { function runRevenueAuctions( IRevenueTrader revenueTrader, IERC20[] memory toSettle, - IERC20[] memory toStart + IERC20[] memory toStart, + TradeKind kind ) external { // Settle auctions for (uint256 i = 0; i < toSettle.length; ++i) { @@ -392,7 +393,7 @@ contract FacadeAct is IFacadeAct { // Start auctions for (uint256 i = 0; i < toStart.length; ++i) { - revenueTrader.manageToken(toStart[i], TradeKind.BATCH_AUCTION); + revenueTrader.manageToken(toStart[i], kind); } } } diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index b6cf37dc77..baba31fa88 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -212,7 +212,8 @@ contract FacadeRead is IFacadeRead { } /// To use this, call via callStatic. - /// If canStart is true, can run FacadeAct.runRecollateralizationAuctions + /// If canStart is true, call backingManager.rebalance(). May require settling a + /// trade first; see auctionsSettleable. /// @return canStart true iff a recollateralization auction can be started /// @return sell The sell token in the auction /// @return buy The buy token in the auction @@ -227,9 +228,21 @@ contract FacadeRead is IFacadeRead { uint256 sellAmount ) { - if (bm.tradesOpen() == 0) { - IERC20[] memory erc20s = bm.main().assetRegistry().erc20s(); + IERC20[] memory erc20s = bm.main().assetRegistry().erc20s(); + + // Settle any settle-able open trades + if (bm.tradesOpen() > 0) { + for (uint256 i = 0; i < erc20s.length; ++i) { + ITrade trade = bm.trades(erc20s[i]); + if (address(trade) != address(0) && trade.canSettle()) { + trade.settle(); + break; // backingManager can only have 1 trade open at a time + } + } + } + // If no auctions ongoing, try to find a new auction to start + if (bm.tradesOpen() == 0) { // Try to launch auctions try bm.rebalance(TradeKind.DUTCH_AUCTION) { // Find the started auction diff --git a/contracts/interfaces/IFacadeAct.sol b/contracts/interfaces/IFacadeAct.sol index d2c7637fc5..09ed320280 100644 --- a/contracts/interfaces/IFacadeAct.sol +++ b/contracts/interfaces/IFacadeAct.sol @@ -33,6 +33,7 @@ interface IFacadeAct { function runRevenueAuctions( IRevenueTrader revenueTrader, IERC20[] memory toSettle, - IERC20[] memory toStart + IERC20[] memory toStart, + TradeKind kind ) external; } diff --git a/contracts/interfaces/IFacadeRead.sol b/contracts/interfaces/IFacadeRead.sol index 492c7b844f..5150d57e77 100644 --- a/contracts/interfaces/IFacadeRead.sol +++ b/contracts/interfaces/IFacadeRead.sol @@ -72,6 +72,8 @@ interface IFacadeRead { ); /// To use this, call via callStatic. + /// If canStart is true, call backingManager.rebalance(). May require settling a + /// trade first; see auctionsSettleable. /// @return canStart true iff a recollateralization auction can be started /// @return sell The sell token in the auction /// @return buy The buy token in the auction diff --git a/test/FacadeRead.test.ts b/test/FacadeRead.test.ts index 993e64eb55..bdf3f67dbc 100644 --- a/test/FacadeRead.test.ts +++ b/test/FacadeRead.test.ts @@ -34,7 +34,7 @@ import { ORACLE_ERROR, } from './fixtures' import { getLatestBlockTimestamp, setNextBlockTimestamp } from './utils/time' -import { CollateralStatus, MAX_UINT256 } from '#/common/constants' +import { CollateralStatus, TradeKind, MAX_UINT256 } from '#/common/constants' import { mintCollaterals } from './utils/tokens' describe('FacadeRead contract', () => { @@ -438,7 +438,12 @@ describe('FacadeRead contract', () => { } // Run revenue auctions - await facadeAct.runRevenueAuctions(trader.address, [], erc20sToStart) + await facadeAct.runRevenueAuctions( + trader.address, + [], + erc20sToStart, + TradeKind.DUTCH_AUCTION + ) // Nothing should be settleable expect((await facade.auctionsSettleable(trader.address)).length).to.equal(0) From 08eb39170044428a8320961f237ea5b488952d72 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 21:36:36 -0400 Subject: [PATCH 204/462] remove TODO --- contracts/interfaces/IRToken.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index 9239ca01d6..41097df947 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -91,7 +91,6 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea /// @custom:interaction function redeemTo(address recipient, uint256 amount) external; - // TODO should probably return the amounts it ends up with, for simulation purposes /// Redeem RToken for a linear combination of historical baskets, to a particular recipient /// @dev Allows partial redemptions up to the minAmounts /// @param recipient The address to receive the backing collateral tokens From 5e72b5f33766cf71feba9f658f4ca9d66f3f20b7 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 21:42:20 -0400 Subject: [PATCH 205/462] nit: redeemToCustom -> redeemCustom --- contracts/interfaces/IRToken.sol | 6 +- contracts/p0/RToken.sol | 7 +- contracts/p1/RToken.sol | 4 +- test/Main.test.ts | 12 ++-- test/RToken.test.ts | 80 +++++++++++------------ test/scenario/BadCollateralPlugin.test.ts | 6 +- 6 files changed, 56 insertions(+), 59 deletions(-) diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index 41097df947..004a7c9161 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -79,13 +79,13 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea function issueTo(address recipient, uint256 amount) external; /// Redeem RToken for basket collateral - /// @dev Use redeemToCustom for non-current baskets + /// @dev Use redeemCustom for non-current baskets /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @custom:interaction function redeem(uint256 amount) external; /// Redeem RToken for basket collateral to a particular recipient - /// @dev Use redeemToCustom for non-current baskets + /// @dev Use redeemCustom for non-current baskets /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @custom:interaction @@ -100,7 +100,7 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea /// @param expectedERC20sOut An array of ERC20s expected out /// @param minAmounts {qTok} The minimum ERC20 quantities the caller should receive /// @custom:interaction - function redeemToCustom( + function redeemCustom( address recipient, uint256 amount, uint48[] memory basketNonces, diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 7a782e6fce..0e28e83a83 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -145,10 +145,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { require(amount > 0, "Cannot redeem zero"); require(amount <= balanceOf(_msgSender()), "insufficient balance"); - require( - main.basketHandler().fullyCollateralized(), - "partial redemption; use redeemToCustom" - ); + require(main.basketHandler().fullyCollateralized(), "partial redemption; use redeemCustom"); // redemption while IFFY/DISABLED allowed // Revert if redemption exceeds either supply throttle @@ -192,7 +189,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// @param expectedERC20sOut An array of ERC20s expected out /// @param minAmounts {qTok} The minimum ERC20 quantities the caller should receive /// @custom:interaction - function redeemToCustom( + function redeemCustom( address recipient, uint256 amount, uint48[] memory basketNonces, diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 920f30589d..f7cc0ab447 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -218,7 +218,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { require(amount > 0, "Cannot redeem zero"); require(amount <= balanceOf(_msgSender()), "insufficient balance"); - require(basketHandler.fullyCollateralized(), "partial redemption; use redeemToCustom"); + require(basketHandler.fullyCollateralized(), "partial redemption; use redeemCustom"); // redemption while IFFY/DISABLED allowed uint256 supply = totalSupply(); @@ -283,7 +283,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// @param expectedERC20sOut An array of ERC20s expected out /// @param minAmounts {qTok} The minimum ERC20 quantities the caller should receive /// @custom:interaction RCEI - function redeemToCustom( + function redeemCustom( address recipient, uint256 amount, uint48[] memory basketNonces, diff --git a/test/Main.test.ts b/test/Main.test.ts index 3c7efd95b2..114a9a0fa0 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -2005,7 +2005,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const balsBefore = await getBalances(addr1.address, expectedTokens) await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, amount, basketNonces, @@ -2075,7 +2075,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, amount, basketNonces, @@ -2090,7 +2090,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, amount, basketNonces, @@ -2134,7 +2134,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const balsBefore = await getBalances(addr1.address, expectedTokens) await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, amount, basketNonces, @@ -2217,7 +2217,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, amount, basketNonces, @@ -2232,7 +2232,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, amount, basketNonces, diff --git a/test/RToken.test.ts b/test/RToken.test.ts index bd4d6d7311..35b3d4aa9f 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -1052,7 +1052,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { it('Should revert if undercollateralized from missing balance #fast', async function () { await token0.connect(owner).burn(backingManager.address, issueAmount.div(4)) await expect(rToken.connect(addr1).redeem(issueAmount.div(2))).to.be.revertedWith( - 'partial redemption; use redeemToCustom' + 'partial redemption; use redeemCustom' ) }) @@ -1064,7 +1064,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // 1st redemption await expect(rToken.connect(addr1).redeem(issueAmount.div(2))).be.revertedWith( - 'partial redemption; use redeemToCustom' + 'partial redemption; use redeemCustom' ) }) @@ -1073,7 +1073,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await assetRegistry.connect(owner).unregister(collateral2.address) await expect(rToken.connect(addr1).redeem(issueAmount)).revertedWith( - 'partial redemption; use redeemToCustom' + 'partial redemption; use redeemCustom' ) }) @@ -1122,7 +1122,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom(addr1.address, 1, [await basketHandler.nonce()], [fp('1')], [], []) + .redeemCustom(addr1.address, 1, [await basketHandler.nonce()], [fp('1')], [], []) ).to.be.revertedWith('invalid basketNonce') // New reference basket @@ -1131,17 +1131,17 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Before the first auction is completed, BOTH redemption methods are bricked await expect(rToken.connect(addr1).redeem(1)).to.be.revertedWith( - 'partial redemption; use redeemToCustom' + 'partial redemption; use redeemCustom' ) const nonce = await basketHandler.nonce() await expect( - rToken.connect(addr1).redeemToCustom(addr1.address, 1, [nonce], [fp('1')], [], []) + rToken.connect(addr1).redeemCustom(addr1.address, 1, [nonce], [fp('1')], [], []) ).to.be.revertedWith('empty redemption') await expect( - rToken.connect(addr1).redeemToCustom(addr1.address, 1, [nonce - 1], [fp('1')], [], []) + rToken.connect(addr1).redeemCustom(addr1.address, 1, [nonce - 1], [fp('1')], [], []) ).to.be.revertedWith('invalid basketNonce') await expect( - rToken.connect(addr1).redeemToCustom(addr1.address, 1, [nonce + 1], [fp('1')], [], []) + rToken.connect(addr1).redeemCustom(addr1.address, 1, [nonce + 1], [fp('1')], [], []) ).to.be.revertedWith('invalid basketNonce') }) @@ -1417,7 +1417,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom(addr1.address, zero, [await basketHandler.nonce()], [fp('1')], [], []) + .redeemCustom(addr1.address, zero, [await basketHandler.nonce()], [fp('1')], [], []) ).to.be.revertedWith('Cannot redeem zero') }) @@ -1427,7 +1427,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr2) - .redeemToCustom(addr2.address, redeemAmount, [nonce], [fp('1')], [], []) + .redeemCustom(addr2.address, redeemAmount, [nonce], [fp('1')], [], []) ).to.be.revertedWith('insufficient balance') }) @@ -1436,7 +1436,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, fp('1'), [nonce, nonce], @@ -1447,7 +1447,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ).to.be.revertedWith('portions do not add up to FIX_ONE') }) - it('Should redeemToCustom RTokens correctly', async function () { + it('Should redeemCustom RTokens correctly', async function () { const redeemAmount = bn('100e18') // Check balances @@ -1467,7 +1467,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Check simulation result const [actualERC20s, actualQuantities] = await rToken .connect(addr1) - .callStatic.redeemToCustom( + .callStatic.redeemCustom( addr1.address, redeemAmount, basketNonces, @@ -1480,7 +1480,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, redeemAmount, basketNonces, @@ -1516,7 +1516,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr2.address, issueAmount, basketNonces, @@ -1559,7 +1559,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ) await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, redeemAmount, basketNonces, @@ -1576,7 +1576,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { // Redeem rTokens with another user await rToken .connect(addr2) - .redeemToCustom( + .redeemCustom( addr2.address, redeemAmount, basketNonces, @@ -1613,7 +1613,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, issueAmount) await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, issueAmount, basketNonces, @@ -1632,7 +1632,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, issueAmount) await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, issueAmount, basketNonces, @@ -1651,7 +1651,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, issueAmount) await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, issueAmount, basketNonces, @@ -1672,7 +1672,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, issueAmount, basketNonces, @@ -1701,7 +1701,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ).to.not.be.reverted await rToken .connect(addr1) - .redeemToCustom(addr1.address, issueAmount.div(2), basketNonces, portions, [], []) + .redeemCustom(addr1.address, issueAmount.div(2), basketNonces, portions, [], []) expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) // Burn the rest @@ -1713,7 +1713,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom(addr1.address, issueAmount.div(2), basketNonces, portions, [], []) + .redeemCustom(addr1.address, issueAmount.div(2), basketNonces, portions, [], []) ).to.be.revertedWith('empty redemption') // Check values @@ -1725,7 +1725,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom(addr1.address, issueAmount.div(2), [2], portions, [], []) + .redeemCustom(addr1.address, issueAmount.div(2), [2], portions, [], []) ).to.be.revertedWith('invalid basketNonce') // Bump primeNonce @@ -1735,21 +1735,21 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom(addr1.address, issueAmount.div(2), [1], portions, [], []) + .redeemCustom(addr1.address, issueAmount.div(2), [1], portions, [], []) ).to.be.revertedWith('invalid basketNonce') // Nonce 2 still doesn't have a reference basket yet await expect( rToken .connect(addr1) - .redeemToCustom(addr1.address, issueAmount.div(2), [2], portions, [], []) + .redeemCustom(addr1.address, issueAmount.div(2), [2], portions, [], []) ).to.be.revertedWith('invalid basketNonce') // Refresh reference basket await basketHandler.connect(owner).refreshBasket() await rToken .connect(addr1) - .redeemToCustom(addr1.address, issueAmount.div(2), [2], portions, [], []) + .redeemCustom(addr1.address, issueAmount.div(2), [2], portions, [], []) }) it('Should prorate redemption if basket is DISABLED from fallen refPerTok() #fast', async function () { @@ -1769,7 +1769,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, issueAmount.div(2), basketNonces, @@ -1786,7 +1786,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, issueAmount.div(2), basketNonces, @@ -1812,7 +1812,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ) await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, issueAmount.div(2), basketNonces, @@ -1829,7 +1829,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { newQuantities.push(fp('0')) await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, issueAmount.div(2), basketNonces, @@ -1850,7 +1850,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, issueAmount, basketNonces, @@ -1877,7 +1877,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ) await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, issueAmount.sub(bn('1e18')), basketNonces, @@ -1904,7 +1904,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, bn(redeemAmount), basketNonces, @@ -1954,7 +1954,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ) await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, redeemAmount, basketNonces, @@ -1991,7 +1991,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, redeemAmount.add(1), basketNonces, @@ -2007,7 +2007,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ) await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, redeemAmount, basketNonces, @@ -2059,7 +2059,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ) await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, throttles.amtRate, basketNonces, @@ -2086,7 +2086,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, redeemAmount.add(1), basketNonces, @@ -2100,7 +2100,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect( rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, redeemAmount, basketNonces, diff --git a/test/scenario/BadCollateralPlugin.test.ts b/test/scenario/BadCollateralPlugin.test.ts index d0198ee97d..ec2e04c0ee 100644 --- a/test/scenario/BadCollateralPlugin.test.ts +++ b/test/scenario/BadCollateralPlugin.test.ts @@ -163,7 +163,7 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { expect(await token0.balanceOf(addr1.address)).to.equal(0) await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, initialBal.div(2), [await basketHandler.nonce()], @@ -186,7 +186,7 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { // Should be able to redeem half the RToken at-par await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, initialBal.div(2), [await basketHandler.nonce()], @@ -236,7 +236,7 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { // Should be able to redeem half the RToken at-par await rToken .connect(addr1) - .redeemToCustom( + .redeemCustom( addr1.address, initialBal.div(2), [await basketHandler.nonce()], From 4fa39311a3e6aceeed54a8288d4161c2cddc2bcc Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 21:54:23 -0400 Subject: [PATCH 206/462] lint clean --- contracts/facade/FacadeRead.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index baba31fa88..73b0eb037e 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -255,6 +255,7 @@ contract FacadeRead is IFacadeRead { sellAmount = trade.sellAmount(); } } + // solhint-disable-next-line no-empty-blocks } catch {} } } From db293979a3634d3db19822fe2c2e59f271bc711e Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 22:47:39 -0400 Subject: [PATCH 207/462] remove console.log --- test/plugins/individual-collateral/collateralTests.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 0d473115b6..e5a0d90e24 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -147,11 +147,6 @@ export default function fn( itClaimsRewards('claims rewards (via collateral.claimRewards())', async () => { const amount = bn('20').mul(bn(10).pow(await ctx.tok.decimals())) await mintCollateralTo(ctx, amount, alice, collateral.address) - console.log( - alice.address, - await ctx.tok.balanceOf(alice.address), - await ctx.tok.balanceOf(collateral.address) - ) await advanceBlocks(1000) await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) From 3d199c577d1adc5d2f54942d3139be6e47910e23 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 22:50:44 -0400 Subject: [PATCH 208/462] .github/workflows.yml try 8192 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index de7b65169a..f0c9aee9e6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -107,7 +107,7 @@ jobs: - run: yarn compile - run: yarn test:scenario env: - NODE_OPTIONS: '--max-old-space-size=4096' + NODE_OPTIONS: '--max-old-space-size=8192' extreme-tests: name: 'Extreme Tests' From 9072627e34dfe48814207f466d647045b81ace66 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 9 May 2023 23:17:36 -0400 Subject: [PATCH 209/462] remove now-invalid WETH scenario test --- test/scenario/WETH.test.ts | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/test/scenario/WETH.test.ts b/test/scenario/WETH.test.ts index 89bd6f4f6e..fcd897c4c3 100644 --- a/test/scenario/WETH.test.ts +++ b/test/scenario/WETH.test.ts @@ -250,31 +250,5 @@ describe(`Self-referential collateral (eg ETH via WETH) - P${IMPLEMENTATION}`, ( // Should be in disabled state, as there are no backups for WETH expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) }) - - it('should be able to switch away from WETH', async () => { - await basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1')]) - await basketHandler.refreshBasket() - - // Should be fully collateralized - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await facadeTest.wholeBasketsHeldBy(rToken.address, backingManager.address)).to.equal( - issueAmt - ) - - // Should view WETH as surplus - await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( - 'already collateralized' - ) - await backingManager.forwardRevenue([weth.address]) - await expect(rsrTrader.manageToken(weth.address, TradeKind.BATCH_AUCTION)).to.emit( - rsrTrader, - 'TradeStarted' - ) - await expect(rTokenTrader.manageToken(weth.address, TradeKind.BATCH_AUCTION)).to.emit( - rTokenTrader, - 'TradeStarted' - ) - }) }) }) From c79e5a72ffdeff534a8498b4f903fe0a87587c8a Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 13:29:47 -0400 Subject: [PATCH 210/462] fix Revenues gas test --- test/Revenues.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index fd391d2c8e..60116921ae 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -3189,7 +3189,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { rewardAmountCOMP = bn('2e18') // COMP Rewards - await compoundMock.setRewards(token3.address, rewardAmountCOMP) + await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) // Collect revenue // Expected values based on Prices between COMP and RSR/RToken = 1 to 1 (for simplification) @@ -3235,7 +3235,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Manage Funds await snapshotGasCost(rsrTrader.manageToken(compToken.address, TradeKind.BATCH_AUCTION)) - await snapshotGasCost(rTokenTrader.manageToken(compToken.address, TradeKind.BATCH_AUCTION)) // Run final auction until all funds are converted // Advance time till auction ended @@ -3252,7 +3251,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Run auctions - Order: Settle trades, then Manage funds // Settle trades await snapshotGasCost(rsrTrader.settleTrade(compToken.address)) - await snapshotGasCost(rTokenTrader.settleTrade(compToken.address)) }) }) }) From 9798efa7e6a7cd6a430f5a447623be34bc253633 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 13:37:42 -0400 Subject: [PATCH 211/462] remove now-unnecessary RevTrader logic leftover from when BackingManager was a caller --- contracts/p1/RevenueTrader.sol | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index eca0b0dc7f..890f270730 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -79,29 +79,21 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { return; } - IAsset sell; - IAsset buy; - // == Refresh == - // do not need to refresh when caller is BackingManager.forwardRevenue() - if (_msgSender() != address(backingManager)) { - if (erc20 == IERC20(address(rToken)) || tokenToBuy == IERC20(address(rToken))) { - // if either token is the RToken, refresh everything - assetRegistry.refresh(); - furnace.melt(); - - sell = assetRegistry.toAsset(erc20); - buy = assetRegistry.toAsset(tokenToBuy); - } else { - // otherwise, refresh just buy + sell - sell = assetRegistry.toAsset(erc20); - buy = assetRegistry.toAsset(tokenToBuy); - sell.refresh(); - buy.refresh(); - } + if (erc20 == IERC20(address(rToken)) || tokenToBuy == IERC20(address(rToken))) { + // if either token is the RToken, refresh everything + assetRegistry.refresh(); + furnace.melt(); + } else { + // otherwise, refresh just buy + sell assets + assetRegistry.toAsset(erc20).refresh(); + assetRegistry.toAsset(tokenToBuy).refresh(); } // == Checks/Effects == + // Above calls should not have changed registered assets, but just to be safe... + IAsset sell = assetRegistry.toAsset(erc20); + IAsset buy = assetRegistry.toAsset(tokenToBuy); require(address(trades[erc20]) == address(0), "trade open"); require(erc20.balanceOf(address(this)) > 0, "0 balance"); From 2f477dbd0ecf1be9aed843cb5cfea02c05a255bf Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 14:09:57 -0400 Subject: [PATCH 212/462] remove FacadeAct.getActCalldata() --- contracts/facade/FacadeAct.sol | 357 +----------- contracts/interfaces/IFacadeAct.sol | 18 +- test/FacadeAct.test.ts | 831 ---------------------------- 3 files changed, 8 insertions(+), 1198 deletions(-) delete mode 100644 test/FacadeAct.test.ts diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 7af37fe1af..93e70315a1 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -2,375 +2,24 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "../interfaces/IAsset.sol"; -import "../interfaces/IAssetRegistry.sol"; import "../interfaces/IFacadeAct.sol"; -import "../interfaces/IRToken.sol"; -import "../interfaces/IStRSR.sol"; -import "../libraries/Fixed.sol"; -import "../p1/BasketHandler.sol"; -import "../p1/BackingManager.sol"; -import "../p1/Furnace.sol"; -import "../p1/RToken.sol"; -import "../p1/RevenueTrader.sol"; -import "../p1/StRSRVotes.sol"; /** * @title Facade - * @notice A UX-friendly layer for non-governance protocol interactions - * @custom:static-call - Use ethers callStatic() in order to get result after update + * @notice A Facade to help batch compound actions that cannot be done from an EOA, solely. */ contract FacadeAct is IFacadeAct { - using FixLib for uint192; - - struct Cache { - IAssetRegistry reg; - BackingManagerP1 bm; - BasketHandlerP1 bh; - RevenueTraderP1 rTokenTrader; - RevenueTraderP1 rsrTrader; - StRSRP1 stRSR; - RTokenP1 rToken; - IERC20 rsr; - } - - /// Returns the next call a keeper of MEV searcher should make in order to progress the system - /// Returns zero bytes to indicate no action should be made - /// @dev This function begins reverting due to blocksize constraints at ~400 registered assets - /// @custom:static-call - function getActCalldata(RTokenP1 rToken) external returns (address, bytes memory) { - // solhint-disable no-empty-blocks - - IMain main = rToken.main(); - Cache memory cache = Cache({ - reg: main.assetRegistry(), - bm: BackingManagerP1(address(main.backingManager())), - bh: BasketHandlerP1(address(main.basketHandler())), - rTokenTrader: RevenueTraderP1(address(main.rTokenTrader())), - rsrTrader: RevenueTraderP1(address(main.rsrTrader())), - stRSR: StRSRP1(address(main.stRSR())), - rsr: main.rsr(), - rToken: rToken - }); - IERC20[] memory erc20s = cache.reg.erc20s(); - - // Refresh assets - cache.reg.refresh(); - - // tend to the basket and auctions - { - // first priority: keep the basket fresh - if (cache.bh.status() == CollateralStatus.DISABLED) { - cache.bh.refreshBasket(); - if (cache.bh.status() != CollateralStatus.DISABLED) { - // cache.bh.refreshBasket(); - return ( - address(cache.bh), - abi.encodeWithSelector(cache.bh.refreshBasket.selector) - ); - } - } - - // see if backingManager settlement is required - if (cache.bm.tradesOpen() > 0) { - for (uint256 i = 0; i < erc20s.length; i++) { - ITrade trade = cache.bm.trades(erc20s[i]); - if (address(trade) != address(0) && trade.canSettle()) { - // try: cache.rTokenTrader.settleTrade(erc20s[i]) - - try cache.bm.settleTrade(erc20s[i]) { - // if succeeded - return ( - address(cache.bm), - abi.encodeWithSelector(cache.bm.settleTrade.selector, erc20s[i]) - ); - } catch {} - } - } - } else if ( - cache.bh.status() == CollateralStatus.SOUND && !cache.bh.fullyCollateralized() - ) { - // try: backingManager.manageTokens([]) - - try cache.bm.rebalance(TradeKind.BATCH_AUCTION) { - return ( - address(cache.bm), - abi.encodeWithSelector(cache.bm.rebalance.selector, TradeKind.BATCH_AUCTION) - ); - } catch {} - } else { - // status() != SOUND || basketHandler.fullyCollateralized - - // check revenue traders - for (uint256 i = 0; i < erc20s.length; i++) { - // rTokenTrader: if there's a trade to settle - ITrade trade = cache.rTokenTrader.trades(erc20s[i]); - if (address(trade) != address(0) && trade.canSettle()) { - // try: cache.rTokenTrader.settleTrade(erc20s[i]) - - try cache.rTokenTrader.settleTrade(erc20s[i]) { - return ( - address(cache.rTokenTrader), - abi.encodeWithSelector( - cache.rTokenTrader.settleTrade.selector, - erc20s[i] - ) - ); - } catch {} - } - - // rsrTrader: if there's a trade to settle - trade = cache.rsrTrader.trades(erc20s[i]); - if (address(trade) != address(0) && trade.canSettle()) { - // try: cache.rTokenTrader.settleTrade(erc20s[i]) - - try cache.rsrTrader.settleTrade(erc20s[i]) { - return ( - address(cache.rsrTrader), - abi.encodeWithSelector( - cache.rsrTrader.settleTrade.selector, - erc20s[i] - ) - ); - } catch {} - } - - // rTokenTrader: check if we can start any trades - uint48 tradesOpen = cache.rTokenTrader.tradesOpen(); - try cache.rTokenTrader.manageToken(erc20s[i], TradeKind.BATCH_AUCTION) { - if (cache.rTokenTrader.tradesOpen() - tradesOpen > 0) { - // A trade started; do cache.rTokenTrader.manageToken - return ( - address(cache.rTokenTrader), - abi.encodeWithSelector( - cache.rTokenTrader.manageToken.selector, - erc20s[i], - TradeKind.BATCH_AUCTION - ) - ); - } - } catch {} - - // rsrTrader: check if we can start any trades - tradesOpen = cache.rsrTrader.tradesOpen(); - try cache.rsrTrader.manageToken(erc20s[i], TradeKind.BATCH_AUCTION) { - if (cache.rsrTrader.tradesOpen() - tradesOpen > 0) { - // A trade started; do cache.rsrTrader.manageToken - return ( - address(cache.rsrTrader), - abi.encodeWithSelector( - cache.rsrTrader.manageToken.selector, - erc20s[i], - TradeKind.BATCH_AUCTION - ) - ); - } - } catch {} - } - - // maybe revenue needs to be forwarded from backingManager - // only perform if basket is SOUND - if (cache.bh.status() == CollateralStatus.SOUND) { - IAsset rsrAsset = cache.reg.toAsset(cache.rsr); - uint192 initialStRSRBal = rsrAsset.bal(address(cache.stRSR)); // {RSR} - try cache.bm.forwardRevenue(erc20s) { - // if this unblocked an auction in either revenue trader, - // then prepare backingManager.manageTokens - for (uint256 i = 0; i < erc20s.length; i++) { - address[] memory twoERC20s = new address[](2); - - // rTokenTrader - { - if (address(erc20s[i]) != address(rToken)) { - // rTokenTrader: check if we can start any trades - uint48 tradesOpen = cache.rTokenTrader.tradesOpen(); - try - cache.rTokenTrader.manageToken( - erc20s[i], - TradeKind.BATCH_AUCTION - ) - { - if (cache.rTokenTrader.tradesOpen() - tradesOpen > 0) { - // always forward RToken + the ERC20 - twoERC20s[0] = address(rToken); - twoERC20s[1] = address(erc20s[i]); - // backingManager.manageTokens([rToken, erc20s[i]) - // forward revenue onward to the revenue traders - return ( - address(cache.bm), - abi.encodeWithSelector( - cache.bm.forwardRevenue.selector, - twoERC20s - ) - ); - } - } catch {} - } - } - - // rsrTrader - { - if (erc20s[i] != cache.rsr) { - // rsrTrader: check if we can start any trades - uint48 tradesOpen = cache.rsrTrader.tradesOpen(); - try - cache.rsrTrader.manageToken( - erc20s[i], - TradeKind.BATCH_AUCTION - ) - { - if (cache.rsrTrader.tradesOpen() - tradesOpen > 0) { - // always forward RSR + the ERC20 - twoERC20s[0] = address(cache.rsr); - twoERC20s[1] = address(erc20s[i]); - // backingManager.manageTokens(rsr, erc20s[i]) - // forward revenue onward to the revenue traders - return ( - address(cache.bm), - abi.encodeWithSelector( - cache.bm.forwardRevenue.selector, - twoERC20s - ) - ); - } - } catch {} - } - } - } - - // forward RToken in isolation only, if it's large enough - { - IAsset rTokenAsset = cache.reg.toAsset(IERC20(address(rToken))); - (uint192 lotLow, ) = rTokenAsset.lotPrice(); - if ( - rTokenAsset.bal(address(cache.rTokenTrader)) >= - minTradeSize(cache.rTokenTrader.minTradeVolume(), lotLow) - ) { - try - cache.rTokenTrader.manageToken( - IERC20(address(rToken)), - TradeKind.BATCH_AUCTION - ) - { - address[] memory oneERC20 = new address[](1); - oneERC20[0] = address(rToken); - return ( - address(cache.bm), - abi.encodeWithSelector( - cache.bm.forwardRevenue.selector, - oneERC20 - ) - ); - } catch {} - } - } - - // forward RSR in isolation only, if it's large enough - // via handoutExcessAssets - { - (uint192 lotLow, ) = rsrAsset.lotPrice(); - - if ( - rsrAsset.bal(address(cache.stRSR)) - initialStRSRBal >= - minTradeSize(cache.rsrTrader.minTradeVolume(), lotLow) - ) { - IERC20[] memory empty = new IERC20[](0); - return ( - address(cache.bm), - abi.encodeWithSelector(cache.bm.forwardRevenue.selector, empty) - ); - } - } - } catch {} - } - } - } - - // check if there are reward tokens to claim - { - // save initial balances - uint256[] memory initialBals = new uint256[](erc20s.length); - for (uint256 i = 0; i < erc20s.length; ++i) { - initialBals[i] = erc20s[i].balanceOf(address(cache.bm)); - } - - uint192 minTradeVolume = cache.bm.minTradeVolume(); // {UoA} - - // prefer restricting to backingManager.claimRewards when possible to save gas - try cache.bm.claimRewards() { - // See if any token bals grew sufficiently - for (uint256 i = 0; i < erc20s.length; ++i) { - (uint192 lotLow, ) = cache.reg.toAsset(erc20s[i]).lotPrice(); // {tok} - - uint256 bal = erc20s[i].balanceOf(address(cache.bm)); - if (bal - initialBals[i] > minTradeSize(minTradeVolume, lotLow)) { - // It's large enough to trade! Return bm.claimRewards as next step. - return ( - address(cache.bm), - abi.encodeWithSelector(cache.bm.claimRewards.selector, rToken) - ); - } - } - } catch {} - - // save initial balances for both Revenue Traders - uint256[] memory initialBalsRTokenTrader = new uint256[](erc20s.length); - uint256[] memory initialBalsRSRTrader = new uint256[](erc20s.length); - for (uint256 i = 0; i < erc20s.length; ++i) { - initialBalsRTokenTrader[i] = erc20s[i].balanceOf(address(cache.rTokenTrader)); - initialBalsRSRTrader[i] = erc20s[i].balanceOf(address(cache.rsrTrader)); - } - - // look at rewards from all sources - try this.claimRewards(rToken) { - // See if any token bals grew sufficiently - for (uint256 i = 0; i < erc20s.length; ++i) { - (uint192 lotLow, ) = cache.reg.toAsset(erc20s[i]).lotPrice(); // {tok} - - // Get balances for revenue traders - uint256 balRTokenTrader = erc20s[i].balanceOf(address(cache.rTokenTrader)); - uint256 balRSRTrader = erc20s[i].balanceOf(address(cache.rsrTrader)); - - // Check both traders - if ( - (balRTokenTrader - initialBalsRTokenTrader[i]) > - minTradeSize(minTradeVolume, lotLow) || - (balRSRTrader - initialBalsRSRTrader[i]) > - minTradeSize(minTradeVolume, lotLow) - ) { - // It's large enough to trade! Return claimRewards as next step. - return ( - address(this), - abi.encodeWithSelector(this.claimRewards.selector, rToken) - ); - } - } - } catch {} - } - - return (address(0), new bytes(0)); - } - - function claimRewards(RTokenP1 rToken) public { + function claimRewards(IRToken rToken) public { IMain main = rToken.main(); main.backingManager().claimRewards(); main.rTokenTrader().claimRewards(); main.rsrTrader().claimRewards(); } - /// Calculates the minTradeSize for an asset based on the given minTradeVolume and price - /// @param minTradeVolume {UoA} The min trade volume, passed in for gas optimization - /// @return {tok} The min trade size for the asset in whole tokens - function minTradeSize(uint192 minTradeVolume, uint192 price) private pure returns (uint192) { - // {tok} = {UoA} / {UoA/tok} - uint192 size = price == 0 ? FIX_MAX : minTradeVolume.div(price, ROUND); - return size > 0 ? size : 1; - } - /// To use this, first call: /// - FacadeRead.auctionsSettleable(revenueTrader) /// - FacadeRead.revenueOverview(revenueTrader) - /// If either arrays returned are non-empty, then can call this function. + /// If either arrays returned are non-empty, then can execute this function productively. /// Logic: /// For each ERC20 in `toSettle`: /// - Settle any open ERC20 trades diff --git a/contracts/interfaces/IFacadeAct.sol b/contracts/interfaces/IFacadeAct.sol index 09ed320280..8b93274299 100644 --- a/contracts/interfaces/IFacadeAct.sol +++ b/contracts/interfaces/IFacadeAct.sol @@ -1,29 +1,21 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.17; -import "../p1/RToken.sol"; +import "../interfaces/IRevenueTrader.sol"; +import "../interfaces/IRToken.sol"; /** * @title IFacadeAct - * @notice A calldata-preparer, useful to MEV searchers and off-chain bots looking to progress an - * RToken. - * - * - @custom:static-call - Use ethers callStatic() in order to get result after update + * @notice A Facade to help batch compound actions that cannot be done from an EOA, solely. v */ interface IFacadeAct { - /// Returns the next call a keeper of MEV searcher should make in order to progress the system - /// Returns zero bytes to indicate no action should be made - /// @dev Don't actually execute this! - /// @custom:static-call - function getActCalldata(RTokenP1 rToken) external returns (address to, bytes memory calldata_); - /// Claims rewards from all places they can accrue. - function claimRewards(RTokenP1 rToken) external; + function claimRewards(IRToken rToken) external; /// To use this, first call: /// - FacadeRead.auctionsSettleable(revenueTrader) /// - FacadeRead.revenueOverview(revenueTrader) - /// If either arrays returned are non-empty, then can call this function. + /// If either arrays returned are non-empty, then can execute this function productively. /// Logic: /// For each ERC20 in `toSettle`: /// - Settle any open ERC20 trades diff --git a/test/FacadeAct.test.ts b/test/FacadeAct.test.ts deleted file mode 100644 index c4c1720a03..0000000000 --- a/test/FacadeAct.test.ts +++ /dev/null @@ -1,831 +0,0 @@ -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' -import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { expect } from 'chai' -import { BigNumber } from 'ethers' -import { ethers } from 'hardhat' -import { - CollateralStatus, - FURNACE_DEST, - STRSR_DEST, - ZERO_ADDRESS, - BN_SCALE_FACTOR, -} from '../common/constants' -import { bn, fp, divCeil } from '../common/numbers' -import { IConfig } from '../common/configuration' -import { advanceTime } from './utils/time' -import { withinQuad } from './utils/matchers' -import { - ATokenFiatCollateral, - ComptrollerMock, - CTokenFiatCollateral, - ERC20Mock, - FacadeAct, - FiatCollateral, - GnosisMock, - IAssetRegistry, - MockV3Aggregator, - StaticATokenMock, - TestIBackingManager, - TestIBasketHandler, - TestIDistributor, - TestIRevenueTrader, - TestIRToken, - TestIStRSR, - USDCMock, - CTokenVaultMock, -} from '../typechain' -import { - Collateral, - Implementation, - IMPLEMENTATION, - ORACLE_ERROR, - PRICE_TIMEOUT, - REVENUE_HIDING, - defaultFixture, -} from './fixtures' -import snapshotGasCost from './utils/snapshotGasCost' -import { useEnv } from '#/utils/env' -import { mintCollaterals } from './utils/tokens' - -const DEFAULT_THRESHOLD = fp('0.01') // 1% - -const describeGas = - IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip - -const describeP1 = IMPLEMENTATION == Implementation.P1 ? describe : describe.skip - -describe('FacadeAct contract', () => { - let owner: SignerWithAddress - let addr1: SignerWithAddress - let addr2: SignerWithAddress - - // Tokens - let initialBal: BigNumber - let token: ERC20Mock - let usdc: USDCMock - let aToken: StaticATokenMock - let cToken: CTokenVaultMock - let aaveToken: ERC20Mock - let compToken: ERC20Mock - let compoundMock: ComptrollerMock - let rsr: ERC20Mock - let basket: Collateral[] - let backupToken1: ERC20Mock - let backupToken2: ERC20Mock - - // Assets - let tokenAsset: FiatCollateral - let usdcAsset: FiatCollateral - let aTokenAsset: ATokenFiatCollateral - let cTokenAsset: CTokenFiatCollateral - let backupCollateral1: FiatCollateral - let backupCollateral2: ATokenFiatCollateral - let collateral: Collateral[] - - let config: IConfig - - // Facade - let facadeAct: FacadeAct - - // Main - let rToken: TestIRToken - let stRSR: TestIStRSR - let basketHandler: TestIBasketHandler - let assetRegistry: IAssetRegistry - let backingManager: TestIBackingManager - let distributor: TestIDistributor - let gnosis: GnosisMock - let rsrTrader: TestIRevenueTrader - let rTokenTrader: TestIRevenueTrader - - // Computes the minBuyAmt for a sellAmt at two prices - // sellPrice + buyPrice should not be the low and high estimates, but rather the oracle prices - const toMinBuyAmt = async ( - sellAmt: BigNumber, - sellPrice: BigNumber, - buyPrice: BigNumber - ): Promise => { - // do all muls first so we don't round unnecessarily - // a = loss due to max trade slippage - // b = loss due to selling token at the low price - // c = loss due to buying token at the high price - // mirrors the math from TradeLib ~L:57 - - const lowSellPrice = sellPrice.sub(sellPrice.mul(ORACLE_ERROR).div(BN_SCALE_FACTOR)) - const highBuyPrice = buyPrice.add(buyPrice.mul(ORACLE_ERROR).div(BN_SCALE_FACTOR)) - const product = sellAmt - .mul(fp('1').sub(await backingManager.maxTradeSlippage())) // (a) - .mul(lowSellPrice) // (b) - - return divCeil(divCeil(product, highBuyPrice), fp('1')) // (c) - } - - beforeEach(async () => { - ;[owner, addr1, addr2] = await ethers.getSigners() - let erc20s: ERC20Mock[] - - // Deploy fixture - ;({ - aaveToken, - compToken, - compoundMock, - assetRegistry, - backingManager, - basketHandler, - distributor, - rsr, - erc20s, - collateral, - basket, - facadeAct, - rToken, - stRSR, - config, - rTokenTrader, - rsrTrader, - gnosis, - } = await loadFixture(defaultFixture)) - - // Get assets and tokens - tokenAsset = basket[0] - usdcAsset = basket[1] - aTokenAsset = basket[2] - cTokenAsset = basket[3] - - token = await ethers.getContractAt('ERC20Mock', await tokenAsset.erc20()) - usdc = await ethers.getContractAt('USDCMock', await usdcAsset.erc20()) - aToken = ( - await ethers.getContractAt('StaticATokenMock', await aTokenAsset.erc20()) - ) - cToken = ( - await ethers.getContractAt('CTokenVaultMock', await cTokenAsset.erc20()) - ) - - // Backup tokens and collaterals - USDT - aUSDT - aUSDC - aBUSD - backupToken1 = erc20s[2] // USDT - backupCollateral1 = collateral[2] - backupToken2 = erc20s[9] // aUSDT - backupCollateral2 = collateral[9] - }) - - // P1 only - describeP1('Keepers', () => { - let issueAmount: BigNumber - - beforeEach(async () => { - // Mint Tokens - initialBal = bn('10000000000e18') - await mintCollaterals(owner, [addr1, addr2], initialBal, basket) - - // Mint RSR - await rsr.connect(owner).mint(addr1.address, initialBal) - - // Issue some RTokens - issueAmount = bn('100e18') - - // Provide approvals - await token.connect(addr1).approve(rToken.address, initialBal) - await usdc.connect(addr1).approve(rToken.address, initialBal) - await aToken.connect(addr1).approve(rToken.address, initialBal) - await cToken.connect(addr1).approve(rToken.address, initialBal) - - // Issue rTokens - await rToken.connect(addr1).issue(issueAmount) - }) - - context('getActCalldata', () => { - it('No call required', async () => { - // Via Facade get next call - No action required - const [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(ZERO_ADDRESS) - expect(data).to.equal('0x') - }) - - it('Basket changes', async () => { - // Register Collateral - await assetRegistry.connect(owner).register(backupCollateral1.address) - await assetRegistry.connect(owner).register(backupCollateral2.address) - - // Set backup configuration - USDT and aUSDT as backup - await basketHandler - .connect(owner) - .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(2), [ - backupToken1.address, - backupToken2.address, - ]) - - // Check initial state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - - // Set Token2 to hard default - Decrease rate - await aToken.setExchangeRate(fp('0.99')) - - // Basket should switch as default is detected immediately - await assetRegistry.refresh() - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - - // Call via Facade - should detect call to Basket handler - const [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(basketHandler.address) - expect(data).to.not.equal('0x') - - await expect( - owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - ).to.emit(basketHandler, 'BasketSet') - - // Check state - Basket switch - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - }) - - it('Basket - Should handle no valid basket after refresh', async () => { - // Redeem all RTokens - await rToken.connect(addr1).redeem(issueAmount) - - // Set simple basket with only one collateral - await basketHandler.connect(owner).setPrimeBasket([aToken.address], [fp('1')]) - - // Set backup config with the same collateral - await basketHandler - .connect(owner) - .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [aToken.address]) - - // Switch basket - await expect(basketHandler.connect(owner).refreshBasket()) - .to.emit(basketHandler, 'BasketSet') - .withArgs(2, [aToken.address], [fp('1')], false) - - // Now default the token, will not be able to find a valid basket - await aToken.setExchangeRate(fp('0.99')) - await assetRegistry.refresh() - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) - - // Call via Facade - should not provide call to basket handler - const [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(ZERO_ADDRESS) - expect(data).to.equal('0x') - }) - - it('Revenues/Rewards', async () => { - const rewardAmountAAVE = bn('0.5e18') - - // AAVE Rewards - await aToken.setRewards(backingManager.address, rewardAmountAAVE) - - // Via Facade get next call - will claim rewards from backingManager - let [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - // Claim and sweep rewards - await owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - - // Collect revenue - // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) - const sellAmt: BigNumber = rewardAmountAAVE.mul(60).div(100) // due to f = 60% - const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) - - const sellAmtRToken: BigNumber = rewardAmountAAVE.sub(sellAmt) // Remainder - const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) - - // Via Facade get next call - will transfer RToken to Trader - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - // Manage tokens in Backing Manager - await owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - - // Next call would start Revenue auction - RTokenTrader - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rTokenTrader.address) - expect(data).to.not.equal('0x') - - // Manage tokens in RTokenTrader - await expect( - owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - ) - .to.emit(rTokenTrader, 'TradeStarted') - .withArgs( - anyValue, - aaveToken.address, - rToken.address, - sellAmtRToken, - withinQuad(minBuyAmtRToken) - ) - - // Via Facade get next call - will open RSR trade - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rsrTrader.address) - expect(data).to.not.equal('0x') - - // Manage tokens in RSRTrader - await expect( - owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - ) - .to.emit(rsrTrader, 'TradeStarted') - .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, minBuyAmt) - - // Check funds in Market - expect(await aaveToken.balanceOf(gnosis.address)).to.equal(rewardAmountAAVE) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // Mock auction by minting the buy tokens (in this case RSR and RToken) - await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) - await rToken.connect(addr1).approve(gnosis.address, minBuyAmtRToken) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmtRToken, - buyAmount: minBuyAmtRToken, - }) - await gnosis.placeBid(1, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: minBuyAmt, - }) - - // Settle RToken trades via Facade - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rTokenTrader.address) - expect(data).to.not.equal('0x') - - // Close auction in RToken Trader - await expect( - owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - ) - .to.emit(rTokenTrader, 'TradeSettled') - .withArgs(anyValue, aaveToken.address, rToken.address, sellAmtRToken, minBuyAmtRToken) - - // Now settle trade in RSR Trader - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rsrTrader.address) - expect(data).to.not.equal('0x') - - // Close auction in RSR Trader - await expect( - owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - ) - .to.emit(rsrTrader, 'TradeSettled') - .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, minBuyAmt) - - // Check no new calls to make from Facade - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(ZERO_ADDRESS) - expect(data).to.equal('0x') - - // distribute Revenue from RToken trader - await rTokenTrader.distributeTokenToBuy() - - // Claim additional Revenue but only send to RSR (to trigger RSR trader directly) - // Set f = 1 - await distributor - .connect(owner) - .setDistribution(FURNACE_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) - - // Avoid dropping 20 qCOMP by making there be exactly 1 distribution share. - await distributor - .connect(owner) - .setDistribution(STRSR_DEST, { rTokenDist: bn(0), rsrDist: bn(1) }) - - // Set new rewards - await aToken.setRewards(backingManager.address, rewardAmountAAVE) - - // Claim new rewards - await backingManager.claimRewards() - - // Via Facade get next call - will transfer RSR to Trader - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - // Manage tokens in Backing Manager - await owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - - // Next call would start Revenue auction - RSR Trader - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rsrTrader.address) - expect(data).to.not.equal('0x') - - // Manage tokens in RTokenTrader - const minBuyAmtAAVE = await toMinBuyAmt(rewardAmountAAVE, fp('1'), fp('1')) - await expect( - owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - ) - .to.emit(rsrTrader, 'TradeStarted') - .withArgs( - anyValue, - aaveToken.address, - rsr.address, - rewardAmountAAVE, - withinQuad(minBuyAmtAAVE) - ) - }) - - it('Revenues - Should handle minTradeVolume = 0', async () => { - // Set minTradeVolume = 0 - await rsrTrader.connect(owner).setMinTradeVolume(bn(0)) - await rTokenTrader.connect(owner).setMinTradeVolume(bn(0)) - - const rewardAmountAAVE = bn('0.5e18') - - // AAVE Rewards - await aToken.setRewards(backingManager.address, rewardAmountAAVE) - - // Via Facade get next call - will claim rewards from backingManager - let [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - // Claim and sweep rewards - await owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - - // Collect revenue - // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) - const sellAmt: BigNumber = rewardAmountAAVE.mul(60).div(100) // due to f = 60% - const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) - - const sellAmtRToken: BigNumber = rewardAmountAAVE.sub(sellAmt) // Remainder - const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) - - // Via Facade get next call - will transfer RToken to Trader - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - // Manage tokens in Backing Manager - await owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - - // Next call would start Revenue auction - RTokenTrader - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rTokenTrader.address) - expect(data).to.not.equal('0x') - - // Manage tokens in RTokenTrader - await expect( - owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - ) - .to.emit(rTokenTrader, 'TradeStarted') - .withArgs( - anyValue, - aaveToken.address, - rToken.address, - sellAmtRToken, - withinQuad(minBuyAmtRToken) - ) - - // Via Facade get next call - will open RSR trade - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rsrTrader.address) - expect(data).to.not.equal('0x') - - // Manage tokens in RSRTrader - await expect( - owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - ) - .to.emit(rsrTrader, 'TradeStarted') - .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, minBuyAmt) - - // Check funds in Market - expect(await aaveToken.balanceOf(gnosis.address)).to.equal(rewardAmountAAVE) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // Mock auction by minting the buy tokens (in this case RSR and RToken) - await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) - await rToken.connect(addr1).approve(gnosis.address, minBuyAmtRToken) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmtRToken, - buyAmount: minBuyAmtRToken, - }) - await gnosis.placeBid(1, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: minBuyAmt, - }) - - // Settle RToken trades via Facade - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rTokenTrader.address) - expect(data).to.not.equal('0x') - - // Close auction in RToken Trader - await expect( - owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - ) - .to.emit(rTokenTrader, 'TradeSettled') - .withArgs(anyValue, aaveToken.address, rToken.address, sellAmtRToken, minBuyAmtRToken) - - // Now settle trade in RSR Trader - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rsrTrader.address) - expect(data).to.not.equal('0x') - - // Close auction in RSR Trader - await expect( - owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - ) - .to.emit(rsrTrader, 'TradeSettled') - .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, minBuyAmt) - }) - - it('Revenues - Should handle ERC20s with invalid claim logic', async () => { - // Set the cToken to revert - await cToken.setRevertClaimRewards(true) - - const rewardAmountCOMP = bn('0.5e18') - - // COMP Rewards - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) - - // Via Facade get next call - will attempt to claim - const [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(ZERO_ADDRESS) - expect(data).to.equal('0x') - - // Check status - nothing claimed - expect(await compToken.balanceOf(backingManager.address)).to.equal(0) - }) - - it('Revenues - Should handle multiple assets with same reward token', async () => { - // Update Reward token for AToken to use same as CToken - const ATokenCollateralFactory = await ethers.getContractFactory('ATokenFiatCollateral') - const chainlinkFeed = ( - await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) - ) - - const newATokenCollateral: ATokenFiatCollateral = ( - await ATokenCollateralFactory.deploy( - { - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: chainlinkFeed.address, - oracleError: ORACLE_ERROR, - erc20: aToken.address, - maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: await aTokenAsset.oracleTimeout(), - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: await aTokenAsset.delayUntilDefault(), - }, - REVENUE_HIDING - ) - ) - // Perform asset swap - await assetRegistry.connect(owner).swapRegistered(newATokenCollateral.address) - - // Refresh basket - await basketHandler.connect(owner).refreshBasket() - - const rewardAmount = bn('0.5e18') - - // COMP Rewards for both tokens, in the RToken - await aToken.setAaveToken(compToken.address) // set it internally in our mock - await aToken.setRewards(backingManager.address, rewardAmount) - await compoundMock.setRewards(backingManager.address, rewardAmount) - - // Via Facade get next call - will Claim and sweep rewards - const [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - await owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), // weird hardhat-ethers bug - }) - - // Check status - rewards claimed for both collaterals - expect(await compToken.balanceOf(backingManager.address)).to.equal(rewardAmount.mul(2)) - }) - - it('Revenues - Should claim rewards in Revenue Traders', async () => { - const rewardAmountAAVE = bn('0.5e18') - - // AAVE Rewards - RSR Trader - await aToken.setRewards(rsrTrader.address, rewardAmountAAVE) - - // Via Facade get next call - will claim rewards from Traders, via Facade - let [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(facadeAct.address) - expect(data).to.not.equal('0x') - - // Claim rewards - await owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - - // Check rewards collected - expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(rewardAmountAAVE) - expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(bn(0)) - - // Via Facade get next call - will create a trade from the RSR Trader trade - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rsrTrader.address) - expect(data).to.not.equal('0x') - - await expect( - owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - ).to.emit(rsrTrader, 'TradeStarted') - - // AAVE Rewards - RToken Trader - await aToken.setRewards(rTokenTrader.address, rewardAmountAAVE) - - // Via Facade get next call - will claim rewards from Traders, via Facade - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(facadeAct.address) - expect(data).to.not.equal('0x') - - // Claim rewards - await owner.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - - // Check rewards collected - there are funds in the RTokenTrader now - expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(bn(0)) // moved to trade - expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(rewardAmountAAVE) - }) - - it('Should not revert if f=1', async () => { - await distributor - .connect(owner) - .setDistribution(FURNACE_DEST, { rTokenDist: 0, rsrDist: 0 }) - // Transfer free tokens to RTokenTrader - const hndAmt: BigNumber = bn('10e18') - await rToken.connect(addr1).transfer(rTokenTrader.address, hndAmt) - - let [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(ZERO_ADDRESS) - expect(data).to.equal('0x') - - // RSR can be distributed with no issues - seed stRSR with half as much - await rsr.connect(addr1).transfer(backingManager.address, hndAmt) - await rsr.connect(addr1).transfer(stRSR.address, hndAmt.div(2)) - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - expect(await rsr.balanceOf(stRSR.address)).to.equal(hndAmt.div(2)) - expect(await rsr.balanceOf(backingManager.address)).to.equal(hndAmt) - expect(await rsr.balanceOf(rsrTrader.address)).to.equal(0) - - // Execute managetokens in Backing Manager - await addr1.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - - // RSR forwarded - expect(await rsr.balanceOf(stRSR.address)).to.equal(hndAmt.add(hndAmt.div(2))) - expect(await rsr.balanceOf(backingManager.address)).to.equal(0) - expect(await rsr.balanceOf(rsrTrader.address)).to.equal(0) - }) - - it('Should not revert if f=0', async () => { - await distributor.connect(owner).setDistribution(STRSR_DEST, { rTokenDist: 0, rsrDist: 0 }) - // Transfer free tokens to RTokenTrader - const hndAmt: BigNumber = bn('10e18') - await rsr.connect(addr1).transfer(rsrTrader.address, hndAmt) - - let [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(ZERO_ADDRESS) - expect(data).to.equal('0x') - - // RToken can be distributed with no issues - await rToken.connect(addr1).transfer(backingManager.address, hndAmt) - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - expect(await rToken.balanceOf(backingManager.address)).to.equal(hndAmt) - expect(await rToken.balanceOf(rTokenTrader.address)).to.equal(0) - - // Execute managetokens in Backing Manager - await addr1.sendTransaction({ - to: addr, - data, - gasLimit: bn('10000000'), - }) - - // RToken forwarded - expect(await rToken.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.balanceOf(rTokenTrader.address)).to.equal(hndAmt) - }) - }) - }) - - describeGas('Gas Reporting', () => { - const numAssets = 128 - - beforeEach(async () => { - const m = await ethers.getContractAt('MainP1', await rToken.main()) - const assetRegistry = await ethers.getContractAt('AssetRegistryP1', await m.assetRegistry()) - const ERC20Factory = await ethers.getContractFactory('ERC20Mock') - const AssetFactory = await ethers.getContractFactory('Asset') - const feed = await tokenAsset.chainlinkFeed() - - // Get to numAssets registered assets - for (let i = 0; i < numAssets; i++) { - const erc20 = await ERC20Factory.deploy('Name', 'Symbol') - const asset = await AssetFactory.deploy( - PRICE_TIMEOUT, - feed, - ORACLE_ERROR, - erc20.address, - config.rTokenMaxTradeVolume, - bn(2).pow(47) - ) - await assetRegistry.connect(owner).register(asset.address) - const assets = await assetRegistry.erc20s() - if (assets.length > numAssets) break - } - expect((await assetRegistry.erc20s()).length).to.be.gte(numAssets) - }) - - it(`getActCalldata - gas reporting for ${numAssets} registered assets`, async () => { - await snapshotGasCost(facadeAct.getActCalldata(rToken.address)) - const [addr, bytes] = await facadeAct.callStatic.getActCalldata(rToken.address) - // Should return 0 addr and 0 bytes, otherwise we didn't use maximum gas - expect(addr).to.equal(ZERO_ADDRESS) - expect(bytes).to.equal('0x') - }) - }) -}) From bf69c3c5931177c89d51de6d156056df60a27938 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 15:05:55 -0400 Subject: [PATCH 213/462] RevenueTraderP1: do not refresh when in same block --- contracts/interfaces/IAsset.sol | 5 ++++- contracts/interfaces/IAssetRegistry.sol | 25 ++++++++++++++++++------ contracts/p0/AssetRegistry.sol | 3 +++ contracts/p1/AssetRegistry.sol | 7 ++++++- contracts/p1/RevenueTrader.sol | 14 +++++++------ contracts/plugins/assets/RTokenAsset.sol | 7 ++++++- 6 files changed, 46 insertions(+), 15 deletions(-) diff --git a/contracts/interfaces/IAsset.sol b/contracts/interfaces/IAsset.sol index faf202c8ca..824b9c4039 100644 --- a/contracts/interfaces/IAsset.sol +++ b/contracts/interfaces/IAsset.sol @@ -49,8 +49,11 @@ interface IAsset is IRewardable { /// @return If the asset is an instance of ICollateral or not function isCollateral() external view returns (bool); - /// @param {UoA} The max trade volume, in UoA + /// @return {UoA} The max trade volume, in UoA function maxTradeVolume() external view returns (uint192); + + /// @return {s} The timestamp of the last refresh() that saved prices + function lastSave() external view returns (uint48); } // Used only in Testing. Strictly speaking an Asset does not need to adhere to this interface diff --git a/contracts/interfaces/IAssetRegistry.sol b/contracts/interfaces/IAssetRegistry.sol index 450aa7e3fe..b5bdacf2f6 100644 --- a/contracts/interfaces/IAssetRegistry.sol +++ b/contracts/interfaces/IAssetRegistry.sol @@ -37,6 +37,25 @@ interface IAssetRegistry is IComponent { /// @custom:interaction function refresh() external; + /// Register `asset` + /// If either the erc20 address or the asset was already registered, fail + /// @return true if the erc20 address was not already registered. + /// @custom:governance + function register(IAsset asset) external returns (bool); + + /// Register `asset` if and only if its erc20 address is already registered. + /// If the erc20 address was not registered, revert. + /// @return swapped If the asset was swapped for a previously-registered asset + /// @custom:governance + function swapRegistered(IAsset asset) external returns (bool swapped); + + /// Unregister an asset, requiring that it is already registered + /// @custom:governance + function unregister(IAsset asset) external; + + /// @return {s} The timestamp of the last refresh + function lastRefresh() external view returns (uint48); + /// @return The corresponding asset for ERC20, or reverts if not registered function toAsset(IERC20 erc20) external view returns (IAsset); @@ -54,10 +73,4 @@ interface IAssetRegistry is IComponent { /// @return The number of registered ERC20s function size() external view returns (uint256); - - function register(IAsset asset) external returns (bool); - - function swapRegistered(IAsset asset) external returns (bool swapped); - - function unregister(IAsset asset) external; } diff --git a/contracts/p0/AssetRegistry.sol b/contracts/p0/AssetRegistry.sol index 2f105886da..cd4b0aded1 100644 --- a/contracts/p0/AssetRegistry.sol +++ b/contracts/p0/AssetRegistry.sol @@ -20,6 +20,8 @@ contract AssetRegistryP0 is ComponentP0, IAssetRegistry { // Registered Assets mapping(IERC20 => IAsset) private assets; + uint48 public lastRefresh; // {s} + function init(IMain main_, IAsset[] memory assets_) public initializer { __Component_init(main_); for (uint256 i = 0; i < assets_.length; i++) { @@ -37,6 +39,7 @@ contract AssetRegistryP0 is ComponentP0, IAssetRegistry { IBasketHandler basketHandler = main.basketHandler(); basketHandler.trackStatus(); + lastRefresh = uint48(block.timestamp); } /// Forbids registering a different asset for an ERC20 that is already registered diff --git a/contracts/p1/AssetRegistry.sol b/contracts/p1/AssetRegistry.sol index 71a17e0544..481673eff7 100644 --- a/contracts/p1/AssetRegistry.sol +++ b/contracts/p1/AssetRegistry.sol @@ -24,6 +24,10 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { // Registered Assets mapping(IERC20 => IAsset) private assets; + // === 3.0.0 === + + uint48 public lastRefresh; // {s} + /* ==== Contract Invariants ==== The contract state is just the mapping assets; _erc20s is ignored in properties. @@ -57,6 +61,7 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { } basketHandler.trackStatus(); + lastRefresh = uint48(block.timestamp); // safer to do this at end than start, actually } /// Register `asset` @@ -211,5 +216,5 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[47] private __gap; + uint256[46] private __gap; } diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 890f270730..cf7af8559a 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -80,14 +80,16 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { } // == Refresh == - if (erc20 == IERC20(address(rToken)) || tokenToBuy == IERC20(address(rToken))) { - // if either token is the RToken, refresh everything + // Skip refresh() if data is from current block + if (erc20 != IERC20(address(rToken)) && tokenToBuy != IERC20(address(rToken))) { + IAsset sell_ = assetRegistry.toAsset(erc20); + IAsset buy_ = assetRegistry.toAsset(tokenToBuy); + if (sell_.lastSave() != uint48(block.timestamp)) sell_.refresh(); + if (buy_.lastSave() != uint48(block.timestamp)) buy_.refresh(); + } else if (assetRegistry.lastRefresh() != uint48(block.timestamp)) { + // Refresh everything only if RToken is being traded assetRegistry.refresh(); furnace.melt(); - } else { - // otherwise, refresh just buy + sell assets - assetRegistry.toAsset(erc20).refresh(); - assetRegistry.toAsset(tokenToBuy).refresh(); } // == Checks/Effects == diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index b1c461878a..22c0ef9fb9 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -22,7 +22,7 @@ contract RTokenAsset is IAsset, VersionedAsset { uint8 public immutable erc20Decimals; - uint192 public immutable override maxTradeVolume; // {UoA} + uint192 public immutable maxTradeVolume; // {UoA} /// @param maxTradeVolume_ {UoA} The max trade volume, in UoA constructor(IRToken erc20_, uint192 maxTradeVolume_) { @@ -137,6 +137,11 @@ contract RTokenAsset is IAsset, VersionedAsset { return _safeWrap(erc20.balanceOf(account)); } + /// @return {s} The timestamp of the last refresh; always 0 since prices are never saved + function lastSave() external pure returns (uint48) { + return 0; + } + /// @return If the asset is an instance of ICollateral or not function isCollateral() external pure virtual returns (bool) { return false; From 3d7f4f7148e25050658236281422b7f98745027d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 19:38:53 -0400 Subject: [PATCH 214/462] remove FacadeMonitor --- contracts/facade/FacadeMonitor.sol | 139 --------- test/FacadeMonitor.ts | 469 ----------------------------- 2 files changed, 608 deletions(-) delete mode 100644 contracts/facade/FacadeMonitor.sol delete mode 100644 test/FacadeMonitor.ts diff --git a/contracts/facade/FacadeMonitor.sol b/contracts/facade/FacadeMonitor.sol deleted file mode 100644 index eedfb30858..0000000000 --- a/contracts/facade/FacadeMonitor.sol +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; - -import "../libraries/Fixed.sol"; - -import "../interfaces/IAssetRegistry.sol"; -import "../interfaces/IBroker.sol"; -import "../p1/RToken.sol"; -import "../p1/BackingManager.sol"; -import "../p1/RevenueTrader.sol"; - -/** - * @title Facade Monitor - * @notice A UX-friendly layer for reading out the state of an RToken, specifically for the Monitor. - * @custom:static-call - Use ethers callStatic() to get result after update; do not execute - */ -contract FacadeMonitor { - using FixLib for uint192; - - struct TradeResponse { - IERC20[] tradesToBeSettled; - IERC20[] tradesToBeStarted; - } - - function getTradesForBackingManager(RTokenP1 rToken) - external - returns (TradeResponse memory response) - { - IMain main = rToken.main(); - - IAssetRegistry assetRegistry = IAssetRegistry(address(main.assetRegistry())); - BackingManagerP1 backingManager = BackingManagerP1(address(main.backingManager())); - - IERC20[] memory erc20s = assetRegistry.erc20s(); - - // Let's check if there are any trades that we can settle. - if (backingManager.tradesOpen() > 0) { - uint256 tradeSettleCount; - IERC20[] memory tradesToBeSettled = new IERC20[](erc20s.length); - - for (uint256 i = 0; i < erc20s.length; ) { - ITrade trade = backingManager.trades(erc20s[i]); - if (address(trade) != address(0) && trade.canSettle()) { - tradesToBeSettled[tradeSettleCount] = erc20s[i]; - - unchecked { - ++tradeSettleCount; - } - } - - unchecked { - ++i; - } - } - - response.tradesToBeSettled = tradesToBeSettled; - } - - // Let's check if there are any trades we can start. - uint48 tradesOpen = backingManager.tradesOpen(); - backingManager.rebalance(TradeKind.DUTCH_AUCTION); - if (backingManager.tradesOpen() - tradesOpen != 0) { - response.tradesToBeStarted = erc20s; - } - } - - function getTradesForRevenueTraders(RTokenP1 rToken) - external - returns (TradeResponse memory rTokenTraderResponse, TradeResponse memory rsrTraderResponse) - { - IMain main = rToken.main(); - - IAssetRegistry assetRegistry = IAssetRegistry(address(main.assetRegistry())); - RevenueTraderP1 rTokenTrader = RevenueTraderP1(address(main.rTokenTrader())); - RevenueTraderP1 rsrTrader = RevenueTraderP1(address(main.rsrTrader())); - - IERC20[] memory erc20s = assetRegistry.erc20s(); - - rTokenTraderResponse = getTradesForTrader(rTokenTrader, erc20s); - rsrTraderResponse = getTradesForTrader(rsrTrader, erc20s); - } - - function getTradesForTrader(RevenueTraderP1 trader, IERC20[] memory erc20s) - internal - returns (TradeResponse memory response) - { - uint256 erc20Count = erc20s.length; - - // Let's check if there are any trades that we can settle. - if (trader.tradesOpen() > 0) { - uint256 tradeSettleCount; - IERC20[] memory tradesToBeSettled = new IERC20[](erc20Count); - - for (uint256 i = 0; i < erc20Count; ) { - ITrade trade = trader.trades(erc20s[i]); - - if (address(trade) != address(0) && trade.canSettle()) { - tradesToBeSettled[tradeSettleCount] = erc20s[i]; - - unchecked { - ++tradeSettleCount; - } - } - - unchecked { - ++i; - } - } - - response.tradesToBeSettled = tradesToBeSettled; - } - - // Let's check if there are any trades we can start. - uint48 tradesOpen = trader.tradesOpen(); - uint256 tradeStartCount; - IERC20[] memory tradesToBeStarted = new IERC20[](erc20Count); - - for (uint256 i = 0; i < erc20Count; ) { - trader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION); - - uint48 newTradesOpen = trader.tradesOpen(); - - if (newTradesOpen - tradesOpen != 0) { - tradesToBeStarted[tradeStartCount] = erc20s[i]; - tradesOpen = newTradesOpen; - - unchecked { - ++tradeStartCount; - } - } - - unchecked { - ++i; - } - } - - response.tradesToBeStarted = tradesToBeStarted; - } -} diff --git a/test/FacadeMonitor.ts b/test/FacadeMonitor.ts deleted file mode 100644 index e64ac004c7..0000000000 --- a/test/FacadeMonitor.ts +++ /dev/null @@ -1,469 +0,0 @@ -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' -import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { expect } from 'chai' -import { BigNumber } from 'ethers' -import { ethers } from 'hardhat' -import { - CollateralStatus, - FURNACE_DEST, - STRSR_DEST, - ZERO_ADDRESS, - BN_SCALE_FACTOR, -} from '../common/constants' -import { bn, fp, toBNDecimals, divCeil } from '../common/numbers' -import { IConfig } from '../common/configuration' -import { advanceTime } from './utils/time' -import { withinQuad } from './utils/matchers' -import { - ATokenFiatCollateral, - CTokenFiatCollateral, - CTokenMock, - ERC20Mock, - FacadeAct, - FiatCollateral, - GnosisMock, - StaticATokenMock, - TestIBackingManager, - TestIBasketHandler, - TestIDistributor, - TestIRevenueTrader, - TestIRToken, - USDCMock, - FacadeMonitor, -} from '../typechain' -import { - Collateral, - Implementation, - IMPLEMENTATION, - ORACLE_ERROR, - defaultFixture, -} from './fixtures' - -const describeP1 = IMPLEMENTATION == Implementation.P1 ? describe : describe.skip - -describe('FacadeMonitor Contract', () => { - let owner: SignerWithAddress - let addr1: SignerWithAddress - let addr2: SignerWithAddress - - // Tokens - let initialBal: BigNumber - let token: ERC20Mock - let usdc: USDCMock - let aToken: StaticATokenMock - let cToken: CTokenMock - let aaveToken: ERC20Mock - let rsr: ERC20Mock - let basket: Collateral[] - - // Assets - let tokenAsset: FiatCollateral - let usdcAsset: FiatCollateral - let aTokenAsset: ATokenFiatCollateral - let cTokenAsset: CTokenFiatCollateral - - let config: IConfig - - // Facade - let facadeAct: FacadeAct - let facadeMonitor: FacadeMonitor - - // Main - let rToken: TestIRToken - let basketHandler: TestIBasketHandler - let backingManager: TestIBackingManager - let distributor: TestIDistributor - let gnosis: GnosisMock - let rsrTrader: TestIRevenueTrader - let rTokenTrader: TestIRevenueTrader - - // Computes the minBuyAmt for a sellAmt at two prices - // sellPrice + buyPrice should not be the low and high estimates, but rather the oracle prices - const toMinBuyAmt = async ( - sellAmt: BigNumber, - sellPrice: BigNumber, - buyPrice: BigNumber - ): Promise => { - // do all muls first so we don't round unnecessarily - // a = loss due to max trade slippage - // b = loss due to selling token at the low price - // c = loss due to buying token at the high price - // mirrors the math from TradeLib ~L:57 - - const lowSellPrice = sellPrice.sub(sellPrice.mul(ORACLE_ERROR).div(BN_SCALE_FACTOR)) - const highBuyPrice = buyPrice.add(buyPrice.mul(ORACLE_ERROR).div(BN_SCALE_FACTOR)) - const product = sellAmt - .mul(fp('1').sub(await backingManager.maxTradeSlippage())) // (a) - .mul(lowSellPrice) // (b) - - return divCeil(divCeil(product, highBuyPrice), fp('1')) // (c) - } - - beforeEach(async () => { - ;[owner, addr1, addr2] = await ethers.getSigners() - - // Deploy fixture - ;({ - aaveToken, - backingManager, - basketHandler, - distributor, - rsr, - basket, - facadeAct, - facadeMonitor, - rToken, - config, - rTokenTrader, - rsrTrader, - gnosis, - } = await loadFixture(defaultFixture)) - - // Get assets and tokens - tokenAsset = basket[0] - usdcAsset = basket[1] - aTokenAsset = basket[2] - cTokenAsset = basket[3] - - token = await ethers.getContractAt('ERC20Mock', await tokenAsset.erc20()) - usdc = await ethers.getContractAt('USDCMock', await usdcAsset.erc20()) - aToken = ( - await ethers.getContractAt('StaticATokenMock', await aTokenAsset.erc20()) - ) - cToken = await ethers.getContractAt('CTokenMock', await cTokenAsset.erc20()) - }) - - // P1 only - describeP1('Keepers - Facade Monitor', () => { - let issueAmount: BigNumber - - beforeEach(async () => { - // Mint Tokens - initialBal = bn('10000000000e18') - await token.connect(owner).mint(addr1.address, initialBal) - await usdc.connect(owner).mint(addr1.address, initialBal) - await aToken.connect(owner).mint(addr1.address, initialBal) - await cToken.connect(owner).mint(addr1.address, initialBal) - - await token.connect(owner).mint(addr2.address, initialBal) - await usdc.connect(owner).mint(addr2.address, initialBal) - await aToken.connect(owner).mint(addr2.address, initialBal) - await cToken.connect(owner).mint(addr2.address, initialBal) - - // Mint RSR - await rsr.connect(owner).mint(addr1.address, initialBal) - - // Issue some RTokens - issueAmount = bn('100e18') - - // Provide approvals - await token.connect(addr1).approve(rToken.address, initialBal) - await usdc.connect(addr1).approve(rToken.address, initialBal) - await aToken.connect(addr1).approve(rToken.address, initialBal) - await cToken.connect(addr1).approve(rToken.address, initialBal) - - // Issue rTokens - await rToken.connect(addr1).issue(issueAmount) - }) - - it('No Trades Available', async () => { - const response = await facadeMonitor.callStatic.getTradesForBackingManager(rToken.address) - - expect(response.tradesToBeSettled.length).to.equal(0) - expect(response.tradesToBeStarted.length).to.equal(0) - }) - - it('Trades in Backing Manager', async () => { - await basketHandler.connect(owner).setPrimeBasket([usdc.address], [fp('1')]) - - await expect(basketHandler.connect(owner).refreshBasket()) - .to.emit(basketHandler, 'BasketSet') - .withArgs(2, [usdc.address], [fp('1')], false) - - const sellAmt: BigNumber = await token.balanceOf(backingManager.address) - const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) - - const response = await facadeMonitor.callStatic.getTradesForBackingManager(rToken.address) - - expect(response.tradesToBeStarted.length > 0).to.be.true - - // Run auction via Facade - let [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - await expect( - owner.sendTransaction({ - to: addr, - data, - }) - ) - .to.emit(backingManager, 'TradeStarted') - .withArgs(anyValue, token.address, usdc.address, sellAmt, toBNDecimals(minBuyAmt, 6).add(1)) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - - // Perform Mock Bids for the new Token (addr1 has balance) - // Get fair price - all tokens - await usdc.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6)) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: toBNDecimals(sellAmt, 6), - }) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - const response2 = await facadeMonitor.callStatic.getTradesForBackingManager(rToken.address) - - expect( - response2.tradesToBeSettled.filter((e) => e != ethers.constants.AddressZero).length > 0 - ).to.be.true - - // Trade is ready to be settled - Call settle trade via Facade - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - // End current auction - await expect( - owner.sendTransaction({ - to: addr, - data, - }) - ) - .to.emit(backingManager, 'TradeSettled') - .withArgs(anyValue, token.address, usdc.address, sellAmt, toBNDecimals(sellAmt, 6)) - - const response3 = await facadeMonitor.callStatic.getTradesForBackingManager(rToken.address) - - expect( - response3.tradesToBeSettled.filter((e) => e != ethers.constants.AddressZero).length == 0 - ).to.be.true - }) - - it('Revenues/Rewards - Traders', async () => { - const rewardAmountAAVE = bn('0.5e18') - - // AAVE Rewards - await aToken.setRewards(backingManager.address, rewardAmountAAVE) - - // Via Facade get next call - will claim rewards from backingManager - let [addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - // Claim and sweep rewards - await owner.sendTransaction({ - to: addr, - data, - }) - - // Collect revenue - // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) - const sellAmt: BigNumber = rewardAmountAAVE.mul(60).div(100) // due to f = 60% - const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) - - const sellAmtRToken: BigNumber = rewardAmountAAVE.sub(sellAmt) // Remainder - const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) - - // Via Facade get next call - will transfer RToken to Trader - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - // Manage tokens in Backing Manager - await owner.sendTransaction({ - to: addr, - data, - }) - - // Next call would start Revenue auction - RTokenTrader - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rTokenTrader.address) - expect(data).to.not.equal('0x') - - const response = await facadeMonitor.callStatic.getTradesForRevenueTraders(rToken.address) - - expect( - response.rTokenTraderResponse.tradesToBeStarted.filter( - (e) => e != ethers.constants.AddressZero - ).length > 0 - ).to.be.true - expect( - response.rsrTraderResponse.tradesToBeStarted.filter( - (e) => e != ethers.constants.AddressZero - ).length > 0 - ).to.be.true - - // Manage tokens in RTokenTrader - await expect( - owner.sendTransaction({ - to: addr, - data, - }) - ) - .to.emit(rTokenTrader, 'TradeStarted') - .withArgs( - anyValue, - aaveToken.address, - rToken.address, - sellAmtRToken, - withinQuad(minBuyAmtRToken) - ) - - // Via Facade get next call - will open RSR trade - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rsrTrader.address) - expect(data).to.not.equal('0x') - - // Manage tokens in RSRTrader - await expect( - owner.sendTransaction({ - to: addr, - data, - }) - ) - .to.emit(rsrTrader, 'TradeStarted') - .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, minBuyAmt) - - // Check funds in Market - expect(await aaveToken.balanceOf(gnosis.address)).to.equal(rewardAmountAAVE) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // Mock auction by minting the buy tokens (in this case RSR and RToken) - await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) - await rToken.connect(addr1).approve(gnosis.address, minBuyAmtRToken) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmtRToken, - buyAmount: minBuyAmtRToken, - }) - await gnosis.placeBid(1, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: minBuyAmt, - }) - - const response2 = await facadeMonitor.callStatic.getTradesForRevenueTraders(rToken.address) - - expect( - response2.rTokenTraderResponse.tradesToBeSettled.filter( - (e) => e != ethers.constants.AddressZero - ).length > 0 - ).to.be.true - expect( - response2.rsrTraderResponse.tradesToBeSettled.filter( - (e) => e != ethers.constants.AddressZero - ).length > 0 - ).to.be.true - - // Settle RToken trades via Facade - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rTokenTrader.address) - expect(data).to.not.equal('0x') - - // Close auction in RToken Trader - await expect( - owner.sendTransaction({ - to: addr, - data, - }) - ) - .to.emit(rTokenTrader, 'TradeSettled') - .withArgs(anyValue, aaveToken.address, rToken.address, sellAmtRToken, minBuyAmtRToken) - - // Now settle trade in RSR Trader - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rsrTrader.address) - expect(data).to.not.equal('0x') - - // Close auction in RSR Trader - await expect( - owner.sendTransaction({ - to: addr, - data, - }) - ) - .to.emit(rsrTrader, 'TradeSettled') - .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, minBuyAmt) - - const response3 = await facadeMonitor.callStatic.getTradesForRevenueTraders(rToken.address) - - expect( - response3.rTokenTraderResponse.tradesToBeSettled.filter( - (e) => e != ethers.constants.AddressZero - ).length == 0 - ).to.be.true - expect( - response3.rsrTraderResponse.tradesToBeSettled.filter( - (e) => e != ethers.constants.AddressZero - ).length == 0 - ).to.be.true - - // Check no new calls to make from Facade - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(ZERO_ADDRESS) - expect(data).to.equal('0x') - - // distribute Revenue from RToken trader - await rTokenTrader.manageToken(rToken.address) - - // Claim additional Revenue but only send to RSR (to trigger RSR trader directly) - // Set f = 1 - await distributor - .connect(owner) - .setDistribution(FURNACE_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) - - // Avoid dropping 20 qCOMP by making there be exactly 1 distribution share. - await distributor - .connect(owner) - .setDistribution(STRSR_DEST, { rTokenDist: bn(0), rsrDist: bn(1) }) - - // Set new rewards - await aToken.setRewards(backingManager.address, rewardAmountAAVE) - - // Claim new rewards - await backingManager.claimRewards() - - // Via Facade get next call - will transfer RSR to Trader - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(backingManager.address) - expect(data).to.not.equal('0x') - - // Manage tokens in Backing Manager - await owner.sendTransaction({ - to: addr, - data, - }) - - // Next call would start Revenue auction - RSR Trader - ;[addr, data] = await facadeAct.callStatic.getActCalldata(rToken.address) - expect(addr).to.equal(rsrTrader.address) - expect(data).to.not.equal('0x') - - // Manage tokens in RTokenTrader - const minBuyAmtAAVE = await toMinBuyAmt(rewardAmountAAVE, fp('1'), fp('1')) - await expect( - owner.sendTransaction({ - to: addr, - data, - }) - ) - .to.emit(rsrTrader, 'TradeStarted') - .withArgs( - anyValue, - aaveToken.address, - rsr.address, - rewardAmountAAVE, - withinQuad(minBuyAmtAAVE) - ) - }) - }) -}) From ecc5c57542504350db5323882a6f460558e3c5c2 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 19:39:48 -0400 Subject: [PATCH 215/462] scrub .md mentions of getActCalldata --- docs/mev.md | 21 --------------------- test/__snapshots__/FacadeAct.test.ts.snap | 3 --- 2 files changed, 24 deletions(-) delete mode 100644 test/__snapshots__/FacadeAct.test.ts.snap diff --git a/docs/mev.md b/docs/mev.md index e99390f64c..12eda2189d 100644 --- a/docs/mev.md +++ b/docs/mev.md @@ -5,24 +5,3 @@ This document is intended to serve as a resource for MEV searchers and others lo ## Overview TODO - -## FacadeAct - -The contract [contracts/facade/FacadeAct.sol](contracts/facade/FacadeAct.sol) provides a single calldata preparation function `getActCalldata(...)` that should be executed via [ethers.callStatic](https://docs.ethers.io/v5/api/contract/contract/#contract-callStatic). - -``` -function getActCalldata(RTokenP1 rToken) external returns (address to, bytes memory calldata_); -``` - -If the zero address is returned, then no action needs to be taken on the RToken instance at the moment. - -If a nonzero address is returned, then the bot/caller can sign a tx addressed to the address returned, with the data bytes from the second return value. This may be a call such as: - -- `rToken.main().furnace().melt()` -- `rToken.main().backingManager().manageTokens([...])` -- `rToken.main().rTokenTrader().manageToken(...)` -- `rToken.main().rsrTrader().manageToken(...)` -- `rToken.main().stRSR().payoutRewards()` -- etc - -You'll definitely want to simulate the tx first though, to understand the gas cost and decide whether you actually want to execute it. diff --git a/test/__snapshots__/FacadeAct.test.ts.snap b/test/__snapshots__/FacadeAct.test.ts.snap deleted file mode 100644 index d0739385f5..0000000000 --- a/test/__snapshots__/FacadeAct.test.ts.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FacadeAct contract Gas Reporting getActCalldata - gas reporting for 128 registered assets 1`] = `19102755`; From a84b1d35f53c9ed204606aec4ade7f6f8e254650 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 19:42:21 -0400 Subject: [PATCH 216/462] scrub mentions of FacadeMonitor --- scripts/deploy.ts | 1 - scripts/deployment/common.ts | 1 - .../phase1-common/0_setup_deployments.ts | 2 +- .../phase1-common/8_deploy_facadeMonitor.ts | 56 ------------------- scripts/verification/4_verify_facade.ts | 8 --- test/fixtures.ts | 7 --- 6 files changed, 1 insertion(+), 74 deletions(-) delete mode 100644 scripts/deployment/phase1-common/8_deploy_facadeMonitor.ts diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 236c73ac5f..978d6cd9bc 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -34,7 +34,6 @@ async function main() { 'phase1-common/5_deploy_deployer.ts', 'phase1-common/6_deploy_facadeWrite.ts', 'phase1-common/7_deploy_facadeAct.ts', - 'phase1-common/8_deploy_facadeMonitor.ts', // ============================================= 'phase2-assets/0_setup_deployments.ts', 'phase2-assets/1_deploy_assets.ts', diff --git a/scripts/deployment/common.ts b/scripts/deployment/common.ts index 10634cdcbe..ecf33f2368 100644 --- a/scripts/deployment/common.ts +++ b/scripts/deployment/common.ts @@ -16,7 +16,6 @@ export interface IDeployments { facadeRead: string facadeWriteLib: string cvxMiningLib: string - facadeMonitor: string facadeWrite: string facadeAct: string deployer: string diff --git a/scripts/deployment/phase1-common/0_setup_deployments.ts b/scripts/deployment/phase1-common/0_setup_deployments.ts index 603da02a07..2cd8266bbe 100644 --- a/scripts/deployment/phase1-common/0_setup_deployments.ts +++ b/scripts/deployment/phase1-common/0_setup_deployments.ts @@ -58,8 +58,8 @@ async function main() { facadeRead: '', facadeAct: '', facadeWriteLib: '', + basketLib: '', facadeWrite: '', - facadeMonitor: '', deployer: '', rsrAsset: '', implementations: { diff --git a/scripts/deployment/phase1-common/8_deploy_facadeMonitor.ts b/scripts/deployment/phase1-common/8_deploy_facadeMonitor.ts deleted file mode 100644 index adec507c6a..0000000000 --- a/scripts/deployment/phase1-common/8_deploy_facadeMonitor.ts +++ /dev/null @@ -1,56 +0,0 @@ -import fs from 'fs' -import hre, { ethers } from 'hardhat' - -import { getChainId, isValidContract } from '../../../common/blockchain-utils' -import { networkConfig } from '../../../common/configuration' -import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../common' -import { validateImplementations } from '../utils' -import { FacadeMonitor } from '../../../typechain' - -let facadeMonitor: FacadeMonitor - -async function main() { - // ==== Read Configuration ==== - const [burner] = await hre.ethers.getSigners() - const chainId = await getChainId(hre) - - console.log(`Deploying Facade Monitor to network ${hre.network.name} (${chainId}) - with burner account: ${burner.address}`) - - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - - const deploymentFilename = getDeploymentFilename(chainId) - const deployments = getDeploymentFile(deploymentFilename) - - await validateImplementations(deployments) - - // Check previous step executed - if (!deployments.rsrAsset) { - throw new Error(`Missing deployed contracts in network ${hre.network.name}`) - } else if (!(await isValidContract(hre, deployments.rsrAsset))) { - throw new Error(`RSR Asset contract not found in network ${hre.network.name}`) - } - - // ******************** Deploy FacadeMonitor ****************************************/ - - // Deploy FacadeMonitor - const FacadeMonitorFactory = await ethers.getContractFactory('FacadeMonitor') - - facadeMonitor = await FacadeMonitorFactory.connect(burner).deploy() - await facadeMonitor.deployed() - - // Write temporary deployments file - deployments.facadeMonitor = facadeMonitor.address - fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) - - console.log(`Deployed to ${hre.network.name} (${chainId}) - FacadeMonitor: ${facadeMonitor.address} - Deployment file: ${deploymentFilename}`) -} - -main().catch((error) => { - console.error(error) - process.exitCode = 1 -}) diff --git a/scripts/verification/4_verify_facade.ts b/scripts/verification/4_verify_facade.ts index e987807a6b..b75b623a3f 100644 --- a/scripts/verification/4_verify_facade.ts +++ b/scripts/verification/4_verify_facade.ts @@ -28,14 +28,6 @@ async function main() { 'contracts/facade/FacadeRead.sol:FacadeRead' ) - /** ******************** Verify FacadeMonitor ****************************************/ - await verifyContract( - chainId, - deployments.facadeMonitor, - [], - 'contracts/facade/FacadeMonitor.sol:FacadeMonitor' - ) - /** ******************** Verify FacadeAct ****************************************/ await verifyContract( chainId, diff --git a/test/fixtures.ts b/test/fixtures.ts index 51832b5888..e99720379b 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -54,7 +54,6 @@ import { RecollateralizationLibP1, USDCMock, NonFiatCollateral, - FacadeMonitor, ZeroDecimalMock, } from '../typechain' import { advanceTime, getLatestBlockTimestamp, setNextBlockTimestamp } from './utils/time' @@ -400,7 +399,6 @@ export interface DefaultFixture extends RSRAndCompAaveAndCollateralAndModuleFixt facade: FacadeRead facadeAct: FacadeAct facadeTest: FacadeTest - facadeMonitor: FacadeMonitor broker: TestIBroker rsrTrader: TestIRevenueTrader rTokenTrader: TestIRevenueTrader @@ -468,10 +466,6 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = const FacadeActFactory: ContractFactory = await ethers.getContractFactory('FacadeAct') const facadeAct = await FacadeActFactory.deploy() - // Deploy FacadeMonitor - const FacadeMonitorFactory: ContractFactory = await ethers.getContractFactory('FacadeMonitor') - const facadeMonitor = await FacadeMonitorFactory.deploy() - // Deploy FacadeTest const FacadeTestFactory: ContractFactory = await ethers.getContractFactory('FacadeTest') const facadeTest = await FacadeTestFactory.deploy() @@ -742,7 +736,6 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = easyAuction, facade, facadeAct, - facadeMonitor, facadeTest, rsrTrader, rTokenTrader, From be1b0de1bf55e1a9def6e0206fd1876f7879625a Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 21:12:12 -0400 Subject: [PATCH 217/462] draft StRSR.withdraw leakyRefresh() --- contracts/p1/StRSR.sol | 89 ++++++++++++++++++++++++++++++------------ 1 file changed, 64 insertions(+), 25 deletions(-) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index b85e7ec9ce..cee8c14156 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -59,9 +59,9 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // Typically: "balances". These are the tokenized staking positions! // era => ({account} => {qStRSR}) mapping(uint256 => mapping(address => uint256)) private stakes; // Stakes per account {qStRSR} - uint256 internal totalStakes; // Total of all stakes {qStRSR} - uint256 internal stakeRSR; // Amount of RSR backing all stakes {qRSR} - uint192 public stakeRate; // The exchange rate between stakes and RSR. D18{qStRSR/qRSR} + uint256 private totalStakes; // Total of all stakes {qStRSR} + uint256 private stakeRSR; // Amount of RSR backing all stakes {qRSR} + uint192 private stakeRate; // The exchange rate between stakes and RSR. D18{qStRSR/qRSR} uint192 private constant MAX_STAKE_RATE = 1e9 * FIX_ONE; // 1e9 D18{qStRSR/qRSR} @@ -81,8 +81,8 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // draftEra => ({account} => {drafts}) mapping(uint256 => mapping(address => CumulativeDraft[])) public draftQueues; // {drafts} mapping(uint256 => mapping(address => uint256)) public firstRemainingDraft; // draft index - uint256 internal totalDrafts; // Total of all drafts {qDrafts} - uint256 internal draftRSR; // Amount of RSR backing all drafts {qRSR} + uint256 private totalDrafts; // Total of all drafts {qDrafts} + uint256 private draftRSR; // Amount of RSR backing all drafts {qRSR} uint192 public draftRate; // The exchange rate between drafts and RSR. D18{qDrafts/qRSR} uint192 private constant MAX_DRAFT_RATE = 1e9 * FIX_ONE; // 1e9 D18{qDrafts/qRSR} @@ -148,7 +148,14 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab uint48 public payoutLastPaid; // {qRSR} How much reward RSR was held the last time rewards were paid out - uint256 internal rsrRewardsAtLastPayout; + uint256 private rsrRewardsAtLastPayout; + + // === 3.0.0 === + // The fraction of draftRSR + stakeRSR that may exit without a refresh + uint192 private constant MAX_LEAK = 5e15; // {1} 0.5% + + uint192 private leak; // {1} stake fraction that has withdrawn without a refresh + uint48 private lastWithdrawRefresh; // {s} timestamp of last refresh() during withdraw() // ====================== @@ -253,7 +260,9 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // // A draft for (totalDrafts' - totalDrafts) drafts // is freshly appended to the caller's draft record. - function unstake(uint256 stakeAmount) external notTradingPausedOrFrozen { + function unstake(uint256 stakeAmount) external { + requireNotTradingPausedOrFrozen(); + address account = _msgSender(); require(stakeAmount > 0, "Cannot withdraw zero"); require(stakes[era][account] >= stakeAmount, "Not enough balance"); @@ -276,7 +285,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab } /// Complete an account's unstaking; callable by anyone - /// @custom:interaction RCEI + /// @custom:interaction CEIC - Warning: violates CEI; has checks at the end // Let: // r = draft[account] // draftAmount = r.queue[endId - 1].drafts - r.queue[r.left-1].drafts @@ -296,46 +305,48 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // // actions: // rsr.transfer(account, rsrOut) - function withdraw(address account, uint256 endId) external notTradingPausedOrFrozen { - // == Refresh == - assetRegistry.refresh(); - - // == Checks + Effects == - require(basketHandler.fullyCollateralized(), "RToken uncollateralized"); - require(basketHandler.isReady(), "basket not ready"); + function withdraw(address account, uint256 endId) external { + requireNotTradingPausedOrFrozen(); uint256 firstId = firstRemainingDraft[draftEra][account]; CumulativeDraft[] storage queue = draftQueues[draftEra][account]; if (endId == 0 || firstId >= endId) return; + // == Checks + Effects == require(endId <= queue.length, "index out-of-bounds"); require(queue[endId - 1].availableAt <= block.timestamp, "withdrawal unavailable"); uint192 oldDrafts = firstId > 0 ? queue[firstId - 1].drafts : 0; uint192 draftAmount = queue[endId - 1].drafts - oldDrafts; - // advance queue past withdrawal - firstRemainingDraft[draftEra][account] = endId; - // ==== Compute RSR amount uint256 newTotalDrafts = totalDrafts - draftAmount; // newDraftRSR: {qRSR} = {qDrafts} * D18 / D18{qDrafts/qRSR} uint256 newDraftRSR = (newTotalDrafts * FIX_ONE_256 + (draftRate - 1)) / draftRate; uint256 rsrAmount = draftRSR - newDraftRSR; + // advance queue past withdrawal + firstRemainingDraft[draftEra][account] = endId; + if (rsrAmount == 0) return; // ==== Transfer RSR from the draft pool totalDrafts = newTotalDrafts; draftRSR = newDraftRSR; + // == Interactions == + leakyRefresh(rsrAmount); + IERC20Upgradeable(address(rsr)).safeTransfer(account, rsrAmount); emit UnstakingCompleted(firstId, endId, draftEra, account, rsrAmount); - // == Interaction == - IERC20Upgradeable(address(rsr)).safeTransfer(account, rsrAmount); + // == Checks == + require(basketHandler.fullyCollateralized(), "RToken uncollateralized"); + require(basketHandler.isReady(), "basket not ready"); } - function cancelUnstake(uint256 endId) external notTradingPausedOrFrozen { + function cancelUnstake(uint256 endId) external { + requireNotTradingPausedOrFrozen(); + address account = _msgSender(); // We specifically allow unstaking when under collateralized @@ -415,7 +426,9 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // other properties: // seized >= rsrAmount, which should be a logical consequence of the above effects - function seizeRSR(uint256 rsrAmount) external notTradingPausedOrFrozen { + function seizeRSR(uint256 rsrAmount) external { + requireNotTradingPausedOrFrozen(); + require(_msgSender() == address(backingManager), "not backing manager"); require(rsrAmount > 0, "Amount cannot be zero"); @@ -659,6 +672,27 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab return rsr.balanceOf(address(this)) - stakeRSR - draftRSR; } + /// Refresh if too much RSR has exited since the last refresh occurred + /// @param rsrWithdrawal {qRSR} How much RSR is being withdrawn + function leakyRefresh(uint256 rsrWithdrawal) private { + uint48 lastRefresh = assetRegistry.lastRefresh(); // {s} + + // Assume rsrWithdrawal has already been taken out of draftRSR + uint192 withdrawal = divuu(rsrWithdrawal, stakeRSR + draftRSR + rsrWithdrawal); // {1} + + leak = lastWithdrawRefresh != lastRefresh ? withdrawal : leak + withdrawal; + lastWithdrawRefresh = lastRefresh; + + if (leak > MAX_LEAK) { + leak = 0; + lastWithdrawRefresh = uint48(block.timestamp); + assetRegistry.refresh(); + } + } + + // contract-size-saver + function requireNotTradingPausedOrFrozen() private notTradingPausedOrFrozen {} + // ==== ERC20 ==== // This section extracted from ERC20; adjusted to work with stakes/eras // name(), symbol(), and decimals() are all auto-generated @@ -873,24 +907,29 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // ==== Gov Param Setters ==== /// @custom:governance - function setUnstakingDelay(uint48 val) public governance { + function setUnstakingDelay(uint48 val) public { + governanceOnly(); require(val > MIN_UNSTAKING_DELAY && val <= MAX_UNSTAKING_DELAY, "invalid unstakingDelay"); emit UnstakingDelaySet(unstakingDelay, val); unstakingDelay = val; } /// @custom:governance - function setRewardRatio(uint192 val) public governance { + function setRewardRatio(uint192 val) public { + governanceOnly(); if (!main.frozen()) _payoutRewards(); require(val <= MAX_REWARD_RATIO, "invalid rewardRatio"); emit RewardRatioSet(rewardRatio, val); rewardRatio = val; } + // contract-size-saver + function governanceOnly() private governance {} + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[30] private __gap; + uint256[29] private __gap; } From ab333657cbad5079b1b05c21519ad986d580cd59 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 21:13:58 -0400 Subject: [PATCH 218/462] nit --- contracts/p1/StRSR.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index cee8c14156..e03e310c69 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -319,15 +319,15 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab uint192 oldDrafts = firstId > 0 ? queue[firstId - 1].drafts : 0; uint192 draftAmount = queue[endId - 1].drafts - oldDrafts; + // advance queue past withdrawal + firstRemainingDraft[draftEra][account] = endId; + // ==== Compute RSR amount uint256 newTotalDrafts = totalDrafts - draftAmount; // newDraftRSR: {qRSR} = {qDrafts} * D18 / D18{qDrafts/qRSR} uint256 newDraftRSR = (newTotalDrafts * FIX_ONE_256 + (draftRate - 1)) / draftRate; uint256 rsrAmount = draftRSR - newDraftRSR; - // advance queue past withdrawal - firstRemainingDraft[draftEra][account] = endId; - if (rsrAmount == 0) return; // ==== Transfer RSR from the draft pool From dabe3de3159ea3f3f876b4696f2ef85fe627b17d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 21:24:33 -0400 Subject: [PATCH 219/462] lint clean --- contracts/p1/StRSR.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index e03e310c69..5fff432dd0 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -691,6 +691,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab } // contract-size-saver + // solhint-disable-next-line no-empty-blocks function requireNotTradingPausedOrFrozen() private notTradingPausedOrFrozen {} // ==== ERC20 ==== From 19ac4441bd5e97e301a9658aa9c20877be0dd29d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 21:37:54 -0400 Subject: [PATCH 220/462] simpler leakyRefresh() --- contracts/p1/StRSR.sol | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 5fff432dd0..1672389703 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -155,7 +155,6 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab uint192 private constant MAX_LEAK = 5e15; // {1} 0.5% uint192 private leak; // {1} stake fraction that has withdrawn without a refresh - uint48 private lastWithdrawRefresh; // {s} timestamp of last refresh() during withdraw() // ====================== @@ -675,17 +674,12 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab /// Refresh if too much RSR has exited since the last refresh occurred /// @param rsrWithdrawal {qRSR} How much RSR is being withdrawn function leakyRefresh(uint256 rsrWithdrawal) private { - uint48 lastRefresh = assetRegistry.lastRefresh(); // {s} - // Assume rsrWithdrawal has already been taken out of draftRSR uint192 withdrawal = divuu(rsrWithdrawal, stakeRSR + draftRSR + rsrWithdrawal); // {1} - leak = lastWithdrawRefresh != lastRefresh ? withdrawal : leak + withdrawal; - lastWithdrawRefresh = lastRefresh; - + leak += withdrawal; if (leak > MAX_LEAK) { leak = 0; - lastWithdrawRefresh = uint48(block.timestamp); assetRegistry.refresh(); } } From 82c5e6f2b708f34bc1731e187b7228de8b554652 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 21:39:01 -0400 Subject: [PATCH 221/462] nit --- contracts/p1/StRSR.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 1672389703..e91022c28f 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -61,7 +61,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab mapping(uint256 => mapping(address => uint256)) private stakes; // Stakes per account {qStRSR} uint256 private totalStakes; // Total of all stakes {qStRSR} uint256 private stakeRSR; // Amount of RSR backing all stakes {qRSR} - uint192 private stakeRate; // The exchange rate between stakes and RSR. D18{qStRSR/qRSR} + uint192 public stakeRate; // The exchange rate between stakes and RSR. D18{qStRSR/qRSR} uint192 private constant MAX_STAKE_RATE = 1e9 * FIX_ONE; // 1e9 D18{qStRSR/qRSR} From 84f843d637f1654d6d0c425340f50c4ef5ed74ba Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 21:43:52 -0400 Subject: [PATCH 222/462] lint clean --- contracts/p1/StRSR.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index e91022c28f..066c7daa62 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -919,6 +919,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab } // contract-size-saver + // solhint-disable-next-line no-empty-blocks function governanceOnly() private governance {} /** From 026dd9588e65dbc84fdd1a0f18ba65e094163e0b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 22:14:45 -0400 Subject: [PATCH 223/462] better leakyRefresh algo --- contracts/p1/StRSR.sol | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 066c7daa62..d2af85c207 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -61,7 +61,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab mapping(uint256 => mapping(address => uint256)) private stakes; // Stakes per account {qStRSR} uint256 private totalStakes; // Total of all stakes {qStRSR} uint256 private stakeRSR; // Amount of RSR backing all stakes {qRSR} - uint192 public stakeRate; // The exchange rate between stakes and RSR. D18{qStRSR/qRSR} + uint192 private stakeRate; // The exchange rate between stakes and RSR. D18{qStRSR/qRSR} uint192 private constant MAX_STAKE_RATE = 1e9 * FIX_ONE; // 1e9 D18{qStRSR/qRSR} @@ -155,6 +155,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab uint192 private constant MAX_LEAK = 5e15; // {1} 0.5% uint192 private leak; // {1} stake fraction that has withdrawn without a refresh + uint48 private lastWithdrawRefresh; // {s} timestamp of last refresh() during withdraw() // ====================== @@ -673,14 +674,25 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab /// Refresh if too much RSR has exited since the last refresh occurred /// @param rsrWithdrawal {qRSR} How much RSR is being withdrawn + /// Effects-Refresh function leakyRefresh(uint256 rsrWithdrawal) private { - // Assume rsrWithdrawal has already been taken out of draftRSR + uint48 lastRefresh = assetRegistry.lastRefresh(); // {s} + bool refreshedElsewhere = lastWithdrawRefresh != lastRefresh; + + // Assumption: rsrWithdrawal has already been taken out of draftRSR uint192 withdrawal = divuu(rsrWithdrawal, stakeRSR + draftRSR + rsrWithdrawal); // {1} - leak += withdrawal; + // == Effects == + leak = refreshedElsewhere ? withdrawal : leak + withdrawal; + lastWithdrawRefresh = lastRefresh; + if (leak > MAX_LEAK) { leak = 0; - assetRegistry.refresh(); + if (!refreshedElsewhere) { + lastWithdrawRefresh = uint48(block.timestamp); + /// == Interaction == + assetRegistry.refresh(); + } } } From 15e882a636d9fa73a1e8a63b35b6141967a12132 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 10 May 2023 22:16:15 -0400 Subject: [PATCH 224/462] nit --- contracts/p1/StRSR.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index d2af85c207..eed5c4cf57 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -688,11 +688,10 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab if (leak > MAX_LEAK) { leak = 0; - if (!refreshedElsewhere) { - lastWithdrawRefresh = uint48(block.timestamp); - /// == Interaction == - assetRegistry.refresh(); - } + lastWithdrawRefresh = uint48(block.timestamp); + + /// == Interaction == + assetRegistry.refresh(); } } From c9771963a00b6712ed616eea6f0f528d788dd351 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 11 May 2023 10:27:29 -0300 Subject: [PATCH 225/462] Upgrades phase 2 - Deployment (#814) * update docs * bump versions * validate and deployment upgrade tasks * update 2.1 format for backwards compatibility * set deployment script to initial deploy for ci * adapt for basket lib * Adapt for basket lib * basket lib message * prompt version * prompt version and skip in CI * fix ci --- .env.example | 5 +- .github/workflows/tests.yml | 2 + docs/solidity-style.md | 25 +- package.json | 10 +- .../mainnet-2.1.0/1-tmp-deployments.json | 6 +- scripts/deployment/common.ts | 17 + .../phase1-common/1_deploy_libraries.ts | 1 + .../phase1-common/2_deploy_implementations.ts | 350 ++++- scripts/deployment/utils.ts | 55 + tasks/index.ts | 1 + tasks/upgrades/utils.ts | 9 + tasks/upgrades/validate-upgrade.ts | 135 ++ utils/env.ts | 1 + yarn.lock | 1132 ++++++++++------- 14 files changed, 1200 insertions(+), 549 deletions(-) create mode 100644 tasks/upgrades/validate-upgrade.ts diff --git a/.env.example b/.env.example index 39f4c7360b..4e9a27b164 100644 --- a/.env.example +++ b/.env.example @@ -39,4 +39,7 @@ PROTO_IMPL=0 # SUBGRAPH_URL= # RPC URL to interact with a tenderly forked network -# TENDERLY_RPC_URL="https://rpc.tenderly.co/fork/15af2920-4719-4e62-a5ea-ab8b9e90a258" \ No newline at end of file +# TENDERLY_RPC_URL="https://rpc.tenderly.co/fork/15af2920-4719-4e62-a5ea-ab8b9e90a258" + +# Skip user prompt +# SKIP_PROMPT=1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f0c9aee9e6..c046a892da 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,6 +24,8 @@ jobs: env: MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} - run: yarn deploy:run --network localhost + env: + SKIP_PROMPT: 1 lint: name: 'Lint Checks' diff --git a/docs/solidity-style.md b/docs/solidity-style.md index 23991fc423..69f23d42c9 100644 --- a/docs/solidity-style.md +++ b/docs/solidity-style.md @@ -282,26 +282,37 @@ The OpenZeppelin documentation has good material on [how to write upgradable con Prior to initial launch, the most glaring consequence of keeping this upgrade pattern is that core P1 contracts cannot rely on their constructor to initialize values in contract state. Instead, each contract must define a separate initializer function to initialize its state. +Following subsequent upgrades, the most important check that has to be performed is related to making sure the storage layouts are compatible and no storage slots are overwritten by mistake. + [writing-upgradable]: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable ### Performing upgrades -When upgrading smart contracts it is crucial to keep in mind the limitations of what can be changed/modified to avoid breaking the contracts. As OZ recommends, we use their upgrades plugin for Hardhat to ensure that implementations are upgrade-safe before upgrading any smart contract. +When upgrading smart contracts it is crucial to keep in mind there are limitations of what can be changed/modified to avoid breaking the contracts. + +To check for upgradeability and perform the required validations we use the **[OpenZeppelin Upgrades Plugin](https://docs.openzeppelin.com/upgrades-plugins/1.x/),** designed for Hardhat. + +The Plugin relies on an internal file (per network) which is stored in the `.openzeppelin` folder in the repository, and is version controlled in Github for Mainnet (there is no need to track local or forked networks). Additional information can be found [here](https://docs.openzeppelin.com/upgrades-plugins/1.x/network-files). + +This file keeps track of deployed “implementation” contracts, and their storage layout at the time of deployment, so they can be used later to be compared with the new version and validate if there are no issues in terms of storage handling. -The recommended process to perform an upgrade is the following: +The **recommended** process to perform an upgrade is the following: -- Create the new implementation version of the contract. This should follow all the recommendations from the article linked above, to make sure the implementation is "Upgrade Safe" +- Ensure metadata of the existing/deployed implementations is created for the required network. This is located in a folder names `.openzeppelin`, which should be persisted in `git` for Production networks. This can be done for prior versions using the `upgrades/force-import.ts` task in our repository. This task is limited to be run only on Mainnet. -- Ensure metadata of the existing/deployed proxies is created for the required network. This is located in a folder names `.openzeppelin`, which should be persisted in `git` for Production networks. Because the initial proxies are deployed via the `Deployer` factory contract, this folder needs to be created using the [forceImport][] function provided by the plugin. A concrete example on how to use this function is provided in our Upgradeability test file (`test/Upgradeability.test.ts`) +- Create the new implementation version of the contract. This should follow all the recommendations from the article linked above, to make sure the implementation is "Upgrade Safe". At anytime you can check for compatibility by running the `upgrades/validate-upgrade.ts` task in our repo, in a Mainnet fork. This task would compare the current code vs. a previously deployed implementation and validate if it is "upgrade safe". Make sure the MAINNET_BLOCK is set up appropiately. -- Using mainnet forking, make sure you perform tests to check the new implementation behaves as expected. Proxies should be updated using the `upgradeProxy` function provided by the plugin to ensure all validations and checks are performed. +- To deploy to Mainnet the new version, make sure you use the script provided in `scripts/deployment/phase1-common/2_deploy_implementations.ts`. If you are upgrading a previous version you need to specify the `LAST_VERSION_DEPLOYED` value at the top of the script. For new, clean deployments just leave that empty. This script will perform all validations on the new code, deploy the new implementation contracts, and register the deployment in the network file. It relies on the `deployImplementation` (for new deployments) or `prepareUpgrade` functions of the OZ Plugin. -- Create a deployemnt script to the required network (Mainnet), using `upgradeProxy`. Ensure the new version of the `.openzeppelin` files are checked into `git` for future reference. +- Ensure the new version of the `.openzeppelin` files are checked into `git` for future reference. For additional information on how to use the plugins and how to perform upgrades on smart contracts please refer to the [OZ docs][upgrades-docs]. [upgrades-docs]: https://docs.openzeppelin.com/upgrades -[forceimport]: https://docs.openzeppelin/upgrades-plugins/1.x/api-hardhat-upgrades#force-import +[forceimport]: https://docs.openzeppelin.com/upgrades-plugins/1.x/api-hardhat-upgrades#force-import +[validateupgrade]: https://docs.openzeppelin.com/upgrades-plugins/1.x/api-hardhat-upgrades#validate-upgrade +[deployimplementation]: https://docs.openzeppelin.com/upgrades-plugins/1.x/api-hardhat-upgrades#deploy-implementation +[prepareupgrade]: https://docs.openzeppelin.com/upgrades-plugins/1.x/api-hardhat-upgrades#prepare-upgrade ### Developer discipline diff --git a/package.json b/package.json index 73f21024f0..29b3d5a206 100644 --- a/package.json +++ b/package.json @@ -52,14 +52,14 @@ "@aave/protocol-v2": "^1.0.1", "@chainlink/contracts": "^0.5.1", "@ethersproject/providers": "^5.7.2", - "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", + "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", "@nomicfoundation/hardhat-network-helpers": "^1.0.8", "@nomicfoundation/hardhat-toolbox": "^2.0.1", - "@nomiclabs/hardhat-ethers": "^2.0.3", + "@nomiclabs/hardhat-ethers": "^2.2.3", "@nomiclabs/hardhat-etherscan": "^3.1.0", "@openzeppelin/contracts": "~4.7.3", "@openzeppelin/contracts-upgradeable": "~4.7.3", - "@openzeppelin/hardhat-upgrades": "^1.21.0", + "@openzeppelin/hardhat-upgrades": "^1.23.0", "@typechain/ethers-v5": "^7.2.0", "@typechain/hardhat": "^2.3.1", "@types/big.js": "^6.1.3", @@ -83,11 +83,11 @@ "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-promise": "^6.0.0", "eth-permit": "^0.2.1", - "ethers": "^5.5.2", + "ethers": "^5.7.2", "fast-check": "^2.24.0", "graphql": "^16.6.0", "graphql-request": "^5.2.0", - "hardhat": "^2.12.2", + "hardhat": "^2.12.3", "hardhat-contract-sizer": "^2.4.0", "hardhat-gas-reporter": "^1.0.8", "hardhat-storage-layout": "^0.1.7", diff --git a/scripts/addresses/mainnet-2.1.0/1-tmp-deployments.json b/scripts/addresses/mainnet-2.1.0/1-tmp-deployments.json index 69360022a5..bc915523d2 100644 --- a/scripts/addresses/mainnet-2.1.0/1-tmp-deployments.json +++ b/scripts/addresses/mainnet-2.1.0/1-tmp-deployments.json @@ -21,7 +21,11 @@ "rToken": "0x5643D5AC6b79ae8467Cf2F416da6D465d8e7D9C1", "stRSR": "0xfDa8C62d86E426D5fB653B6c44a455Bb657b693f" }, - "trade": "0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439" + "trade": "0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439", + "trading": { + "gnosisTrade": "0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439", + "dutchTrade": "0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439" + } }, "facadeMonitor": "0xF3458200eDe2C5A592757dc0BA9A915e9CCA77C6", "cvxMiningLib": "0xA6B8934a82874788043A75d50ca74a18732DC660", diff --git a/scripts/deployment/common.ts b/scripts/deployment/common.ts index 10634cdcbe..69f17cd408 100644 --- a/scripts/deployment/common.ts +++ b/scripts/deployment/common.ts @@ -75,3 +75,20 @@ export const getDeploymentFile = ( throw new Error(`Failed to read ${path}. Maybe the file is badly generated?`) } } + +export const writeComponentDeployment = ( + deployments: IDeployments, + deploymentFilename: string, + name: string, + implAddr: string, + logDesc: string, + prevAddr?: string +) => { + const field = name as keyof typeof deployments.implementations.components + + // Write temporary deployments file for component + deployments.implementations.components[field] = implAddr + fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) + + console.log(` ${logDesc} Implementation: ${implAddr} ${prevAddr == implAddr ? '- SKIPPED' : ''}`) +} diff --git a/scripts/deployment/phase1-common/1_deploy_libraries.ts b/scripts/deployment/phase1-common/1_deploy_libraries.ts index ad67a1e4e4..1ec3696a2b 100644 --- a/scripts/deployment/phase1-common/1_deploy_libraries.ts +++ b/scripts/deployment/phase1-common/1_deploy_libraries.ts @@ -56,6 +56,7 @@ async function main() { console.log(`Deployed to ${hre.network.name} (${chainId}): TradingLib: ${tradingLib.address} + BasketLib: ${basketLib.address} CvxMiningLib: ${cvxMiningLib.address} Deployment file: ${deploymentFilename}`) } diff --git a/scripts/deployment/phase1-common/2_deploy_implementations.ts b/scripts/deployment/phase1-common/2_deploy_implementations.ts index 055c522497..a6dfd53e60 100644 --- a/scripts/deployment/phase1-common/2_deploy_implementations.ts +++ b/scripts/deployment/phase1-common/2_deploy_implementations.ts @@ -1,9 +1,15 @@ import fs from 'fs' -import hre, { ethers } from 'hardhat' - +import hre, { ethers, upgrades } from 'hardhat' import { getChainId, isValidContract } from '../../../common/blockchain-utils' import { networkConfig } from '../../../common/configuration' -import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../common' +import { getEmptyDeployment, prompt, validateImplementations } from '../utils' +import { + getDeploymentFile, + getDeploymentFilename, + IDeployments, + writeComponentDeployment, +} from '../common' + import { AssetRegistryP1, BackingManagerP1, @@ -26,35 +32,22 @@ let brokerImpl: BrokerP1 let distribImpl: DistributorP1 let furnaceImpl: FurnaceP1 let mainImpl: MainP1 -let revTraderImpl: RevenueTraderP1 +let rsrTraderImpl: RevenueTraderP1 +let rTokenTraderImpl: RevenueTraderP1 let rTokenImpl: RTokenP1 let stRSRImpl: StRSRP1Votes let gnosisTradeImpl: GnosisTrade let dutchTradeImpl: DutchTrade -const writeComponentDeployment = ( - deployments: IDeployments, - deploymentFilename: string, - name: string, - implAddr: string, - logDesc: string -) => { - const field = name as keyof typeof deployments.implementations.components - - // Write temporary deployments file for component - deployments.implementations.components[field] = implAddr - fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) - - console.log(` ${logDesc} Implementation: ${implAddr}`) -} - async function main() { // ==== Read Configuration ==== const [burner] = await hre.ethers.getSigners() const chainId = await getChainId(hre) - console.log(`Deploying implementations to network ${hre.network.name} (${chainId}) - with burner account: ${burner.address}`) + // Request last deployed version - if empty, perform new deployment + const LAST_DEPLOYED_VERSION = await prompt( + 'Enter the last deployed version (e.g: "2.1.0"), or leave empty for new deployment: ' + ) if (!networkConfig[chainId]) { throw new Error(`Missing network configuration for ${hre.network.name}`) @@ -63,26 +56,75 @@ async function main() { const deploymentFilename = getDeploymentFilename(chainId) const deployments = getDeploymentFile(deploymentFilename) - if (!deployments.tradingLib) { - throw new Error(`Missing pre-requisite addresses in network ${hre.network.name}`) - } else if (!deployments.basketLib) { + if (!deployments.tradingLib || !deployments.basketLib) { throw new Error(`Missing pre-requisite addresses in network ${hre.network.name}`) } else if (!(await isValidContract(hre, deployments.tradingLib))) { throw new Error(`TradingLib contract not found in network ${hre.network.name}`) + } else if (!(await isValidContract(hre, deployments.basketLib))) { + throw new Error(`BasketLib contract not found in network ${hre.network.name}`) } - // ******************** Deploy Main ********************************/ + // Check if this is an upgrade or a new deployment + let upgrade = false + let prevDeployments: IDeployments = getEmptyDeployment() + let prevDeploymentFilename = '' + if (LAST_DEPLOYED_VERSION.length > 0) { + // Get Previously Deployed addresses + // If running on Mainnet or fork, use Mainnet deployments + if ( + hre.network.name == 'mainnet' || + hre.network.name == 'localhost' || + hre.network.name == 'hardhat' + ) { + prevDeploymentFilename = getDeploymentFilename(1, `mainnet-${LAST_DEPLOYED_VERSION}`) + } else { + prevDeploymentFilename = getDeploymentFilename( + chainId, + `${hre.network.name}-${LAST_DEPLOYED_VERSION}` + ) + } + + prevDeployments = getDeploymentFile(prevDeploymentFilename) + await validateImplementations(prevDeployments) + + // Set upgrade flag + upgrade = true + } + if (!upgrade) { + console.log(`Deploying implementations to network ${hre.network.name} (${chainId}) + with burner account: ${burner.address}`) + } else { + console.log(`Deploying upgrade implementations for ${LAST_DEPLOYED_VERSION} to network ${hre.network.name} (${chainId}) + with burner account: ${burner.address}`) + } + + // ******************** Deploy Main ********************************/ const MainImplFactory = await ethers.getContractFactory('MainP1') - mainImpl = await MainImplFactory.connect(burner).deploy() - await mainImpl.deployed() + let mainImplAddr = '' + if (!upgrade) { + mainImplAddr = await upgrades.deployImplementation(MainImplFactory, { + kind: 'uups', + }) + } else { + mainImplAddr = await upgrades.prepareUpgrade( + prevDeployments.implementations.main, + MainImplFactory, + { + kind: 'uups', + } + ) + } + mainImpl = await ethers.getContractAt('MainP1', mainImplAddr) // Write temporary deployments file deployments.implementations.main = mainImpl.address fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) console.log(`Deployed to ${hre.network.name} (${chainId}): - Main Implementation: ${mainImpl.address}`) + Main Implementation: ${mainImpl.address} ${ + mainImpl.address == prevDeployments.implementations.main ? '- SKIPPED' : '' + }`) // ******************** Deploy GnosisTrade ********************************/ @@ -94,7 +136,7 @@ async function main() { deployments.implementations.trading.gnosisTrade = gnosisTradeImpl.address fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) - console.log(`GnosisTrade Implementation: ${gnosisTradeImpl.address}`) + console.log(` GnosisTrade Implementation: ${gnosisTradeImpl.address}`) // ******************** Deploy DutchTrade ********************************/ @@ -106,21 +148,36 @@ async function main() { deployments.implementations.trading.dutchTrade = dutchTradeImpl.address fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) - console.log(`DutchTrade Implementation: ${dutchTradeImpl.address}`) + console.log(` DutchTrade Implementation: ${dutchTradeImpl.address}`) // ******************** Deploy Components ********************************/ // 1. ******* Asset Registry ********/ const AssetRegImplFactory = await ethers.getContractFactory('AssetRegistryP1') - assetRegImpl = await AssetRegImplFactory.connect(burner).deploy() - await assetRegImpl.deployed() + let assetRegImplAddr = '' + if (!upgrade) { + assetRegImplAddr = await upgrades.deployImplementation(AssetRegImplFactory, { + kind: 'uups', + }) + } else { + assetRegImplAddr = await upgrades.prepareUpgrade( + prevDeployments.implementations.components.assetRegistry, + AssetRegImplFactory, + { + kind: 'uups', + } + ) + } + + assetRegImpl = await ethers.getContractAt('AssetRegistryP1', assetRegImplAddr) writeComponentDeployment( deployments, deploymentFilename, 'assetRegistry', assetRegImpl.address, - 'AssetRegistry' + 'AssetRegistry', + prevDeployments.implementations.components.assetRegistry ) // 2. ******* Backing Manager ***********/ @@ -129,102 +186,271 @@ async function main() { RecollateralizationLibP1: deployments.tradingLib, }, }) - backingMgrImpl = await BackingMgrImplFactory.connect(burner).deploy() - await backingMgrImpl.deployed() + let backingMgrImplAddr = '' + if (!upgrade) { + backingMgrImplAddr = await upgrades.deployImplementation(BackingMgrImplFactory, { + kind: 'uups', + unsafeAllow: ['external-library-linking'], + }) + } else { + backingMgrImplAddr = await upgrades.prepareUpgrade( + prevDeployments.implementations.components.backingManager, + BackingMgrImplFactory, + { + kind: 'uups', + unsafeAllow: ['external-library-linking'], + } + ) + } + + backingMgrImpl = ( + await ethers.getContractAt('BackingManagerP1', backingMgrImplAddr) + ) writeComponentDeployment( deployments, deploymentFilename, 'backingManager', backingMgrImpl.address, - 'BackingManager' + 'BackingManager', + prevDeployments.implementations.components.backingManager ) // 3. ********* Basket Handler *************/ const BskHandlerImplFactory = await ethers.getContractFactory('BasketHandlerP1', { libraries: { BasketLibP1: deployments.basketLib }, }) - bskHndlrImpl = await BskHandlerImplFactory.connect(burner).deploy() - await bskHndlrImpl.deployed() + let bskHndlrImplAddr = '' + if (!upgrade) { + bskHndlrImplAddr = await upgrades.deployImplementation(BskHandlerImplFactory, { + kind: 'uups', + unsafeAllow: ['external-library-linking'], + }) + } else { + bskHndlrImplAddr = await upgrades.prepareUpgrade( + prevDeployments.implementations.components.basketHandler, + BskHandlerImplFactory, + { + kind: 'uups', + unsafeAllow: ['external-library-linking'], + } + ) + } + + bskHndlrImpl = await ethers.getContractAt('BasketHandlerP1', bskHndlrImplAddr) writeComponentDeployment( deployments, deploymentFilename, 'basketHandler', bskHndlrImpl.address, - 'BasketHandler' + 'BasketHandler', + prevDeployments.implementations.components.basketHandler ) // 4. *********** Broker *************/ const BrokerImplFactory = await ethers.getContractFactory('BrokerP1') - brokerImpl = await BrokerImplFactory.connect(burner).deploy() - await brokerImpl.deployed() + let brokerImplAddr = '' + if (!upgrade) { + brokerImplAddr = await upgrades.deployImplementation(BrokerImplFactory, { + kind: 'uups', + }) + } else { + brokerImplAddr = await upgrades.prepareUpgrade( + prevDeployments.implementations.components.broker, + BrokerImplFactory, + { + kind: 'uups', + } + ) + } - writeComponentDeployment(deployments, deploymentFilename, 'broker', brokerImpl.address, 'Broker') + brokerImpl = await ethers.getContractAt('BrokerP1', brokerImplAddr) + + writeComponentDeployment( + deployments, + deploymentFilename, + 'broker', + brokerImpl.address, + 'Broker', + prevDeployments.implementations.components.broker + ) // 5. *********** Distributor *************/ const DistribImplFactory = await ethers.getContractFactory('DistributorP1') - distribImpl = await DistribImplFactory.connect(burner).deploy() - await distribImpl.deployed() + let distribImplAddr = '' + if (!upgrade) { + distribImplAddr = await upgrades.deployImplementation(DistribImplFactory, { + kind: 'uups', + }) + } else { + distribImplAddr = await upgrades.prepareUpgrade( + prevDeployments.implementations.components.distributor, + DistribImplFactory, + { + kind: 'uups', + } + ) + } + + distribImpl = await ethers.getContractAt('DistributorP1', distribImplAddr) writeComponentDeployment( deployments, deploymentFilename, 'distributor', distribImpl.address, - 'Distributor' + 'Distributor', + prevDeployments.implementations.components.distributor ) // 6. *********** Furnace *************/ const FurnaceImplFactory = await ethers.getContractFactory('FurnaceP1') - furnaceImpl = await FurnaceImplFactory.connect(burner).deploy() - await furnaceImpl.deployed() + let furnaceImplAddr = '' + if (!upgrade) { + furnaceImplAddr = await upgrades.deployImplementation(FurnaceImplFactory, { + kind: 'uups', + }) + } else { + furnaceImplAddr = await upgrades.prepareUpgrade( + prevDeployments.implementations.components.furnace, + FurnaceImplFactory, + { + kind: 'uups', + } + ) + } + + furnaceImpl = await ethers.getContractAt('FurnaceP1', furnaceImplAddr) writeComponentDeployment( deployments, deploymentFilename, 'furnace', furnaceImpl.address, - 'Furnace' + 'Furnace', + prevDeployments.implementations.components.furnace ) // 7. *********** RevenueTrader *************/ const RevTraderImplFactory = await ethers.getContractFactory('RevenueTraderP1') - revTraderImpl = await RevTraderImplFactory.connect(burner).deploy() - await revTraderImpl.deployed() + let rsrTraderImplAddr = '' + let rTokenTraderImplAddr = '' + if (!upgrade) { + rsrTraderImplAddr = await upgrades.deployImplementation(RevTraderImplFactory, { + kind: 'uups', + }) + rTokenTraderImplAddr = rsrTraderImplAddr // Both equal in initial deployment + } else { + // RSR Trader + rsrTraderImplAddr = await upgrades.prepareUpgrade( + prevDeployments.implementations.components.rsrTrader, + RevTraderImplFactory, + { + kind: 'uups', + } + ) + + // If Traders have different implementations, upgrade separately + if ( + prevDeployments.implementations.components.rsrTrader != + prevDeployments.implementations.components.rTokenTrader + ) { + // RToken Trader + rTokenTraderImplAddr = await upgrades.prepareUpgrade( + prevDeployments.implementations.components.rTokenTrader, + RevTraderImplFactory, + { + kind: 'uups', + } + ) + } else { + // Both use the same implementation + rTokenTraderImplAddr = rsrTraderImplAddr + } + } + + rsrTraderImpl = await ethers.getContractAt('RevenueTraderP1', rsrTraderImplAddr) + rTokenTraderImpl = ( + await ethers.getContractAt('RevenueTraderP1', rTokenTraderImplAddr) + ) writeComponentDeployment( deployments, deploymentFilename, 'rsrTrader', - revTraderImpl.address, - 'RSR Trader' + rsrTraderImpl.address, + 'RSR Trader', + prevDeployments.implementations.components.rsrTrader ) writeComponentDeployment( deployments, deploymentFilename, 'rTokenTrader', - revTraderImpl.address, - 'RToken Trader' + rTokenTraderImpl.address, + 'RToken Trader', + prevDeployments.implementations.components.rTokenTrader ) // 8. *********** RToken *************/ const RTokenImplFactory = await ethers.getContractFactory('RTokenP1') - rTokenImpl = await RTokenImplFactory.connect(burner).deploy() - await rTokenImpl.deployed() + let rTokenImplAddr = '' + if (!upgrade) { + rTokenImplAddr = await upgrades.deployImplementation(RTokenImplFactory, { + kind: 'uups', + }) + } else { + rTokenImplAddr = await upgrades.prepareUpgrade( + prevDeployments.implementations.components.rToken, + RTokenImplFactory, + { + kind: 'uups', + } + ) + } + + rTokenImpl = await ethers.getContractAt('RTokenP1', rTokenImplAddr) - writeComponentDeployment(deployments, deploymentFilename, 'rToken', rTokenImpl.address, 'RToken') + writeComponentDeployment( + deployments, + deploymentFilename, + 'rToken', + rTokenImpl.address, + 'RToken', + prevDeployments.implementations.components.rToken + ) // 9. *********** StRSR *************/ const StRSRImplFactory = await ethers.getContractFactory('StRSRP1Votes') - stRSRImpl = await StRSRImplFactory.connect(burner).deploy() - await stRSRImpl.deployed() + let stRSRImplAddr = '' + if (!upgrade) { + stRSRImplAddr = await upgrades.deployImplementation(StRSRImplFactory, { + kind: 'uups', + }) + } else { + stRSRImplAddr = await upgrades.prepareUpgrade( + prevDeployments.implementations.components.stRSR, + StRSRImplFactory, + { + kind: 'uups', + } + ) + } + + stRSRImpl = await ethers.getContractAt('StRSRP1Votes', stRSRImplAddr) - writeComponentDeployment(deployments, deploymentFilename, 'stRSR', stRSRImpl.address, 'StRSR') + writeComponentDeployment( + deployments, + deploymentFilename, + 'stRSR', + stRSRImpl.address, + 'StRSR', + prevDeployments.implementations.components.stRSR + ) - console.log(` Deployment file: ${deploymentFilename}`) + console.log(`Deployment file: ${deploymentFilename}`) } main().catch((error) => { diff --git a/scripts/deployment/utils.ts b/scripts/deployment/utils.ts index 9679c0b15d..8954dacbed 100644 --- a/scripts/deployment/utils.ts +++ b/scripts/deployment/utils.ts @@ -1,4 +1,5 @@ import hre from 'hardhat' +import * as readline from 'readline' import axios from 'axios' import { exec } from 'child_process' import { BigNumber, BigNumberish } from 'ethers' @@ -149,3 +150,57 @@ export const getEtherscanBaseURL = (chainId: number, api = false) => { else prefix = chainId == 1 ? '' : `${hre.network.name}.` return `https://${prefix}etherscan.io` } + +export const getEmptyDeployment = (): IDeployments => { + return { + prerequisites: { + RSR: '', + RSR_FEED: '', + GNOSIS_EASY_AUCTION: '', + }, + tradingLib: '', + basketLib: '', + facadeRead: '', + facadeWriteLib: '', + cvxMiningLib: '', + facadeMonitor: '', + facadeWrite: '', + facadeAct: '', + deployer: '', + rsrAsset: '', + implementations: { + main: '', + trading: { gnosisTrade: '', dutchTrade: '' }, + components: { + assetRegistry: '', + backingManager: '', + basketHandler: '', + broker: '', + distributor: '', + furnace: '', + rsrTrader: '', + rTokenTrader: '', + rToken: '', + stRSR: '', + }, + }, + } +} + +export const prompt = async (query: string): Promise => { + if (!useEnv('SKIP_PROMPT')) { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }) + + return new Promise((resolve) => + rl.question(query, (ans) => { + rl.close() + resolve(ans) + }) + ) + } else { + return '' + } +} diff --git a/tasks/index.ts b/tasks/index.ts index c359b39ab2..e542e85840 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -19,6 +19,7 @@ import './deployment/empty-wallet' import './deployment/cancel-tx' import './deployment/sign-msg' import './upgrades/force-import' +import './upgrades/validate-upgrade' import './testing/mint-tokens' import './testing/upgrade-checker' import './testing/tenderly' diff --git a/tasks/upgrades/utils.ts b/tasks/upgrades/utils.ts index bef8173467..26d400bea7 100644 --- a/tasks/upgrades/utils.ts +++ b/tasks/upgrades/utils.ts @@ -17,6 +17,15 @@ export const validateDeployments = async ( throw new Error(`TradingLib contract not found in network ${hre.network.name}`) } + // Check basket lib defined + if (!deployments.basketLib) { + throw new Error( + `Missing deployed BasketLib for version ${version} in network ${hre.network.name}` + ) + } else if (!(await isValidContract(hre, deployments.basketLib))) { + throw new Error(`BasketLib contract not found in network ${hre.network.name}`) + } + // Check Main implementation is defined if (!deployments.implementations.main) { throw new Error( diff --git a/tasks/upgrades/validate-upgrade.ts b/tasks/upgrades/validate-upgrade.ts new file mode 100644 index 0000000000..0d4ad757d4 --- /dev/null +++ b/tasks/upgrades/validate-upgrade.ts @@ -0,0 +1,135 @@ +import { task } from 'hardhat/config' +import { HardhatRuntimeEnvironment } from 'hardhat/types' +import { networkConfig } from '../../common/configuration' +import { getChainId } from '../../common/blockchain-utils' +import { validateDeployments } from './utils' +import { ContractFactory } from 'ethers' +import { + IDeployments, + getDeploymentFilename, + getDeploymentFile, +} from '../../scripts/deployment/common' +import { BasketLibP1, RecollateralizationLibP1 } from '../../typechain' + +task('validate-upgrade', 'Validates if upgrade to new version is safe') + .addParam('ver', 'the version of the currently deployed implementations') + .setAction(async (params, hre) => { + const chainId = await getChainId(hre) + + // ********** Read config ********** + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (hre.network.name != 'localhost' && hre.network.name != 'hardhat') { + throw new Error('Only run this on a local fork') + } + + // Get Deployed addresses, use Mainnet deployments (we are in a Mainnet fork) + const deploymentFilename = getDeploymentFilename(1, `mainnet-${params.ver}`) + const deployments = getDeploymentFile(deploymentFilename) + + await validateDeployments(hre, deployments, params.ver) + + console.log( + `Validating upgrades from version ${params.ver} in network ${hre.network.name} (${chainId})` + ) + + // Deploy required libraries + // TradingLib + const TradingLibFactory: ContractFactory = await hre.ethers.getContractFactory( + 'RecollateralizationLibP1' + ) + const tradingLib: RecollateralizationLibP1 = ( + await TradingLibFactory.deploy() + ) + await tradingLib.deployed() + + // BasketLib + const BasketLibFactory: ContractFactory = await hre.ethers.getContractFactory('BasketLibP1') + const basketLib: BasketLibP1 = await BasketLibFactory.deploy() + await basketLib.deployed() + + await validateUpgrade(hre, deployments.implementations.main, 'MainP1') + await validateUpgrade(hre, deployments.implementations.components.rToken, 'RTokenP1') + await validateUpgrade(hre, deployments.implementations.components.stRSR, 'StRSRP1Votes') + await validateUpgrade( + hre, + deployments.implementations.components.assetRegistry, + 'AssetRegistryP1' + ) + await validateUpgrade( + hre, + deployments.implementations.components.basketHandler, + 'BasketHandlerP1', + undefined, + basketLib.address, + ['external-library-linking'] + ) + + await validateUpgrade( + hre, + deployments.implementations.components.backingManager, + 'BackingManagerP1', + tradingLib.address, + undefined, + ['external-library-linking', 'delegatecall'] + ) + await validateUpgrade(hre, deployments.implementations.components.distributor, 'DistributorP1') + await validateUpgrade(hre, deployments.implementations.components.furnace, 'FurnaceP1') + await validateUpgrade(hre, deployments.implementations.components.broker, 'BrokerP1') + await validateUpgrade( + hre, + deployments.implementations.components.rsrTrader, + 'RevenueTraderP1', + undefined, + ['delegatecall'] + ) + await validateUpgrade( + hre, + deployments.implementations.components.rTokenTrader, + 'RevenueTraderP1', + undefined, + ['delegatecall'] + ) + }) + +const validateUpgrade = async ( + hre: HardhatRuntimeEnvironment, + prevImplAddress: string, + factoryName: string, + tradingLibAddress?: string, + basketLibAddress?: string, + unsafeAllow?: any[] +) => { + // Get Contract Factory + let contractFactory: ContractFactory + if (tradingLibAddress) { + // BackingManagerP1 + contractFactory = await hre.ethers.getContractFactory(factoryName, { + libraries: { + RecollateralizationLibP1: tradingLibAddress, + }, + }) + } else if (basketLibAddress) { + // BasketHandlerP1 + contractFactory = await hre.ethers.getContractFactory(factoryName, { + libraries: { + BasketLibP1: basketLibAddress, + }, + }) + } else { + contractFactory = await hre.ethers.getContractFactory(factoryName) + } + + const ver = await (await hre.ethers.getContractAt('Versioned', prevImplAddress)).version() + + await hre.upgrades.validateUpgrade(prevImplAddress, contractFactory, { + kind: 'uups', + unsafeAllow, + }) + + console.log( + `* Validated ${factoryName} upgrade (from version: ${ver}): address ${prevImplAddress} - OK!` + ) +} diff --git a/utils/env.ts b/utils/env.ts index 5a92b094d2..540a910714 100644 --- a/utils/env.ts +++ b/utils/env.ts @@ -20,6 +20,7 @@ type IEnvVars = | 'EXTREME' | 'SUBGRAPH_URL' | 'TENDERLY_RPC_URL' + | 'SKIP_PROMPT' export function useEnv(key: IEnvVars | IEnvVars[], _default = ''): string { if (typeof key === 'string') { diff --git a/yarn.lock b/yarn.lock index ccf2644e47..bf05859a5c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24,6 +24,46 @@ __metadata: languageName: node linkType: hard +"@aws-crypto/sha256-js@npm:1.2.2": + version: 1.2.2 + resolution: "@aws-crypto/sha256-js@npm:1.2.2" + dependencies: + "@aws-crypto/util": ^1.2.2 + "@aws-sdk/types": ^3.1.0 + tslib: ^1.11.1 + checksum: b6aeb71f88ecc219c5473803345bb15150ecd056a337582638dd60fb2344e0ff63908c684ef55268b249290fe0776e8e6fc830605f0aad850ff325b9cfe0dc6a + languageName: node + linkType: hard + +"@aws-crypto/util@npm:^1.2.2": + version: 1.2.2 + resolution: "@aws-crypto/util@npm:1.2.2" + dependencies: + "@aws-sdk/types": ^3.1.0 + "@aws-sdk/util-utf8-browser": ^3.0.0 + tslib: ^1.11.1 + checksum: 54d72ce4945b52f3fcbcb62574a55bc038cc3ff165742f340cabca1bdc979faf69c97709cf56daf434e4ad69e33582a04a64da33b4e4e13b25c6ff67f8abe5ae + languageName: node + linkType: hard + +"@aws-sdk/types@npm:^3.1.0": + version: 3.329.0 + resolution: "@aws-sdk/types@npm:3.329.0" + dependencies: + tslib: ^2.5.0 + checksum: 2bbcd8e6ba2f813dc220c60e7ac0fa0d0990d49f88030fd8235479586716016c8f9d7aeaf1f4c1a64694275f33690bd1dd4ed46e127983201061e063da73f426 + languageName: node + linkType: hard + +"@aws-sdk/util-utf8-browser@npm:^3.0.0": + version: 3.259.0 + resolution: "@aws-sdk/util-utf8-browser@npm:3.259.0" + dependencies: + tslib: ^2.3.1 + checksum: b6a1e580da1c9b62c749814182a7649a748ca4253edb4063aa521df97d25b76eae3359eb1680b86f71aac668e05cc05c514379bca39ebf4ba998ae4348412da8 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.18.6": version: 7.18.6 resolution: "@babel/code-frame@npm:7.18.6" @@ -402,6 +442,52 @@ __metadata: languageName: node linkType: hard +"@chainsafe/as-sha256@npm:^0.3.1": + version: 0.3.1 + resolution: "@chainsafe/as-sha256@npm:0.3.1" + checksum: 58ea733be1657b0e31dbf48b0dba862da0833df34a81c1460c7352f04ce90874f70003cbf34d0afb9e5e53a33ee2d63a261a8b12462be85b2ba0a6f7f13d6150 + languageName: node + linkType: hard + +"@chainsafe/persistent-merkle-tree@npm:^0.4.2": + version: 0.4.2 + resolution: "@chainsafe/persistent-merkle-tree@npm:0.4.2" + dependencies: + "@chainsafe/as-sha256": ^0.3.1 + checksum: f9cfcb2132a243992709715dbd28186ab48c7c0c696f29d30857693cca5526bf753974a505ef68ffd5623bbdbcaa10f9083f4dd40bf99eb6408e451cc26a1a9e + languageName: node + linkType: hard + +"@chainsafe/persistent-merkle-tree@npm:^0.5.0": + version: 0.5.0 + resolution: "@chainsafe/persistent-merkle-tree@npm:0.5.0" + dependencies: + "@chainsafe/as-sha256": ^0.3.1 + checksum: 2c67203da776c79cd3a6132e2d672fe132393b2e63dc71604e3134acc8c0ec25cc5e431051545939ea0f7c5ff2066fb806b9e5cab974ca085d046226a1671f7d + languageName: node + linkType: hard + +"@chainsafe/ssz@npm:^0.10.0": + version: 0.10.2 + resolution: "@chainsafe/ssz@npm:0.10.2" + dependencies: + "@chainsafe/as-sha256": ^0.3.1 + "@chainsafe/persistent-merkle-tree": ^0.5.0 + checksum: 6bb70cf741d0a19dd0b28b3f6f067b96fa39f556e2eefa6ac745b21db9c3b3a8393dc3cca8ff4a6ce065ed71ddc3fb1b2b390a92004b9d01067c26e2558e5503 + languageName: node + linkType: hard + +"@chainsafe/ssz@npm:^0.9.2": + version: 0.9.4 + resolution: "@chainsafe/ssz@npm:0.9.4" + dependencies: + "@chainsafe/as-sha256": ^0.3.1 + "@chainsafe/persistent-merkle-tree": ^0.4.2 + case: ^1.6.3 + checksum: c6eaedeae9e5618b3c666ff4507a27647f665a8dcf17d5ca86da4ed4788c5a93868f256d0005467d184fdf35ec03f323517ec2e55ec42492d769540a2ec396bc + languageName: node + linkType: hard + "@colors/colors@npm:1.5.0": version: 1.5.0 resolution: "@colors/colors@npm:1.5.0" @@ -472,24 +558,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abi@npm:5.6.4, @ethersproject/abi@npm:^5.0.0-beta.146, @ethersproject/abi@npm:^5.1.2, @ethersproject/abi@npm:^5.6.3": - version: 5.6.4 - resolution: "@ethersproject/abi@npm:5.6.4" - dependencies: - "@ethersproject/address": ^5.6.1 - "@ethersproject/bignumber": ^5.6.2 - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/constants": ^5.6.1 - "@ethersproject/hash": ^5.6.1 - "@ethersproject/keccak256": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/strings": ^5.6.1 - checksum: b5e70fa13a29e1143131a0ed25053a3d355c07353e13d436f42add33f40753b5541a088cf31a1ccca6448bb1d773a41ece0bf8367490d3f2ad394a4c26f4876f - languageName: node - linkType: hard - -"@ethersproject/abi@npm:^5.0.9, @ethersproject/abi@npm:^5.7.0": +"@ethersproject/abi@npm:5.7.0, @ethersproject/abi@npm:^5.0.9, @ethersproject/abi@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abi@npm:5.7.0" dependencies: @@ -506,22 +575,24 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abstract-provider@npm:5.6.1, @ethersproject/abstract-provider@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/abstract-provider@npm:5.6.1" +"@ethersproject/abi@npm:^5.0.0-beta.146, @ethersproject/abi@npm:^5.1.2, @ethersproject/abi@npm:^5.6.3": + version: 5.6.4 + resolution: "@ethersproject/abi@npm:5.6.4" dependencies: + "@ethersproject/address": ^5.6.1 "@ethersproject/bignumber": ^5.6.2 "@ethersproject/bytes": ^5.6.1 + "@ethersproject/constants": ^5.6.1 + "@ethersproject/hash": ^5.6.1 + "@ethersproject/keccak256": ^5.6.1 "@ethersproject/logger": ^5.6.0 - "@ethersproject/networks": ^5.6.3 "@ethersproject/properties": ^5.6.0 - "@ethersproject/transactions": ^5.6.2 - "@ethersproject/web": ^5.6.1 - checksum: a1be8035d9e67fd41a336e2d38f5cf03b7a2590243749b4cf807ad73906b5a298e177ebe291cb5b54262ded4825169bf82968e0e5b09fbea17444b903faeeab0 + "@ethersproject/strings": ^5.6.1 + checksum: b5e70fa13a29e1143131a0ed25053a3d355c07353e13d436f42add33f40753b5541a088cf31a1ccca6448bb1d773a41ece0bf8367490d3f2ad394a4c26f4876f languageName: node linkType: hard -"@ethersproject/abstract-provider@npm:^5.7.0": +"@ethersproject/abstract-provider@npm:5.7.0, @ethersproject/abstract-provider@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abstract-provider@npm:5.7.0" dependencies: @@ -536,20 +607,22 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abstract-signer@npm:5.6.2, @ethersproject/abstract-signer@npm:^5.6.2": - version: 5.6.2 - resolution: "@ethersproject/abstract-signer@npm:5.6.2" +"@ethersproject/abstract-provider@npm:^5.6.1": + version: 5.6.1 + resolution: "@ethersproject/abstract-provider@npm:5.6.1" dependencies: - "@ethersproject/abstract-provider": ^5.6.1 "@ethersproject/bignumber": ^5.6.2 "@ethersproject/bytes": ^5.6.1 "@ethersproject/logger": ^5.6.0 + "@ethersproject/networks": ^5.6.3 "@ethersproject/properties": ^5.6.0 - checksum: 09f3dd1309b37bb3803057d618e4a831668e010e22047f52f1719f2b6f50b63805f1bec112b1603880d6c6b7d403ed187611ff1b14ae1f151141ede186a04996 + "@ethersproject/transactions": ^5.6.2 + "@ethersproject/web": ^5.6.1 + checksum: a1be8035d9e67fd41a336e2d38f5cf03b7a2590243749b4cf807ad73906b5a298e177ebe291cb5b54262ded4825169bf82968e0e5b09fbea17444b903faeeab0 languageName: node linkType: hard -"@ethersproject/abstract-signer@npm:^5.7.0": +"@ethersproject/abstract-signer@npm:5.7.0, @ethersproject/abstract-signer@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abstract-signer@npm:5.7.0" dependencies: @@ -562,20 +635,20 @@ __metadata: languageName: node linkType: hard -"@ethersproject/address@npm:5.6.1, @ethersproject/address@npm:^5.0.2, @ethersproject/address@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/address@npm:5.6.1" +"@ethersproject/abstract-signer@npm:^5.6.2": + version: 5.6.2 + resolution: "@ethersproject/abstract-signer@npm:5.6.2" dependencies: + "@ethersproject/abstract-provider": ^5.6.1 "@ethersproject/bignumber": ^5.6.2 "@ethersproject/bytes": ^5.6.1 - "@ethersproject/keccak256": ^5.6.1 "@ethersproject/logger": ^5.6.0 - "@ethersproject/rlp": ^5.6.1 - checksum: 262096ef05a1b626c161a72698a5d8b06aebf821fe01a1651ab40f80c29ca2481b96be7f972745785fd6399906509458c4c9a38f3bc1c1cb5afa7d2f76f7309a + "@ethersproject/properties": ^5.6.0 + checksum: 09f3dd1309b37bb3803057d618e4a831668e010e22047f52f1719f2b6f50b63805f1bec112b1603880d6c6b7d403ed187611ff1b14ae1f151141ede186a04996 languageName: node linkType: hard -"@ethersproject/address@npm:^5.7.0": +"@ethersproject/address@npm:5.7.0, @ethersproject/address@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/address@npm:5.7.0" dependencies: @@ -588,16 +661,20 @@ __metadata: languageName: node linkType: hard -"@ethersproject/base64@npm:5.6.1, @ethersproject/base64@npm:^5.6.1": +"@ethersproject/address@npm:^5.0.2, @ethersproject/address@npm:^5.6.1": version: 5.6.1 - resolution: "@ethersproject/base64@npm:5.6.1" + resolution: "@ethersproject/address@npm:5.6.1" dependencies: + "@ethersproject/bignumber": ^5.6.2 "@ethersproject/bytes": ^5.6.1 - checksum: d21c5c297e1b8bc48fe59012c0cd70a90df7772fac07d9cc3da499d71d174d9f48edfd83495d4a1496cb70e8d1b33fb5b549a9529c5c2f97bb3a07d3f33a3fe8 + "@ethersproject/keccak256": ^5.6.1 + "@ethersproject/logger": ^5.6.0 + "@ethersproject/rlp": ^5.6.1 + checksum: 262096ef05a1b626c161a72698a5d8b06aebf821fe01a1651ab40f80c29ca2481b96be7f972745785fd6399906509458c4c9a38f3bc1c1cb5afa7d2f76f7309a languageName: node linkType: hard -"@ethersproject/base64@npm:^5.7.0": +"@ethersproject/base64@npm:5.7.0, @ethersproject/base64@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/base64@npm:5.7.0" dependencies: @@ -606,17 +683,16 @@ __metadata: languageName: node linkType: hard -"@ethersproject/basex@npm:5.6.1, @ethersproject/basex@npm:^5.6.1": +"@ethersproject/base64@npm:^5.6.1": version: 5.6.1 - resolution: "@ethersproject/basex@npm:5.6.1" + resolution: "@ethersproject/base64@npm:5.6.1" dependencies: "@ethersproject/bytes": ^5.6.1 - "@ethersproject/properties": ^5.6.0 - checksum: a14b75d2c25d0ac00ce0098e5bd338d4cce7a68c583839b2bc4e3512ffcb14498b18cbcb4e05b695d216d2a23814d0c335385f35b3118735cc4895234db5ae1c + checksum: d21c5c297e1b8bc48fe59012c0cd70a90df7772fac07d9cc3da499d71d174d9f48edfd83495d4a1496cb70e8d1b33fb5b549a9529c5c2f97bb3a07d3f33a3fe8 languageName: node linkType: hard -"@ethersproject/basex@npm:^5.7.0": +"@ethersproject/basex@npm:5.7.0, @ethersproject/basex@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/basex@npm:5.7.0" dependencies: @@ -626,18 +702,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bignumber@npm:5.6.2, @ethersproject/bignumber@npm:^5.6.2": - version: 5.6.2 - resolution: "@ethersproject/bignumber@npm:5.6.2" - dependencies: - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - bn.js: ^5.2.1 - checksum: 9cf31c10274f1b6d45b16aed29f43729e8f5edec38c8ec8bb90d6b44f0eae14fda6519536228d23916a375ce11e71a77279a912d653ea02503959910b6bf9de7 - languageName: node - linkType: hard - -"@ethersproject/bignumber@npm:^5.7.0": +"@ethersproject/bignumber@npm:5.7.0, @ethersproject/bignumber@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bignumber@npm:5.7.0" dependencies: @@ -648,16 +713,18 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bytes@npm:5.6.1, @ethersproject/bytes@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/bytes@npm:5.6.1" +"@ethersproject/bignumber@npm:^5.6.2": + version: 5.6.2 + resolution: "@ethersproject/bignumber@npm:5.6.2" dependencies: + "@ethersproject/bytes": ^5.6.1 "@ethersproject/logger": ^5.6.0 - checksum: d06ffe3bf12aa8a6588d99b82e40b46a2cbb8b057fc650aad836e3e8c95d4559773254eeeb8fed652066dcf8082e527e37cd2b9fff7ac8cabc4de7c49459a7eb + bn.js: ^5.2.1 + checksum: 9cf31c10274f1b6d45b16aed29f43729e8f5edec38c8ec8bb90d6b44f0eae14fda6519536228d23916a375ce11e71a77279a912d653ea02503959910b6bf9de7 languageName: node linkType: hard -"@ethersproject/bytes@npm:^5.7.0": +"@ethersproject/bytes@npm:5.7.0, @ethersproject/bytes@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bytes@npm:5.7.0" dependencies: @@ -666,16 +733,16 @@ __metadata: languageName: node linkType: hard -"@ethersproject/constants@npm:5.6.1, @ethersproject/constants@npm:^5.6.1": +"@ethersproject/bytes@npm:^5.6.1": version: 5.6.1 - resolution: "@ethersproject/constants@npm:5.6.1" + resolution: "@ethersproject/bytes@npm:5.6.1" dependencies: - "@ethersproject/bignumber": ^5.6.2 - checksum: 3c6abcee60f1620796dc40210a638b601ad8a2d3f6668a69c42a5ca361044f21296b16d1d43b8a00f7c28b385de4165983a8adf671e0983f5ef07459dfa84997 + "@ethersproject/logger": ^5.6.0 + checksum: d06ffe3bf12aa8a6588d99b82e40b46a2cbb8b057fc650aad836e3e8c95d4559773254eeeb8fed652066dcf8082e527e37cd2b9fff7ac8cabc4de7c49459a7eb languageName: node linkType: hard -"@ethersproject/constants@npm:^5.7.0": +"@ethersproject/constants@npm:5.7.0, @ethersproject/constants@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/constants@npm:5.7.0" dependencies: @@ -684,25 +751,16 @@ __metadata: languageName: node linkType: hard -"@ethersproject/contracts@npm:5.6.2": - version: 5.6.2 - resolution: "@ethersproject/contracts@npm:5.6.2" +"@ethersproject/constants@npm:^5.6.1": + version: 5.6.1 + resolution: "@ethersproject/constants@npm:5.6.1" dependencies: - "@ethersproject/abi": ^5.6.3 - "@ethersproject/abstract-provider": ^5.6.1 - "@ethersproject/abstract-signer": ^5.6.2 - "@ethersproject/address": ^5.6.1 "@ethersproject/bignumber": ^5.6.2 - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/constants": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/transactions": ^5.6.2 - checksum: c5a36ce3d0b88dc80db0135aaf39a71c0f14e262fd14172ae557d8943e69d3a2ba52c8f73f67639db0c235ea51155a97ff3584d431b92686f4c711b1004e6f87 + checksum: 3c6abcee60f1620796dc40210a638b601ad8a2d3f6668a69c42a5ca361044f21296b16d1d43b8a00f7c28b385de4165983a8adf671e0983f5ef07459dfa84997 languageName: node linkType: hard -"@ethersproject/contracts@npm:^5.7.0": +"@ethersproject/contracts@npm:5.7.0, @ethersproject/contracts@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/contracts@npm:5.7.0" dependencies: @@ -720,23 +778,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/hash@npm:5.6.1, @ethersproject/hash@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/hash@npm:5.6.1" - dependencies: - "@ethersproject/abstract-signer": ^5.6.2 - "@ethersproject/address": ^5.6.1 - "@ethersproject/bignumber": ^5.6.2 - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/keccak256": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/strings": ^5.6.1 - checksum: 1338b578a51bc5cb692c17b1cabc51e484e9e3e009c4ffec13032332fc7e746c115968de1c259133cdcdad55fa96c5c8a5144170190c62b968a3fedb5b1d2cdb - languageName: node - linkType: hard - -"@ethersproject/hash@npm:^5.7.0": +"@ethersproject/hash@npm:5.7.0, @ethersproject/hash@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/hash@npm:5.7.0" dependencies: @@ -753,58 +795,64 @@ __metadata: languageName: node linkType: hard -"@ethersproject/hdnode@npm:5.6.2, @ethersproject/hdnode@npm:^5.6.2": - version: 5.6.2 - resolution: "@ethersproject/hdnode@npm:5.6.2" +"@ethersproject/hash@npm:^5.6.1": + version: 5.6.1 + resolution: "@ethersproject/hash@npm:5.6.1" dependencies: "@ethersproject/abstract-signer": ^5.6.2 - "@ethersproject/basex": ^5.6.1 + "@ethersproject/address": ^5.6.1 "@ethersproject/bignumber": ^5.6.2 "@ethersproject/bytes": ^5.6.1 + "@ethersproject/keccak256": ^5.6.1 "@ethersproject/logger": ^5.6.0 - "@ethersproject/pbkdf2": ^5.6.1 "@ethersproject/properties": ^5.6.0 - "@ethersproject/sha2": ^5.6.1 - "@ethersproject/signing-key": ^5.6.2 "@ethersproject/strings": ^5.6.1 - "@ethersproject/transactions": ^5.6.2 - "@ethersproject/wordlists": ^5.6.1 - checksum: b096882ac75d6738c085bf7cdaaf06b6b89055b8e98469df4abf00d600a6131299ec25ca3bc71986cc79d70ddf09ec00258e7ce7e94c45d5ffb83aa616eaaaae + checksum: 1338b578a51bc5cb692c17b1cabc51e484e9e3e009c4ffec13032332fc7e746c115968de1c259133cdcdad55fa96c5c8a5144170190c62b968a3fedb5b1d2cdb languageName: node linkType: hard -"@ethersproject/json-wallets@npm:5.6.1, @ethersproject/json-wallets@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/json-wallets@npm:5.6.1" +"@ethersproject/hdnode@npm:5.7.0, @ethersproject/hdnode@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/hdnode@npm:5.7.0" dependencies: - "@ethersproject/abstract-signer": ^5.6.2 - "@ethersproject/address": ^5.6.1 - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/hdnode": ^5.6.2 - "@ethersproject/keccak256": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/pbkdf2": ^5.6.1 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/random": ^5.6.1 - "@ethersproject/strings": ^5.6.1 - "@ethersproject/transactions": ^5.6.2 - aes-js: 3.0.0 - scrypt-js: 3.0.1 - checksum: 811b3596aaf1c1a64a8acef0c4fe0123a660349e6cbd5e970b1f9461966fd06858be0f154543bbd962a0ef0d369db52c6254c6b5264c172d44315085a2a6c454 + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/basex": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/pbkdf2": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/sha2": ^5.7.0 + "@ethersproject/signing-key": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + "@ethersproject/wordlists": ^5.7.0 + checksum: bfe5ca2d89a42de73655f853170ef4766b933c5f481cddad709b3aca18823275b096e572f92d1602a052f80b426edde44ad6b9d028799775a7dad4a5bbed2133 languageName: node linkType: hard -"@ethersproject/keccak256@npm:5.6.1, @ethersproject/keccak256@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/keccak256@npm:5.6.1" +"@ethersproject/json-wallets@npm:5.7.0, @ethersproject/json-wallets@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/json-wallets@npm:5.7.0" dependencies: - "@ethersproject/bytes": ^5.6.1 - js-sha3: 0.8.0 - checksum: fdc950e22a1aafc92fdf749cdc5b8952b85e8cee8872d807c5f40be31f58675d30e0eca5e676876b93f2cd22ac63a344d384d116827ee80928c24b7c299991f5 + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/hdnode": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/pbkdf2": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/random": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + aes-js: 3.0.0 + scrypt-js: 3.0.1 + checksum: f583458d22db62efaaf94d38dd243482776a45bf90f9f3882fbad5aa0b8fd288b41eb7c1ff8ec0b99c9b751088e43d6173530db64dd33c59f9d8daa8d7ad5aa2 languageName: node linkType: hard -"@ethersproject/keccak256@npm:^5.7.0": +"@ethersproject/keccak256@npm:5.7.0, @ethersproject/keccak256@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/keccak256@npm:5.7.0" dependencies: @@ -814,30 +862,31 @@ __metadata: languageName: node linkType: hard -"@ethersproject/logger@npm:5.6.0, @ethersproject/logger@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/logger@npm:5.6.0" - checksum: 6eee38a973c7a458552278971c109a3e5df3c257e433cb959da9a287ea04628d1f510d41b83bd5f9da5ddc05d97d307ed2162a9ba1b4fcc50664e4f60061636c +"@ethersproject/keccak256@npm:^5.6.1": + version: 5.6.1 + resolution: "@ethersproject/keccak256@npm:5.6.1" + dependencies: + "@ethersproject/bytes": ^5.6.1 + js-sha3: 0.8.0 + checksum: fdc950e22a1aafc92fdf749cdc5b8952b85e8cee8872d807c5f40be31f58675d30e0eca5e676876b93f2cd22ac63a344d384d116827ee80928c24b7c299991f5 languageName: node linkType: hard -"@ethersproject/logger@npm:^5.7.0": +"@ethersproject/logger@npm:5.7.0, @ethersproject/logger@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/logger@npm:5.7.0" checksum: 075ab2f605f1fd0813f2e39c3308f77b44a67732b36e712d9bc085f22a84aac4da4f71b39bee50fe78da3e1c812673fadc41180c9970fe5e486e91ea17befe0d languageName: node linkType: hard -"@ethersproject/networks@npm:5.6.4, @ethersproject/networks@npm:^5.6.3": - version: 5.6.4 - resolution: "@ethersproject/networks@npm:5.6.4" - dependencies: - "@ethersproject/logger": ^5.6.0 - checksum: d41c07497de4ace3f57e972428685a8703a867600cf01f2bc15a21fcb7f99afb3f05b3d8dbb29ac206473368f30d60b98dc445cc38403be4cbe6f804f70e5173 +"@ethersproject/logger@npm:^5.6.0": + version: 5.6.0 + resolution: "@ethersproject/logger@npm:5.6.0" + checksum: 6eee38a973c7a458552278971c109a3e5df3c257e433cb959da9a287ea04628d1f510d41b83bd5f9da5ddc05d97d307ed2162a9ba1b4fcc50664e4f60061636c languageName: node linkType: hard -"@ethersproject/networks@npm:^5.7.0": +"@ethersproject/networks@npm:5.7.1, @ethersproject/networks@npm:^5.7.0": version: 5.7.1 resolution: "@ethersproject/networks@npm:5.7.1" dependencies: @@ -846,26 +895,26 @@ __metadata: languageName: node linkType: hard -"@ethersproject/pbkdf2@npm:5.6.1, @ethersproject/pbkdf2@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/pbkdf2@npm:5.6.1" +"@ethersproject/networks@npm:^5.6.3": + version: 5.6.4 + resolution: "@ethersproject/networks@npm:5.6.4" dependencies: - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/sha2": ^5.6.1 - checksum: 316006373828a189bf22b7a08df7dd7ffe24e5f2c83e6d09d922ce663892cc14c7d27524dc4e51993d51e4464a7b7ce5e7b23453bdc85e3c6d4d5c41aa7227cf + "@ethersproject/logger": ^5.6.0 + checksum: d41c07497de4ace3f57e972428685a8703a867600cf01f2bc15a21fcb7f99afb3f05b3d8dbb29ac206473368f30d60b98dc445cc38403be4cbe6f804f70e5173 languageName: node linkType: hard -"@ethersproject/properties@npm:5.6.0, @ethersproject/properties@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/properties@npm:5.6.0" +"@ethersproject/pbkdf2@npm:5.7.0, @ethersproject/pbkdf2@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/pbkdf2@npm:5.7.0" dependencies: - "@ethersproject/logger": ^5.6.0 - checksum: adcb6a843dcdf809262d77d6fbe52acdd48703327b298f78e698b76784e89564fb81791d27eaee72b1a6aaaf5688ea2ae7a95faabdef8b4aecc99989fec55901 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/sha2": ^5.7.0 + checksum: b895adb9e35a8a127e794f7aadc31a2424ef355a70e51cde10d457e3e888bb8102373199a540cf61f2d6b9a32e47358f9c65b47d559f42bf8e596b5fd67901e9 languageName: node linkType: hard -"@ethersproject/properties@npm:^5.7.0": +"@ethersproject/properties@npm:5.7.0, @ethersproject/properties@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/properties@npm:5.7.0" dependencies: @@ -874,35 +923,16 @@ __metadata: languageName: node linkType: hard -"@ethersproject/providers@npm:5.6.8": - version: 5.6.8 - resolution: "@ethersproject/providers@npm:5.6.8" +"@ethersproject/properties@npm:^5.6.0": + version: 5.6.0 + resolution: "@ethersproject/properties@npm:5.6.0" dependencies: - "@ethersproject/abstract-provider": ^5.6.1 - "@ethersproject/abstract-signer": ^5.6.2 - "@ethersproject/address": ^5.6.1 - "@ethersproject/base64": ^5.6.1 - "@ethersproject/basex": ^5.6.1 - "@ethersproject/bignumber": ^5.6.2 - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/constants": ^5.6.1 - "@ethersproject/hash": ^5.6.1 "@ethersproject/logger": ^5.6.0 - "@ethersproject/networks": ^5.6.3 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/random": ^5.6.1 - "@ethersproject/rlp": ^5.6.1 - "@ethersproject/sha2": ^5.6.1 - "@ethersproject/strings": ^5.6.1 - "@ethersproject/transactions": ^5.6.2 - "@ethersproject/web": ^5.6.1 - bech32: 1.1.4 - ws: 7.4.6 - checksum: 27dc2005e1ae7a6d498bb0bbacc6ad1f7164a599cf5aaad7c51cfd7c4d36d0cc5c7c40ba504f9017c746e8a0f008f15ad24e9961816793b49755dcb5c01540c0 + checksum: adcb6a843dcdf809262d77d6fbe52acdd48703327b298f78e698b76784e89564fb81791d27eaee72b1a6aaaf5688ea2ae7a95faabdef8b4aecc99989fec55901 languageName: node linkType: hard -"@ethersproject/providers@npm:^5.7.0, @ethersproject/providers@npm:^5.7.2": +"@ethersproject/providers@npm:5.7.2, @ethersproject/providers@npm:^5.7.0, @ethersproject/providers@npm:^5.7.1, @ethersproject/providers@npm:^5.7.2": version: 5.7.2 resolution: "@ethersproject/providers@npm:5.7.2" dependencies: @@ -930,27 +960,27 @@ __metadata: languageName: node linkType: hard -"@ethersproject/random@npm:5.6.1, @ethersproject/random@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/random@npm:5.6.1" +"@ethersproject/random@npm:5.7.0, @ethersproject/random@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/random@npm:5.7.0" dependencies: - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - checksum: 55517d65eee6dcc0848ef10a825245d61553a6c1bec15d2f69d9430ce4568d9af32013e2aa96c8336545465a24a1fd04defbe9e9f76a5ee110dc5128d4111c11 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + checksum: 017829c91cff6c76470852855108115b0b52c611b6be817ed1948d56ba42d6677803ec2012aa5ae298a7660024156a64c11fcf544e235e239ab3f89f0fff7345 languageName: node linkType: hard -"@ethersproject/random@npm:^5.7.0": +"@ethersproject/rlp@npm:5.7.0, @ethersproject/rlp@npm:^5.7.0": version: 5.7.0 - resolution: "@ethersproject/random@npm:5.7.0" + resolution: "@ethersproject/rlp@npm:5.7.0" dependencies: "@ethersproject/bytes": ^5.7.0 "@ethersproject/logger": ^5.7.0 - checksum: 017829c91cff6c76470852855108115b0b52c611b6be817ed1948d56ba42d6677803ec2012aa5ae298a7660024156a64c11fcf544e235e239ab3f89f0fff7345 + checksum: bce165b0f7e68e4d091c9d3cf47b247cac33252df77a095ca4281d32d5eeaaa3695d9bc06b2b057c5015353a68df89f13a4a54a72e888e4beeabbe56b15dda6e languageName: node linkType: hard -"@ethersproject/rlp@npm:5.6.1, @ethersproject/rlp@npm:^5.6.1": +"@ethersproject/rlp@npm:^5.6.1": version: 5.6.1 resolution: "@ethersproject/rlp@npm:5.6.1" dependencies: @@ -960,39 +990,32 @@ __metadata: languageName: node linkType: hard -"@ethersproject/rlp@npm:^5.7.0": +"@ethersproject/sha2@npm:5.7.0, @ethersproject/sha2@npm:^5.7.0": version: 5.7.0 - resolution: "@ethersproject/rlp@npm:5.7.0" + resolution: "@ethersproject/sha2@npm:5.7.0" dependencies: "@ethersproject/bytes": ^5.7.0 "@ethersproject/logger": ^5.7.0 - checksum: bce165b0f7e68e4d091c9d3cf47b247cac33252df77a095ca4281d32d5eeaaa3695d9bc06b2b057c5015353a68df89f13a4a54a72e888e4beeabbe56b15dda6e - languageName: node - linkType: hard - -"@ethersproject/sha2@npm:5.6.1, @ethersproject/sha2@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/sha2@npm:5.6.1" - dependencies: - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/logger": ^5.6.0 hash.js: 1.1.7 - checksum: 04313cb4a8e24ce8b5736f9d08906764fbfdab19bc64adef363cf570defa72926d8faae19aed805e1caee737f5efecdc60a4c89fd2b1ee2b3ba0eb9555cae3ae + checksum: 09321057c022effbff4cc2d9b9558228690b5dd916329d75c4b1ffe32ba3d24b480a367a7cc92d0f0c0b1c896814d03351ae4630e2f1f7160be2bcfbde435dbc languageName: node linkType: hard -"@ethersproject/sha2@npm:^5.7.0": +"@ethersproject/signing-key@npm:5.7.0, @ethersproject/signing-key@npm:^5.7.0": version: 5.7.0 - resolution: "@ethersproject/sha2@npm:5.7.0" + resolution: "@ethersproject/signing-key@npm:5.7.0" dependencies: "@ethersproject/bytes": ^5.7.0 "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + bn.js: ^5.2.1 + elliptic: 6.5.4 hash.js: 1.1.7 - checksum: 09321057c022effbff4cc2d9b9558228690b5dd916329d75c4b1ffe32ba3d24b480a367a7cc92d0f0c0b1c896814d03351ae4630e2f1f7160be2bcfbde435dbc + checksum: 8f8de09b0aac709683bbb49339bc0a4cd2f95598f3546436c65d6f3c3a847ffa98e06d35e9ed2b17d8030bd2f02db9b7bd2e11c5cf8a71aad4537487ab4cf03a languageName: node linkType: hard -"@ethersproject/signing-key@npm:5.6.2, @ethersproject/signing-key@npm:^5.6.2": +"@ethersproject/signing-key@npm:^5.6.2": version: 5.6.2 resolution: "@ethersproject/signing-key@npm:5.6.2" dependencies: @@ -1006,35 +1029,32 @@ __metadata: languageName: node linkType: hard -"@ethersproject/signing-key@npm:^5.7.0": +"@ethersproject/solidity@npm:5.7.0": version: 5.7.0 - resolution: "@ethersproject/signing-key@npm:5.7.0" + resolution: "@ethersproject/solidity@npm:5.7.0" dependencies: + "@ethersproject/bignumber": ^5.7.0 "@ethersproject/bytes": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 "@ethersproject/logger": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - bn.js: ^5.2.1 - elliptic: 6.5.4 - hash.js: 1.1.7 - checksum: 8f8de09b0aac709683bbb49339bc0a4cd2f95598f3546436c65d6f3c3a847ffa98e06d35e9ed2b17d8030bd2f02db9b7bd2e11c5cf8a71aad4537487ab4cf03a + "@ethersproject/sha2": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + checksum: 9a02f37f801c96068c3e7721f83719d060175bc4e80439fe060e92bd7acfcb6ac1330c7e71c49f4c2535ca1308f2acdcb01e00133129aac00581724c2d6293f3 languageName: node linkType: hard -"@ethersproject/solidity@npm:5.6.1": - version: 5.6.1 - resolution: "@ethersproject/solidity@npm:5.6.1" +"@ethersproject/strings@npm:5.7.0, @ethersproject/strings@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/strings@npm:5.7.0" dependencies: - "@ethersproject/bignumber": ^5.6.2 - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/keccak256": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/sha2": ^5.6.1 - "@ethersproject/strings": ^5.6.1 - checksum: a31bd7b98314824d15e28350ee1a21c10e32d2f71579b46c72eab06b895dba147efe966874444a30b17846f9c2ad74043152ec49d4401148262afffb30727087 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + checksum: 5ff78693ae3fdf3cf23e1f6dc047a61e44c8197d2408c42719fef8cb7b7b3613a4eec88ac0ed1f9f5558c74fe0de7ae3195a29ca91a239c74b9f444d8e8b50df languageName: node linkType: hard -"@ethersproject/strings@npm:5.6.1, @ethersproject/strings@npm:^5.6.1": +"@ethersproject/strings@npm:^5.6.1": version: 5.6.1 resolution: "@ethersproject/strings@npm:5.6.1" dependencies: @@ -1045,18 +1065,24 @@ __metadata: languageName: node linkType: hard -"@ethersproject/strings@npm:^5.7.0": +"@ethersproject/transactions@npm:5.7.0, @ethersproject/transactions@npm:^5.7.0": version: 5.7.0 - resolution: "@ethersproject/strings@npm:5.7.0" + resolution: "@ethersproject/transactions@npm:5.7.0" dependencies: + "@ethersproject/address": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 "@ethersproject/bytes": ^5.7.0 "@ethersproject/constants": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 "@ethersproject/logger": ^5.7.0 - checksum: 5ff78693ae3fdf3cf23e1f6dc047a61e44c8197d2408c42719fef8cb7b7b3613a4eec88ac0ed1f9f5558c74fe0de7ae3195a29ca91a239c74b9f444d8e8b50df + "@ethersproject/properties": ^5.7.0 + "@ethersproject/rlp": ^5.7.0 + "@ethersproject/signing-key": ^5.7.0 + checksum: a31b71996d2b283f68486241bff0d3ea3f1ba0e8f1322a8fffc239ccc4f4a7eb2ea9994b8fd2f093283fd75f87bae68171e01b6265261f821369aca319884a79 languageName: node linkType: hard -"@ethersproject/transactions@npm:5.6.2, @ethersproject/transactions@npm:^5.6.2": +"@ethersproject/transactions@npm:^5.6.2": version: 5.6.2 resolution: "@ethersproject/transactions@npm:5.6.2" dependencies: @@ -1073,58 +1099,54 @@ __metadata: languageName: node linkType: hard -"@ethersproject/transactions@npm:^5.7.0": +"@ethersproject/units@npm:5.7.0": version: 5.7.0 - resolution: "@ethersproject/transactions@npm:5.7.0" + resolution: "@ethersproject/units@npm:5.7.0" dependencies: - "@ethersproject/address": ^5.7.0 "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 "@ethersproject/constants": ^5.7.0 - "@ethersproject/keccak256": ^5.7.0 "@ethersproject/logger": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/rlp": ^5.7.0 - "@ethersproject/signing-key": ^5.7.0 - checksum: a31b71996d2b283f68486241bff0d3ea3f1ba0e8f1322a8fffc239ccc4f4a7eb2ea9994b8fd2f093283fd75f87bae68171e01b6265261f821369aca319884a79 + checksum: 304714f848cd32e57df31bf545f7ad35c2a72adae957198b28cbc62166daa929322a07bff6e9c9ac4577ab6aa0de0546b065ed1b2d20b19e25748b7d475cb0fc languageName: node linkType: hard -"@ethersproject/units@npm:5.6.1": - version: 5.6.1 - resolution: "@ethersproject/units@npm:5.6.1" +"@ethersproject/wallet@npm:5.7.0": + version: 5.7.0 + resolution: "@ethersproject/wallet@npm:5.7.0" dependencies: - "@ethersproject/bignumber": ^5.6.2 - "@ethersproject/constants": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - checksum: 79cc7c35181fc3bd76fc33d95f1c8d2a20a6339dfc22745184967481b66e0782ee12bbf75b4269119152cbd23bf7980b900978d885b5da72cfb74cf897411065 + "@ethersproject/abstract-provider": ^5.7.0 + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/hash": ^5.7.0 + "@ethersproject/hdnode": ^5.7.0 + "@ethersproject/json-wallets": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/random": ^5.7.0 + "@ethersproject/signing-key": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + "@ethersproject/wordlists": ^5.7.0 + checksum: a4009bf7331eddab38e3015b5e9101ef92de7f705b00a6196b997db0e5635b6d83561674d46c90c6f77b87c0500fe4a6b0183ba13749efc22db59c99deb82fbd languageName: node linkType: hard -"@ethersproject/wallet@npm:5.6.2": - version: 5.6.2 - resolution: "@ethersproject/wallet@npm:5.6.2" +"@ethersproject/web@npm:5.7.1, @ethersproject/web@npm:^5.7.0": + version: 5.7.1 + resolution: "@ethersproject/web@npm:5.7.1" dependencies: - "@ethersproject/abstract-provider": ^5.6.1 - "@ethersproject/abstract-signer": ^5.6.2 - "@ethersproject/address": ^5.6.1 - "@ethersproject/bignumber": ^5.6.2 - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/hash": ^5.6.1 - "@ethersproject/hdnode": ^5.6.2 - "@ethersproject/json-wallets": ^5.6.1 - "@ethersproject/keccak256": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/random": ^5.6.1 - "@ethersproject/signing-key": ^5.6.2 - "@ethersproject/transactions": ^5.6.2 - "@ethersproject/wordlists": ^5.6.1 - checksum: 88603a4797b8f489c76671ff096ad3630ad1226640032594cfb3376398b41c1c4875076f1cf6521854c42e4496cafd2171e6dc301669cbf6c972ba13e97be5b0 + "@ethersproject/base64": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + checksum: 7028c47103f82fd2e2c197ce0eecfacaa9180ffeec7de7845b1f4f9b19d84081b7a48227aaddde05a4aaa526af574a9a0ce01cc0fc75e3e371f84b38b5b16b2b languageName: node linkType: hard -"@ethersproject/web@npm:5.6.1, @ethersproject/web@npm:^5.6.1": +"@ethersproject/web@npm:^5.6.1": version: 5.6.1 resolution: "@ethersproject/web@npm:5.6.1" dependencies: @@ -1137,29 +1159,16 @@ __metadata: languageName: node linkType: hard -"@ethersproject/web@npm:^5.7.0": - version: 5.7.1 - resolution: "@ethersproject/web@npm:5.7.1" +"@ethersproject/wordlists@npm:5.7.0, @ethersproject/wordlists@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/wordlists@npm:5.7.0" dependencies: - "@ethersproject/base64": ^5.7.0 "@ethersproject/bytes": ^5.7.0 + "@ethersproject/hash": ^5.7.0 "@ethersproject/logger": ^5.7.0 "@ethersproject/properties": ^5.7.0 "@ethersproject/strings": ^5.7.0 - checksum: 7028c47103f82fd2e2c197ce0eecfacaa9180ffeec7de7845b1f4f9b19d84081b7a48227aaddde05a4aaa526af574a9a0ce01cc0fc75e3e371f84b38b5b16b2b - languageName: node - linkType: hard - -"@ethersproject/wordlists@npm:5.6.1, @ethersproject/wordlists@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/wordlists@npm:5.6.1" - dependencies: - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/hash": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/strings": ^5.6.1 - checksum: 3be4f300705b3f4f2b1dfa3948aac2e5030ab6216086578ec5cd2fad130b6b30d2a6a3c54d94c6669601ed62b56e7052232bc0a934a451ef3320fd6513734729 + checksum: 30eb6eb0731f9ef5faa44bf9c0c6e950bcaaef61e4d2d9ce0ae6d341f4e2d6d1f4ab4f8880bfce03b7aac4b862fb740e1421170cfbf8e2aafc359277d49e6e97 languageName: node linkType: hard @@ -1421,170 +1430,171 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-block@npm:^4.0.0": - version: 4.0.0 - resolution: "@nomicfoundation/ethereumjs-block@npm:4.0.0" +"@nomicfoundation/ethereumjs-block@npm:5.0.1": + version: 5.0.1 + resolution: "@nomicfoundation/ethereumjs-block@npm:5.0.1" dependencies: - "@nomicfoundation/ethereumjs-common": ^3.0.0 - "@nomicfoundation/ethereumjs-rlp": ^4.0.0 - "@nomicfoundation/ethereumjs-trie": ^5.0.0 - "@nomicfoundation/ethereumjs-tx": ^4.0.0 - "@nomicfoundation/ethereumjs-util": ^8.0.0 + "@nomicfoundation/ethereumjs-common": 4.0.1 + "@nomicfoundation/ethereumjs-rlp": 5.0.1 + "@nomicfoundation/ethereumjs-trie": 6.0.1 + "@nomicfoundation/ethereumjs-tx": 5.0.1 + "@nomicfoundation/ethereumjs-util": 9.0.1 ethereum-cryptography: 0.1.3 - checksum: a57a33dda7724f0a46ef2e0ca0dbb1b427268f4135e8c23eee9ab5730a79369d52122faba7a010d71bca3046f7ce644ed95e4a34d5f2221ecaa5d94886d84b11 + ethers: ^5.7.1 + checksum: 02591bc9ba02b56edc5faf75a7991d6b9430bd98542864f2f6ab202f0f4aed09be156fdba60948375beb10e524ffa4e461475edc8a15b3098b1c58ff59a0137e languageName: node linkType: hard -"@nomicfoundation/ethereumjs-blockchain@npm:^6.0.0": - version: 6.0.0 - resolution: "@nomicfoundation/ethereumjs-blockchain@npm:6.0.0" - dependencies: - "@nomicfoundation/ethereumjs-block": ^4.0.0 - "@nomicfoundation/ethereumjs-common": ^3.0.0 - "@nomicfoundation/ethereumjs-ethash": ^2.0.0 - "@nomicfoundation/ethereumjs-rlp": ^4.0.0 - "@nomicfoundation/ethereumjs-trie": ^5.0.0 - "@nomicfoundation/ethereumjs-util": ^8.0.0 +"@nomicfoundation/ethereumjs-blockchain@npm:7.0.1": + version: 7.0.1 + resolution: "@nomicfoundation/ethereumjs-blockchain@npm:7.0.1" + dependencies: + "@nomicfoundation/ethereumjs-block": 5.0.1 + "@nomicfoundation/ethereumjs-common": 4.0.1 + "@nomicfoundation/ethereumjs-ethash": 3.0.1 + "@nomicfoundation/ethereumjs-rlp": 5.0.1 + "@nomicfoundation/ethereumjs-trie": 6.0.1 + "@nomicfoundation/ethereumjs-tx": 5.0.1 + "@nomicfoundation/ethereumjs-util": 9.0.1 abstract-level: ^1.0.3 debug: ^4.3.3 ethereum-cryptography: 0.1.3 level: ^8.0.0 lru-cache: ^5.1.1 memory-level: ^1.0.0 - checksum: 5605c1d249924321de98c1728b5b832ee6488b690a42c829db21afa96f5c152c73afdec6aa4758cb9b24ec7ac19ec9f3146b63cf837e1b91d364e4c37b497881 + checksum: 8b7a4e3613c2abbf59e92a927cb074d1df8640fbf6a0ec4be7fcb5ecaead1310ebbe3a41613c027253742f6dccca6eaeee8dde0a38315558de156313d0c8f313 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-common@npm:^3.0.0": - version: 3.0.0 - resolution: "@nomicfoundation/ethereumjs-common@npm:3.0.0" +"@nomicfoundation/ethereumjs-common@npm:4.0.1": + version: 4.0.1 + resolution: "@nomicfoundation/ethereumjs-common@npm:4.0.1" dependencies: - "@nomicfoundation/ethereumjs-util": ^8.0.0 + "@nomicfoundation/ethereumjs-util": 9.0.1 crc-32: ^1.2.0 - checksum: 6a62908e5ccd8a4f56b841bd6ba9eef21dffafdd505f18b6b886d86ba4287cd12a2c632d521c5fddf2c6fca5a840f580d7601d89820098f6c1f8311db41e496b + checksum: af5b599bcc07430b57017e516b0bad70af04e812b970be9bfae0c1d3433ab26656b3d1db71717b3b0fb38a889db2b93071b45adc1857000e7cd58a99a8e29495 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-ethash@npm:^2.0.0": - version: 2.0.0 - resolution: "@nomicfoundation/ethereumjs-ethash@npm:2.0.0" +"@nomicfoundation/ethereumjs-ethash@npm:3.0.1": + version: 3.0.1 + resolution: "@nomicfoundation/ethereumjs-ethash@npm:3.0.1" dependencies: - "@nomicfoundation/ethereumjs-block": ^4.0.0 - "@nomicfoundation/ethereumjs-rlp": ^4.0.0 - "@nomicfoundation/ethereumjs-util": ^8.0.0 + "@nomicfoundation/ethereumjs-block": 5.0.1 + "@nomicfoundation/ethereumjs-rlp": 5.0.1 + "@nomicfoundation/ethereumjs-util": 9.0.1 abstract-level: ^1.0.3 bigint-crypto-utils: ^3.0.23 ethereum-cryptography: 0.1.3 - checksum: 60133df2d450179f2ab26e8784b1bd79b37411bb047a7dace655499749893750f0f8d6d573f182ebcf4dba35f2da6301b0ad1b80dbe7637bb0d5155ccb189fda + checksum: beeec9788a9ed57020ee47271447715bdc0a98990a0bd0e9d598c6de74ade836db17c0590275e6aab12fa9b0fbd81f1d02e3cdf1fb8497583cec693ec3ed6aed languageName: node linkType: hard -"@nomicfoundation/ethereumjs-evm@npm:^1.0.0": - version: 1.0.0 - resolution: "@nomicfoundation/ethereumjs-evm@npm:1.0.0" +"@nomicfoundation/ethereumjs-evm@npm:2.0.1": + version: 2.0.1 + resolution: "@nomicfoundation/ethereumjs-evm@npm:2.0.1" dependencies: - "@nomicfoundation/ethereumjs-common": ^3.0.0 - "@nomicfoundation/ethereumjs-util": ^8.0.0 - "@types/async-eventemitter": ^0.2.1 - async-eventemitter: ^0.2.4 + "@ethersproject/providers": ^5.7.1 + "@nomicfoundation/ethereumjs-common": 4.0.1 + "@nomicfoundation/ethereumjs-tx": 5.0.1 + "@nomicfoundation/ethereumjs-util": 9.0.1 debug: ^4.3.3 ethereum-cryptography: 0.1.3 mcl-wasm: ^0.7.1 rustbn.js: ~0.2.0 - checksum: d1ffaa1a02c1f78099a5cfe802f2738c498063e383a51ede4b7194c809d7bdb8d322edfea4d83090c8c1b83b42fa9febbd571c35f5cf27f18d47fb664f3ab61e + checksum: 0aa2e1460e1c311506fd3bf9d03602c7c3a5e03f352173a55a274a9cc1840bd774692d1c4e5c6e82a7eee015a7cf1585f1c5be02cfdf54cc2a771421820e3f84 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-rlp@npm:^4.0.0, @nomicfoundation/ethereumjs-rlp@npm:^4.0.0-beta.2": - version: 4.0.0 - resolution: "@nomicfoundation/ethereumjs-rlp@npm:4.0.0" +"@nomicfoundation/ethereumjs-rlp@npm:5.0.1": + version: 5.0.1 + resolution: "@nomicfoundation/ethereumjs-rlp@npm:5.0.1" bin: rlp: bin/rlp - checksum: b358d239e5a24884f0446d52159c8115b0eb1d6907179dc968df5054dccea7eff72f2d12522c911b6e08bb4b5d3f5f8e1d86a45cb1a24a4831cbb109743d4407 + checksum: 5a51d2cf92b84e50ce516cbdadff5d39cb4c6b71335e92eaf447dfb7d88f5499d78d599024b9252efd7ba99495de36f4d983cec6a89e77db286db691fc6328f7 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-statemanager@npm:^1.0.0": - version: 1.0.0 - resolution: "@nomicfoundation/ethereumjs-statemanager@npm:1.0.0" +"@nomicfoundation/ethereumjs-statemanager@npm:2.0.1": + version: 2.0.1 + resolution: "@nomicfoundation/ethereumjs-statemanager@npm:2.0.1" dependencies: - "@nomicfoundation/ethereumjs-common": ^3.0.0 - "@nomicfoundation/ethereumjs-rlp": ^4.0.0 - "@nomicfoundation/ethereumjs-trie": ^5.0.0 - "@nomicfoundation/ethereumjs-util": ^8.0.0 + "@nomicfoundation/ethereumjs-common": 4.0.1 + "@nomicfoundation/ethereumjs-rlp": 5.0.1 debug: ^4.3.3 ethereum-cryptography: 0.1.3 - functional-red-black-tree: ^1.0.1 - checksum: fad02ea922fbe25328186ea2eb43bdba63def57822f373ce213be26125ee8d3c90cf3b6f626e6876637cdb842e3c2b788fb8891fcf1aca3fd655e1c0d9a7e936 + ethers: ^5.7.1 + js-sdsl: ^4.1.4 + checksum: 157b503fa3e45a3695ba2eba5b089b56719f7790274edd09c95bb0d223570820127f6a2cbfcb14f2d9d876d1440ea4dccb04a4922fa9e9e34b416fddd6517c20 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-trie@npm:^5.0.0": - version: 5.0.0 - resolution: "@nomicfoundation/ethereumjs-trie@npm:5.0.0" +"@nomicfoundation/ethereumjs-trie@npm:6.0.1": + version: 6.0.1 + resolution: "@nomicfoundation/ethereumjs-trie@npm:6.0.1" dependencies: - "@nomicfoundation/ethereumjs-rlp": ^4.0.0 - "@nomicfoundation/ethereumjs-util": ^8.0.0 + "@nomicfoundation/ethereumjs-rlp": 5.0.1 + "@nomicfoundation/ethereumjs-util": 9.0.1 + "@types/readable-stream": ^2.3.13 ethereum-cryptography: 0.1.3 readable-stream: ^3.6.0 - checksum: 468de7ffe05473f0f05940e74bba01652dd9a4ff155a13e0a5395551e53557afde47d98f496f6323824bccfaeee8de4e22fef9b7f88d3bbd4e97cadc54e2e4f9 + checksum: 7001c3204120fd4baba673b4bb52015594f5ad28311f24574cd16f38c015ef87ed51188d6f46d6362ffb9da589359a9e0f99e6068ef7a2f61cb66213e2f493d7 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-tx@npm:^4.0.0": - version: 4.0.0 - resolution: "@nomicfoundation/ethereumjs-tx@npm:4.0.0" +"@nomicfoundation/ethereumjs-tx@npm:5.0.1": + version: 5.0.1 + resolution: "@nomicfoundation/ethereumjs-tx@npm:5.0.1" dependencies: - "@nomicfoundation/ethereumjs-common": ^3.0.0 - "@nomicfoundation/ethereumjs-rlp": ^4.0.0 - "@nomicfoundation/ethereumjs-util": ^8.0.0 + "@chainsafe/ssz": ^0.9.2 + "@ethersproject/providers": ^5.7.2 + "@nomicfoundation/ethereumjs-common": 4.0.1 + "@nomicfoundation/ethereumjs-rlp": 5.0.1 + "@nomicfoundation/ethereumjs-util": 9.0.1 ethereum-cryptography: 0.1.3 - checksum: d2c0e3384aaa9f3b58232c531a4efd524be257e7257f23c3beed6ec9cf5fba6345cb632b3a464ae0a2aa99fd9e4a2d3e2d5c501593c5466e6ab629f05255791e + checksum: aa3829e4a43f5e10cfd66b87eacb3e737ba98f5e3755a3e6a4ccfbc257dbf10d926838cc3acb8fef8afa3362a023b7fd11b53e6ba53f94bb09c345f083cd29a8 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-util@npm:^8.0.0": - version: 8.0.0 - resolution: "@nomicfoundation/ethereumjs-util@npm:8.0.0" +"@nomicfoundation/ethereumjs-util@npm:9.0.1": + version: 9.0.1 + resolution: "@nomicfoundation/ethereumjs-util@npm:9.0.1" dependencies: - "@nomicfoundation/ethereumjs-rlp": ^4.0.0-beta.2 + "@chainsafe/ssz": ^0.10.0 + "@nomicfoundation/ethereumjs-rlp": 5.0.1 ethereum-cryptography: 0.1.3 - checksum: a39be4c8d3dea4fae1e969b47138d718cac31bf248bb517766a42c97ca5850ca3ddf16c66d8e404fa0a0363fd6898ae2e716d75da2ed4113e610d26026e4cefb + checksum: 5f8a50a25c68c974b717f36ad0a5828b786ce1aaea3c874663c2014593fa387de5ad5c8cea35e94379df306dbd1a58c55b310779fd82197dcb993d5dbd4de7a1 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-vm@npm:^6.0.0": - version: 6.0.0 - resolution: "@nomicfoundation/ethereumjs-vm@npm:6.0.0" - dependencies: - "@nomicfoundation/ethereumjs-block": ^4.0.0 - "@nomicfoundation/ethereumjs-blockchain": ^6.0.0 - "@nomicfoundation/ethereumjs-common": ^3.0.0 - "@nomicfoundation/ethereumjs-evm": ^1.0.0 - "@nomicfoundation/ethereumjs-rlp": ^4.0.0 - "@nomicfoundation/ethereumjs-statemanager": ^1.0.0 - "@nomicfoundation/ethereumjs-trie": ^5.0.0 - "@nomicfoundation/ethereumjs-tx": ^4.0.0 - "@nomicfoundation/ethereumjs-util": ^8.0.0 - "@types/async-eventemitter": ^0.2.1 - async-eventemitter: ^0.2.4 +"@nomicfoundation/ethereumjs-vm@npm:7.0.1": + version: 7.0.1 + resolution: "@nomicfoundation/ethereumjs-vm@npm:7.0.1" + dependencies: + "@nomicfoundation/ethereumjs-block": 5.0.1 + "@nomicfoundation/ethereumjs-blockchain": 7.0.1 + "@nomicfoundation/ethereumjs-common": 4.0.1 + "@nomicfoundation/ethereumjs-evm": 2.0.1 + "@nomicfoundation/ethereumjs-rlp": 5.0.1 + "@nomicfoundation/ethereumjs-statemanager": 2.0.1 + "@nomicfoundation/ethereumjs-trie": 6.0.1 + "@nomicfoundation/ethereumjs-tx": 5.0.1 + "@nomicfoundation/ethereumjs-util": 9.0.1 debug: ^4.3.3 ethereum-cryptography: 0.1.3 - functional-red-black-tree: ^1.0.1 mcl-wasm: ^0.7.1 rustbn.js: ~0.2.0 - checksum: 3c0e10b377579d74bfdcfd056d5545b605f767982e41038d036c8219a50fe3564c7f146fdd04385d64f48f94b9d95c378d7a37955c5100c46c568a29f54ea737 + checksum: 0f637316322744140d6f75d894c21b8055e27a94c72dd8ae9b0b9b93c0d54d7f30fa2aaf909e802e183a3f1020b4aa6a8178dedb823a4ce70a227ac7b432f8c1 languageName: node linkType: hard -"@nomicfoundation/hardhat-chai-matchers@npm:^1.0.3": - version: 1.0.3 - resolution: "@nomicfoundation/hardhat-chai-matchers@npm:1.0.3" +"@nomicfoundation/hardhat-chai-matchers@npm:^1.0.6": + version: 1.0.6 + resolution: "@nomicfoundation/hardhat-chai-matchers@npm:1.0.6" dependencies: "@ethersproject/abi": ^5.1.2 "@types/chai-as-promised": ^7.1.3 chai-as-promised: ^7.1.1 - chalk: ^2.4.2 deep-eql: ^4.0.1 ordinal: ^1.0.3 peerDependencies: @@ -1592,7 +1602,7 @@ __metadata: chai: ^4.2.0 ethers: ^5.0.0 hardhat: ^2.9.4 - checksum: bee445e7ed53d6ee00b85c5c76ca79b7cd1792487481803a29357803b09f92b626b6429a50522a8398c8010ad4fd1f0106c79416dc8c01aea644e7360ea9c203 + checksum: c388e5ed9068f2ba7c227737ab7312dd03405d5fab195247b061f2fa52e700fbd0fb65359c2d4f2086f2905bfca642c19a9122d034533edd936f89aea65ac7f2 languageName: node linkType: hard @@ -1743,13 +1753,13 @@ __metadata: languageName: node linkType: hard -"@nomiclabs/hardhat-ethers@npm:^2.0.3": - version: 2.0.6 - resolution: "@nomiclabs/hardhat-ethers@npm:2.0.6" +"@nomiclabs/hardhat-ethers@npm:^2.2.3": + version: 2.2.3 + resolution: "@nomiclabs/hardhat-ethers@npm:2.2.3" peerDependencies: ethers: ^5.0.0 hardhat: ^2.0.0 - checksum: 82319a2615804abae2ca70834a20bfc2874c742094fadeaea55e5c788e589794836a3a2eaaf19d8aecdadf1fd0bd7a4f9eac2c32bbda9ed081c8e2e3d4fe55cc + checksum: 72321317e55eb510306e04c42353c5f7ceb42d086fc76cc740120da6e1635b7ad5bbf23a8d6b02bd590754adcf646618933111624085ab249b1ff3482e773226 languageName: node linkType: hard @@ -1821,13 +1831,15 @@ __metadata: languageName: node linkType: hard -"@openzeppelin/hardhat-upgrades@npm:^1.21.0": - version: 1.21.0 - resolution: "@openzeppelin/hardhat-upgrades@npm:1.21.0" +"@openzeppelin/hardhat-upgrades@npm:^1.23.0": + version: 1.25.0 + resolution: "@openzeppelin/hardhat-upgrades@npm:1.25.0" dependencies: - "@openzeppelin/upgrades-core": ^1.20.0 + "@openzeppelin/upgrades-core": ^1.26.0 chalk: ^4.1.0 debug: ^4.1.1 + defender-admin-client: ^1.39.0 + platform-deploy-client: ^0.3.2 proper-lockfile: ^4.1.1 peerDependencies: "@nomiclabs/hardhat-ethers": ^2.0.0 @@ -1839,13 +1851,13 @@ __metadata: optional: true bin: migrate-oz-cli-project: dist/scripts/migrate-oz-cli-project.js - checksum: dada5df033bc29d4104a574bd8a28733c56cc86cd0d1ec17a6f074546d0ae589ba4ac417ebdb2ad7685061258cdefc406e672beeb3ec3f8874b5673badb1b0ef + checksum: 1647917371d2a4316287940f12e471200c0d143f01ddf485aacc309531d6597e0377243f5c7463d56167c0eefbc983964c15fd7228b7852cc1e47545b1a78e87 languageName: node linkType: hard -"@openzeppelin/upgrades-core@npm:^1.20.0": - version: 1.20.0 - resolution: "@openzeppelin/upgrades-core@npm:1.20.0" +"@openzeppelin/upgrades-core@npm:^1.26.0": + version: 1.26.0 + resolution: "@openzeppelin/upgrades-core@npm:1.26.0" dependencies: cbor: ^8.0.0 chalk: ^4.1.0 @@ -1854,7 +1866,7 @@ __metadata: ethereumjs-util: ^7.0.3 proper-lockfile: ^4.1.1 solidity-ast: ^0.4.15 - checksum: 682829324dfcd15779f7f99390eb23549bf240715bcf080855e69fa1ea2e87a63b2f190e658793cdedeb3d7baf8c36a53bf35c3cf8eab8afb1c9a28554d8829c + checksum: 7bc90d2157fc887be24f1351ca00904203f28bca138f89ff9f7ac6ac18910feca889f0b59766bd9ff4a8698ec58aec35348c504fd37fe48a4311156b81802d00 languageName: node linkType: hard @@ -2049,13 +2061,6 @@ __metadata: languageName: node linkType: hard -"@types/async-eventemitter@npm:^0.2.1": - version: 0.2.1 - resolution: "@types/async-eventemitter@npm:0.2.1" - checksum: 36ba0a6f52082f76b19b9123a2fa0497f94fe15218fa54040cc45f0edff483ec3be93a38c177cd4dab79f5e32333fbdf3682d4dc94197438e86694b1fddd6896 - languageName: node - linkType: hard - "@types/babel__traverse@npm:^7.0.6": version: 7.17.1 resolution: "@types/babel__traverse@npm:7.17.1" @@ -2261,6 +2266,16 @@ __metadata: languageName: node linkType: hard +"@types/readable-stream@npm:^2.3.13": + version: 2.3.15 + resolution: "@types/readable-stream@npm:2.3.15" + dependencies: + "@types/node": "*" + safe-buffer: ~5.1.1 + checksum: ec36f525cad09b6c65a1dafcb5ad99b9e2ed824ec49b7aa23180ac427e5d35b8a0706193ecd79ab4253a283ad485ba03d5917a98daaaa144f0ea34f4823e9d82 + languageName: node + linkType: hard + "@types/secp256k1@npm:^4.0.1": version: 4.0.3 resolution: "@types/secp256k1@npm:4.0.3" @@ -2557,6 +2572,19 @@ __metadata: languageName: node linkType: hard +"amazon-cognito-identity-js@npm:^6.0.1": + version: 6.2.0 + resolution: "amazon-cognito-identity-js@npm:6.2.0" + dependencies: + "@aws-crypto/sha256-js": 1.2.2 + buffer: 4.9.2 + fast-base64-decode: ^1.0.0 + isomorphic-unfetch: ^3.0.0 + js-cookie: ^2.2.1 + checksum: 9b976ceac2a2648bfa707190d683214168cf1a70083152355b36e5b87b1aedbbb38dab8b71904ee968de25e90ebcf1e86ffaa21d809373d31acd73f62b6c7c1c + languageName: node + linkType: hard + "amdefine@npm:>=0.0.4": version: 1.0.1 resolution: "amdefine@npm:1.0.1" @@ -2834,12 +2862,12 @@ __metadata: languageName: node linkType: hard -"async-eventemitter@npm:^0.2.4": - version: 0.2.4 - resolution: "async-eventemitter@npm:0.2.4" +"async-retry@npm:^1.3.3": + version: 1.3.3 + resolution: "async-retry@npm:1.3.3" dependencies: - async: ^2.4.0 - checksum: b9e77e0f58ebd7188c50c23d613d1263e0ab501f5e677e02b57cc97d7032beaf60aafa189887e7105569c791e212df4af00b608be1e9a4c425911d577124911e + retry: 0.13.1 + checksum: 38a7152ff7265a9321ea214b9c69e8224ab1febbdec98efbbde6e562f17ff68405569b796b1c5271f354aef8783665d29953f051f68c1fc45306e61aec82fdc4 languageName: node linkType: hard @@ -2850,15 +2878,6 @@ __metadata: languageName: node linkType: hard -"async@npm:^2.4.0": - version: 2.6.4 - resolution: "async@npm:2.6.4" - dependencies: - lodash: ^4.17.14 - checksum: a52083fb32e1ebe1d63e5c5624038bb30be68ff07a6c8d7dfe35e47c93fc144bd8652cbec869e0ac07d57dde387aa5f1386be3559cdee799cb1f789678d88e19 - languageName: node - linkType: hard - "asynckit@npm:^0.4.0": version: 0.4.0 resolution: "asynckit@npm:0.4.0" @@ -2887,6 +2906,15 @@ __metadata: languageName: node linkType: hard +"axios@npm:^0.21.2": + version: 0.21.4 + resolution: "axios@npm:0.21.4" + dependencies: + follow-redirects: ^1.14.0 + checksum: 44245f24ac971e7458f3120c92f9d66d1fc695e8b97019139de5b0cc65d9b8104647db01e5f46917728edfc0cfd88eb30fc4c55e6053eef4ace76768ce95ff3c + languageName: node + linkType: hard + "axios@npm:^0.24.0": version: 0.24.0 resolution: "axios@npm:0.24.0" @@ -2947,7 +2975,7 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.3.1": +"base64-js@npm:^1.0.2, base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 @@ -3160,6 +3188,17 @@ __metadata: languageName: node linkType: hard +"buffer@npm:4.9.2": + version: 4.9.2 + resolution: "buffer@npm:4.9.2" + dependencies: + base64-js: ^1.0.2 + ieee754: ^1.1.4 + isarray: ^1.0.0 + checksum: 8801bc1ba08539f3be70eee307a8b9db3d40f6afbfd3cf623ab7ef41dffff1d0a31de0addbe1e66e0ca5f7193eeb667bfb1ecad3647f8f1b0750de07c13295c3 + languageName: node + linkType: hard + "buffer@npm:^6.0.3": version: 6.0.3 resolution: "buffer@npm:6.0.3" @@ -3177,6 +3216,15 @@ __metadata: languageName: node linkType: hard +"busboy@npm:^1.6.0": + version: 1.6.0 + resolution: "busboy@npm:1.6.0" + dependencies: + streamsearch: ^1.1.0 + checksum: 32801e2c0164e12106bf236291a00795c3c4e4b709ae02132883fe8478ba2ae23743b11c5735a0aae8afe65ac4b6ca4568b91f0d9fed1fdbc32ede824a73746e + languageName: node + linkType: hard + "bytes@npm:3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" @@ -3280,6 +3328,13 @@ __metadata: languageName: node linkType: hard +"case@npm:^1.6.3": + version: 1.6.3 + resolution: "case@npm:1.6.3" + checksum: febe73278f910b0d28aab7efd6f51c235f9aa9e296148edb56dfb83fd58faa88308c30ce9a0122b6e53e0362c44f4407105bd5ef89c46860fc2b184e540fd68d + languageName: node + linkType: hard + "caseless@npm:^0.12.0, caseless@npm:~0.12.0": version: 0.12.0 resolution: "caseless@npm:0.12.0" @@ -3902,6 +3957,32 @@ __metadata: languageName: node linkType: hard +"defender-admin-client@npm:^1.39.0": + version: 1.43.0 + resolution: "defender-admin-client@npm:1.43.0" + dependencies: + axios: ^0.21.2 + defender-base-client: 1.43.0 + ethers: ^5.7.2 + lodash: ^4.17.19 + node-fetch: ^2.6.0 + checksum: 489e1f9822d5f36d04b2668eee411f7b46747a88128e4184ac4b6c9fd2fc6caacfac3541429ffa086b3639c3aeb4fc5b87f53bc0e151790c27de5db5804dca61 + languageName: node + linkType: hard + +"defender-base-client@npm:1.43.0, defender-base-client@npm:^1.40.0": + version: 1.43.0 + resolution: "defender-base-client@npm:1.43.0" + dependencies: + amazon-cognito-identity-js: ^6.0.1 + async-retry: ^1.3.3 + axios: ^0.21.2 + lodash: ^4.17.19 + node-fetch: ^2.6.0 + checksum: e0862837112fd8e00252e11dc32b7f7afa1e25b48ca0ad0cd8b2d443024fa3bf17b683bc0922e8464251b7919378925ce36e8227f7355a7978ef303db7b2ba77 + languageName: node + linkType: hard + "define-properties@npm:^1.1.2, define-properties@npm:^1.1.3, define-properties@npm:^1.1.4": version: 1.1.4 resolution: "define-properties@npm:1.1.4" @@ -4736,41 +4817,41 @@ __metadata: languageName: node linkType: hard -"ethers@npm:^5.5.2": - version: 5.6.9 - resolution: "ethers@npm:5.6.9" - dependencies: - "@ethersproject/abi": 5.6.4 - "@ethersproject/abstract-provider": 5.6.1 - "@ethersproject/abstract-signer": 5.6.2 - "@ethersproject/address": 5.6.1 - "@ethersproject/base64": 5.6.1 - "@ethersproject/basex": 5.6.1 - "@ethersproject/bignumber": 5.6.2 - "@ethersproject/bytes": 5.6.1 - "@ethersproject/constants": 5.6.1 - "@ethersproject/contracts": 5.6.2 - "@ethersproject/hash": 5.6.1 - "@ethersproject/hdnode": 5.6.2 - "@ethersproject/json-wallets": 5.6.1 - "@ethersproject/keccak256": 5.6.1 - "@ethersproject/logger": 5.6.0 - "@ethersproject/networks": 5.6.4 - "@ethersproject/pbkdf2": 5.6.1 - "@ethersproject/properties": 5.6.0 - "@ethersproject/providers": 5.6.8 - "@ethersproject/random": 5.6.1 - "@ethersproject/rlp": 5.6.1 - "@ethersproject/sha2": 5.6.1 - "@ethersproject/signing-key": 5.6.2 - "@ethersproject/solidity": 5.6.1 - "@ethersproject/strings": 5.6.1 - "@ethersproject/transactions": 5.6.2 - "@ethersproject/units": 5.6.1 - "@ethersproject/wallet": 5.6.2 - "@ethersproject/web": 5.6.1 - "@ethersproject/wordlists": 5.6.1 - checksum: e4a029ad55da2355cb7b0ff178b38b0df27f9013604b0600c246dba297223ac2ce8ef0380758fa535cd82ea46bceb4a71aeb29949e1693f3a9c60d4cdaceb208 +"ethers@npm:^5.7.1, ethers@npm:^5.7.2": + version: 5.7.2 + resolution: "ethers@npm:5.7.2" + dependencies: + "@ethersproject/abi": 5.7.0 + "@ethersproject/abstract-provider": 5.7.0 + "@ethersproject/abstract-signer": 5.7.0 + "@ethersproject/address": 5.7.0 + "@ethersproject/base64": 5.7.0 + "@ethersproject/basex": 5.7.0 + "@ethersproject/bignumber": 5.7.0 + "@ethersproject/bytes": 5.7.0 + "@ethersproject/constants": 5.7.0 + "@ethersproject/contracts": 5.7.0 + "@ethersproject/hash": 5.7.0 + "@ethersproject/hdnode": 5.7.0 + "@ethersproject/json-wallets": 5.7.0 + "@ethersproject/keccak256": 5.7.0 + "@ethersproject/logger": 5.7.0 + "@ethersproject/networks": 5.7.1 + "@ethersproject/pbkdf2": 5.7.0 + "@ethersproject/properties": 5.7.0 + "@ethersproject/providers": 5.7.2 + "@ethersproject/random": 5.7.0 + "@ethersproject/rlp": 5.7.0 + "@ethersproject/sha2": 5.7.0 + "@ethersproject/signing-key": 5.7.0 + "@ethersproject/solidity": 5.7.0 + "@ethersproject/strings": 5.7.0 + "@ethersproject/transactions": 5.7.0 + "@ethersproject/units": 5.7.0 + "@ethersproject/wallet": 5.7.0 + "@ethersproject/web": 5.7.1 + "@ethersproject/wordlists": 5.7.0 + checksum: b7c08cf3e257185a7946117dbbf764433b7ba0e77c27298dec6088b3bc871aff711462b0621930c56880ff0a7ceb8b1d3a361ffa259f93377b48e34107f62553 languageName: node linkType: hard @@ -4864,6 +4945,13 @@ __metadata: languageName: node linkType: hard +"fast-base64-decode@npm:^1.0.0": + version: 1.0.0 + resolution: "fast-base64-decode@npm:1.0.0" + checksum: 4c59eb1775a7f132333f296c5082476fdcc8f58d023c42ed6d378d2e2da4c328c7a71562f271181a725dd17cdaa8f2805346cc330cdbad3b8e4b9751508bd0a3 + languageName: node + linkType: hard + "fast-check@npm:^2.24.0": version: 2.25.0 resolution: "fast-check@npm:2.25.0" @@ -5088,6 +5176,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.14.0": + version: 1.15.2 + resolution: "follow-redirects@npm:1.15.2" + peerDependenciesMeta: + debug: + optional: true + checksum: faa66059b66358ba65c234c2f2a37fcec029dc22775f35d9ad6abac56003268baf41e55f9ee645957b32c7d9f62baf1f0b906e68267276f54ec4b4c597c2b190 + languageName: node + linkType: hard + "forever-agent@npm:~0.6.1": version: 0.6.1 resolution: "forever-agent@npm:0.6.1" @@ -5634,22 +5732,22 @@ fsevents@~2.1.1: languageName: node linkType: hard -"hardhat@npm:^2.12.2": - version: 2.12.2 - resolution: "hardhat@npm:2.12.2" +"hardhat@npm:^2.12.3": + version: 2.14.0 + resolution: "hardhat@npm:2.14.0" dependencies: "@ethersproject/abi": ^5.1.2 "@metamask/eth-sig-util": ^4.0.0 - "@nomicfoundation/ethereumjs-block": ^4.0.0 - "@nomicfoundation/ethereumjs-blockchain": ^6.0.0 - "@nomicfoundation/ethereumjs-common": ^3.0.0 - "@nomicfoundation/ethereumjs-evm": ^1.0.0 - "@nomicfoundation/ethereumjs-rlp": ^4.0.0 - "@nomicfoundation/ethereumjs-statemanager": ^1.0.0 - "@nomicfoundation/ethereumjs-trie": ^5.0.0 - "@nomicfoundation/ethereumjs-tx": ^4.0.0 - "@nomicfoundation/ethereumjs-util": ^8.0.0 - "@nomicfoundation/ethereumjs-vm": ^6.0.0 + "@nomicfoundation/ethereumjs-block": 5.0.1 + "@nomicfoundation/ethereumjs-blockchain": 7.0.1 + "@nomicfoundation/ethereumjs-common": 4.0.1 + "@nomicfoundation/ethereumjs-evm": 2.0.1 + "@nomicfoundation/ethereumjs-rlp": 5.0.1 + "@nomicfoundation/ethereumjs-statemanager": 2.0.1 + "@nomicfoundation/ethereumjs-trie": 6.0.1 + "@nomicfoundation/ethereumjs-tx": 5.0.1 + "@nomicfoundation/ethereumjs-util": 9.0.1 + "@nomicfoundation/ethereumjs-vm": 7.0.1 "@nomicfoundation/solidity-analyzer": ^0.1.0 "@sentry/node": ^5.18.1 "@types/bn.js": ^5.1.0 @@ -5685,7 +5783,7 @@ fsevents@~2.1.1: source-map-support: ^0.5.13 stacktrace-parser: ^0.1.10 tsort: 0.0.1 - undici: ^5.4.0 + undici: ^5.14.0 uuid: ^8.3.2 ws: ^7.4.6 peerDependencies: @@ -5697,8 +5795,8 @@ fsevents@~2.1.1: typescript: optional: true bin: - hardhat: internal/cli/cli.js - checksum: cd45bf9d4f15d967bd2d8154cc5bc60e7d0d4bab80caea909d36e3660437fc9f6b4faf41736ed3b75b11ff15ac9a9b68828eebb1026ed9fa8d9eb87242f061d2 + hardhat: internal/cli/bootstrap.js + checksum: 7a11ad4650759851306d65c30252ccffa2aca9cb461c66f3fcef5f29d38fe66402d6bc295d293670fa0a72bf3572cc95029c5cd0b0fd45f45edd99d6eb5b7586 languageName: node linkType: hard @@ -5938,7 +6036,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"ieee754@npm:^1.2.1": +"ieee754@npm:^1.1.4, ieee754@npm:^1.2.1": version: 1.2.1 resolution: "ieee754@npm:1.2.1" checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e @@ -6294,7 +6392,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"isarray@npm:~1.0.0": +"isarray@npm:^1.0.0, isarray@npm:~1.0.0": version: 1.0.0 resolution: "isarray@npm:1.0.0" checksum: f032df8e02dce8ec565cf2eb605ea939bdccea528dbcf565cdf92bfa2da9110461159d86a537388ef1acef8815a330642d7885b29010e8f7eac967c9993b65ab @@ -6308,6 +6406,16 @@ fsevents@~2.1.1: languageName: node linkType: hard +"isomorphic-unfetch@npm:^3.0.0": + version: 3.1.0 + resolution: "isomorphic-unfetch@npm:3.1.0" + dependencies: + node-fetch: ^2.6.1 + unfetch: ^4.2.0 + checksum: 82b92fe4ec2823a81ab0fc0d11bd94d710e6f9a940d56b3cba31896d4345ec9ffc7949f4ff31ebcae84f6b95f7ebf3474c4c7452b834eb4078ea3f2c37e459c5 + languageName: node + linkType: hard + "isstream@npm:~0.1.2": version: 0.1.2 resolution: "isstream@npm:0.1.2" @@ -6469,6 +6577,20 @@ fsevents@~2.1.1: languageName: node linkType: hard +"js-cookie@npm:^2.2.1": + version: 2.2.1 + resolution: "js-cookie@npm:2.2.1" + checksum: 9b1fb980a1c5e624fd4b28ea4867bb30c71e04c4484bb3a42766344c533faa684de9498e443425479ec68609e96e27b60614bfe354877c449c631529b6d932f2 + languageName: node + linkType: hard + +"js-sdsl@npm:^4.1.4": + version: 4.4.0 + resolution: "js-sdsl@npm:4.4.0" + checksum: 7bb08a2d746ab7ff742720339aa006c631afe05e77d11eda988c1c35fae8e03e492e4e347e883e786e3ce6170685d4780c125619111f0730c11fdb41b04059c7 + languageName: node + linkType: hard + "js-sha3@npm:0.5.7": version: 0.5.7 resolution: "js-sha3@npm:0.5.7" @@ -7407,6 +7529,20 @@ fsevents@~2.1.1: languageName: node linkType: hard +"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1": + version: 2.6.11 + resolution: "node-fetch@npm:2.6.11" + dependencies: + whatwg-url: ^5.0.0 + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 249d0666a9497553384d46b5ab296ba223521ac88fed4d8a17d6ee6c2efb0fc890f3e8091cafe7f9fba8151a5b8d925db2671543b3409a56c3cd522b468b47b3 + languageName: node + linkType: hard + "node-gyp-build@npm:^4.2.0, node-gyp-build@npm:^4.3.0": version: 4.5.0 resolution: "node-gyp-build@npm:4.5.0" @@ -7882,6 +8018,19 @@ fsevents@~2.1.1: languageName: node linkType: hard +"platform-deploy-client@npm:^0.3.2": + version: 0.3.3 + resolution: "platform-deploy-client@npm:0.3.3" + dependencies: + "@ethersproject/abi": ^5.6.3 + axios: ^0.21.2 + defender-base-client: ^1.40.0 + lodash: ^4.17.19 + node-fetch: ^2.6.0 + checksum: 2e3385497e009e74633eac9755d492d1372ed4cf54141949959812c6c3c793bab2dbdb494524c520d3886d201107c4227f0aa10300fa5db2914e3ea1425b3121 + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -8268,14 +8417,14 @@ fsevents@~2.1.1: "@aave/protocol-v2": ^1.0.1 "@chainlink/contracts": ^0.5.1 "@ethersproject/providers": ^5.7.2 - "@nomicfoundation/hardhat-chai-matchers": ^1.0.3 + "@nomicfoundation/hardhat-chai-matchers": ^1.0.6 "@nomicfoundation/hardhat-network-helpers": ^1.0.8 "@nomicfoundation/hardhat-toolbox": ^2.0.1 - "@nomiclabs/hardhat-ethers": ^2.0.3 + "@nomiclabs/hardhat-ethers": ^2.2.3 "@nomiclabs/hardhat-etherscan": ^3.1.0 "@openzeppelin/contracts": ~4.7.3 "@openzeppelin/contracts-upgradeable": ~4.7.3 - "@openzeppelin/hardhat-upgrades": ^1.21.0 + "@openzeppelin/hardhat-upgrades": ^1.23.0 "@typechain/ethers-v5": ^7.2.0 "@typechain/hardhat": ^2.3.1 "@types/big.js": ^6.1.3 @@ -8299,11 +8448,11 @@ fsevents@~2.1.1: eslint-plugin-prettier: ^4.0.0 eslint-plugin-promise: ^6.0.0 eth-permit: ^0.2.1 - ethers: ^5.5.2 + ethers: ^5.7.2 fast-check: ^2.24.0 graphql: ^16.6.0 graphql-request: ^5.2.0 - hardhat: ^2.12.2 + hardhat: ^2.12.3 hardhat-contract-sizer: ^2.4.0 hardhat-gas-reporter: ^1.0.8 hardhat-storage-layout: ^0.1.7 @@ -8413,6 +8562,13 @@ resolve@1.17.0: languageName: node linkType: hard +"retry@npm:0.13.1": + version: 0.13.1 + resolution: "retry@npm:0.13.1" + checksum: 47c4d5be674f7c13eee4cfe927345023972197dbbdfba5d3af7e461d13b44de1bfd663bfc80d2f601f8ef3fc8164c16dd99655a221921954a65d044a2fc1233b + languageName: node + linkType: hard + "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -9008,6 +9164,13 @@ resolve@1.17.0: languageName: node linkType: hard +"streamsearch@npm:^1.1.0": + version: 1.1.0 + resolution: "streamsearch@npm:1.1.0" + checksum: 1cce16cea8405d7a233d32ca5e00a00169cc0e19fbc02aa839959985f267335d435c07f96e5e0edd0eadc6d39c98d5435fb5bbbdefc62c41834eadc5622ad942 + languageName: node + linkType: hard + "string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" @@ -9446,13 +9609,20 @@ resolve@1.17.0: languageName: node linkType: hard -"tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": +"tslib@npm:^1.11.1, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd languageName: node linkType: hard +"tslib@npm:^2.3.1, tslib@npm:^2.5.0": + version: 2.5.0 + resolution: "tslib@npm:2.5.0" + checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1 + languageName: node + linkType: hard + "tsort@npm:0.0.1": version: 0.0.1 resolution: "tsort@npm:0.0.1" @@ -9624,6 +9794,15 @@ typescript@^4.4.2: languageName: node linkType: hard +"undici@npm:^5.14.0": + version: 5.22.0 + resolution: "undici@npm:5.22.0" + dependencies: + busboy: ^1.6.0 + checksum: 8dc55240a60ae7680798df344e8f46ad0f872ed0fa434fb94cc4fd2b5b2f8053bdf11994d15902999d3880f9bf7cd875a2e90883d2702bf0f366dacd9cbf3fc6 + languageName: node + linkType: hard + "undici@npm:^5.4.0": version: 5.6.0 resolution: "undici@npm:5.6.0" @@ -9631,6 +9810,13 @@ typescript@^4.4.2: languageName: node linkType: hard +"unfetch@npm:^4.2.0": + version: 4.2.0 + resolution: "unfetch@npm:4.2.0" + checksum: 6a4b2557e1d921eaa80c4425ce27a404945ec26491ed06e62598f333996a91a44c7908cb26dc7c2746d735762b13276cf4aa41829b4c8f438dde63add3045d7a + languageName: node + linkType: hard + "unique-filename@npm:^1.1.1": version: 1.1.1 resolution: "unique-filename@npm:1.1.1" From 38a16077ef3be72e6046da325e2a0f042ef75a6b Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 11 May 2023 10:23:02 -0300 Subject: [PATCH 226/462] store erc20s in deployment file --- scripts/deployment/common.ts | 1 + .../phase2-assets/0_setup_deployments.ts | 1 + .../phase2-assets/1_deploy_assets.ts | 2 ++ .../phase2-assets/2_deploy_collateral.ts | 30 +++++++++++++++---- .../phase2-assets/assets/deploy_crv.ts | 1 + .../phase2-assets/assets/deploy_cvx.ts | 1 + .../deploy_convex_rToken_metapool_plugin.ts | 1 + .../deploy_convex_stable_metapool_plugin.ts | 1 + .../deploy_convex_stable_plugin.ts | 1 + .../deploy_convex_volatile_plugin.ts | 1 + .../deploy_ctokenv3_usdc_collateral.ts | 1 + .../deploy_flux_finance_collateral.ts | 13 ++++---- .../deploy_lido_wsteth_collateral.ts | 1 + .../deploy_rocket_pool_reth_collateral.ts | 1 + 14 files changed, 46 insertions(+), 10 deletions(-) diff --git a/scripts/deployment/common.ts b/scripts/deployment/common.ts index 69f17cd408..c0a0145cae 100644 --- a/scripts/deployment/common.ts +++ b/scripts/deployment/common.ts @@ -27,6 +27,7 @@ export interface IDeployments { export interface IAssetCollDeployments { assets: ITokens collateral: ITokens & IPlugins + erc20s: ITokens & IPlugins } export interface IRTokenDeployments { diff --git a/scripts/deployment/phase2-assets/0_setup_deployments.ts b/scripts/deployment/phase2-assets/0_setup_deployments.ts index 24417421c7..1000201c08 100644 --- a/scripts/deployment/phase2-assets/0_setup_deployments.ts +++ b/scripts/deployment/phase2-assets/0_setup_deployments.ts @@ -37,6 +37,7 @@ async function main() { const deployments: IAssetCollDeployments = { assets: {}, collateral: {}, + erc20s: {}, } fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) diff --git a/scripts/deployment/phase2-assets/1_deploy_assets.ts b/scripts/deployment/phase2-assets/1_deploy_assets.ts index d45e32538f..67d0c52b76 100644 --- a/scripts/deployment/phase2-assets/1_deploy_assets.ts +++ b/scripts/deployment/phase2-assets/1_deploy_assets.ts @@ -48,6 +48,7 @@ async function main() { await (await ethers.getContractAt('Asset', stkAAVEAsset)).refresh() assetCollDeployments.assets.stkAAVE = stkAAVEAsset + assetCollDeployments.erc20s.stkAAVE = networkConfig[chainId].tokens.stkAAVE deployedAssets.push(stkAAVEAsset.toString()) /******** Deploy Comp Asset **************************/ @@ -62,6 +63,7 @@ async function main() { await (await ethers.getContractAt('Asset', compAsset)).refresh() assetCollDeployments.assets.COMP = compAsset + assetCollDeployments.erc20s.COMP = networkConfig[chainId].tokens.COMP deployedAssets.push(compAsset.toString()) /**************************************************************/ diff --git a/scripts/deployment/phase2-assets/2_deploy_collateral.ts b/scripts/deployment/phase2-assets/2_deploy_collateral.ts index d6265e788c..d7655f8897 100644 --- a/scripts/deployment/phase2-assets/2_deploy_collateral.ts +++ b/scripts/deployment/phase2-assets/2_deploy_collateral.ts @@ -59,6 +59,7 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) assetCollDeployments.collateral.DAI = daiCollateral + assetCollDeployments.erc20s.DAI = networkConfig[chainId].tokens.DAI deployedCollateral.push(daiCollateral.toString()) /******** Deploy Fiat Collateral - USDC **************************/ @@ -80,6 +81,7 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) assetCollDeployments.collateral.USDC = usdcCollateral + assetCollDeployments.erc20s.USDC = networkConfig[chainId].tokens.USDC deployedCollateral.push(usdcCollateral.toString()) /******** Deploy Fiat Collateral - USDT **************************/ @@ -101,6 +103,7 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) assetCollDeployments.collateral.USDT = usdtCollateral + assetCollDeployments.erc20s.USDT = networkConfig[chainId].tokens.USDT deployedCollateral.push(usdtCollateral.toString()) /******** Deploy Fiat Collateral - USDP **************************/ @@ -122,6 +125,7 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) assetCollDeployments.collateral.USDP = usdpCollateral + assetCollDeployments.erc20s.USDP = networkConfig[chainId].tokens.USDP deployedCollateral.push(usdpCollateral.toString()) /******** Deploy Fiat Collateral - TUSD **************************/ @@ -141,6 +145,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.TUSD = tusdCollateral + assetCollDeployments.erc20s.TUSD = networkConfig[chainId].tokens.TUSD deployedCollateral.push(tusdCollateral.toString()) /******** Deploy Fiat Collateral - BUSD **************************/ @@ -162,6 +167,7 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) assetCollDeployments.collateral.BUSD = busdCollateral + assetCollDeployments.erc20s.BUSD = networkConfig[chainId].tokens.BUSD deployedCollateral.push(busdCollateral.toString()) /******** Deploy AToken Fiat Collateral - aDAI **************************/ @@ -205,6 +211,7 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) assetCollDeployments.collateral.aDAI = aDaiCollateral + assetCollDeployments.erc20s.aDAI = adaiStaticToken.address deployedCollateral.push(aDaiCollateral.toString()) /******** Deploy AToken Fiat Collateral - aUSDC **************************/ @@ -246,6 +253,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.aUSDC = aUsdcCollateral + assetCollDeployments.erc20s.aUSDC = ausdcStaticToken.address deployedCollateral.push(aUsdcCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -289,6 +297,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.aUSDT = aUsdtCollateral + assetCollDeployments.erc20s.aUSDT = ausdtStaticToken.address deployedCollateral.push(aUsdtCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -331,6 +340,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.aBUSD = aBusdCollateral + assetCollDeployments.erc20s.aBUSD = abusdStaticToken.address deployedCollateral.push(aBusdCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -374,6 +384,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.aUSDP = aUsdpCollateral + assetCollDeployments.erc20s.aUSDP = ausdpStaticToken.address deployedCollateral.push(aUsdpCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -399,13 +410,14 @@ async function main() { targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString() + revenueHiding: revenueHiding.toString(), }) collateral = await ethers.getContractAt('ICollateral', cDaiCollateral) await (await collateral.refresh()).wait() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.cDAI = cDaiCollateral + assetCollDeployments.erc20s.cDAI = cDaiVault.address deployedCollateral.push(cDaiCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -430,13 +442,14 @@ async function main() { targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString() + revenueHiding: revenueHiding.toString(), }) collateral = await ethers.getContractAt('ICollateral', cUsdcCollateral) await (await collateral.refresh()).wait() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.cUSDC = cUsdcCollateral + assetCollDeployments.erc20s.cUSDC = cUsdcVault.address deployedCollateral.push(cUsdcCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -461,13 +474,14 @@ async function main() { targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString() + revenueHiding: revenueHiding.toString(), }) collateral = await ethers.getContractAt('ICollateral', cUsdtCollateral) await (await collateral.refresh()).wait() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.cUSDT = cUsdtCollateral + assetCollDeployments.erc20s.cUSDT = cUsdtVault.address deployedCollateral.push(cUsdtCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -492,13 +506,14 @@ async function main() { targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.02').toString(), // 2% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString() + revenueHiding: revenueHiding.toString(), }) collateral = await ethers.getContractAt('ICollateral', cUsdpCollateral) await (await collateral.refresh()).wait() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.cUSDP = cUsdpCollateral + assetCollDeployments.erc20s.cUSDP = cUsdpVault.address deployedCollateral.push(cUsdpCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -529,13 +544,14 @@ async function main() { targetName: hre.ethers.utils.formatBytes32String('BTC'), defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString() + revenueHiding: revenueHiding.toString(), }) collateral = await ethers.getContractAt('ICollateral', cWBTCCollateral) await (await collateral.refresh()).wait() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.cWBTC = cWBTCCollateral + assetCollDeployments.erc20s.cWBTC = cWBTCVault.address deployedCollateral.push(cWBTCCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -566,6 +582,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.cETH = cETHCollateral + assetCollDeployments.erc20s.cETH = cETHVault.address deployedCollateral.push(cETHCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -589,6 +606,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.WBTC = wBTCCollateral + assetCollDeployments.erc20s.WBTC = networkConfig[chainId].tokens.WBTC deployedCollateral.push(wBTCCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -609,6 +627,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.WETH = wETHCollateral + assetCollDeployments.erc20s.WETH = networkConfig[chainId].tokens.WETH deployedCollateral.push(wETHCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -634,6 +653,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.EURT = eurtCollateral + assetCollDeployments.erc20s.EURT = networkConfig[chainId].tokens.EURT deployedCollateral.push(eurtCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) diff --git a/scripts/deployment/phase2-assets/assets/deploy_crv.ts b/scripts/deployment/phase2-assets/assets/deploy_crv.ts index a5bd2c1241..f0db202b9b 100644 --- a/scripts/deployment/phase2-assets/assets/deploy_crv.ts +++ b/scripts/deployment/phase2-assets/assets/deploy_crv.ts @@ -48,6 +48,7 @@ async function main() { await (await ethers.getContractAt('Asset', crvAsset)).refresh() assetCollDeployments.assets.CRV = crvAsset + assetCollDeployments.erc20s.CRV = networkConfig[chainId].tokens.CRV deployedAssets.push(crvAsset.toString()) /**************************************************************/ diff --git a/scripts/deployment/phase2-assets/assets/deploy_cvx.ts b/scripts/deployment/phase2-assets/assets/deploy_cvx.ts index 2870cb2430..1c5aaa57ce 100644 --- a/scripts/deployment/phase2-assets/assets/deploy_cvx.ts +++ b/scripts/deployment/phase2-assets/assets/deploy_cvx.ts @@ -48,6 +48,7 @@ async function main() { await (await ethers.getContractAt('Asset', cvxAsset)).refresh() assetCollDeployments.assets.CVX = cvxAsset + assetCollDeployments.erc20s.CVX = networkConfig[chainId].tokens.CVX deployedAssets.push(cvxAsset.toString()) /**************************************************************/ diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts index 73750b7fcc..948ed79212 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts @@ -114,6 +114,7 @@ async function main() { ) assetCollDeployments.collateral.cvxeUSDFRAXBP = collateral.address + assetCollDeployments.erc20s.cvxeUSDFRAXBP = wPool.address deployedCollateral.push(collateral.address.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts index e32489c304..cb4b6fd25d 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts @@ -121,6 +121,7 @@ async function main() { ) assetCollDeployments.collateral.cvxMIM3Pool = collateral.address + assetCollDeployments.erc20s.cvxMIM3Pool = wPool.address deployedCollateral.push(collateral.address.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts index 14c7a2ddef..d562c90deb 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts @@ -111,6 +111,7 @@ async function main() { ) assetCollDeployments.collateral.cvx3Pool = collateral.address + assetCollDeployments.erc20s.cvx3Pool = w3Pool.address deployedCollateral.push(collateral.address.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts index e32b6df73a..e026042015 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts @@ -120,6 +120,7 @@ async function main() { ) assetCollDeployments.collateral.cvxTriCrypto = collateral.address + assetCollDeployments.erc20s.cvxTriCrypto = w3Pool.address deployedCollateral.push(collateral.address.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts index 07f502b4d3..652b41c1e6 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts @@ -75,6 +75,7 @@ async function main() { console.log(`Deployed CompoundV3 USDC to ${hre.network.name} (${chainId}): ${collateral.address}`) assetCollDeployments.collateral.cUSDCv3 = collateral.address + assetCollDeployments.erc20s.cUSDCv3 = erc20.address deployedCollateral.push(collateral.address.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts index ae52a67fe6..683ff1d672 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts @@ -14,7 +14,6 @@ import { } from '../../common' import { priceTimeout, oracleTimeout, revenueHiding } from '../../utils' import { ICollateral } from '../../../../typechain' -import { ZERO_ADDRESS } from '#/tasks/deployment/create-deployer-registry' async function main() { // ==== Read Configuration ==== @@ -62,13 +61,14 @@ async function main() { targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString() + revenueHiding: revenueHiding.toString(), }) let collateral = await ethers.getContractAt('ICollateral', fUsdcCollateral) await (await collateral.refresh()).wait() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.fUSDC = fUsdcCollateral + assetCollDeployments.erc20s.fUSDC = fUsdcVault.address deployedCollateral.push(fUsdcCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -93,13 +93,14 @@ async function main() { targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString() + revenueHiding: revenueHiding.toString(), }) collateral = await ethers.getContractAt('ICollateral', fUsdtCollateral) await (await collateral.refresh()).wait() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.fUSDT = fUsdtCollateral + assetCollDeployments.erc20s.fUSDT = fUsdtVault.address deployedCollateral.push(fUsdtCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -124,13 +125,14 @@ async function main() { targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString() + revenueHiding: revenueHiding.toString(), }) collateral = await ethers.getContractAt('ICollateral', fDaiCollateral) await collateral.refresh() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.fDAI = fDaiCollateral + assetCollDeployments.erc20s.fDAI = fDaiVault.address deployedCollateral.push(fDaiCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -155,13 +157,14 @@ async function main() { targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.02').toString(), // 2% delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString() + revenueHiding: revenueHiding.toString(), }) collateral = await ethers.getContractAt('ICollateral', fFRAXCollateral) await collateral.refresh() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.fFRAX = fFRAXCollateral + assetCollDeployments.erc20s.fFRAX = fFraxVault.address deployedCollateral.push(fFRAXCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts index c72084395f..4f85427eb4 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts @@ -71,6 +71,7 @@ async function main() { console.log(`Deployed Lido wStETH to ${hre.network.name} (${chainId}): ${collateral.address}`) assetCollDeployments.collateral.wstETH = collateral.address + assetCollDeployments.erc20s.wstETH = networkConfig[chainId].tokens.wstETH deployedCollateral.push(collateral.address.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts index 35a9b07d73..cc8a2e4cd7 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts @@ -69,6 +69,7 @@ async function main() { console.log(`Deployed Rocketpool rETH to ${hre.network.name} (${chainId}): ${collateral.address}`) assetCollDeployments.collateral.rETH = collateral.address + assetCollDeployments.erc20s.rETH = networkConfig[chainId].tokens.rETH deployedCollateral.push(collateral.address.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) From 5ee6c1ce1cb48067e281dd003501943ddaccf7b1 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 11 May 2023 11:01:29 -0300 Subject: [PATCH 227/462] improve logging --- .../phase2-assets/2_deploy_collateral.ts | 32 +++++++++++++++++++ .../deploy_convex_rToken_metapool_plugin.ts | 4 +++ .../deploy_convex_stable_metapool_plugin.ts | 6 +++- .../deploy_convex_stable_plugin.ts | 4 +++ .../deploy_convex_volatile_plugin.ts | 4 +++ .../deploy_ctokenv3_usdc_collateral.ts | 2 ++ .../deploy_flux_finance_collateral.ts | 22 +++++++++++++ 7 files changed, 73 insertions(+), 1 deletion(-) diff --git a/scripts/deployment/phase2-assets/2_deploy_collateral.ts b/scripts/deployment/phase2-assets/2_deploy_collateral.ts index d7655f8897..c121fbf2a6 100644 --- a/scripts/deployment/phase2-assets/2_deploy_collateral.ts +++ b/scripts/deployment/phase2-assets/2_deploy_collateral.ts @@ -400,6 +400,10 @@ async function main() { networkConfig[chainId].COMPTROLLER! ) + await cDaiVault.deployed() + + console.log(`Deployed Vault for cDAI on ${hre.network.name} (${chainId}): ${cDaiVault.address} `) + const { collateral: cDaiCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.DAI, @@ -432,6 +436,12 @@ async function main() { networkConfig[chainId].COMPTROLLER! ) + await cUsdcVault.deployed() + + console.log( + `Deployed Vault for cUSDC on ${hre.network.name} (${chainId}): ${cUsdcVault.address} ` + ) + const { collateral: cUsdcCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, @@ -464,6 +474,12 @@ async function main() { networkConfig[chainId].COMPTROLLER! ) + await cUsdtVault.deployed() + + console.log( + `Deployed Vault for cUSDT on ${hre.network.name} (${chainId}): ${cUsdtVault.address} ` + ) + const { collateral: cUsdtCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDT, @@ -496,6 +512,12 @@ async function main() { networkConfig[chainId].COMPTROLLER! ) + await cUsdpVault.deployed() + + console.log( + `Deployed Vault for cUSDP on ${hre.network.name} (${chainId}): ${cUsdpVault.address} ` + ) + const { collateral: cUsdpCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDP, @@ -528,6 +550,12 @@ async function main() { networkConfig[chainId].COMPTROLLER! ) + await cWBTCVault.deployed() + + console.log( + `Deployed Vault for cWBTC on ${hre.network.name} (${chainId}): ${cWBTCVault.address} ` + ) + const wbtcOracleError = fp('0.02') // 2% const btcOracleError = fp('0.005') // 0.5% const combinedBTCWBTCError = combinedError(wbtcOracleError, btcOracleError) @@ -566,6 +594,10 @@ async function main() { networkConfig[chainId].COMPTROLLER! ) + await cETHVault.deployed() + + console.log(`Deployed Vault for cETH on ${hre.network.name} (${chainId}): ${cETHVault.address} `) + const { collateral: cETHCollateral } = await hre.run('deploy-ctoken-selfreferential-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.ETH, diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts index 948ed79212..36eef8c219 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts @@ -75,6 +75,10 @@ async function main() { await wPool.deployed() await (await wPool.initialize(eUSD_FRAX_BP_POOL_ID)).wait() + console.log( + `Deployed wrapper for Convex eUSD/FRAX Metapool on ${hre.network.name} (${chainId}): ${wPool.address} ` + ) + const collateral = await CvxStableCollateralFactory.connect( deployer ).deploy( diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts index cb4b6fd25d..7eaf5dc89a 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts @@ -81,6 +81,10 @@ async function main() { await wPool.deployed() await (await wPool.initialize(MIM_THREE_POOL_POOL_ID)).wait() + console.log( + `Deployed wrapper for Convex Stable MIM/3Pool on ${hre.network.name} (${chainId}): ${wPool.address} ` + ) + const collateral = await CvxStableCollateralFactory.connect( deployer ).deploy( @@ -117,7 +121,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) console.log( - `Deployed Convex RToken Metapool Collateral to ${hre.network.name} (${chainId}): ${collateral.address}` + `Deployed Convex Metapool Collateral to ${hre.network.name} (${chainId}): ${collateral.address}` ) assetCollDeployments.collateral.cvxMIM3Pool = collateral.address diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts index d562c90deb..106003a22e 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts @@ -75,6 +75,10 @@ async function main() { await w3Pool.deployed() await (await w3Pool.initialize(THREE_POOL_CVX_POOL_ID)).wait() + console.log( + `Deployed wrapper for Convex Stable 3Pool on ${hre.network.name} (${chainId}): ${w3Pool.address} ` + ) + const collateral = await CvxStableCollateralFactory.connect(deployer).deploy( { erc20: w3Pool.address, diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts index e026042015..f0c7b73328 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts @@ -78,6 +78,10 @@ async function main() { await w3Pool.deployed() await (await w3Pool.initialize(TRI_CRYPTO_CVX_POOL_ID)).wait() + console.log( + `Deployed wrapper for Convex Volatile TriCrypto on ${hre.network.name} (${chainId}): ${w3Pool.address} ` + ) + const collateral = await CvxVolatileCollateralFactory.connect( deployer ).deploy( diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts index 652b41c1e6..a6d0bd5e86 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts @@ -51,6 +51,8 @@ async function main() { ) await erc20.deployed() + console.log(`Deployed wrapper for cUSDCv3 on ${hre.network.name} (${chainId}): ${erc20.address} `) + const CTokenV3Factory: ContractFactory = await hre.ethers.getContractFactory('CTokenV3Collateral') const collateral = await CTokenV3Factory.connect(deployer).deploy( diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts index 683ff1d672..f186889189 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts @@ -51,6 +51,12 @@ async function main() { networkConfig[chainId].COMPTROLLER! ) + await fUsdcVault.deployed() + + console.log( + `Deployed Vault for fUSDC on ${hre.network.name} (${chainId}): ${fUsdcVault.address} ` + ) + const { collateral: fUsdcCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, @@ -83,6 +89,12 @@ async function main() { networkConfig[chainId].COMPTROLLER! ) + await fUsdtVault.deployed() + + console.log( + `Deployed Vault for fUSDT on ${hre.network.name} (${chainId}): ${fUsdtVault.address} ` + ) + const { collateral: fUsdtCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDT, @@ -115,6 +127,10 @@ async function main() { networkConfig[chainId].COMPTROLLER! ) + await fDaiVault.deployed() + + console.log(`Deployed Vault for fDAI on ${hre.network.name} (${chainId}): ${fDaiVault.address} `) + const { collateral: fDaiCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.DAI, @@ -147,6 +163,12 @@ async function main() { networkConfig[chainId].COMPTROLLER! ) + await fFraxVault.deployed() + + console.log( + `Deployed Vault for fFRAX on ${hre.network.name} (${chainId}): ${fFraxVault.address} ` + ) + const { collateral: fFRAXCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.FRAX, From 7c9884419c6069913e2fb78fe69fc0967ba0f9f4 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 11 May 2023 13:39:22 -0300 Subject: [PATCH 228/462] prettier on tasks --- tasks/testing/mint-tokens.ts | 4 +- tasks/testing/tenderly.ts | 28 +++-- .../upgrade-checker-utils/constants.ts | 4 +- .../upgrade-checker-utils/governance.ts | 74 +++++------- tasks/testing/upgrade-checker-utils/logs.ts | 92 +++++++------- .../testing/upgrade-checker-utils/oracles.ts | 10 +- .../testing/upgrade-checker-utils/rewards.ts | 16 +-- .../testing/upgrade-checker-utils/rtokens.ts | 32 +++-- tasks/testing/upgrade-checker-utils/trades.ts | 26 ++-- .../upgrade-checker-utils/upgrades/2_1_0.ts | 114 ++++++++++-------- utils/time.ts | 5 +- 11 files changed, 214 insertions(+), 191 deletions(-) diff --git a/tasks/testing/mint-tokens.ts b/tasks/testing/mint-tokens.ts index 409431fc8c..fd11d63b4c 100644 --- a/tasks/testing/mint-tokens.ts +++ b/tasks/testing/mint-tokens.ts @@ -110,10 +110,10 @@ task('give-rsr', 'Mints RSR to an address on a tenderly fork') const rsr = await hre.ethers.getContractAt('ERC20Mock', networkConfig[chainId].tokens.RSR!) - const rsrWhale = "0x6bab6EB87Aa5a1e4A8310C73bDAAA8A5dAAd81C1" + const rsrWhale = '0x6bab6EB87Aa5a1e4A8310C73bDAAA8A5dAAd81C1' await whileImpersonating(hre, rsrWhale, async (signer) => { await rsr.connect(signer).transfer(params.address, fp('100e6')) }) console.log(`100m RSR sent to ${params.address}`) - }) \ No newline at end of file + }) diff --git a/tasks/testing/tenderly.ts b/tasks/testing/tenderly.ts index 54ce555163..0622a6f68b 100644 --- a/tasks/testing/tenderly.ts +++ b/tasks/testing/tenderly.ts @@ -15,12 +15,12 @@ task('give-eth', 'Mints ETH to an address on a tenderly fork') throw new Error(`Missing network configuration for ${hre.network.name}`) } - const forkProvider = new hre.ethers.providers.JsonRpcProvider(params.rpc); + const forkProvider = new hre.ethers.providers.JsonRpcProvider(params.rpc) await forkProvider.send('tenderly_setBalance', [ - [params.address], - hre.ethers.utils.hexValue(hre.ethers.utils.parseUnits('10', 'ether').toHexString()), - ]); + [params.address], + hre.ethers.utils.hexValue(hre.ethers.utils.parseUnits('10', 'ether').toHexString()), + ]) console.log(`10 ETH sent to ${params.address}`) }) @@ -39,24 +39,26 @@ task('give-rsr-tenderly', 'Mints RSR to an address on a tenderly fork') const rsr = await hre.ethers.getContractAt('ERC20Mock', networkConfig[chainId].tokens.RSR!) const unsignedTx = await rsr.populateTransaction['transfer'](params.address, fp('100e6')) - const rsrWhale = "0x6bab6EB87Aa5a1e4A8310C73bDAAA8A5dAAd81C1" - const transactionParameters = [{ + const rsrWhale = '0x6bab6EB87Aa5a1e4A8310C73bDAAA8A5dAAd81C1' + const transactionParameters = [ + { to: rsr.address, from: rsrWhale, data: unsignedTx.data, gas: hre.ethers.utils.hexValue(300000), gasPrice: hre.ethers.utils.hexValue(1), - value: hre.ethers.utils.hexValue(0) - }]; + value: hre.ethers.utils.hexValue(0), + }, + ] - const forkProvider = new hre.ethers.providers.JsonRpcProvider(params.rpc); + const forkProvider = new hre.ethers.providers.JsonRpcProvider(params.rpc) await forkProvider.send('tenderly_setBalance', [ - [rsrWhale], - hre.ethers.utils.hexValue(hre.ethers.utils.parseUnits('1', 'ether').toHexString()), - ]); + [rsrWhale], + hre.ethers.utils.hexValue(hre.ethers.utils.parseUnits('1', 'ether').toHexString()), + ]) const txHash = await forkProvider.send('eth_sendTransaction', transactionParameters) console.log(`100m RSR sent to ${params.address}`) - }) \ No newline at end of file + }) diff --git a/tasks/testing/upgrade-checker-utils/constants.ts b/tasks/testing/upgrade-checker-utils/constants.ts index 97d8e250a8..fb1f30e5ee 100644 --- a/tasks/testing/upgrade-checker-utils/constants.ts +++ b/tasks/testing/upgrade-checker-utils/constants.ts @@ -1,7 +1,7 @@ -import { networkConfig } from "#/common/configuration"; +import { networkConfig } from '#/common/configuration' export const whales: { [key: string]: string } = { [networkConfig['1'].tokens.USDT!.toLowerCase()]: '0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503', [networkConfig['1'].tokens.USDC!.toLowerCase()]: '0x756D64Dc5eDb56740fC617628dC832DDBCfd373c', [networkConfig['1'].tokens.RSR!.toLowerCase()]: '0x6bab6EB87Aa5a1e4A8310C73bDAAA8A5dAAd81C1', -} \ No newline at end of file +} diff --git a/tasks/testing/upgrade-checker-utils/governance.ts b/tasks/testing/upgrade-checker-utils/governance.ts index caab430b7f..7b312efb34 100644 --- a/tasks/testing/upgrade-checker-utils/governance.ts +++ b/tasks/testing/upgrade-checker-utils/governance.ts @@ -1,10 +1,10 @@ -import { ProposalState } from "#/common/constants" -import { bn } from "#/common/numbers" -import { whileImpersonating } from "#/utils/impersonation" -import { Delegate, Proposal, getDelegates, getProposalDetails } from "#/utils/subgraph" -import { advanceBlocks, advanceTime } from "#/utils/time" -import { BigNumber, PopulatedTransaction } from "ethers" -import { HardhatRuntimeEnvironment } from "hardhat/types" +import { ProposalState } from '#/common/constants' +import { bn } from '#/common/numbers' +import { whileImpersonating } from '#/utils/impersonation' +import { Delegate, Proposal, getDelegates, getProposalDetails } from '#/utils/subgraph' +import { advanceBlocks, advanceTime } from '#/utils/time' +import { BigNumber, PopulatedTransaction } from 'ethers' +import { HardhatRuntimeEnvironment } from 'hardhat/types' export const passAndExecuteProposal = async ( hre: HardhatRuntimeEnvironment, @@ -24,7 +24,7 @@ export const passAndExecuteProposal = async ( // Advance time to start voting const votingDelay = await governor.votingDelay() await advanceBlocks(hre, votingDelay.add(1)) - + // Check proposal state propState = await governor.state(proposalId) if (propState != ProposalState.Active) { @@ -39,7 +39,7 @@ export const passAndExecuteProposal = async ( let whales: Array = await getDelegates(rtokenAddress.toLowerCase()) const startBlock = await governor.proposalSnapshot(proposalId) const quorum = await governor.quorum(startBlock) - + let quorumNotReached = true let currentVoteAmount = BigNumber.from(0) let i = 0 @@ -51,16 +51,16 @@ export const passAndExecuteProposal = async ( quorumNotReached = false } } - + whales = whales.slice(0, i) - + // cast enough votes to pass the proposal for (const whale of whales) { await whileImpersonating(hre, whale.address, async (signer) => { await governor.connect(signer).castVote(proposalId, 1) }) } - + // Advance time till voting is complete const votingPeriod = await governor.votingPeriod() await advanceBlocks(hre, votingPeriod.add(1)) @@ -78,16 +78,12 @@ export const passAndExecuteProposal = async ( console.log(`Prop ${proposalId} is SUCCEEDED, moving to QUEUED...`) if (!proposal) { - proposal = await getProposalDetails( - `${governorAddress.toLowerCase()}-${proposalId}` - ) + proposal = await getProposalDetails(`${governorAddress.toLowerCase()}-${proposalId}`) } - descriptionHash = hre.ethers.utils.keccak256( - hre.ethers.utils.toUtf8Bytes(proposal.description) - ) + descriptionHash = hre.ethers.utils.keccak256(hre.ethers.utils.toUtf8Bytes(proposal.description)) // Queue propoal await governor.queue(proposal.targets, proposal.values, proposal.calldatas, descriptionHash) - + // Check proposal state propState = await governor.state(proposalId) if (propState != ProposalState.Queued) { @@ -99,21 +95,17 @@ export const passAndExecuteProposal = async ( console.log(`Prop ${proposalId} is QUEUED, moving to EXECUTED...`) if (!proposal) { - proposal = await getProposalDetails( - `${governorAddress.toLowerCase()}-${proposalId}` - ) + proposal = await getProposalDetails(`${governorAddress.toLowerCase()}-${proposalId}`) } - descriptionHash = hre.ethers.utils.keccak256( - hre.ethers.utils.toUtf8Bytes(proposal.description) - ) + descriptionHash = hre.ethers.utils.keccak256(hre.ethers.utils.toUtf8Bytes(proposal.description)) const timelock = await hre.ethers.getContractAt('TimelockController', await governor.timelock()) const minDelay = await timelock.getMinDelay() - + // Advance time required by timelock await advanceTime(hre, minDelay.add(1).toString()) await advanceBlocks(hre, 1) - + // Execute await governor.execute(proposal.targets, proposal.values, proposal.calldatas, descriptionHash) @@ -126,7 +118,7 @@ export const passAndExecuteProposal = async ( console.log(`Prop ${proposalId} is EXECUTED.`) } - + export const stakeAndDelegateRsr = async ( hre: HardhatRuntimeEnvironment, rtokenAddress: string, @@ -136,14 +128,8 @@ export const stakeAndDelegateRsr = async ( const governor = await hre.ethers.getContractAt('Governance', governorAddress) const rToken = await hre.ethers.getContractAt('RTokenP1', rtokenAddress) const main = await hre.ethers.getContractAt('IMain', await rToken.main()) - const stRSR = await hre.ethers.getContractAt( - 'StRSRP1Votes', - await main.stRSR() - ) - const rsr = await hre.ethers.getContractAt( - 'StRSRP1Votes', - await main.rsr() - ) + const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) + const rsr = await hre.ethers.getContractAt('StRSRP1Votes', await main.rsr()) await whileImpersonating(hre, user, async (signer) => { const bal = await rsr.balanceOf(signer.address) @@ -161,11 +147,15 @@ export const buildProposal = (txs: Array, description: str targets, values, calldatas, - description + description, } } -export type ProposalBuilder = (hre: HardhatRuntimeEnvironment, rTokenAddress: string, governorAddress: string) => Promise +export type ProposalBuilder = ( + hre: HardhatRuntimeEnvironment, + rTokenAddress: string, + governorAddress: string +) => Promise export const proposeUpgrade = async ( hre: HardhatRuntimeEnvironment, @@ -176,11 +166,11 @@ export const proposeUpgrade = async ( console.log(`\nGenerating and proposing proposal...`) const [tester] = await hre.ethers.getSigners() - await hre.run("give-rsr", {address: tester.address}) + await hre.run('give-rsr', { address: tester.address }) await stakeAndDelegateRsr(hre, rTokenAddress, governorAddress, tester.address) const proposal = await proposalBuilder(hre, rTokenAddress, governorAddress) - + const governor = await hre.ethers.getContractAt('Governance', governorAddress) const call = await governor.populateTransaction.propose( @@ -203,5 +193,5 @@ export const proposeUpgrade = async ( console.log('\nSuccessfully proposed!') console.log(`Proposal ID: ${resp.events![0].args!.proposalId}`) - return { ...proposal, proposalId: resp.events![0].args!.proposalId} -} \ No newline at end of file + return { ...proposal, proposalId: resp.events![0].args!.proposalId } +} diff --git a/tasks/testing/upgrade-checker-utils/logs.ts b/tasks/testing/upgrade-checker-utils/logs.ts index b2c6db412d..ec25ff1afb 100644 --- a/tasks/testing/upgrade-checker-utils/logs.ts +++ b/tasks/testing/upgrade-checker-utils/logs.ts @@ -1,51 +1,53 @@ -import { ITokens, networkConfig } from "#/common/configuration"; +import { ITokens, networkConfig } from '#/common/configuration' const tokens: { [key: string]: string } = { - [networkConfig['1'].tokens.USDC!.toLowerCase()]: 'USDC', - [networkConfig['1'].tokens.USDT!.toLowerCase()]: 'USDT', - [networkConfig['1'].tokens.USDP!.toLowerCase()]: 'USDP', - [networkConfig['1'].tokens.TUSD!.toLowerCase()]: 'TUSD', - [networkConfig['1'].tokens.BUSD!.toLowerCase()]: 'BUSD', - [networkConfig['1'].tokens.FRAX!.toLowerCase()]: 'FRAX', - [networkConfig['1'].tokens.MIM!.toLowerCase()]: 'MIM', - [networkConfig['1'].tokens.eUSD!.toLowerCase()]: 'eUSD', - [networkConfig['1'].tokens.aDAI!.toLowerCase()]: 'aDAI', - [networkConfig['1'].tokens.aUSDC!.toLowerCase()]: 'aUSDC', - [networkConfig['1'].tokens.aUSDT!.toLowerCase()]: 'aUSDT', - [networkConfig['1'].tokens.aBUSD!.toLowerCase()]: 'aBUSD', - [networkConfig['1'].tokens.aUSDP!.toLowerCase()]: 'aUSDP', - [networkConfig['1'].tokens.aWETH!.toLowerCase()]: 'aWETH', - [networkConfig['1'].tokens.cDAI!.toLowerCase()]: 'cDAI', - [networkConfig['1'].tokens.cUSDC!.toLowerCase()]: 'cUSDC', - [networkConfig['1'].tokens.cUSDT!.toLowerCase()]: 'cUSDT', - [networkConfig['1'].tokens.cUSDP!.toLowerCase()]: 'cUSDP', - [networkConfig['1'].tokens.cETH!.toLowerCase()]: 'cETH', - [networkConfig['1'].tokens.cWBTC!.toLowerCase()]: 'cWBTC', - [networkConfig['1'].tokens.fUSDC!.toLowerCase()]: 'fUSDC', - [networkConfig['1'].tokens.fUSDT!.toLowerCase()]: 'fUSDT', - [networkConfig['1'].tokens.fFRAX!.toLowerCase()]: 'fFRAX', - [networkConfig['1'].tokens.fDAI!.toLowerCase()]: 'fDAI', - [networkConfig['1'].tokens.AAVE!.toLowerCase()]: 'AAVE', - [networkConfig['1'].tokens.stkAAVE!.toLowerCase()]: 'stkAAVE', - [networkConfig['1'].tokens.COMP!.toLowerCase()]: 'COMP', - [networkConfig['1'].tokens.WETH!.toLowerCase()]: 'WETH', - [networkConfig['1'].tokens.WBTC!.toLowerCase()]: 'WBTC', - [networkConfig['1'].tokens.EURT!.toLowerCase()]: 'EURT', - [networkConfig['1'].tokens.RSR!.toLowerCase()]: 'RSR', - [networkConfig['1'].tokens.CRV!.toLowerCase()]: 'CRV', - [networkConfig['1'].tokens.CVX!.toLowerCase()]: 'CVX', - [networkConfig['1'].tokens.ankrETH!.toLowerCase()]: 'ankrETH', - [networkConfig['1'].tokens.frxETH!.toLowerCase()]: 'frxETH', - [networkConfig['1'].tokens.sfrxETH!.toLowerCase()]: 'sfrxETH', - [networkConfig['1'].tokens.stETH!.toLowerCase()]: 'stETH', - [networkConfig['1'].tokens.wstETH!.toLowerCase()]: 'wstETH', - [networkConfig['1'].tokens.rETH!.toLowerCase()]: 'rETH', - [networkConfig['1'].tokens.cUSDCv3!.toLowerCase()]: 'cUSDCv3', - [networkConfig['1'].tokens.DAI!.toLowerCase()]: 'DAI', - ['0x60C384e226b120d93f3e0F4C502957b2B9C32B15'.toLowerCase()]: 'saUSDC', - ['0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9'.toLowerCase()]: 'saUSDT' + [networkConfig['1'].tokens.USDC!.toLowerCase()]: 'USDC', + [networkConfig['1'].tokens.USDT!.toLowerCase()]: 'USDT', + [networkConfig['1'].tokens.USDP!.toLowerCase()]: 'USDP', + [networkConfig['1'].tokens.TUSD!.toLowerCase()]: 'TUSD', + [networkConfig['1'].tokens.BUSD!.toLowerCase()]: 'BUSD', + [networkConfig['1'].tokens.FRAX!.toLowerCase()]: 'FRAX', + [networkConfig['1'].tokens.MIM!.toLowerCase()]: 'MIM', + [networkConfig['1'].tokens.eUSD!.toLowerCase()]: 'eUSD', + [networkConfig['1'].tokens.aDAI!.toLowerCase()]: 'aDAI', + [networkConfig['1'].tokens.aUSDC!.toLowerCase()]: 'aUSDC', + [networkConfig['1'].tokens.aUSDT!.toLowerCase()]: 'aUSDT', + [networkConfig['1'].tokens.aBUSD!.toLowerCase()]: 'aBUSD', + [networkConfig['1'].tokens.aUSDP!.toLowerCase()]: 'aUSDP', + [networkConfig['1'].tokens.aWETH!.toLowerCase()]: 'aWETH', + [networkConfig['1'].tokens.cDAI!.toLowerCase()]: 'cDAI', + [networkConfig['1'].tokens.cUSDC!.toLowerCase()]: 'cUSDC', + [networkConfig['1'].tokens.cUSDT!.toLowerCase()]: 'cUSDT', + [networkConfig['1'].tokens.cUSDP!.toLowerCase()]: 'cUSDP', + [networkConfig['1'].tokens.cETH!.toLowerCase()]: 'cETH', + [networkConfig['1'].tokens.cWBTC!.toLowerCase()]: 'cWBTC', + [networkConfig['1'].tokens.fUSDC!.toLowerCase()]: 'fUSDC', + [networkConfig['1'].tokens.fUSDT!.toLowerCase()]: 'fUSDT', + [networkConfig['1'].tokens.fFRAX!.toLowerCase()]: 'fFRAX', + [networkConfig['1'].tokens.fDAI!.toLowerCase()]: 'fDAI', + [networkConfig['1'].tokens.AAVE!.toLowerCase()]: 'AAVE', + [networkConfig['1'].tokens.stkAAVE!.toLowerCase()]: 'stkAAVE', + [networkConfig['1'].tokens.COMP!.toLowerCase()]: 'COMP', + [networkConfig['1'].tokens.WETH!.toLowerCase()]: 'WETH', + [networkConfig['1'].tokens.WBTC!.toLowerCase()]: 'WBTC', + [networkConfig['1'].tokens.EURT!.toLowerCase()]: 'EURT', + [networkConfig['1'].tokens.RSR!.toLowerCase()]: 'RSR', + [networkConfig['1'].tokens.CRV!.toLowerCase()]: 'CRV', + [networkConfig['1'].tokens.CVX!.toLowerCase()]: 'CVX', + [networkConfig['1'].tokens.ankrETH!.toLowerCase()]: 'ankrETH', + [networkConfig['1'].tokens.frxETH!.toLowerCase()]: 'frxETH', + [networkConfig['1'].tokens.sfrxETH!.toLowerCase()]: 'sfrxETH', + [networkConfig['1'].tokens.stETH!.toLowerCase()]: 'stETH', + [networkConfig['1'].tokens.wstETH!.toLowerCase()]: 'wstETH', + [networkConfig['1'].tokens.rETH!.toLowerCase()]: 'rETH', + [networkConfig['1'].tokens.cUSDCv3!.toLowerCase()]: 'cUSDCv3', + [networkConfig['1'].tokens.DAI!.toLowerCase()]: 'DAI', + ['0x60C384e226b120d93f3e0F4C502957b2B9C32B15'.toLowerCase()]: 'saUSDC', + ['0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9'.toLowerCase()]: 'saUSDT', } export const logToken = (tokenAddress: string) => { - return tokens[tokenAddress.toLowerCase()] ? tokens[tokenAddress.toLowerCase()] : tokenAddress.toLowerCase() + return tokens[tokenAddress.toLowerCase()] + ? tokens[tokenAddress.toLowerCase()] + : tokenAddress.toLowerCase() } diff --git a/tasks/testing/upgrade-checker-utils/oracles.ts b/tasks/testing/upgrade-checker-utils/oracles.ts index 6e9f6a4fd6..198592464b 100644 --- a/tasks/testing/upgrade-checker-utils/oracles.ts +++ b/tasks/testing/upgrade-checker-utils/oracles.ts @@ -1,6 +1,6 @@ -import { setCode } from "@nomicfoundation/hardhat-network-helpers" -import { EACAggregatorProxyMock } from "@typechain/EACAggregatorProxyMock" -import { HardhatRuntimeEnvironment } from "hardhat/types" +import { setCode } from '@nomicfoundation/hardhat-network-helpers' +import { EACAggregatorProxyMock } from '@typechain/EACAggregatorProxyMock' +import { HardhatRuntimeEnvironment } from 'hardhat/types' export const overrideOracle = async ( hre: HardhatRuntimeEnvironment, @@ -16,7 +16,7 @@ export const overrideOracle = async ( await setCode(oracleAddress, bytecode) return hre.ethers.getContractAt('EACAggregatorProxyMock', oracleAddress) } - + export const pushOraclesForward = async (hre: HardhatRuntimeEnvironment, rTokenAddress: string) => { console.log(`\nPushing oracles forward for RToken ${rTokenAddress}...`) const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) @@ -47,4 +47,4 @@ export const pushOracleForward = async (hre: HardhatRuntimeEnvironment, asset: s const initPrice = await realChainlinkFeed.latestRoundData() const oracle = await overrideOracle(hre, realChainlinkFeed.address) await oracle.updateAnswer(initPrice.answer) -} \ No newline at end of file +} diff --git a/tasks/testing/upgrade-checker-utils/rewards.ts b/tasks/testing/upgrade-checker-utils/rewards.ts index d486ee6ecc..efe8106e81 100644 --- a/tasks/testing/upgrade-checker-utils/rewards.ts +++ b/tasks/testing/upgrade-checker-utils/rewards.ts @@ -1,10 +1,10 @@ -import { fp } from "#/common/numbers" -import { whileImpersonating } from "#/utils/impersonation" -import { advanceBlocks, advanceTime } from "#/utils/time" -import { IRewardable } from "@typechain/IRewardable" -import { formatEther } from "ethers/lib/utils" -import { HardhatRuntimeEnvironment } from "hardhat/types" -import { runTrade } from "../upgrade-checker-utils/trades" +import { fp } from '#/common/numbers' +import { whileImpersonating } from '#/utils/impersonation' +import { advanceBlocks, advanceTime } from '#/utils/time' +import { IRewardable } from '@typechain/IRewardable' +import { formatEther } from 'ethers/lib/utils' +import { HardhatRuntimeEnvironment } from 'hardhat/types' +import { runTrade } from '../upgrade-checker-utils/trades' const claimRewards = async (claimer: IRewardable) => { const resp = await claimer.claimRewards() @@ -59,4 +59,4 @@ export const claimRsrRewards = async (hre: HardhatRuntimeEnvironment, rtokenAddr } console.log('Successfully claimed and distributed RSR rewards') -} \ No newline at end of file +} diff --git a/tasks/testing/upgrade-checker-utils/rtokens.ts b/tasks/testing/upgrade-checker-utils/rtokens.ts index b712750aee..ef3588a193 100644 --- a/tasks/testing/upgrade-checker-utils/rtokens.ts +++ b/tasks/testing/upgrade-checker-utils/rtokens.ts @@ -1,11 +1,11 @@ -import { bn } from "#/common/numbers" -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" -import { BigNumber } from "ethers" -import { Interface, LogDescription, formatEther } from "ethers/lib/utils" -import { HardhatRuntimeEnvironment } from "hardhat/types" -import { runTrade } from "./trades" -import { logToken } from "./logs" -import { CollateralStatus } from "#/common/constants" +import { bn } from '#/common/numbers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { BigNumber } from 'ethers' +import { Interface, LogDescription, formatEther } from 'ethers/lib/utils' +import { HardhatRuntimeEnvironment } from 'hardhat/types' +import { runTrade } from './trades' +import { logToken } from './logs' +import { CollateralStatus } from '#/common/constants' type Balances = { [key: string]: BigNumber } @@ -104,10 +104,18 @@ export const recollateralize = async (hre: HardhatRuntimeEnvironment, rtokenAddr const resp = await r.wait() for (const event of resp.events!) { let parsedLog: LogDescription | undefined - try { parsedLog = iface.parseLog(event) } catch {} + try { + parsedLog = iface.parseLog(event) + } catch {} if (parsedLog && parsedLog.name == 'TradeStarted') { tradesRemain = true - console.log(`\n====== Trade Started: sell ${logToken(parsedLog.args.sell)} / buy ${logToken(parsedLog.args.buy)} ======\n\tmbuyAmount: ${parsedLog.args.minBuyAmount}\n\tsellAmount: ${parsedLog.args.sellAmount}`) + console.log( + `\n====== Trade Started: sell ${logToken(parsedLog.args.sell)} / buy ${logToken( + parsedLog.args.buy + )} ======\n\tmbuyAmount: ${parsedLog.args.minBuyAmount}\n\tsellAmount: ${ + parsedLog.args.sellAmount + }` + ) await runTrade(hre, backingManager, parsedLog.args.sell, false) } } @@ -119,5 +127,5 @@ export const recollateralize = async (hre: HardhatRuntimeEnvironment, rtokenAddr throw new Error(`Basket is not SOUND after recollateralizing new basket`) } - console.log("Recollateralization complete!") -} \ No newline at end of file + console.log('Recollateralization complete!') +} diff --git a/tasks/testing/upgrade-checker-utils/trades.ts b/tasks/testing/upgrade-checker-utils/trades.ts index bd3c65413d..541056ecf3 100644 --- a/tasks/testing/upgrade-checker-utils/trades.ts +++ b/tasks/testing/upgrade-checker-utils/trades.ts @@ -1,13 +1,13 @@ -import { whileImpersonating } from "#/utils/impersonation" -import { advanceTime, getLatestBlockTimestamp } from "#/utils/time" -import { getTrade } from "#/utils/trades" -import { TestITrading } from "@typechain/TestITrading" -import { BigNumber } from "ethers" -import { HardhatRuntimeEnvironment } from "hardhat/types" +import { whileImpersonating } from '#/utils/impersonation' +import { advanceTime, getLatestBlockTimestamp } from '#/utils/time' +import { getTrade } from '#/utils/trades' +import { TestITrading } from '@typechain/TestITrading' +import { BigNumber } from 'ethers' +import { HardhatRuntimeEnvironment } from 'hardhat/types' import { QUEUE_START } from '#/common/constants' -import { whales } from "./constants" -import { bn, fp } from "#/common/numbers" -import { logToken } from "./logs" +import { whales } from './constants' +import { bn, fp } from '#/common/numbers' +import { logToken } from './logs' export const runTrade = async ( hre: HardhatRuntimeEnvironment, @@ -33,11 +33,11 @@ export const runTrade = async ( const buyDecimals = await buytoken.decimals() let buyAmount = bidExact ? sellAmount : sellAmount.mul(worstPrice).div(fp('1')) if (buyDecimals > sellDecimals) { - buyAmount = buyAmount.mul(bn(10**(buyDecimals - sellDecimals))) + buyAmount = buyAmount.mul(bn(10 ** (buyDecimals - sellDecimals))) } else if (sellDecimals > buyDecimals) { - buyAmount = buyAmount.div(bn(10**(sellDecimals - buyDecimals))) + buyAmount = buyAmount.div(bn(10 ** (sellDecimals - buyDecimals))) } - buyAmount = buyAmount.add(fp('1').div(bn(10**(18 - buyDecimals)))) + buyAmount = buyAmount.add(fp('1').div(bn(10 ** (18 - buyDecimals)))) const gnosis = await hre.ethers.getContractAt('EasyAuction', await trade.gnosis()) await whileImpersonating(hre, whales[buyTokenAddress.toLowerCase()], async (whale) => { @@ -58,4 +58,4 @@ export const runTrade = async ( await advanceTime(hre, BigNumber.from(endTime).sub(lastTimestamp).toString()) await trader.settleTrade(tradeToken) console.log(`Settled trade for ${logToken(buyTokenAddress)}.`) -} \ No newline at end of file +} diff --git a/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts b/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts index 7268103081..b986fa09f6 100644 --- a/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts +++ b/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts @@ -1,28 +1,35 @@ -import { whileImpersonating } from "#/utils/impersonation" -import { HardhatRuntimeEnvironment } from "hardhat/types" -import { ProposalBuilder, buildProposal } from "../governance" -import { Proposal } from "#/utils/subgraph" -import { overrideOracle, pushOracleForward, pushOraclesForward } from "../oracles" -import { networkConfig } from "#/common/configuration" -import { recollateralize } from "../rtokens" -import { bn, fp } from "#/common/numbers" -import { advanceBlocks, advanceTime, getLatestBlockTimestamp } from "#/utils/time" -import { LogDescription, Interface } from "ethers/lib/utils" -import { logToken } from "../logs" -import { runTrade } from "../trades" -import { CollateralStatus, QUEUE_START } from "#/common/constants" -import { getTrade } from "#/utils/trades" -import { whales } from "../constants" -import { BigNumber } from "ethers" - -export default async (hre: HardhatRuntimeEnvironment, rTokenAddress: string, governorAddress: string) => { +import { whileImpersonating } from '#/utils/impersonation' +import { HardhatRuntimeEnvironment } from 'hardhat/types' +import { ProposalBuilder, buildProposal } from '../governance' +import { Proposal } from '#/utils/subgraph' +import { overrideOracle, pushOracleForward, pushOraclesForward } from '../oracles' +import { networkConfig } from '#/common/configuration' +import { recollateralize } from '../rtokens' +import { bn, fp } from '#/common/numbers' +import { advanceBlocks, advanceTime, getLatestBlockTimestamp } from '#/utils/time' +import { LogDescription, Interface } from 'ethers/lib/utils' +import { logToken } from '../logs' +import { runTrade } from '../trades' +import { CollateralStatus, QUEUE_START } from '#/common/constants' +import { getTrade } from '#/utils/trades' +import { whales } from '../constants' +import { BigNumber } from 'ethers' + +export default async ( + hre: HardhatRuntimeEnvironment, + rTokenAddress: string, + governorAddress: string +) => { console.log('\n* * * * * Run checks for release 2.1.0...') const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) const main = await hre.ethers.getContractAt('IMain', await rToken.main()) const governor = await hre.ethers.getContractAt('Governance', governorAddress) const timelock = await hre.ethers.getContractAt('TimelockController', await governor.timelock()) const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) - const basketHandler = await hre.ethers.getContractAt('BasketHandlerP1', await main.basketHandler()) + const basketHandler = await hre.ethers.getContractAt( + 'BasketHandlerP1', + await main.basketHandler() + ) // check Broker updates const broker = await hre.ethers.getContractAt('BrokerP1', await main.broker()) @@ -47,7 +54,9 @@ export default async (hre: HardhatRuntimeEnvironment, rTokenAddress: string, gov } if (postTrade != newTrade.address) { - throw new Error(`setTradeImplementation() failure: received: ${postTrade} / expected: ${newTrade.address}`) + throw new Error( + `setTradeImplementation() failure: received: ${postTrade} / expected: ${newTrade.address}` + ) } await whileImpersonating(hre, timelock.address, async (govSigner) => { @@ -67,11 +76,16 @@ export default async (hre: HardhatRuntimeEnvironment, rTokenAddress: string, gov await whileImpersonating(hre, timelock.address, async (govSigner) => { await basketHandler .connect(govSigner) - .setBackupConfig(hre.ethers.utils.formatBytes32String('USD'), bn(1), [networkConfig['1'].tokens.USDT!]) + .setBackupConfig(hre.ethers.utils.formatBytes32String('USD'), bn(1), [ + networkConfig['1'].tokens.USDT!, + ]) }) const ar = await hre.ethers.getContractAt('AssetRegistryP1', await main.assetRegistry()) - const backingManager = await hre.ethers.getContractAt('BackingManagerP1', await main.backingManager()) + const backingManager = await hre.ethers.getContractAt( + 'BackingManagerP1', + await main.backingManager() + ) const usdcCollat = await ar.toColl(networkConfig['1'].tokens.USDC!) const usdc = await hre.ethers.getContractAt('FiatCollateral', usdcCollat) const oracle = await overrideOracle(hre, await usdc.chainlinkFeed()) @@ -80,8 +94,8 @@ export default async (hre: HardhatRuntimeEnvironment, rTokenAddress: string, gov await ar.refresh() // default - await advanceTime(hre, 60*60*25) - await advanceBlocks(hre, 5*60*25) + await advanceTime(hre, 60 * 60 * 25) + await advanceBlocks(hre, 5 * 60 * 25) // push other oracles forward console.log(`\nPushing some oracles forward for RToken ${rTokenAddress}...`) @@ -98,7 +112,7 @@ export default async (hre: HardhatRuntimeEnvironment, rTokenAddress: string, gov await basketHandler.refreshBasket() const tradingDelay = await backingManager.tradingDelay() - await advanceBlocks(hre, tradingDelay/12 + 1) + await advanceBlocks(hre, tradingDelay / 12 + 1) await advanceTime(hre, tradingDelay + 1) const iface: Interface = backingManager.interface @@ -112,9 +126,17 @@ export default async (hre: HardhatRuntimeEnvironment, rTokenAddress: string, gov const resp = await r.wait() for (const event of resp.events!) { let parsedLog: LogDescription | undefined - try { parsedLog = iface.parseLog(event) } catch {} + try { + parsedLog = iface.parseLog(event) + } catch {} if (parsedLog && parsedLog.name == 'TradeStarted') { - console.log(`\n====== Trade Started: sell ${logToken(parsedLog.args.sell)} / buy ${logToken(parsedLog.args.buy)} ======\n\tmbuyAmount: ${parsedLog.args.minBuyAmount}\n\tsellAmount: ${parsedLog.args.sellAmount}`) + console.log( + `\n====== Trade Started: sell ${logToken(parsedLog.args.sell)} / buy ${logToken( + parsedLog.args.buy + )} ======\n\tmbuyAmount: ${parsedLog.args.minBuyAmount}\n\tsellAmount: ${ + parsedLog.args.sellAmount + }` + ) // // run trade const tradeToken = parsedLog.args.sell @@ -129,20 +151,20 @@ export default async (hre: HardhatRuntimeEnvironment, rTokenAddress: string, gov we're only placing a half bid */ const sellAmount = (await trade.initBal()).div(2) - + const sellToken = await hre.ethers.getContractAt('ERC20Mock', await trade.sell()) const sellDecimals = await sellToken.decimals() const buytoken = await hre.ethers.getContractAt('ERC20Mock', await buyTokenAddress) const buyDecimals = await buytoken.decimals() let buyAmount = sellAmount.mul(worstPrice).div(fp('1')) if (buyDecimals > sellDecimals) { - buyAmount = buyAmount.mul(bn(10**(buyDecimals - sellDecimals))) + buyAmount = buyAmount.mul(bn(10 ** (buyDecimals - sellDecimals))) } else if (sellDecimals > buyDecimals) { - buyAmount = buyAmount.div(bn(10**(sellDecimals - buyDecimals))) + buyAmount = buyAmount.div(bn(10 ** (sellDecimals - buyDecimals))) } buyAmount = buyAmount.add(1) // need 1 wei to be at min price - + const gnosis = await hre.ethers.getContractAt('EasyAuction', await trade.gnosis()) await whileImpersonating(hre, whales[buyTokenAddress.toLowerCase()], async (whale) => { const sellToken = await hre.ethers.getContractAt('ERC20Mock', buyTokenAddress) @@ -157,7 +179,7 @@ export default async (hre: HardhatRuntimeEnvironment, rTokenAddress: string, gov hre.ethers.constants.HashZero ) }) - + const lastTimestamp = await getLatestBlockTimestamp(hre) await advanceTime(hre, BigNumber.from(endTime).sub(lastTimestamp).toString()) await backingManager.settleTrade(tradeToken) @@ -169,7 +191,7 @@ export default async (hre: HardhatRuntimeEnvironment, rTokenAddress: string, gov await recollateralize(hre, rTokenAddress) - console.log("\n2.1.0 check succeeded!") + console.log('\n2.1.0 check succeeded!') } export const proposal_2_1_0: ProposalBuilder = async ( @@ -179,28 +201,24 @@ export const proposal_2_1_0: ProposalBuilder = async ( ): Promise => { const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) const main = await hre.ethers.getContractAt('IMain', await rToken.main()) - const broker = await hre.ethers.getContractAt( - 'BrokerP1', - await main.broker() - ) - const stRSR = await hre.ethers.getContractAt( - 'StRSRP1Votes', - await main.stRSR() - ) + const broker = await hre.ethers.getContractAt('BrokerP1', await main.broker()) + const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) const basketHandler = await hre.ethers.getContractAt( 'BasketHandlerP1', await main.basketHandler() ) const txs = [ - await broker.populateTransaction.upgradeTo("0x89209a52d085D975b14555F3e828F43fb7EaF3B7"), - await stRSR.populateTransaction.upgradeTo("0xfDa8C62d86E426D5fB653B6c44a455Bb657b693f"), - await basketHandler.populateTransaction.upgradeTo("0x5c13b3b6f40aD4bF7aa4793F844BA24E85482030"), - await rToken.populateTransaction.upgradeTo("0x5643D5AC6b79ae8467Cf2F416da6D465d8e7D9C1"), - await broker.populateTransaction.setTradeImplementation("0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439") + await broker.populateTransaction.upgradeTo('0x89209a52d085D975b14555F3e828F43fb7EaF3B7'), + await stRSR.populateTransaction.upgradeTo('0xfDa8C62d86E426D5fB653B6c44a455Bb657b693f'), + await basketHandler.populateTransaction.upgradeTo('0x5c13b3b6f40aD4bF7aa4793F844BA24E85482030'), + await rToken.populateTransaction.upgradeTo('0x5643D5AC6b79ae8467Cf2F416da6D465d8e7D9C1'), + await broker.populateTransaction.setTradeImplementation( + '0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439' + ), ] - const description = "Upgrade Broker implementation and set new trade plugin" + const description = 'Upgrade Broker implementation and set new trade plugin' return buildProposal(txs, description) -} \ No newline at end of file +} diff --git a/utils/time.ts b/utils/time.ts index 2b176ba553..cb1ffa1578 100644 --- a/utils/time.ts +++ b/utils/time.ts @@ -46,7 +46,10 @@ export const advanceBlocks = async (hre: HardhatRuntimeEnvironment, blocks: numb await hre.network.provider.send('hardhat_setNextBlockBaseFeePerGas', ['0x0']) // Temporary fix - Hardhat issue } -export const advanceBlocksTenderly = async (hre: HardhatRuntimeEnvironment, blocks: number | BigNumber) => { +export const advanceBlocksTenderly = async ( + hre: HardhatRuntimeEnvironment, + blocks: number | BigNumber +) => { let blockString: string = BigNumber.isBigNumber(blocks) ? blocks.toHexString() : '0x' + blocks.toString(16) From 7153431ae4a76559f787f191ed76aed19f2dcaac Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 11 May 2023 13:39:37 -0300 Subject: [PATCH 229/462] prettier on contracts --- .../plugins/mocks/EACAggregatorProxyMock.sol | 53 +++++++++---------- .../mocks/RewardableERC20VaultTest.sol | 6 +-- contracts/vendor/oz/ERC4626.sol | 39 +++++++++++--- contracts/vendor/oz/IERC4626.sol | 14 +++-- 4 files changed, 70 insertions(+), 42 deletions(-) diff --git a/contracts/plugins/mocks/EACAggregatorProxyMock.sol b/contracts/plugins/mocks/EACAggregatorProxyMock.sol index 2417fba442..2b553b10a6 100644 --- a/contracts/plugins/mocks/EACAggregatorProxyMock.sol +++ b/contracts/plugins/mocks/EACAggregatorProxyMock.sol @@ -73,9 +73,7 @@ interface AggregatorV3Interface_ { // getRoundData and latestRoundData should both raise "No data present" // if they do not have data to report, instead of returning unset values // which could be misinterpreted as actual reported values. - function getRoundData( - uint80 _roundId - ) + function getRoundData(uint80 _roundId) external view returns ( @@ -117,7 +115,7 @@ contract AggregatorProxy is AggregatorV2V3Interface, Owned { uint256 private constant PHASE_OFFSET = 64; uint256 private constant PHASE_SIZE = 16; - uint256 private constant MAX_ID = 2 ** (PHASE_OFFSET + PHASE_SIZE) - 1; + uint256 private constant MAX_ID = 2**(PHASE_OFFSET + PHASE_SIZE) - 1; constructor(address _aggregator) Owned() { setAggregator(_aggregator); @@ -175,9 +173,13 @@ contract AggregatorProxy is AggregatorV2V3Interface, Owned { * an already answered Aggregator or use the recommended getRoundData * instead which includes better verification information. */ - function getTimestamp( - uint256 _roundId - ) public view virtual override returns (uint256 updatedAt) { + function getTimestamp(uint256 _roundId) + public + view + virtual + override + returns (uint256 updatedAt) + { if (_roundId > MAX_ID) return 0; (uint16 _phaseId, uint64 aggregatorRoundId) = parseIds(_roundId); @@ -227,9 +229,7 @@ contract AggregatorProxy is AggregatorV2V3Interface, Owned { * (Only some AggregatorV3Interface_ implementations return meaningful values) * @dev Note that answer and updatedAt may change between queries. */ - function getRoundData( - uint80 _roundId - ) + function getRoundData(uint80 _roundId) public view virtual @@ -315,9 +315,7 @@ contract AggregatorProxy is AggregatorV2V3Interface, Owned { * @return answeredInRound is the round ID of the round in which the answer * was computed. */ - function proposedGetRoundData( - uint80 _roundId - ) + function proposedGetRoundData(uint80 _roundId) public view virtual @@ -445,7 +443,17 @@ contract AggregatorProxy is AggregatorV2V3Interface, Owned { uint256 updatedAt, uint80 answeredInRound, uint16 _phaseId - ) internal pure returns (uint80, int256, uint256, uint256, uint80) { + ) + internal + pure + returns ( + uint80, + int256, + uint256, + uint256, + uint80 + ) + { return ( addPhase(_phaseId, uint64(roundId)), answer, @@ -481,10 +489,7 @@ interface AccessControllerInterface { contract EACAggregatorProxy is AggregatorProxy { AccessControllerInterface public accessController; - constructor( - address _aggregator, - address _accessController - ) AggregatorProxy(_aggregator) { + constructor(address _aggregator, address _accessController) AggregatorProxy(_aggregator) { setController(_accessController); } @@ -587,9 +592,7 @@ contract EACAggregatorProxy is AggregatorProxy { * (Only some AggregatorV3Interface_ implementations return meaningful values) * @dev Note that answer and updatedAt may change between queries. */ - function getRoundData( - uint80 _roundId - ) + function getRoundData(uint80 _roundId) public view virtual @@ -657,9 +660,7 @@ contract EACAggregatorProxy is AggregatorProxy { * @return answeredInRound is the round ID of the round in which the answer * was computed. */ - function proposedGetRoundData( - uint80 _roundId - ) + function proposedGetRoundData(uint80 _roundId) public view override @@ -764,9 +765,7 @@ contract EACAggregatorProxyMock is EACAggregatorProxy { __latestAnsweredRound = _roundId; } - function getRoundData( - uint80 _roundId - ) + function getRoundData(uint80 _roundId) public view override diff --git a/contracts/plugins/mocks/RewardableERC20VaultTest.sol b/contracts/plugins/mocks/RewardableERC20VaultTest.sol index bc368ece29..394fe26311 100644 --- a/contracts/plugins/mocks/RewardableERC20VaultTest.sol +++ b/contracts/plugins/mocks/RewardableERC20VaultTest.sol @@ -5,16 +5,14 @@ import "../assets/vaults/RewardableERC20Vault.sol"; import "./ERC20MockRewarding.sol"; contract RewardableERC20VaultTest is RewardableERC20Vault { - constructor( ERC20 _asset, string memory _name, string memory _symbol, ERC20 _rewardToken - ) RewardableERC20Vault(_asset, _name, _symbol, _rewardToken) { - } + ) RewardableERC20Vault(_asset, _name, _symbol, _rewardToken) {} function _claimAssetRewards() internal virtual override { ERC20MockRewarding(asset()).claim(); } -} \ No newline at end of file +} diff --git a/contracts/vendor/oz/ERC4626.sol b/contracts/vendor/oz/ERC4626.sol index 087503c716..958d06dee4 100644 --- a/contracts/vendor/oz/ERC4626.sol +++ b/contracts/vendor/oz/ERC4626.sol @@ -180,7 +180,11 @@ abstract contract ERC4626 is ERC20, IERC4626 { } /** @dev See {IERC4626-withdraw}. */ - function withdraw(uint256 assets, address receiver, address owner) public virtual override returns (uint256) { + function withdraw( + uint256 assets, + address receiver, + address owner + ) public virtual override returns (uint256) { require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max"); uint256 shares = previewWithdraw(assets); @@ -190,7 +194,11 @@ abstract contract ERC4626 is ERC20, IERC4626 { } /** @dev See {IERC4626-redeem}. */ - function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256) { + function redeem( + uint256 shares, + address receiver, + address owner + ) public virtual override returns (uint256) { require(shares <= maxRedeem(owner), "ERC4626: redeem more than max"); uint256 assets = previewRedeem(shares); @@ -202,21 +210,36 @@ abstract contract ERC4626 is ERC20, IERC4626 { /** * @dev Internal conversion function (from assets to shares) with support for rounding direction. */ - function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { - return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding); + function _convertToShares(uint256 assets, Math.Rounding rounding) + internal + view + virtual + returns (uint256) + { + return assets.mulDiv(totalSupply() + 10**_decimalsOffset(), totalAssets() + 1, rounding); } /** * @dev Internal conversion function (from shares to assets) with support for rounding direction. */ - function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { - return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); + function _convertToAssets(uint256 shares, Math.Rounding rounding) + internal + view + virtual + returns (uint256) + { + return shares.mulDiv(totalAssets() + 1, totalSupply() + 10**_decimalsOffset(), rounding); } /** * @dev Deposit/mint common workflow. */ - function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { + function _deposit( + address caller, + address receiver, + uint256 assets, + uint256 shares + ) internal virtual { // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, // calls the vault, which is assumed not malicious. @@ -259,4 +282,4 @@ abstract contract ERC4626 is ERC20, IERC4626 { function _decimalsOffset() internal view virtual returns (uint8) { return 0; } -} \ No newline at end of file +} diff --git a/contracts/vendor/oz/IERC4626.sol b/contracts/vendor/oz/IERC4626.sol index bace5aaf01..e11727a727 100644 --- a/contracts/vendor/oz/IERC4626.sol +++ b/contracts/vendor/oz/IERC4626.sol @@ -187,7 +187,11 @@ interface IERC4626 is IERC20, IERC20Metadata { * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. * Those methods should be performed separately. */ - function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + function withdraw( + uint256 assets, + address receiver, + address owner + ) external returns (uint256 shares); /** * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, @@ -228,5 +232,9 @@ interface IERC4626 is IERC20, IERC20Metadata { * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. * Those methods should be performed separately. */ - function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); -} \ No newline at end of file + function redeem( + uint256 shares, + address receiver, + address owner + ) external returns (uint256 assets); +} From 1c77886d9a74a4bafb0dd3a87385a349e6fe469d Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 11 May 2023 13:40:24 -0300 Subject: [PATCH 230/462] prettier on config --- hardhat.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index c142a04d8e..6efd3320df 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -76,7 +76,7 @@ const config: HardhatUserConfig = { }, // gasPrice: 10_000_000_000, gasMultiplier: 1.015, // 1.5% buffer; seen failures on RToken deployment and asset refreshes - } + }, }, solidity: { compilers: [ From b13d9216166706a8b4eb5231445c324467c62bfd Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 11 May 2023 13:40:38 -0300 Subject: [PATCH 231/462] prettier on docs --- docs/writing-collateral-plugins.md | 40 ++++++++++++++++-------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/docs/writing-collateral-plugins.md b/docs/writing-collateral-plugins.md index 29951bfea3..01fe3d75d4 100644 --- a/docs/writing-collateral-plugins.md +++ b/docs/writing-collateral-plugins.md @@ -2,50 +2,52 @@ This document describes the general process a developer should follow when writing a new collateral plugin. -For details of what collateral plugins are and how they function, see [collateral.md](./collateral.md). Be sure to read and understand [collateral.md](./collateral.md) before beginning the process of writing a new collateral plugin. +For details of what collateral plugins are and how they function, see [collateral.md](./collateral.md). Be sure to read and understand [collateral.md](./collateral.md) before beginning the process of writing a new collateral plugin. ## Pre-implementation Questions -Here are some basic questions to answer before beginning to write a new collateral plugin. Think of the answers like an outline for an essay: they will help gauge how much work is required to write the plugin, they will guide the final implementation, and they will contain all of the human-readable details that can then be directly translated into code. +Here are some basic questions to answer before beginning to write a new collateral plugin. Think of the answers like an outline for an essay: they will help gauge how much work is required to write the plugin, they will guide the final implementation, and they will contain all of the human-readable details that can then be directly translated into code. 1. **How will this plugin define the different units?** - - {tok}: - - {ref}: - - {target}: - - {UoA}: + - {tok}: + - {ref}: + - {target}: + - {UoA}: 2. **Does the target collateral require a wrapper?** (eg. aTokens require the StaticAToken wrapper, to stabilize their rebasing nature) -3. **How will the 3 internal prices be defined?** (eg. chainlink feeds, exchange rate view functions, calculations involving multiple sources) For [chainlink feeds](https://data.chain.link/ethereum/mainnet), include the address, error (deviation threshold), and timeout (heartbeat). For on-chain exchange rates, include the function calls and/or github links to examples. - - {ref/tok}: - - {target/ref}: - - {UoA/target}: -4. **For each of these prices, what are the critical trust assumptions? Can any of these be manipulated within the course of a transaction?** - - eg. chainlink feeds require trusting the chainlink protocol and the individual oracles for that price feed - - eg. the frxETH/ETH exchange rate requires trusting the FRAX multisig to correctly push timely updates - - eg. yearn vaults can have their `pricePerShare` increased via direct vault donations +3. **How will the 3 internal prices be defined?** (eg. chainlink feeds, exchange rate view functions, calculations involving multiple sources) For [chainlink feeds](https://data.chain.link/ethereum/mainnet), include the address, error (deviation threshold), and timeout (heartbeat). For on-chain exchange rates, include the function calls and/or github links to examples. + - {ref/tok}: + - {target/ref}: + - {UoA/target}: +4. **For each of these prices, what are the critical trust assumptions? Can any of these be manipulated within the course of a transaction?** + - eg. chainlink feeds require trusting the chainlink protocol and the individual oracles for that price feed + - eg. the frxETH/ETH exchange rate requires trusting the FRAX multisig to correctly push timely updates + - eg. yearn vaults can have their `pricePerShare` increased via direct vault donations 5. **Are there any protocol-specific metrics that should be monitored to signal a default in the underlying collateral?** 6. **If this plugin requires unique unit & price abstractions, what do they look like?** 7. **What amount of revenue should this plugin hide? (a minimum of `1e-6`% is recommended, but some collateral may require higher thresholds, and, in rare cases, `0` can be used)** -8. **Are there rewards that can be claimed by holding this collateral? If so, how are they claimed?** Include a github link to the callable function or an example of how to claim. -9. **Does the collateral need to be "refreshed" in order to update its internal state before refreshing the plugin?** Include a github link to the callable function. +8. **Are there rewards that can be claimed by holding this collateral? If so, how are they claimed?** Include a github link to the callable function or an example of how to claim. +9. **Does the collateral need to be "refreshed" in order to update its internal state before refreshing the plugin?** Include a github link to the callable function. ## Implementation -The collateral plugin should be aptly named and placed in a folder (along with any additional contracts needed) named for the collateral's protocol under `contracts/plugins/assets/`. It should contain a README.md that gives an overview of the protocol being plugged into and contains the answers to the above questions. +The collateral plugin should be aptly named and placed in a folder (along with any additional contracts needed) named for the collateral's protocol under `contracts/plugins/assets/`. It should contain a README.md that gives an overview of the protocol being plugged into and contains the answers to the above questions. Details for setting up a local dev environment for this repo can be found in [dev-env.md](./dev-env.md). ## Testing -The test suite for a collateral plugin should be aptly named and placed in a folder named for the collateral's protocol under `test/individual-collateral/`. The test suite will not look or act like a normal .test.ts file, but rather it will import a generic test file and pass it an object containing the necessary fixture class that extends `CollateralTestSuiteFixtures`. You can find explanations of the various pieces that make up a `CollateralTestSuiteFixtures` in `test/plugins/individual-collateral/pluginTestTypes.ts`, and feel free to look at any existing collateral test suites for examples of how best to build this fixture. +The test suite for a collateral plugin should be aptly named and placed in a folder named for the collateral's protocol under `test/individual-collateral/`. The test suite will not look or act like a normal .test.ts file, but rather it will import a generic test file and pass it an object containing the necessary fixture class that extends `CollateralTestSuiteFixtures`. You can find explanations of the various pieces that make up a `CollateralTestSuiteFixtures` in `test/plugins/individual-collateral/pluginTestTypes.ts`, and feel free to look at any existing collateral test suites for examples of how best to build this fixture. -Collateral plugin tests must be run on a mainnet fork to ensure they properly integrate with the target protocol. Set the desired fork block in the `constants.ts` file you create for use in the plugin test suite. If used elsewhere, you can also set a fork block in `test/integration/fork-block-numbers.ts`. +Collateral plugin tests must be run on a mainnet fork to ensure they properly integrate with the target protocol. Set the desired fork block in the `constants.ts` file you create for use in the plugin test suite. If used elsewhere, you can also set a fork block in `test/integration/fork-block-numbers.ts`. In your `.env` file, set: + ``` FORK=1 ``` To run a specific test suite: + ``` npx hardhat test test/plugins/individual-collateral//.test.ts ``` From 89e6f460cc03a893cee13c8ea2b74b35c479c831 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 11 May 2023 15:00:19 -0400 Subject: [PATCH 232/462] comment nit --- contracts/p1/StRSR.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index eed5c4cf57..e47ea12a5d 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -690,7 +690,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab leak = 0; lastWithdrawRefresh = uint48(block.timestamp); - /// == Interaction == + /// == Refresh == assetRegistry.refresh(); } } From 6d6fee40ee5c28d7830bf4446a9457fba36ce13c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 11 May 2023 15:00:58 -0400 Subject: [PATCH 233/462] add RToken version to contract documentation for Facades --- contracts/facade/FacadeAct.sol | 1 + contracts/facade/FacadeRead.sol | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 93e70315a1..4e0b1181e0 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -7,6 +7,7 @@ import "../interfaces/IFacadeAct.sol"; /** * @title Facade * @notice A Facade to help batch compound actions that cannot be done from an EOA, solely. + * For use with ^3.0.0 RTokens. */ contract FacadeAct is IFacadeAct { function claimRewards(IRToken rToken) public { diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 73b0eb037e..f041af0879 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -15,7 +15,7 @@ import "../p1/StRSRVotes.sol"; /** * @title Facade - * @notice A UX-friendly layer for reading out the state of an RToken in summary views. + * @notice A UX-friendly layer for reading out the state of a ^3.0.0 RToken in summary views. * @custom:static-call - Use ethers callStatic() to get result after update; do not execute */ contract FacadeRead is IFacadeRead { From a68acfaa146a37e5825db397fd983f937e4acbb2 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 11 May 2023 15:17:37 -0400 Subject: [PATCH 234/462] add comment to TradingP1 mixin clarifying Multicall vs MulticallUpgradeable --- contracts/p1/mixins/Trading.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 90ab1cbfe3..431bb181bf 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -12,7 +12,10 @@ import "./Component.sol"; import "./RewardableLib.sol"; /// Abstract trading mixin for all Traders, to be paired with TradingLib -/// @dev See docs/security for discussion of Multicall safety +/// @dev The use of Multicall here instead of MulticallUpgradeable cannot be changed +/// changed without breaking <3.0.0 RTokens. The only difference in +/// MulticallUpgradeable is the 50 slot storage gap and an empty constructor. +/// It should be fine to leave the non-upgradeable Multicall here permanently. abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeable, ITrading { using FixLib for uint192; using SafeERC20Upgradeable for IERC20Upgradeable; From 3030aa2f64bd2f20cc9fbd5507b67130e2c5b3e3 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 11 May 2023 15:18:48 -0400 Subject: [PATCH 235/462] remove kruft mention of TradingLib --- contracts/p1/mixins/Trading.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 431bb181bf..5e2e25d177 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -11,7 +11,7 @@ import "../../libraries/Fixed.sol"; import "./Component.sol"; import "./RewardableLib.sol"; -/// Abstract trading mixin for all Traders, to be paired with TradingLib +/// Abstract trading mixin for BackingManager + RevenueTrader. /// @dev The use of Multicall here instead of MulticallUpgradeable cannot be changed /// changed without breaking <3.0.0 RTokens. The only difference in /// MulticallUpgradeable is the 50 slot storage gap and an empty constructor. From 655adbc2cb2b750841a55a86f143636a3c2a57ae Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 11 May 2023 16:44:36 -0300 Subject: [PATCH 236/462] add tweaks for better upgrade checking --- .../phase1-common/2_deploy_implementations.ts | 7 +++++-- tasks/upgrades/utils.ts | 15 ++++++++------- tasks/upgrades/validate-upgrade.ts | 2 ++ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/scripts/deployment/phase1-common/2_deploy_implementations.ts b/scripts/deployment/phase1-common/2_deploy_implementations.ts index a6dfd53e60..f023e1f786 100644 --- a/scripts/deployment/phase1-common/2_deploy_implementations.ts +++ b/scripts/deployment/phase1-common/2_deploy_implementations.ts @@ -190,7 +190,7 @@ async function main() { if (!upgrade) { backingMgrImplAddr = await upgrades.deployImplementation(BackingMgrImplFactory, { kind: 'uups', - unsafeAllow: ['external-library-linking'], + unsafeAllow: ['external-library-linking', 'delegatecall'], }) } else { backingMgrImplAddr = await upgrades.prepareUpgrade( @@ -198,7 +198,7 @@ async function main() { BackingMgrImplFactory, { kind: 'uups', - unsafeAllow: ['external-library-linking'], + unsafeAllow: ['external-library-linking', 'delegatecall'], } ) } @@ -340,6 +340,7 @@ async function main() { if (!upgrade) { rsrTraderImplAddr = await upgrades.deployImplementation(RevTraderImplFactory, { kind: 'uups', + unsafeAllow: ['delegatecall'], }) rTokenTraderImplAddr = rsrTraderImplAddr // Both equal in initial deployment } else { @@ -349,6 +350,7 @@ async function main() { RevTraderImplFactory, { kind: 'uups', + unsafeAllow: ['delegatecall'], } ) @@ -363,6 +365,7 @@ async function main() { RevTraderImplFactory, { kind: 'uups', + unsafeAllow: ['delegatecall'], } ) } else { diff --git a/tasks/upgrades/utils.ts b/tasks/upgrades/utils.ts index 26d400bea7..5618aecaed 100644 --- a/tasks/upgrades/utils.ts +++ b/tasks/upgrades/utils.ts @@ -18,13 +18,14 @@ export const validateDeployments = async ( } // Check basket lib defined - if (!deployments.basketLib) { - throw new Error( - `Missing deployed BasketLib for version ${version} in network ${hre.network.name}` - ) - } else if (!(await isValidContract(hre, deployments.basketLib))) { - throw new Error(`BasketLib contract not found in network ${hre.network.name}`) - } + // Only enable after 3.0.0 for future releases + // if (!deployments.basketLib) { + // throw new Error( + // `Missing deployed BasketLib for version ${version} in network ${hre.network.name}` + // ) + // } else if (!(await isValidContract(hre, deployments.basketLib))) { + // throw new Error(`BasketLib contract not found in network ${hre.network.name}`) + // } // Check Main implementation is defined if (!deployments.implementations.main) { diff --git a/tasks/upgrades/validate-upgrade.ts b/tasks/upgrades/validate-upgrade.ts index 0d4ad757d4..21c199e4fb 100644 --- a/tasks/upgrades/validate-upgrade.ts +++ b/tasks/upgrades/validate-upgrade.ts @@ -83,6 +83,7 @@ task('validate-upgrade', 'Validates if upgrade to new version is safe') deployments.implementations.components.rsrTrader, 'RevenueTraderP1', undefined, + undefined, ['delegatecall'] ) await validateUpgrade( @@ -90,6 +91,7 @@ task('validate-upgrade', 'Validates if upgrade to new version is safe') deployments.implementations.components.rTokenTrader, 'RevenueTraderP1', undefined, + undefined, ['delegatecall'] ) }) From c085b970af5dc71aab2cd9a3ab4ee213b76f352f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 12 May 2023 09:49:47 -0400 Subject: [PATCH 237/462] remove hardhat-storage-layout --- hardhat.config.ts | 1 - package.json | 4 +--- yarn.lock | 28 ---------------------------- 3 files changed, 1 insertion(+), 32 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 6efd3320df..c45c39c87b 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -7,7 +7,6 @@ import '@openzeppelin/hardhat-upgrades' import '@typechain/hardhat' import 'hardhat-contract-sizer' import 'hardhat-gas-reporter' -import 'hardhat-storage-layout' import 'solidity-coverage' import { useEnv } from '#/utils/env' diff --git a/package.json b/package.json index 29b3d5a206..615b17c035 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,7 @@ "prettier": "prettier --ignore-path .gitignore --loglevel warn --write \"./**/*.{js,ts,sol,json,md}\"", "size": "hardhat size-contracts", "slither": "python3 tools/slither.py", - "prepare": "husky install", - "storage": "hardhat compile && hardhat check" + "prepare": "husky install" }, "repository": { "type": "git", @@ -90,7 +89,6 @@ "hardhat": "^2.12.3", "hardhat-contract-sizer": "^2.4.0", "hardhat-gas-reporter": "^1.0.8", - "hardhat-storage-layout": "^0.1.7", "husky": "^7.0.0", "lodash": "^4.17.21", "lodash.get": "^4.4.2", diff --git a/yarn.lock b/yarn.lock index bf05859a5c..fbe9c08df9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3722,15 +3722,6 @@ __metadata: languageName: node linkType: hard -"console-table-printer@npm:^2.9.0": - version: 2.11.1 - resolution: "console-table-printer@npm:2.11.1" - dependencies: - simple-wcswidth: ^1.0.1 - checksum: 1c5ec6c5edb8f454ac78ce2708c96b29986db4c1f369b32acc1931f87658a65984a8cf8731ca548dea670ff04119c740a7d658eeba9633d0202983c71d957096 - languageName: node - linkType: hard - "convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.7.0": version: 1.8.0 resolution: "convert-source-map@npm:1.8.0" @@ -5721,17 +5712,6 @@ fsevents@~2.1.1: languageName: node linkType: hard -"hardhat-storage-layout@npm:^0.1.7": - version: 0.1.7 - resolution: "hardhat-storage-layout@npm:0.1.7" - dependencies: - console-table-printer: ^2.9.0 - peerDependencies: - hardhat: ^2.0.3 - checksum: 8d27d6b16c1ebdffa032ba6b99c61996df4601dcbaf7d770c474806b492a56de9d21b4190086ab40f50a3a1f2e9851cc81034a9cfd2e21368941977324f96fd4 - languageName: node - linkType: hard - "hardhat@npm:^2.12.3": version: 2.14.0 resolution: "hardhat@npm:2.14.0" @@ -8455,7 +8435,6 @@ fsevents@~2.1.1: hardhat: ^2.12.3 hardhat-contract-sizer: ^2.4.0 hardhat-gas-reporter: ^1.0.8 - hardhat-storage-layout: ^0.1.7 husky: ^7.0.0 lodash: ^4.17.21 lodash.get: ^4.4.2 @@ -8900,13 +8879,6 @@ resolve@1.17.0: languageName: node linkType: hard -"simple-wcswidth@npm:^1.0.1": - version: 1.0.1 - resolution: "simple-wcswidth@npm:1.0.1" - checksum: dc5bf4cb131d9c386825d1355add2b1ecc408b37dc2c2334edd7a1a4c9f527e6b594dedcdbf6d949bce2740c3a332e39af1183072a2d068e40d9e9146067a37f - languageName: node - linkType: hard - "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" From eaf449276b7a2ad723930fa017f1ae6eb2643c23 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 12 May 2023 10:51:50 -0400 Subject: [PATCH 238/462] implement + test withdrawalLeak gov param --- common/configuration.ts | 1 + contracts/interfaces/IDeployer.sol | 1 + contracts/interfaces/IStRSR.sol | 8 ++- contracts/p0/Deployer.sol | 3 +- contracts/p0/StRSR.sol | 49 +++++++++++++-- contracts/p1/Deployer.sol | 3 +- contracts/p1/StRSR.sol | 35 +++++++---- test/Main.test.ts | 3 +- test/ZZStRSR.test.ts | 99 +++++++++++++++++++++++++++++- test/fixtures.ts | 1 + test/integration/fixtures.ts | 1 + 11 files changed, 179 insertions(+), 25 deletions(-) diff --git a/common/configuration.ts b/common/configuration.ts index 8ab7565361..f291146471 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -316,6 +316,7 @@ export interface IConfig { longFreeze: BigNumber rewardRatio: BigNumber unstakingDelay: BigNumber + withdrawalLeak: BigNumber warmupPeriod: BigNumber tradingDelay: BigNumber batchAuctionLength: BigNumber diff --git a/contracts/interfaces/IDeployer.sol b/contracts/interfaces/IDeployer.sol index d7cd56a555..d338d1160e 100644 --- a/contracts/interfaces/IDeployer.sol +++ b/contracts/interfaces/IDeployer.sol @@ -34,6 +34,7 @@ struct DeploymentParams { // // === StRSR === uint48 unstakingDelay; // {s} the "thawing time" of staked RSR before withdrawal + uint192 withdrawalLeak; // {1} fraction of RSR that can be withdrawn without refresh // // === BasketHandler === uint48 warmupPeriod; // {s} how long to wait until issuance/trading after regaining SOUND diff --git a/contracts/interfaces/IStRSR.sol b/contracts/interfaces/IStRSR.sol index cef4d9d5cb..5c465d0ad0 100644 --- a/contracts/interfaces/IStRSR.sol +++ b/contracts/interfaces/IStRSR.sol @@ -95,6 +95,7 @@ interface IStRSR is IERC20MetadataUpgradeable, IERC20PermitUpgradeable, ICompone event UnstakingDelaySet(uint48 indexed oldVal, uint48 indexed newVal); event RewardRatioSet(uint192 indexed oldVal, uint192 indexed newVal); + event WithdrawalLeakSet(uint192 indexed oldVal, uint192 indexed newVal); // Initialization function init( @@ -102,7 +103,8 @@ interface IStRSR is IERC20MetadataUpgradeable, IERC20PermitUpgradeable, ICompone string memory name_, string memory symbol_, uint48 unstakingDelay_, - uint192 rewardRatio_ + uint192 rewardRatio_, + uint192 withdrawalLeak_ ) external; /// Gather and payout rewards from rsrTrader @@ -148,6 +150,10 @@ interface TestIStRSR is IStRSR { function setUnstakingDelay(uint48) external; + function withdrawalLeak() external view returns (uint48); + + function setWithdrawalLeak(uint192) external; + function increaseAllowance(address, uint256) external returns (bool); function decreaseAllowance(address, uint256) external returns (bool); diff --git a/contracts/p0/Deployer.sol b/contracts/p0/Deployer.sol index 652a557a83..70689b8f9b 100644 --- a/contracts/p0/Deployer.sol +++ b/contracts/p0/Deployer.sol @@ -128,7 +128,8 @@ contract DeployerP0 is IDeployer, Versioned { stRSRName, stRSRSymbol, params.unstakingDelay, - params.rewardRatio + params.rewardRatio, + params.withdrawalLeak ); } diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index 521bceec80..ea60d1ab8d 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -35,6 +35,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { uint48 public constant MIN_UNSTAKING_DELAY = PERIOD * 2; // {s} uint48 public constant MAX_UNSTAKING_DELAY = 31536000; // {s} 1 year uint192 public constant MAX_REWARD_RATIO = 1e18; + uint192 public constant MAX_WITHDRAWAL_LEAK = 3e17; // {1} 30% // ==== ERC20Permit ==== @@ -94,16 +95,22 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { // Min exchange rate {qRSR/qStRSR} (compile-time constant) uint192 private constant MIN_EXCHANGE_RATE = uint192(1e9); // 1e-9 + // Withdrawal Leak + uint192 private leaked; // {1} stake fraction that has withdrawn without a refresh + uint48 private lastWithdrawRefresh; // {s} timestamp of last refresh() during withdraw() + // ==== Gov Params ==== uint48 public unstakingDelay; uint192 public rewardRatio; + uint192 public withdrawalLeak; // {1} gov param -- % RSR that can be withdrawn without refresh function init( IMain main_, string memory name_, string memory symbol_, uint48 unstakingDelay_, - uint192 rewardRatio_ + uint192 rewardRatio_, + uint192 withdrawalLeak_ ) public initializer { require(bytes(name_).length > 0, "name empty"); require(bytes(symbol_).length > 0, "symbol empty"); @@ -115,6 +122,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { rsrRewardsAtLastPayout = main_.rsr().balanceOf(address(this)); setUnstakingDelay(unstakingDelay_); setRewardRatio(rewardRatio_); + setWithdrawalLeak(withdrawalLeak_); era = 1; } @@ -187,12 +195,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { /// Complete delayed staking for an account, up to but not including draft ID `endId` /// @custom:interaction function withdraw(address account, uint256 endId) external notTradingPausedOrFrozen { - // Call state keepers - main.poke(); - IBasketHandler bh = main.basketHandler(); - require(bh.fullyCollateralized(), "RToken uncollateralized"); - require(bh.isReady(), "basket not ready"); Withdrawal[] storage queue = withdrawals[account]; if (endId == 0) return; @@ -213,6 +216,13 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { queue[i].stakeAmount = 0; } + // Refresh + leakyRefresh(total); + + // Checks + require(bh.fullyCollateralized(), "RToken uncollateralized"); + require(bh.isReady(), "basket not ready"); + // Execute accumulated withdrawals emit UnstakingCompleted(start, i, era, account, total); main.rsr().safeTransfer(account, total); @@ -349,6 +359,27 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { } } + /// Refresh if too much RSR has exited since the last refresh occurred + /// @param rsrWithdrawal {qRSR} How much RSR is being withdrawn + function leakyRefresh(uint256 rsrWithdrawal) private { + uint48 lastRefresh = main.assetRegistry().lastRefresh(); // {s} + + // {1} Assumption: rsrWithdrawal has already been taken out of draftRSR + uint192 withdrawal = toFix(rsrWithdrawal).divu( + rsrBacking + rsrBeingWithdrawn() + rsrWithdrawal, + CEIL + ); + + bool refreshedElsewhere = lastWithdrawRefresh != lastRefresh; + leaked = refreshedElsewhere ? withdrawal : leaked + withdrawal; + + if (leaked > withdrawalLeak) { + leaked = 0; + main.assetRegistry().refresh(); + } + lastWithdrawRefresh = main.assetRegistry().lastRefresh(); + } + function exchangeRate() public view returns (uint192) { return (rsrBacking == 0 || totalStaked == 0) ? FIX_ONE : divuu(rsrBacking, totalStaked); } @@ -572,4 +603,10 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { emit RewardRatioSet(rewardRatio, val); rewardRatio = val; } + + function setWithdrawalLeak(uint192 val) public governance { + require(val <= MAX_WITHDRAWAL_LEAK, "invalid withdrawalLeak"); + emit WithdrawalLeakSet(withdrawalLeak, val); + withdrawalLeak = val; + } } diff --git a/contracts/p1/Deployer.sol b/contracts/p1/Deployer.sol index 41472dd020..2077fc4144 100644 --- a/contracts/p1/Deployer.sol +++ b/contracts/p1/Deployer.sol @@ -218,7 +218,8 @@ contract DeployerP1 is IDeployer, Versioned { stRSRName, stRSRSymbol, params.unstakingDelay, - params.rewardRatio + params.rewardRatio, + params.withdrawalLeak ); } diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index e47ea12a5d..f132a2d3e1 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -152,10 +152,11 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // === 3.0.0 === // The fraction of draftRSR + stakeRSR that may exit without a refresh - uint192 private constant MAX_LEAK = 5e15; // {1} 0.5% + uint192 private constant MAX_WITHDRAWAL_LEAK = 3e17; // {1} 30% - uint192 private leak; // {1} stake fraction that has withdrawn without a refresh - uint48 private lastWithdrawRefresh; // {s} timestamp of last refresh() during withdraw() + uint192 private leaked; // {1} stake fraction that has withdrawn without a refresh + uint48 private withdrawRefresh; // {s} timestamp of last refresh() during withdraw() + uint192 public withdrawalLeak; // {1} gov param -- % RSR that can be withdrawn without refresh // ====================== @@ -170,7 +171,8 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab string calldata name_, string calldata symbol_, uint48 unstakingDelay_, - uint192 rewardRatio_ + uint192 rewardRatio_, + uint192 withdrawalLeak_ ) external initializer { require(bytes(name_).length > 0, "name empty"); require(bytes(symbol_).length > 0, "symbol empty"); @@ -188,6 +190,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab rsrRewardsAtLastPayout = main_.rsr().balanceOf(address(this)); setUnstakingDelay(unstakingDelay_); setRewardRatio(rewardRatio_); + setWithdrawalLeak(withdrawalLeak_); beginEra(); beginDraftEra(); @@ -677,18 +680,18 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab /// Effects-Refresh function leakyRefresh(uint256 rsrWithdrawal) private { uint48 lastRefresh = assetRegistry.lastRefresh(); // {s} - bool refreshedElsewhere = lastWithdrawRefresh != lastRefresh; // Assumption: rsrWithdrawal has already been taken out of draftRSR - uint192 withdrawal = divuu(rsrWithdrawal, stakeRSR + draftRSR + rsrWithdrawal); // {1} + uint256 totalRSR = stakeRSR + draftRSR + rsrWithdrawal; // {qRSR} + uint192 withdrawal = _safeWrap((rsrWithdrawal * FIX_ONE + totalRSR - 1) / totalRSR); // {1} // == Effects == - leak = refreshedElsewhere ? withdrawal : leak + withdrawal; - lastWithdrawRefresh = lastRefresh; + leaked = withdrawRefresh != lastRefresh ? withdrawal : leaked + withdrawal; + withdrawRefresh = lastRefresh; - if (leak > MAX_LEAK) { - leak = 0; - lastWithdrawRefresh = uint48(block.timestamp); + if (leaked > withdrawalLeak) { + leaked = 0; + withdrawRefresh = uint48(block.timestamp); /// == Refresh == assetRegistry.refresh(); @@ -929,6 +932,14 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab rewardRatio = val; } + /// @custom:governance + function setWithdrawalLeak(uint192 val) public { + governanceOnly(); + require(val <= MAX_WITHDRAWAL_LEAK, "invalid withdrawalLeak"); + emit WithdrawalLeakSet(withdrawalLeak, val); + withdrawalLeak = val; + } + // contract-size-saver // solhint-disable-next-line no-empty-blocks function governanceOnly() private governance {} @@ -938,5 +949,5 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[29] private __gap; + uint256[28] private __gap; } diff --git a/test/Main.test.ts b/test/Main.test.ts index 114a9a0fa0..7aea0cb6f3 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -425,7 +425,8 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { 'stRTKNRSR Token', 'stRTKNRSR', config.unstakingDelay, - config.rewardRatio + config.rewardRatio, + config.withdrawalLeak ) ).to.be.revertedWith('Initializable: contract is already initialized') }) diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 09011b46be..855709bc69 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -15,6 +15,7 @@ import { StRSRP0, StRSRP1Votes, StaticATokenMock, + IAssetRegistry, TestIBackingManager, TestIBasketHandler, TestIMain, @@ -69,6 +70,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { let basketHandler: TestIBasketHandler let rToken: TestIRToken let facade: FacadeRead + let assetRegistry: IAssetRegistry // StRSR let stRSR: TestIStRSR @@ -158,6 +160,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { basketHandler, rToken, facade, + assetRegistry, } = await loadFixture(defaultFixture)) // Mint initial amounts of RSR @@ -226,10 +229,24 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { } await expect( - newStRSR.init(main.address, '', 'rtknRSR', config.unstakingDelay, config.rewardRatio) + newStRSR.init( + main.address, + '', + 'rtknRSR', + config.unstakingDelay, + config.rewardRatio, + config.withdrawalLeak + ) ).to.be.revertedWith('name empty') await expect( - newStRSR.init(main.address, 'rtknRSR Token', '', config.unstakingDelay, config.rewardRatio) + newStRSR.init( + main.address, + 'rtknRSR Token', + '', + config.unstakingDelay, + config.rewardRatio, + config.withdrawalLeak + ) ).to.be.revertedWith('symbol empty') }) }) @@ -272,7 +289,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { await expect(stRSR.connect(owner).setRewardRatio(newRatio)) .to.emit(stRSR, 'RewardRatioSet') - .withArgs(stRSR.rewardRatio, newRatio) + .withArgs(config.rewardRatio, newRatio) expect(await stRSR.rewardRatio()).to.equal(newRatio) @@ -287,6 +304,27 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { ) }) + it('Should allow to update withdrawalLeak if Owner and perform validations', async () => { + // Setup a new value + const newLeak: BigNumber = fp('0.1') // 10% + + await expect(stRSR.connect(owner).setWithdrawalLeak(newLeak)) + .to.emit(stRSR, 'WithdrawalLeakSet') + .withArgs(config.withdrawalLeak, newLeak) + + expect(await stRSR.withdrawalLeak()).to.equal(newLeak) + + // Try to update again if not owner + await expect(stRSR.connect(addr1).setWithdrawalLeak(bn('0'))).to.be.revertedWith( + 'governance only' + ) + + // Cannot update with withdrawalLeak > max + await expect(stRSR.connect(owner).setWithdrawalLeak(fp('0.3').add(1))).to.be.revertedWith( + 'invalid withdrawalLeak' + ) + }) + it('Should payout rewards before updating the reward ratio', async () => { const startBal = await rsr.balanceOf(addr1.address) const stakeAmt = bn('100e18') @@ -640,6 +678,61 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { expect(await rsr.balanceOf(addr1.address)).to.equal(initialBal.sub(amount)) // RSR wasn't returned }) + describe('Withdrawal Leak', () => { + const withdrawalLeak = fp('0.1') // 10% + const stake = bn('1000e18') + + beforeEach(async () => { + stkWithdrawalDelay = bn(await stRSR.unstakingDelay()).toNumber() + + // Stake + await rsr.connect(addr1).approve(stRSR.address, stake) + await stRSR.connect(addr1).stake(stake) + + // Set Withdrawal Leak + await expect(stRSR.connect(owner).setWithdrawalLeak(withdrawalLeak)) + .to.emit(stRSR, 'WithdrawalLeakSet') + .withArgs(config.withdrawalLeak, withdrawalLeak) + }) + + it('Should refresh above withdrawal leak only', async () => { + const withdrawal = stake.mul(withdrawalLeak).div(fp('1')) + await stRSR.connect(addr1).unstake(withdrawal) + await stRSR.connect(addr1).unstake(1) + await stRSR.connect(addr1).unstake(1) + + // Move forward past stakingWithdrawalDelaylay + await advanceToTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay) + + let lastRefresh = await assetRegistry.lastRefresh() + + // Should not refresh + await stRSR.withdraw(addr1.address, 1) + expect(await assetRegistry.lastRefresh()).to.eq(lastRefresh) + + // Should refresh + await stRSR.withdraw(addr1.address, 2) + expect(await assetRegistry.lastRefresh()).to.be.gt(lastRefresh) + lastRefresh = await assetRegistry.lastRefresh() + + // Should not refresh + await stRSR.withdraw(addr1.address, 3) + expect(await assetRegistry.lastRefresh()).to.eq(lastRefresh) + }) + + it('Should prevent unstaking', async () => { + const withdrawal = stake.mul(withdrawalLeak).div(fp('1')).add(1) + await stRSR.connect(addr1).unstake(withdrawal) + + // Move forward past stakingWithdrawalDelaylay + await advanceToTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay) + + // Depeg collateral + await setOraclePrice(collateral1.address, bn('0.5e8')) + await expect(stRSR.withdraw(addr1.address, 1)).to.be.revertedWith('basket not ready') + }) + }) + context('With deposits and withdrawals', () => { let amount1: BigNumber let amount2: BigNumber diff --git a/test/fixtures.ts b/test/fixtures.ts index e99720379b..f0b0baad8f 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -438,6 +438,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = longFreeze: bn('2592000'), // 30 days rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks + withdrawalLeak: fp('0'), // 0%; always refresh warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index 2d803d8e80..aa9b5e43fe 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -646,6 +646,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = longFreeze: bn('2592000'), // 30 days rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks + withdrawalLeak: fp('0'), // 0%; always refresh warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes From e127370425dd52aa6bfabda12982c365cc409e6a Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 12 May 2023 10:52:10 -0400 Subject: [PATCH 239/462] nit: var name --- contracts/p1/StRSR.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index f132a2d3e1..41917de3cc 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -155,7 +155,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab uint192 private constant MAX_WITHDRAWAL_LEAK = 3e17; // {1} 30% uint192 private leaked; // {1} stake fraction that has withdrawn without a refresh - uint48 private withdrawRefresh; // {s} timestamp of last refresh() during withdraw() + uint48 private lastWithdrawRefresh; // {s} timestamp of last refresh() during withdraw() uint192 public withdrawalLeak; // {1} gov param -- % RSR that can be withdrawn without refresh // ====================== @@ -686,12 +686,12 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab uint192 withdrawal = _safeWrap((rsrWithdrawal * FIX_ONE + totalRSR - 1) / totalRSR); // {1} // == Effects == - leaked = withdrawRefresh != lastRefresh ? withdrawal : leaked + withdrawal; - withdrawRefresh = lastRefresh; + leaked = lastWithdrawRefresh != lastRefresh ? withdrawal : leaked + withdrawal; + lastWithdrawRefresh = lastRefresh; if (leaked > withdrawalLeak) { leaked = 0; - withdrawRefresh = uint48(block.timestamp); + lastWithdrawRefresh = uint48(block.timestamp); /// == Refresh == assetRegistry.refresh(); From 127786375bc723fb5a7bc86f2dade0f3fb3a193f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 12 May 2023 11:12:38 -0400 Subject: [PATCH 240/462] solve contract size --- contracts/p1/StRSR.sol | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 41917de3cc..a24178bb9e 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -223,18 +223,10 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab if (!main.frozen()) _payoutRewards(); - // Compute stake amount - // This is not an overflow risk according to our expected ranges: - // rsrAmount <= 1e29, totalStaked <= 1e38, 1e29 * 1e38 < 2^256. - // stakeAmount: how many stRSR the user shall receive. - // pick stakeAmount as big as we can such that (newTotalStakes <= newStakeRSR * stakeRate) - uint256 newStakeRSR = stakeRSR + rsrAmount; - // newTotalStakes: {qStRSR} = D18{qStRSR/qRSR} * {qRSR} / D18 - uint256 newTotalStakes = (stakeRate * newStakeRSR) / FIX_ONE; - uint256 stakeAmount = newTotalStakes - totalStakes; + address account = _msgSender(); // Update staked - address account = _msgSender(); + uint256 stakeAmount = toDrafts(rsrAmount); stakeRSR += rsrAmount; _mint(account, stakeAmount); @@ -385,11 +377,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab emit UnstakingCancelled(firstId, endId, draftEra, account, rsrAmount); - uint256 newStakeRSR = stakeRSR + rsrAmount; - // newTotalStakes: {qStRSR} = D18{qStRSR/qRSR} * {qRSR} / D18 - uint256 newTotalStakes = (stakeRate * newStakeRSR) / FIX_ONE; - uint256 stakeAmount = newTotalStakes - totalStakes; - + uint256 stakeAmount = toDrafts(rsrAmount); // {qDraft} stakeRSR += rsrAmount; _mint(account, stakeAmount); } @@ -698,6 +686,18 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab } } + /// @return {qDraft} The given rsrAmount in drafts + function toDrafts(uint256 rsrAmount) private view returns (uint256) { + // This is not an overflow risk according to our expected ranges: + // rsrAmount <= 1e29, totalStaked <= 1e38, 1e29 * 1e38 < 2^256. + // stakeAmount: how many stRSR the user shall receive. + // pick stakeAmount as big as we can such that (newTotalStakes <= newStakeRSR * stakeRate) + uint256 newStakeRSR = stakeRSR + rsrAmount; + // newTotalStakes: {qStRSR} = D18{qStRSR/qRSR} * {qRSR} / D18 + uint256 newTotalStakes = (stakeRate * newStakeRSR) / FIX_ONE; + return newTotalStakes - totalStakes; + } + // contract-size-saver // solhint-disable-next-line no-empty-blocks function requireNotTradingPausedOrFrozen() private notTradingPausedOrFrozen {} From 7309e1d535786f405af5ec28b8b56460bc8b3f24 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 12 May 2023 11:20:49 -0400 Subject: [PATCH 241/462] even simpler --- contracts/p1/StRSR.sol | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index a24178bb9e..59e40812e7 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -75,7 +75,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // Drafts: share of the withdrawing tokens. Not transferrable and not revenue-earning. struct CumulativeDraft { // Avoid re-using uint192 in order to avoid confusion with our type system; 176 is enough - uint176 drafts; // Total amount of drafts that will become available // {qDraft} + uint176 drafts; // Total amount of drafts that will become available // {qDrafts} uint64 availableAt; // When the last of the drafts will become available } // draftEra => ({account} => {drafts}) @@ -223,18 +223,11 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab if (!main.frozen()) _payoutRewards(); - address account = _msgSender(); - - // Update staked - uint256 stakeAmount = toDrafts(rsrAmount); - stakeRSR += rsrAmount; - _mint(account, stakeAmount); - - // Transfer RSR from account to this contract - emit Staked(era, account, rsrAmount, stakeAmount); + // Mint new stakes + mintStakes(_msgSender(), rsrAmount); // == Interactions == - IERC20Upgradeable(address(rsr)).safeTransferFrom(account, address(this), rsrAmount); + IERC20Upgradeable(address(rsr)).safeTransferFrom(_msgSender(), address(this), rsrAmount); } /// Begins a delayed unstaking for `amount` StRSR @@ -377,9 +370,8 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab emit UnstakingCancelled(firstId, endId, draftEra, account, rsrAmount); - uint256 stakeAmount = toDrafts(rsrAmount); // {qDraft} - stakeRSR += rsrAmount; - _mint(account, stakeAmount); + // Mint new stakes + mintStakes(account, rsrAmount); } /// @param rsrAmount {qRSR} @@ -686,8 +678,9 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab } } - /// @return {qDraft} The given rsrAmount in drafts - function toDrafts(uint256 rsrAmount) private view returns (uint256) { + /// Mint stakes to an account + /// @param rsrAmount {qRSR} The RSR amount being staked + function mintStakes(address account, uint256 rsrAmount) private { // This is not an overflow risk according to our expected ranges: // rsrAmount <= 1e29, totalStaked <= 1e38, 1e29 * 1e38 < 2^256. // stakeAmount: how many stRSR the user shall receive. @@ -695,7 +688,13 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab uint256 newStakeRSR = stakeRSR + rsrAmount; // newTotalStakes: {qStRSR} = D18{qStRSR/qRSR} * {qRSR} / D18 uint256 newTotalStakes = (stakeRate * newStakeRSR) / FIX_ONE; - return newTotalStakes - totalStakes; + uint256 stakeAmount = newTotalStakes - totalStakes; + + stakeRSR += rsrAmount; + _mint(account, stakeAmount); + + // Transfer RSR from account to this contract + emit Staked(era, account, rsrAmount, stakeAmount); } // contract-size-saver From bfa151a006d293178309ed8c42d87cc42d1255e6 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 12 May 2023 11:22:34 -0400 Subject: [PATCH 242/462] nit --- contracts/p1/StRSR.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 59e40812e7..99d66d70d6 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -678,7 +678,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab } } - /// Mint stakes to an account + /// Mint stakes corresponding to rsrAmount to an account /// @param rsrAmount {qRSR} The RSR amount being staked function mintStakes(address account, uint256 rsrAmount) private { // This is not an overflow risk according to our expected ranges: @@ -690,10 +690,9 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab uint256 newTotalStakes = (stakeRate * newStakeRSR) / FIX_ONE; uint256 stakeAmount = newTotalStakes - totalStakes; + // Transfer RSR from account to this contract stakeRSR += rsrAmount; _mint(account, stakeAmount); - - // Transfer RSR from account to this contract emit Staked(era, account, rsrAmount, stakeAmount); } From efa2a74effed2fe638e6ce058cd974a3e6806aaa Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 12 May 2023 11:45:52 -0400 Subject: [PATCH 243/462] fix StRSR extreme tests --- test/ZZStRSR.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 855709bc69..c427a9b68d 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -54,7 +54,7 @@ const describeGas = const describeExtreme = IMPLEMENTATION == Implementation.P1 && useEnv('EXTREME') ? describe.only : describe -describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { +describe(`StRSRP${IMPLEMENTATION} contract`, () => { let owner: SignerWithAddress let addr1: SignerWithAddress let addr2: SignerWithAddress @@ -3112,7 +3112,7 @@ describeExtreme(`StRSRP${IMPLEMENTATION} contract`, () => { }) }) - describe(`Extreme Bounds ${SLOW ? 'slow mode' : 'fast mode'})`, () => { + describeExtreme(`Extreme Bounds ${SLOW ? 'slow mode' : 'fast mode'})`, () => { // Dimensions // // StRSR economics can be broken down into 4 "places" that RSR can be. From 7a8703b8bd2ebafa66bf9902ee0e7aebcb67d705 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 12 May 2023 11:50:12 -0400 Subject: [PATCH 244/462] only run extreme tests when EXTREME=1 --- test/Furnace.test.ts | 2 +- test/RToken.test.ts | 2 +- test/ZTradingExtremes.test.ts | 2 +- test/ZZStRSR.test.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index 4b4b0bf01a..12d8da2244 100644 --- a/test/Furnace.test.ts +++ b/test/Furnace.test.ts @@ -32,7 +32,7 @@ const describeGas = IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip const describeExtreme = - IMPLEMENTATION == Implementation.P1 && useEnv('EXTREME') ? describe.only : describe + IMPLEMENTATION == Implementation.P1 && useEnv('EXTREME') ? describe.only : describe.skip describe(`FurnaceP${IMPLEMENTATION} contract`, () => { let owner: SignerWithAddress diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 35b3d4aa9f..87e2321f0b 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -64,7 +64,7 @@ const describeGas = IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip const describeExtreme = - IMPLEMENTATION == Implementation.P1 && useEnv('EXTREME') ? describe.only : describe + IMPLEMENTATION == Implementation.P1 && useEnv('EXTREME') ? describe.only : describe.skip describe(`RTokenP${IMPLEMENTATION} contract`, () => { let owner: SignerWithAddress diff --git a/test/ZTradingExtremes.test.ts b/test/ZTradingExtremes.test.ts index 9088b9fe2d..103afb886d 100644 --- a/test/ZTradingExtremes.test.ts +++ b/test/ZTradingExtremes.test.ts @@ -42,7 +42,7 @@ import { setOraclePrice } from './utils/oracles' import { useEnv } from '#/utils/env' const describeExtreme = - IMPLEMENTATION == Implementation.P1 && useEnv('EXTREME') ? describe.only : describe + IMPLEMENTATION == Implementation.P1 && useEnv('EXTREME') ? describe.only : describe.skip describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, () => { let owner: SignerWithAddress diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index c427a9b68d..ecc31f6822 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -52,7 +52,7 @@ const describeGas = IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip const describeExtreme = - IMPLEMENTATION == Implementation.P1 && useEnv('EXTREME') ? describe.only : describe + IMPLEMENTATION == Implementation.P1 && useEnv('EXTREME') ? describe.only : describe.skip describe(`StRSRP${IMPLEMENTATION} contract`, () => { let owner: SignerWithAddress From db72f7b69abaf1f3d704948b8f46bfeba737edbd Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 12 May 2023 12:34:57 -0400 Subject: [PATCH 245/462] fix failing tests --- contracts/interfaces/IStRSR.sol | 2 +- test/Upgradeability.test.ts | 9 ++++++++- .../aave/ATokenFiatCollateral.test.ts | 1 + .../compoundv2/CTokenFiatCollateral.test.ts | 1 + 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/contracts/interfaces/IStRSR.sol b/contracts/interfaces/IStRSR.sol index 5c465d0ad0..cf503404f7 100644 --- a/contracts/interfaces/IStRSR.sol +++ b/contracts/interfaces/IStRSR.sol @@ -150,7 +150,7 @@ interface TestIStRSR is IStRSR { function setUnstakingDelay(uint48) external; - function withdrawalLeak() external view returns (uint48); + function withdrawalLeak() external view returns (uint192); function setWithdrawalLeak(uint192) external; diff --git a/test/Upgradeability.test.ts b/test/Upgradeability.test.ts index e2e6e801aa..3a6e62135c 100644 --- a/test/Upgradeability.test.ts +++ b/test/Upgradeability.test.ts @@ -366,7 +366,14 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { it('Should deploy valid implementation - StRSR', async () => { const newStRSR: StRSRP1Votes = await upgrades.deployProxy( StRSRFactory, - [main.address, 'rtknRSR Token', 'rtknRSR', config.unstakingDelay, config.rewardRatio], + [ + main.address, + 'rtknRSR Token', + 'rtknRSR', + config.unstakingDelay, + config.rewardRatio, + config.withdrawalLeak, + ], { initializer: 'init', kind: 'uups', diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index e39bb678e3..2b32897bff 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -117,6 +117,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi longFreeze: bn('2592000'), // 30 days rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks + withdrawalLeak: fp('0'), // 0%; always refresh tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes dutchAuctionLength: bn('600'), // 10 minutes diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index e7b1ef6868..fa67c78178 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -117,6 +117,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi longFreeze: bn('2592000'), // 30 days rewardRatio: bn('1069671574938'), // approx. half life of 90 days unstakingDelay: bn('1209600'), // 2 weeks + withdrawalLeak: fp('0'), // 0%; always refresh warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes From 0192cda6af416e050b8518fc1965ff4b5ff60996 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sun, 14 May 2023 22:27:19 -0400 Subject: [PATCH 246/462] gas snapshots --- test/__snapshots__/Broker.test.ts.snap | 16 ++++----- test/__snapshots__/FacadeWrite.test.ts.snap | 6 ++-- test/__snapshots__/Furnace.test.ts.snap | 34 +++++++++---------- test/__snapshots__/Main.test.ts.snap | 12 +++---- test/__snapshots__/RToken.test.ts.snap | 12 +++---- .../Recollateralization.test.ts.snap | 14 ++++---- test/__snapshots__/Revenues.test.ts.snap | 28 +++++++-------- test/__snapshots__/ZZStRSR.test.ts.snap | 18 +++++----- .../__snapshots__/Collateral.test.ts.snap | 10 +++--- .../RewardableERC20Vault.test.ts.snap | 12 +++---- .../__snapshots__/MaxBasketSize.test.ts.snap | 18 +++++----- 11 files changed, 88 insertions(+), 92 deletions(-) diff --git a/test/__snapshots__/Broker.test.ts.snap b/test/__snapshots__/Broker.test.ts.snap index 561e9b8d22..d894fc2cd7 100644 --- a/test/__snapshots__/Broker.test.ts.snap +++ b/test/__snapshots__/Broker.test.ts.snap @@ -1,21 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `233033`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `233100`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `350111`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `350227`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `352247`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `352276`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `354385`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `354414`; exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63429`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Initialize Trade 1`] = `451337`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Initialize Trade 1`] = `451427`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 1`] = `552332`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 1`] = `552471`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 2`] = `540170`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 2`] = `540309`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 3`] = `542308`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 3`] = `542447`; exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Settle Trade 1`] = `113056`; diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index 54b3999448..b5b5e716b1 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8876425`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `9202775`; -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5666876`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464736`; -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Without governance 1`] = `333864`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Without governance 1`] = `114917`; diff --git a/test/__snapshots__/Furnace.test.ts.snap b/test/__snapshots__/Furnace.test.ts.snap index b38156e039..9cdf75c39e 100644 --- a/test/__snapshots__/Furnace.test.ts.snap +++ b/test/__snapshots__/Furnace.test.ts.snap @@ -1,35 +1,35 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 1`] = `84028`; +exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 1`] = `83816`; -exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 2`] = `92765`; +exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 2`] = `92565`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 1`] = `84028`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 1`] = `83816`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 2`] = `81248`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 2`] = `81048`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 3`] = `81248`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 3`] = `81048`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 4`] = `81248`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 4`] = `81048`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 5`] = `81248`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 5`] = `81048`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 6`] = `81248`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 6`] = `81048`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 7`] = `81248`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 7`] = `81048`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 8`] = `81248`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 8`] = `81048`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 9`] = `81248`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 9`] = `81048`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 10`] = `81248`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 10`] = `81048`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 11`] = `81248`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 11`] = `81048`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 1`] = `64128`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 1`] = `63916`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 2`] = `80760`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 2`] = `80548`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 3`] = `81248`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 3`] = `81048`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 4`] = `40967`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 4`] = `40755`; diff --git a/test/__snapshots__/Main.test.ts.snap b/test/__snapshots__/Main.test.ts.snap index 906c5f3264..5d8cce6b50 100644 --- a/test/__snapshots__/Main.test.ts.snap +++ b/test/__snapshots__/Main.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `288006`; +exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `341691`; -exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `192869`; +exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `192993`; -exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `192869`; +exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `192993`; -exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `169302`; +exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `164122`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `83511`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80394`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `73023`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `69906`; diff --git a/test/__snapshots__/RToken.test.ts.snap b/test/__snapshots__/RToken.test.ts.snap index 858ed29909..d884307c95 100644 --- a/test/__snapshots__/RToken.test.ts.snap +++ b/test/__snapshots__/RToken.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `750756`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `772417`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `576782`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `610624`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `515671`; +exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `586825`; -exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56592`; +exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56570`; -exports[`RTokenP1 contract Gas Reporting Transfer 2`] = `34692`; +exports[`RTokenP1 contract Gas Reporting Transfer 2`] = `34670`; -exports[`RTokenP1 contract Gas Reporting Transfer 3`] = `51792`; +exports[`RTokenP1 contract Gas Reporting Transfer 3`] = `51770`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 16319f1692..59bed92f22 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1386334`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1387984`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1499583`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1514304`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1754914`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1755363`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1699249`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1699806`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184833`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1789003`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1790090`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `212933`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `212916`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index 51294f5db2..189c3841f3 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -1,29 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 1`] = `169846`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 1`] = `165168`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `169788`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `165132`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `169788`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `165132`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `251305`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `208818`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `217047`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229482`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `217047`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212382`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `721689`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `758101`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `973038`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1288954`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `243131`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `320785`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `195284`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `273846`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `704589`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `741001`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `46886`; - -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 7`] = `171806`; - -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 8`] = `43755`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `251645`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index 9b3e18127a..107dfcea64 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`StRSRP1 contract Gas Reporting Stake 1`] = `152014`; +exports[`StRSRP1 contract Gas Reporting Stake 1`] = `152134`; -exports[`StRSRP1 contract Gas Reporting Stake 2`] = `147214`; +exports[`StRSRP1 contract Gas Reporting Stake 2`] = `147334`; -exports[`StRSRP1 contract Gas Reporting Transfer 1`] = `63289`; +exports[`StRSRP1 contract Gas Reporting Transfer 1`] = `63389`; -exports[`StRSRP1 contract Gas Reporting Transfer 2`] = `41389`; +exports[`StRSRP1 contract Gas Reporting Transfer 2`] = `41489`; -exports[`StRSRP1 contract Gas Reporting Transfer 3`] = `58501`; +exports[`StRSRP1 contract Gas Reporting Transfer 3`] = `58601`; -exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `222532`; +exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `222609`; -exports[`StRSRP1 contract Gas Reporting Unstake 2`] = `139681`; +exports[`StRSRP1 contract Gas Reporting Unstake 2`] = `139758`; -exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `496786`; +exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `555770`; -exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `467652`; +exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `509774`; diff --git a/test/plugins/__snapshots__/Collateral.test.ts.snap b/test/plugins/__snapshots__/Collateral.test.ts.snap index 74894c925a..cc9cc3ac12 100644 --- a/test/plugins/__snapshots__/Collateral.test.ts.snap +++ b/test/plugins/__snapshots__/Collateral.test.ts.snap @@ -1,11 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral contracts Gas Reporting Force Updates - Hard Default - ATokens/CTokens 1`] = `69710`; +exports[`Collateral contracts Gas Reporting Force Updates - Hard Default - ATokens/CTokens 1`] = `69709`; -exports[`Collateral contracts Gas Reporting Force Updates - Hard Default - ATokens/CTokens 2`] = `70846`; +exports[`Collateral contracts Gas Reporting Force Updates - Hard Default - ATokens/CTokens 2`] = `70845`; -exports[`Collateral contracts Gas Reporting Force Updates - Soft Default 1`] = `59421`; +exports[`Collateral contracts Gas Reporting Force Updates - Soft Default 1`] = `59399`; -exports[`Collateral contracts Gas Reporting Force Updates - Soft Default 2`] = `52153`; +exports[`Collateral contracts Gas Reporting Force Updates - Soft Default 2`] = `52131`; -exports[`Collateral contracts Gas Reporting Force Updates - Soft Default 3`] = `51871`; +exports[`Collateral contracts Gas Reporting Force Updates - Soft Default 3`] = `51849`; diff --git a/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap b/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap index 85d7a7c8ad..dd642750d6 100644 --- a/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap +++ b/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Gas Reporting RewardableERC20Vault claimRewards 1`] = `159809`; +exports[`Gas Reporting RewardableERC20Vault claimRewards 1`] = `159832`; -exports[`Gas Reporting RewardableERC20Vault claimRewards 2`] = `83048`; +exports[`Gas Reporting RewardableERC20Vault claimRewards 2`] = `83066`; -exports[`Gas Reporting RewardableERC20Vault deposit 1`] = `118977`; +exports[`Gas Reporting RewardableERC20Vault deposit 1`] = `119033`; -exports[`Gas Reporting RewardableERC20Vault deposit 2`] = `85331`; +exports[`Gas Reporting RewardableERC20Vault deposit 2`] = `85387`; -exports[`Gas Reporting RewardableERC20Vault withdraw 1`] = `98012`; +exports[`Gas Reporting RewardableERC20Vault withdraw 1`] = `98068`; -exports[`Gas Reporting RewardableERC20Vault withdraw 2`] = `66512`; +exports[`Gas Reporting RewardableERC20Vault withdraw 2`] = `66568`; diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index 8589a955d1..56b7e55d3a 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `11325033`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `11745798`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `8297573`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9481844`; -exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2361006`; +exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2292984`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `8453412`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `12996783`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `19816331`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `25811511`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10461171`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10752586`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `7440373`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8471544`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4082384`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4535205`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `13528754`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `17426207`; From 585222b0e13abdd8ed7741cdb87c8b1f2d0ddb58 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 17 May 2023 09:10:21 -0300 Subject: [PATCH 247/462] new auction length validations --- contracts/p0/Broker.sol | 12 +++++++----- contracts/p1/Broker.sol | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index f89beb626a..6ae5f93eb6 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -22,7 +22,7 @@ contract BrokerP0 is ComponentP0, IBroker { using SafeERC20 for IERC20Metadata; uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration -1 week - uint48 public constant MIN_AUCTION_LENGTH = ONE_BLOCK; // {s} min auction length - 1 block + uint48 public constant MIN_AUCTION_LENGTH = ONE_BLOCK * 2; // {s} min auction length - 2 block // warning: blocktime <= 12s assumption // Added for interface compatibility with P1 @@ -135,7 +135,8 @@ contract BrokerP0 is ComponentP0, IBroker { /// @custom:governance function setBatchAuctionLength(uint48 newAuctionLength) public governance { require( - newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH, + newAuctionLength == 0 || + (newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH), "invalid batchAuctionLength" ); emit BatchAuctionLengthSet(batchAuctionLength, newAuctionLength); @@ -156,7 +157,8 @@ contract BrokerP0 is ComponentP0, IBroker { /// @custom:governance function setDutchAuctionLength(uint48 newAuctionLength) public governance { require( - newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH, + newAuctionLength == 0 || + (newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH), "invalid dutchAuctionLength" ); emit DutchAuctionLengthSet(dutchAuctionLength, newAuctionLength); @@ -166,7 +168,7 @@ contract BrokerP0 is ComponentP0, IBroker { // === Private === function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { - require(batchAuctionLength > 0, "batchAuctionLength unset"); + require(batchAuctionLength > 0, "batch auctions not enabled"); GnosisTrade trade = new GnosisTrade(); trades[address(trade)] = true; @@ -190,7 +192,7 @@ contract BrokerP0 is ComponentP0, IBroker { } function newDutchAuction(TradeRequest memory req, ITrading caller) private returns (ITrade) { - require(dutchAuctionLength > 0, "dutchAuctionLength unset"); + require(dutchAuctionLength > 0, "dutch auctions not enabled"); DutchTrade trade = new DutchTrade(); trades[address(trade)] = true; diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index a8a34c121b..308164c2a3 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -23,7 +23,7 @@ contract BrokerP1 is ComponentP1, IBroker { using Clones for address; uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration - 1 week - uint48 public constant MIN_AUCTION_LENGTH = ONE_BLOCK; // {s} min auction length - 1 block + uint48 public constant MIN_AUCTION_LENGTH = ONE_BLOCK * 2; // {s} min auction length - 2 blocks // warning: blocktime <= 12s assumption IBackingManager private backingManager; @@ -150,7 +150,8 @@ contract BrokerP1 is ComponentP1, IBroker { /// @custom:governance function setBatchAuctionLength(uint48 newAuctionLength) public governance { require( - newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH, + newAuctionLength == 0 || + (newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH), "invalid batchAuctionLength" ); emit BatchAuctionLengthSet(batchAuctionLength, newAuctionLength); @@ -171,7 +172,8 @@ contract BrokerP1 is ComponentP1, IBroker { /// @custom:governance function setDutchAuctionLength(uint48 newAuctionLength) public governance { require( - newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH, + newAuctionLength == 0 || + (newAuctionLength >= MIN_AUCTION_LENGTH && newAuctionLength <= MAX_AUCTION_LENGTH), "invalid dutchAuctionLength" ); emit DutchAuctionLengthSet(dutchAuctionLength, newAuctionLength); @@ -187,7 +189,7 @@ contract BrokerP1 is ComponentP1, IBroker { // === Private === function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { - require(batchAuctionLength > 0, "batchAuctionLength unset"); + require(batchAuctionLength > 0, "batch auctions not enabled"); GnosisTrade trade = GnosisTrade(address(batchTradeImplementation).clone()); trades[address(trade)] = true; @@ -211,7 +213,7 @@ contract BrokerP1 is ComponentP1, IBroker { } function newDutchAuction(TradeRequest memory req, ITrading caller) private returns (ITrade) { - require(dutchAuctionLength > 0, "dutchAuctionLength unset"); + require(dutchAuctionLength > 0, "dutch auctions not enabled"); DutchTrade trade = DutchTrade(address(dutchTradeImplementation).clone()); trades[address(trade)] = true; From f74264ceb4ccc76d5b898162fc6a31bdfcf6cdca Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 17 May 2023 09:10:47 -0300 Subject: [PATCH 248/462] adjust broker tests --- test/Broker.test.ts | 20 ++++++++++++++++++-- test/Revenues.test.ts | 23 +++++++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 3e69b65e02..7accd108fb 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -284,7 +284,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) it('Should perform validations on batchAuctionLength', async () => { - let invalidValue: BigNumber = bn(0) + let invalidValue: BigNumber = bn(1) // Attempt to update await expect(broker.connect(owner).setBatchAuctionLength(invalidValue)).to.be.revertedWith( @@ -297,10 +297,18 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await expect(broker.connect(owner).setBatchAuctionLength(invalidValue)).to.be.revertedWith( 'invalid batchAuctionLength' ) + + // Allows to set it to zero to disable feature + await expect(broker.connect(owner).setBatchAuctionLength(bn(0))) + .to.emit(broker, 'BatchAuctionLengthSet') + .withArgs(config.batchAuctionLength, bn(0)) + + // Check value was updated + expect(await broker.batchAuctionLength()).to.equal(bn(0)) }) it('Should perform validations on dutchAuctionLength', async () => { - let invalidValue: BigNumber = bn(0) + let invalidValue: BigNumber = bn(13) // Attempt to update await expect(broker.connect(owner).setDutchAuctionLength(invalidValue)).to.be.revertedWith( @@ -313,6 +321,14 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await expect(broker.connect(owner).setDutchAuctionLength(invalidValue)).to.be.revertedWith( 'invalid dutchAuctionLength' ) + + // Allows to set it to zero to disable feature + await expect(broker.connect(owner).setDutchAuctionLength(bn(0))) + .to.emit(broker, 'DutchAuctionLengthSet') + .withArgs(config.dutchAuctionLength, bn(0)) + + // Check value was updated + expect(await broker.dutchAuctionLength()).to.equal(bn(0)) }) it('Should allow to update disabled if Owner', async () => { diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 60116921ae..49e0c4682b 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -615,7 +615,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { minBuyAmtRToken.div(bn('1e15')) ) }) - it('Should be able to start a dust auction BATCH_AUCTION', async () => { + it('Should be able to start a dust auction BATCH_AUCTION, if enabled', async () => { const minTrade = bn('1e18') await rTokenTrader.connect(owner).setMinTradeVolume(minTrade) @@ -626,12 +626,21 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await token0.connect(addr1).transfer(rTokenTrader.address, dustAmount) const p1RevenueTrader = await ethers.getContractAt('RevenueTraderP1', rTokenTrader.address) + + // Disable batch auctions, should not start auction + await broker.connect(owner).setBatchAuctionLength(bn(0)) + await expect( + await p1RevenueTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) + ).to.be.revertedWith('No va') + + // Enable batch auctions (normal flow) + await broker.connect(owner).setBatchAuctionLength(config.batchAuctionLength) await expect( await p1RevenueTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) ).to.emit(rTokenTrader, 'TradeStarted') }) - it('Should be able to start a dust auction DUTCH_AUCTION', async () => { + it('Should be able to start a dust auction DUTCH_AUCTION, if enabled', async () => { const minTrade = bn('1e18') await rTokenTrader.connect(owner).setMinTradeVolume(minTrade) @@ -642,6 +651,16 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await token0.connect(addr1).transfer(rTokenTrader.address, dustAmount) const p1RevenueTrader = await ethers.getContractAt('RevenueTraderP1', rTokenTrader.address) + + // Disable dutch auctions, should not start auction + await broker.connect(owner).setDutchAuctionLength(bn(0)) + await expect( + await p1RevenueTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + ).to.be.revertedWith('No va') + + // Enable batch auctions (normal flow) + await broker.connect(owner).setDutchAuctionLength(config.dutchAuctionLength) + await expect( await p1RevenueTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) ).to.emit(rTokenTrader, 'TradeStarted') From 68df6a85d31a6573dcce6c49b7c9dfaa75546a12 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 17 May 2023 09:12:15 -0300 Subject: [PATCH 249/462] fix comment --- contracts/p0/Broker.sol | 2 +- test/Revenues.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 6ae5f93eb6..cb43f30a27 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -22,7 +22,7 @@ contract BrokerP0 is ComponentP0, IBroker { using SafeERC20 for IERC20Metadata; uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration -1 week - uint48 public constant MIN_AUCTION_LENGTH = ONE_BLOCK * 2; // {s} min auction length - 2 block + uint48 public constant MIN_AUCTION_LENGTH = ONE_BLOCK * 2; // {s} min auction length - 2 blocks // warning: blocktime <= 12s assumption // Added for interface compatibility with P1 diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 49e0c4682b..ddd9de8c05 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -631,7 +631,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await broker.connect(owner).setBatchAuctionLength(bn(0)) await expect( await p1RevenueTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) - ).to.be.revertedWith('No va') + ).to.be.revertedWith('batch auctions not enabled') // Enable batch auctions (normal flow) await broker.connect(owner).setBatchAuctionLength(config.batchAuctionLength) @@ -656,7 +656,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await broker.connect(owner).setDutchAuctionLength(bn(0)) await expect( await p1RevenueTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) - ).to.be.revertedWith('No va') + ).to.be.revertedWith('dutch auctions not enabled') // Enable batch auctions (normal flow) await broker.connect(owner).setDutchAuctionLength(config.dutchAuctionLength) From b647511842d5e53dc5fb7999aea6a97fa9d2e10d Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 17 May 2023 09:25:39 -0300 Subject: [PATCH 250/462] fix revenue tests --- test/Revenues.test.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index ddd9de8c05..37866e8fc1 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -630,14 +630,16 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Disable batch auctions, should not start auction await broker.connect(owner).setBatchAuctionLength(bn(0)) await expect( - await p1RevenueTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) + p1RevenueTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) ).to.be.revertedWith('batch auctions not enabled') // Enable batch auctions (normal flow) await broker.connect(owner).setBatchAuctionLength(config.batchAuctionLength) - await expect( - await p1RevenueTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) - ).to.emit(rTokenTrader, 'TradeStarted') + + await expect(p1RevenueTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) }) it('Should be able to start a dust auction DUTCH_AUCTION, if enabled', async () => { @@ -655,15 +657,16 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Disable dutch auctions, should not start auction await broker.connect(owner).setDutchAuctionLength(bn(0)) await expect( - await p1RevenueTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + p1RevenueTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) ).to.be.revertedWith('dutch auctions not enabled') // Enable batch auctions (normal flow) await broker.connect(owner).setDutchAuctionLength(config.dutchAuctionLength) - await expect( - await p1RevenueTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) - ).to.emit(rTokenTrader, 'TradeStarted') + await expect(p1RevenueTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) }) it('Should only be able to start a dust auction BATCH_AUCTION (and not DUTCH_AUCTION) if oracle has failed', async () => { @@ -680,9 +683,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expect( p1RevenueTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) ).to.revertedWith('bad sell pricing') - await expect( - await p1RevenueTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) - ).to.emit(rTokenTrader, 'TradeStarted') + await expect(p1RevenueTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + rTokenTrader, + 'TradeStarted' + ) }) it('Should not launch an auction for 1 qTok', async () => { From 0b85b088d18fe75c8b363fde28f0788fa420c451 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 17 May 2023 18:28:24 -0400 Subject: [PATCH 251/462] make first offered dutch auction price exactly highPrice; + comments --- contracts/plugins/trading/DutchTrade.sol | 37 +++++++++++++----------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 8f2a06f9e6..d49899b963 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -32,19 +32,20 @@ contract DutchTrade is ITrade { TradeStatus public status; // reentrancy protection - ITrading public origin; // creator + ITrading public origin; // initializer // === Auction === IERC20Metadata public sell; IERC20Metadata public buy; uint192 public sellAmount; // {sellTok} - uint48 public startTime; // {s} when the dutch auction began - uint48 public endTime; // {s} when the dutch auction ends, if no bids are received + // The auction runs from [startTime, endTime) + uint48 public startTime; // {s} when the dutch auction begins (1 block after init()) + uint48 public endTime; // {s} when the dutch auction ends if no bids are received uint192 public middlePrice; // {buyTok/sellTok} The price at which the function is piecewise uint192 public lowPrice; // {buyTok/sellTok} The price the auction ends at - // highPrice is always 1.5x the middlePrice + // highPrice is always 1.5x the middlePrice, so we don't need to track it explicitly // === Bid === address public bidder; @@ -71,11 +72,15 @@ contract DutchTrade is ITrade { uint256 sellAmount_, uint48 auctionLength ) external stateTransition(TradeStatus.NOT_STARTED, TradeStatus.OPEN) { - assert(address(sell_) != address(0) && address(buy_) != address(0)); // misuse of contract + assert( + address(sell_) != address(0) && + address(buy_) != address(0) && + auctionLength >= 2 * ONE_BLOCK + ); // misuse by caller // Only start dutch auctions under well-defined prices // - // In the BackingManager this may end up recalculating the RToken price + // may end up recalculating the RToken price (uint192 sellLow, uint192 sellHigh) = sell_.price(); // {UoA/sellTok} (uint192 buyLow, uint192 buyHigh) = buy_.price(); // {UoA/buyTok} require(sellLow > 0 && sellHigh < FIX_MAX, "bad sell pricing"); @@ -87,8 +92,8 @@ contract DutchTrade is ITrade { require(sellAmount_ <= sell.balanceOf(address(this)), "unfunded trade"); sellAmount = shiftl_toFix(sellAmount_, -int8(sell.decimals())); // {sellTok} - startTime = uint48(block.timestamp); - endTime = uint48(block.timestamp) + auctionLength; + startTime = uint48(block.timestamp) + ONE_BLOCK; + endTime = startTime + auctionLength; // {UoA} uint192 maxTradeVolume = fixMin(sell_.maxTradeVolume(), buy_.maxTradeVolume()); @@ -124,21 +129,19 @@ contract DutchTrade is ITrade { require(bidder == address(0), "bid received"); // {qBuyTok} - amountIn = bidAmount(uint48(block.timestamp)); + amountIn = bidAmount(uint48(block.timestamp)); // enforces auction ongoing // Transfer in buy tokens bidder = msg.sender; buy.safeTransferFrom(bidder, address(this), amountIn); - // TODO examine reentrancy - think it's probably ok - // the other candidate design is ditch the bid() function entirely and have them transfer - // tokens directly into this contract followed by origin.settleTrade(), but I don't like - // that pattern because it means humans cannot bid without a smart contract helper. - // also requires changing the function signature of settle() to accept the caller address - - // settle() via callback; may also start a new Trade + // status must begin OPEN assert(status == TradeStatus.OPEN); + + // settle() via callback origin.settleTrade(sell); + + // confirm callback succeeded assert(status == TradeStatus.CLOSED); } @@ -191,7 +194,7 @@ contract DutchTrade is ITrade { /// @param timestamp {s} The block timestamp to get price for /// @return {qBuyTok} The amount of buy tokens required to purchase the lot function bidAmount(uint48 timestamp) public view returns (uint256) { - require(timestamp > startTime, "cannot bid block auction was created"); + require(timestamp >= startTime, "cannot bid block auction was created"); require(timestamp < endTime, "auction over"); uint192 progression = divuu(timestamp - startTime, endTime - startTime); From 339fcea4f484db3ee6de274c54ce2fd5ec67a002 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 17 May 2023 19:03:34 -0400 Subject: [PATCH 252/462] further simplification --- contracts/plugins/trading/DutchTrade.sol | 113 +++++++++++++---------- 1 file changed, 65 insertions(+), 48 deletions(-) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index d49899b963..f8b5c5bb21 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -60,6 +60,31 @@ contract DutchTrade is ITrade { status = end; } + // === Public Bid Helper === + + /// Calculates how much buy token is needed to purchase the lot, at a particular timestamp + /// @param timestamp {s} The block timestamp to get price for + /// @return {qBuyTok} The amount of buy tokens required to purchase the lot + function bidAmount(uint48 timestamp) public view returns (uint256) { + /// Price Curve: + /// - 1.5 * middlePrice down to the middlePrice for first 15% of auction + /// - middlePrice down to lowPrice for the last 85% of auction + + require(timestamp >= startTime, "cannot bid block auction was created"); + require(timestamp < endTime, "auction over"); + + uint192 progression = divuu(timestamp - startTime, endTime - startTime); + // assert(progression <= FIX_ONE); obviously true by inspection + + // {buyTok/sellTok} + uint192 price = _price(progression); + + // {qBuyTok} = {sellTok} * {buyTok/sellTok} + return sellAmount.mul(price, CEIL).shiftl_toUint(int8(buy.decimals()), CEIL); + } + + // === External === + /// @param origin_ The Trader that originated the trade /// @param sell_ The asset being sold by the protocol /// @param buy_ The asset being bought by the protocol @@ -95,31 +120,18 @@ contract DutchTrade is ITrade { startTime = uint48(block.timestamp) + ONE_BLOCK; endTime = startTime + auctionLength; - // {UoA} - uint192 maxTradeVolume = fixMin(sell_.maxTradeVolume(), buy_.maxTradeVolume()); - uint192 minTradeVolume = origin.minTradeVolume(); - - // Apply sliding slippage from 0% at maxTradeVolume to maxTradeSlippage() at minTradeVolume - uint192 slippage = origin.maxTradeSlippage(); // {1} - if (minTradeVolume < maxTradeVolume) { - // {UoA} = {sellTok} * {UoA/sellTok} - uint192 auctionVolume = sellAmount.mul(sellHigh, FLOOR); - - if (auctionVolume > minTradeVolume && auctionVolume <= maxTradeVolume) { - // {1} = {1} * ({UoA} - {UoA}} / ({UoA} - {UoA}) - slippage = slippage.mul( - FIX_ONE - divuu(auctionVolume - minTradeVolume, maxTradeVolume - minTradeVolume) - ); - } else if (auctionVolume > maxTradeVolume) { - slippage = 0; - } - } + uint192 slippage = _slippage( + sellAmount.mul(sellHigh, FLOOR), // auctionVolume + origin.minTradeVolume(), // minTradeVolume + fixMin(sell_.maxTradeVolume(), buy_.maxTradeVolume()) // maxTradeVolume + ); // {1} + // {buyTok/sellTok} = {1} * {UoA/sellTok} / {UoA/buyTok} lowPrice = sellLow.mulDiv(FIX_ONE - slippage, buyHigh, FLOOR); middlePrice = sellHigh.div(buyLow, CEIL); // no additional slippage // highPrice = 1.5 * middlePrice - require(lowPrice <= middlePrice, "asset inverted pricing"); + assert(lowPrice <= middlePrice); } /// Bid for the auction lot at the current price; settling atomically via a callback @@ -153,7 +165,7 @@ contract DutchTrade is ITrade { stateTransition(TradeStatus.OPEN, TradeStatus.CLOSED) returns (uint256 soldAmt, uint256 boughtAmt) { - require(msg.sender == address(origin), "only origin can settle"); // origin.settleTrade() + require(msg.sender == address(origin), "only origin can settle"); // Received bid if (bidder != address(0)) { @@ -185,38 +197,43 @@ contract DutchTrade is ITrade { return status == TradeStatus.OPEN && (bidder != address(0) || block.timestamp >= endTime); } - // === Bid Helper === - - /// Calculates how much buy token is needed to purchase the lot, at a particular timestamp - /// Price Curve: - /// - 1.5 * middlePrice down to the middlePrice for first 15% of auction - /// - middlePrice down to lowPrice for the last 85% of auction - /// @param timestamp {s} The block timestamp to get price for - /// @return {qBuyTok} The amount of buy tokens required to purchase the lot - function bidAmount(uint48 timestamp) public view returns (uint256) { - require(timestamp >= startTime, "cannot bid block auction was created"); - require(timestamp < endTime, "auction over"); - - uint192 progression = divuu(timestamp - startTime, endTime - startTime); - // assert(progression <= FIX_ONE); - - // {buyTok/sellTok} - uint192 price; + // === Private === + + /// Return a sliding % from 0 (at maxTradeVolume) to maxTradeSlippage (at minTradeVolume) + /// @param auctionVolume {UoA} The actual auction volume + /// @param minTradeVolume {UoA} The minimum trade volume + /// @param maxTradeVolume {UoA} The maximum trade volume + /// @return slippage {1} The fraction of auctionVolume that should be permitted as slippage + function _slippage( + uint192 auctionVolume, + uint192 minTradeVolume, + uint192 maxTradeVolume + ) private view returns (uint192 slippage) { + slippage = origin.maxTradeSlippage(); // {1} + if (maxTradeVolume <= minTradeVolume || auctionVolume < minTradeVolume) return slippage; + if (auctionVolume > maxTradeVolume) return 0; // 0% slippage beyond maxTradeVolume + + // {1} = {1} * ({UoA} - {UoA}} / ({UoA} - {UoA}) + return + slippage.mul( + FIX_ONE - divuu(auctionVolume - minTradeVolume, maxTradeVolume - minTradeVolume) + ); + } + /// Return the price of the auction based on a particular progression + /// @param progression {1} The progression of the auction + /// @return {buyTok/sellTok} + function _price(uint192 progression) private view returns (uint192) { + // Fast decay -- 15th percentile case if (progression < FIFTEEN_PERCENT) { - // Fast decay -- 15th percentile case - // highPrice is 1.5x middlePrice uint192 highPrice = middlePrice + middlePrice.mul(FIFTY_PERCENT); - price = highPrice - (highPrice - middlePrice).mulDiv(progression, FIFTEEN_PERCENT); - } else { - // Slow decay -- 85th percentile case - price = - middlePrice - - (middlePrice - lowPrice).mulDiv(progression - FIFTEEN_PERCENT, EIGHTY_FIVE_PERCENT); + return highPrice - (highPrice - middlePrice).mulDiv(progression, FIFTEEN_PERCENT); } - // {qBuyTok} = {sellTok} * {buyTok/sellTok} - return sellAmount.mul(price, CEIL).shiftl_toUint(int8(buy.decimals()), CEIL); + // Slow decay -- 85th percentile case + return + middlePrice - + (middlePrice - lowPrice).mulDiv(progression - FIFTEEN_PERCENT, EIGHTY_FIVE_PERCENT); } } From e043d10eda01a54b6f9497abdb6d55f2744e30be Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 17 May 2023 23:58:52 -0400 Subject: [PATCH 253/462] remove kruft --dev depedency --- package.json | 1 - yarn.lock | 8 -------- 2 files changed, 9 deletions(-) diff --git a/package.json b/package.json index 615b17c035..1294fb5a57 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "@openzeppelin/hardhat-upgrades": "^1.23.0", "@typechain/ethers-v5": "^7.2.0", "@typechain/hardhat": "^2.3.1", - "@types/big.js": "^6.1.3", "@types/chai": "^4.3.0", "@types/lodash": "^4.14.177", "@types/mocha": "^9.0.0", diff --git a/yarn.lock b/yarn.lock index fbe9c08df9..f1b32350fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2070,13 +2070,6 @@ __metadata: languageName: node linkType: hard -"@types/big.js@npm:^6.1.3": - version: 6.1.5 - resolution: "@types/big.js@npm:6.1.5" - checksum: 5eb1472c1fb05fef37971aed449aa8dbe485fde1ca1c316911ae8cbd437ecd1f3a56964fc7d439bdcbe8e2585ec3febabc205d291177d5a0119bafaf872604b8 - languageName: node - linkType: hard - "@types/bn.js@npm:^4.11.3": version: 4.11.6 resolution: "@types/bn.js@npm:4.11.6" @@ -8407,7 +8400,6 @@ fsevents@~2.1.1: "@openzeppelin/hardhat-upgrades": ^1.23.0 "@typechain/ethers-v5": ^7.2.0 "@typechain/hardhat": ^2.3.1 - "@types/big.js": ^6.1.3 "@types/chai": ^4.3.0 "@types/lodash": ^4.14.177 "@types/mocha": ^9.0.0 From 849f1e390f856daacb8a652ccf50d5e52ee8085e Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 00:00:25 -0400 Subject: [PATCH 254/462] DutchTrade: 30% / 70% auction split with 4/5 as the exp base --- contracts/plugins/trading/DutchTrade.sol | 78 ++++++++++++++---------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index f8b5c5bb21..9cef8d71fa 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -7,22 +7,30 @@ import "../../libraries/Fixed.sol"; import "../../interfaces/IAsset.sol"; import "../../interfaces/ITrade.sol"; -uint192 constant FIFTEEN_PERCENT = 15e16; // {1} -uint192 constant FIFTY_PERCENT = 50e16; // {1} -uint192 constant EIGHTY_FIVE_PERCENT = 85e16; // {1} +uint192 constant THIRTY_PERCENT = 3e17; // {1} 30% +uint192 constant FIFTY_PERCENT = 5e17; // {1} 50% +uint192 constant SEVENTY_PERCENT = 7e17; // {1} 70% + +uint192 constant MAX_EXP = 31 * FIX_ONE; // {1} (5/4)^31 = 1009 +// by using 4/5 as the base of the price exponential, the avg loss due to precision is exactly 10% +uint192 constant BASE = 8e17; // {1} (4/5) /** * @title DutchTrade * @notice Implements a wholesale dutch auction via a piecewise falling-price mechansim. - * Over the first 15% of the auction the price falls from the ~150% pricepoint to the - * best price, as given by the price range. Over the last 85% of the auction it falls - * from the best price to the worst price. The worst price is additionally discounted by - * the maxTradeSlippage based on how far between minTradeVolume and maxTradeVolume the trade is. + * Over the first 30% of the auction the price falls from ~1000x the best plausible price + * down to the best expected price, exponentially. The price decreases by 20% each time. + * This period DOES NOT expect to receive a bid; it defends against manipulated prices. + * + * Over the last 70% of the auction the price falls from the best expected price to the worst + * price, linearly. The worst price is further discounted by the maxTradeSlippage as a fraction + * of how far from minTradeVolume to maxTradeVolume the trade lies. + * At maxTradeVolume, no further discount is applied. * * To bid: - * - Call `bidAmount()` to check price at various timestamps - * - Wait until desirable block is reached - * - Provide approval of buy tokens and call bid(). Swap will be atomic + * - Call `bidAmount()` view to check prices at various timestamps + * - Wait until desirable a block is reached + * - Provide approval of buy tokens and call bid(). The swap will be atomic */ contract DutchTrade is ITrade { using FixLib for uint192; @@ -39,13 +47,13 @@ contract DutchTrade is ITrade { IERC20Metadata public buy; uint192 public sellAmount; // {sellTok} - // The auction runs from [startTime, endTime) + // The auction runs from [startTime, endTime], inclusive uint48 public startTime; // {s} when the dutch auction begins (1 block after init()) uint48 public endTime; // {s} when the dutch auction ends if no bids are received + // highPrice is always 8192x the middlePrice, so we don't need to track it explicitly uint192 public middlePrice; // {buyTok/sellTok} The price at which the function is piecewise uint192 public lowPrice; // {buyTok/sellTok} The price the auction ends at - // highPrice is always 1.5x the middlePrice, so we don't need to track it explicitly // === Bid === address public bidder; @@ -67,17 +75,14 @@ contract DutchTrade is ITrade { /// @return {qBuyTok} The amount of buy tokens required to purchase the lot function bidAmount(uint48 timestamp) public view returns (uint256) { /// Price Curve: - /// - 1.5 * middlePrice down to the middlePrice for first 15% of auction - /// - middlePrice down to lowPrice for the last 85% of auction + /// - first 30%: exponentially 4/5ths the price from 1009x the middlePrice to 1x + /// - last 70%: decrease linearly from middlePrice to lowPrice require(timestamp >= startTime, "cannot bid block auction was created"); - require(timestamp < endTime, "auction over"); - - uint192 progression = divuu(timestamp - startTime, endTime - startTime); - // assert(progression <= FIX_ONE); obviously true by inspection + require(timestamp <= endTime, "auction over"); // {buyTok/sellTok} - uint192 price = _price(progression); + uint192 price = _price(timestamp); // {qBuyTok} = {sellTok} * {buyTok/sellTok} return sellAmount.mul(price, CEIL).shiftl_toUint(int8(buy.decimals()), CEIL); @@ -117,14 +122,15 @@ contract DutchTrade is ITrade { require(sellAmount_ <= sell.balanceOf(address(this)), "unfunded trade"); sellAmount = shiftl_toFix(sellAmount_, -int8(sell.decimals())); // {sellTok} - startTime = uint48(block.timestamp) + ONE_BLOCK; + startTime = uint48(block.timestamp) + ONE_BLOCK; // start in the next block endTime = startTime + auctionLength; + // {1} uint192 slippage = _slippage( sellAmount.mul(sellHigh, FLOOR), // auctionVolume origin.minTradeVolume(), // minTradeVolume fixMin(sell_.maxTradeVolume(), buy_.maxTradeVolume()) // maxTradeVolume - ); // {1} + ); // {buyTok/sellTok} = {1} * {UoA/sellTok} / {UoA/buyTok} lowPrice = sellLow.mulDiv(FIX_ONE - slippage, buyHigh, FLOOR); @@ -138,7 +144,7 @@ contract DutchTrade is ITrade { /// @dev Caller must have provided approval /// @return amountIn {qBuyTok} The quantity of tokens the bidder paid function bid() external returns (uint256 amountIn) { - require(bidder == address(0), "bid received"); + require(bidder == address(0), "bid already received"); // {qBuyTok} amountIn = bidAmount(uint48(block.timestamp)); // enforces auction ongoing @@ -194,7 +200,7 @@ contract DutchTrade is ITrade { /// @return True if the trade can be settled. // Guaranteed to be true some time after init(), until settle() is called function canSettle() external view returns (bool) { - return status == TradeStatus.OPEN && (bidder != address(0) || block.timestamp >= endTime); + return status == TradeStatus.OPEN && (bidder != address(0) || block.timestamp > endTime); } // === Private === @@ -220,20 +226,26 @@ contract DutchTrade is ITrade { ); } - /// Return the price of the auction based on a particular progression - /// @param progression {1} The progression of the auction + /// Return the price of the auction based on a particular % progression + /// @param timestamp {s} The block timestamp /// @return {buyTok/sellTok} - function _price(uint192 progression) private view returns (uint192) { - // Fast decay -- 15th percentile case - if (progression < FIFTEEN_PERCENT) { - // highPrice is 1.5x middlePrice - uint192 highPrice = middlePrice + middlePrice.mul(FIFTY_PERCENT); - return highPrice - (highPrice - middlePrice).mulDiv(progression, FIFTEEN_PERCENT); + function _price(uint48 timestamp) private view returns (uint192) { + uint192 progression = divuu(timestamp - startTime, endTime - startTime); // {1} + + // Fast exponential decay -- 30th percentile case + if (progression < THIRTY_PERCENT) { + uint192 exp = MAX_EXP.mulDiv(THIRTY_PERCENT - progression, THIRTY_PERCENT); + + // return middePrice / ((4/5) ^ exp) + // this reverts for middlePrice >= 6.21654046e36 * FIX_ONE + // safe uint48 downcast: exp is at-most 31 + // {buyTok/sellTok} = {buyTok/sellTok} / {1} ^ {1} + return middlePrice.div(BASE.powu(uint48(exp.toUint())), CEIL); } - // Slow decay -- 85th percentile case + // Slow linear decay -- 70th percentile case return middlePrice - - (middlePrice - lowPrice).mulDiv(progression - FIFTEEN_PERCENT, EIGHTY_FIVE_PERCENT); + (middlePrice - lowPrice).mulDiv(progression - THIRTY_PERCENT, SEVENTY_PERCENT); } } From 65824a817beb35add8d05b451dffedaf5d8c2b09 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 00:00:39 -0400 Subject: [PATCH 255/462] Revenues tests --- test/Revenues.test.ts | 24 ++++++++++++++---------- test/utils/trades.ts | 24 ++++++++++++++---------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 37866e8fc1..21db1801c3 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2251,7 +2251,8 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) it('Should quote piecewise-falling price correctly throughout entirety of auction', async () => { - issueAmount = issueAmount.div(2) + await broker.connect(owner).setDutchAuctionLength(1240) // 20.66 minutes + issueAmount = issueAmount.div(10000) await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) const trade = await ethers.getContractAt( @@ -2262,10 +2263,12 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const start = await trade.startTime() const end = await trade.endTime() + await advanceToTimestamp(start) - // Simulate 5 minutes of blocks, should swap at right price each time - for (let now = await getLatestBlockTimestamp(); now < end; now += 12) { + // Simulate 20 minutes of blocks, should swap at right price each time + for (let now = await getLatestBlockTimestamp(); now <= end; now += 12) { const actual = await trade.connect(addr1).bidAmount(now) + console.log(actual) const expected = await dutchBuyAmount( fp(now - start).div(end - start), rTokenAsset.address, @@ -2277,7 +2280,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(actual).to.equal(expected) const staticResult = await trade.connect(addr1).callStatic.bid() - expect(staticResult).to.equal(expected) + expect(staticResult).to.equal(actual) await advanceToTimestamp((await getLatestBlockTimestamp()) + 12) } }) @@ -2290,7 +2293,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await rTokenTrader.trades(token0.address) ) await rToken.connect(addr1).approve(trade.address, initialBal) - await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 1) + await advanceToTimestamp((await trade.endTime()) + 1) await expect( trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) ).to.be.revertedWith('auction over') @@ -2306,7 +2309,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await backingManager.tradesOpen()).to.equal(0) }) - it('Should bid in final second of auction and not launch another auction', async () => { + it('Should bid at exactly endTime() and not launch another auction', async () => { await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) const trade = await ethers.getContractAt( @@ -2315,16 +2318,17 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) await rToken.connect(addr1).approve(trade.address, initialBal) - // Snipe auction at 1s left - await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 3) - await trade.connect(addr1).bid() + // Snipe auction at 0s left + await advanceToTimestamp((await trade.endTime()) - 1) + await expect(trade.bidAmount(await trade.endTime())).to.not.be.reverted + await trade.connect(addr1).bid() // timestamp should be exactly endTime() expect(await trade.canSettle()).to.equal(false) expect(await trade.status()).to.equal(2) // Status.CLOSED expect(await trade.bidder()).to.equal(addr1.address) expect(await token0.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(4))) const expected = await dutchBuyAmount( - fp('299').div(300), // after all txs in this test, will be left at 299/300s + fp('300').div(300), rTokenAsset.address, collateral0.address, issueAmount, diff --git a/test/utils/trades.ts b/test/utils/trades.ts index 9182a8885f..e1c72fffd3 100644 --- a/test/utils/trades.ts +++ b/test/utils/trades.ts @@ -1,3 +1,4 @@ +import { Decimal } from 'decimal.js' import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { expect } from 'chai' @@ -105,16 +106,19 @@ export const dutchBuyAmount = async ( const lowPrice = sellLow.mul(fp('1').sub(slippage)).div(buyHigh) const middlePrice = divCeil(sellHigh.mul(fp('1')), buyLow) - const highPrice = middlePrice.add(divCeil(middlePrice, bn('2'))) // 50% above middlePrice - - const price = progression.lt(fp('0.15')) - ? highPrice.sub(highPrice.sub(middlePrice).mul(progression).div(fp('0.15'))) - : middlePrice.sub( - middlePrice - .sub(lowPrice) - .mul(progression.sub(fp('0.15'))) - .div(fp('0.85')) - ) + let price: BigNumber + if (progression.lt(fp('0.30'))) { + const exp = bn('31').mul(fp('0.30').sub(progression)).div(fp('0.30')) + const divisor = new Decimal('4').div(5).pow(exp.toString()) + price = divCeil(middlePrice.mul(fp('1')), fp(divisor.toString())) + } else { + price = middlePrice.sub( + middlePrice + .sub(lowPrice) + .mul(progression.sub(fp('0.30'))) + .div(fp('0.70')) + ) + } return divCeil(outAmount.mul(price), fp('1')) } From 3e91bf1d1bc5ad3e22d8cb8e1ec42dc8769b2557 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 00:32:17 -0400 Subject: [PATCH 256/462] Recollateralization tests --- contracts/plugins/trading/DutchTrade.sol | 8 ++++---- test/Recollateralization.test.ts | 25 +++++++++++++----------- test/fixtures.ts | 2 +- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 9cef8d71fa..376adfa058 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -19,7 +19,7 @@ uint192 constant BASE = 8e17; // {1} (4/5) * @title DutchTrade * @notice Implements a wholesale dutch auction via a piecewise falling-price mechansim. * Over the first 30% of the auction the price falls from ~1000x the best plausible price - * down to the best expected price, exponentially. The price decreases by 20% each time. + * down to the best expected price in a geometric series. The price decreases by 20% each time. * This period DOES NOT expect to receive a bid; it defends against manipulated prices. * * Over the last 70% of the auction the price falls from the best expected price to the worst @@ -232,15 +232,15 @@ contract DutchTrade is ITrade { function _price(uint48 timestamp) private view returns (uint192) { uint192 progression = divuu(timestamp - startTime, endTime - startTime); // {1} - // Fast exponential decay -- 30th percentile case + // Fast geometric decay -- 30th percentile case if (progression < THIRTY_PERCENT) { uint192 exp = MAX_EXP.mulDiv(THIRTY_PERCENT - progression, THIRTY_PERCENT); - // return middePrice / ((4/5) ^ exp) - // this reverts for middlePrice >= 6.21654046e36 * FIX_ONE + // middlePrice * ((5/4) ^ exp) = middlePrice / ((4/5) ^ exp) // safe uint48 downcast: exp is at-most 31 // {buyTok/sellTok} = {buyTok/sellTok} / {1} ^ {1} return middlePrice.div(BASE.powu(uint48(exp.toUint())), CEIL); + // this reverts for middlePrice >= 6.21654046e36 * FIX_ONE } // Slow linear decay -- 70th percentile case diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 48ab75a45f..b66b175bc7 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -3141,7 +3141,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) context('DutchTrade', () => { - const auctionLength = 300 + const auctionLength = 1240 // 20.66 minutes beforeEach(async () => { await broker.connect(owner).setDutchAuctionLength(auctionLength) @@ -3182,7 +3182,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ) // Check the empty buffer block as well - await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength) + await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength + 12) await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.be.revertedWith( 'already rebalancing' ) @@ -3192,6 +3192,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) it('Should quote piecewise-falling price correctly throughout entirety of auction', async () => { + // issueAmount = issueAmount.div(10000) await backingManager.rebalance(TradeKind.DUTCH_AUCTION) const trade = await ethers.getContractAt( 'DutchTrade', @@ -3201,9 +3202,10 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { const start = await trade.startTime() const end = await trade.endTime() + await advanceToTimestamp(start) - // Simulate 5 minutes of blocks, should swap at right price each time - for (let now = await getLatestBlockTimestamp(); now < end; now += 12) { + // Simulate 20 minutes of blocks, should swap at right price each time + for (let now = await getLatestBlockTimestamp(); now <= end; now += 12) { const actual = await trade.connect(addr1).bidAmount(now) const expected = divCeil( await dutchBuyAmount( @@ -3231,7 +3233,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await backingManager.trades(token0.address) ) await token1.connect(addr1).approve(trade.address, initialBal) - await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 1) + await advanceToTimestamp((await trade.endTime()) + 1) await expect( trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) ).to.be.revertedWith('auction over') @@ -3259,8 +3261,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ) await token1.connect(addr1).approve(trade1.address, initialBal) - // Snipe auction at 1s left - await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 3) + // Snipe auction at 0s left + await advanceToTimestamp((await trade1.endTime()) - 1) await trade1.connect(addr1).bid() expect(await trade1.canSettle()).to.equal(false) expect(await trade1.status()).to.equal(2) // Status.CLOSED @@ -3269,7 +3271,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { const expected = divCeil( await dutchBuyAmount( - fp('299').div(300), // after all txs so far, at 299/300s + fp('300').div(300), // after all txs so far, at 300/300s collateral0.address, collateral1.address, issueAmount, @@ -3297,19 +3299,20 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) it('even under worst-possible bids', async () => { + await token1.connect(addr1).approve(trade2.address, initialBal) + // Advance to final second of auction - await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength - 3) + await advanceToTimestamp((await trade2.endTime()) - 1) expect(await trade2.status()).to.equal(1) // TradeStatus.OPEN expect(await trade2.canSettle()).to.equal(false) // Bid + settle RSR auction - await token1.connect(addr1).approve(trade2.address, initialBal) await expect(trade2.connect(addr1).bid()).to.emit(backingManager, 'TradeSettled') }) it('via fallback to Batch Auction', async () => { // Advance past auction timeout - await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength) + await advanceToTimestamp((await trade2.endTime()) + 1) expect(await trade2.status()).to.equal(1) // TradeStatus.OPEN expect(await trade2.canSettle()).to.equal(true) diff --git a/test/fixtures.ts b/test/fixtures.ts index f0b0baad8f..2aaed4b32f 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -442,7 +442,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('600'), // 10 minutes + dutchAuctionLength: bn('1240'), // 20.66 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { From 5f94e2ddfc3112b622d9b9fffa6f8c6c3fe84f4d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 10:39:19 -0400 Subject: [PATCH 257/462] remove prime nonce and update tests (#824) --- contracts/interfaces/IBasketHandler.sol | 8 +--- contracts/p0/BasketHandler.sol | 14 +----- contracts/p1/BasketHandler.sol | 14 +----- test/Main.test.ts | 2 +- test/RToken.test.ts | 64 +++++++------------------ 5 files changed, 22 insertions(+), 80 deletions(-) diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index aa3882af56..59ea67ce68 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -20,16 +20,10 @@ struct BasketRange { */ interface IBasketHandler is IComponent { /// Emitted when the prime basket is set - /// @param nonce {basketNonce} The first nonce under the prime basket; may not exist yet /// @param erc20s The collateral tokens for the prime basket /// @param targetAmts {target/BU} A list of quantities of target unit per basket unit /// @param targetNames Each collateral token's targetName - event PrimeBasketSet( - uint256 indexed nonce, - IERC20[] erc20s, - uint192[] targetAmts, - bytes32[] targetNames - ); + event PrimeBasketSet(IERC20[] erc20s, uint192[] targetAmts, bytes32[] targetNames); /// Emitted when the reference basket is set /// @param nonce {basketNonce} The basket nonce diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 4b7f842988..7cf607fd29 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -145,10 +145,6 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { uint48 private lastStatusTimestamp; CollateralStatus private lastStatus; - // Nonce of the first reference basket from the current history - // There can be 0 to any number of baskets with nonce >= primeNonce - uint48 public primeNonce; // {basketNonce} - // A history of baskets by basket nonce; includes current basket mapping(uint48 => Basket) private basketHistory; @@ -273,8 +269,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { config.targetNames[erc20s[i]] = names[i]; } - primeNonce = nonce + 1; // set primeNonce to the next nonce - emit PrimeBasketSet(primeNonce, erc20s, targetAmts, names); + emit PrimeBasketSet(erc20s, targetAmts, names); } /// Set the backup configuration for some target name @@ -458,8 +453,6 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { uint192[] memory portions, uint192 amount ) external view returns (address[] memory erc20s, uint256[] memory quantities) { - // directly after upgrade the primeNonce will be 0, which is not a valid value - require(primeNonce > 0, "primeNonce uninitialized"); require(basketNonces.length == portions.length, "portions does not mirror basketNonces"); IERC20[] memory erc20sAll = new IERC20[](main.assetRegistry().size()); @@ -469,10 +462,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // Calculate the linear combination basket for (uint48 i = 0; i < basketNonces.length; ++i) { - require( - basketNonces[i] >= primeNonce && basketNonces[i] <= nonce, - "invalid basketNonce" - ); // will always revert directly after setPrimeBasket() + require(basketNonces[i] <= nonce, "invalid basketNonce"); Basket storage b = basketHistory[basketNonces[i]]; // Add-in refAmts contribution from historical basket diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 95a81f8faf..32a8c5dfef 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -70,10 +70,6 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // === Historical basket nonces === // Added in 3.0.0 - // Nonce of the first reference basket from the current prime basket history - // There can be 0 to any number of baskets with nonce >= primeNonce - uint48 public primeNonce; // {basketNonce} - // A history of baskets by basket nonce; includes current basket mapping(uint48 => Basket) private basketHistory; @@ -207,8 +203,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { config.targetNames[erc20s[i]] = names[i]; } - primeNonce = nonce + 1; // set primeNonce to the next nonce - emit PrimeBasketSet(primeNonce, erc20s, targetAmts, names); + emit PrimeBasketSet(erc20s, targetAmts, names); } /// Set the backup configuration for some target name @@ -398,8 +393,6 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { uint192[] memory portions, uint192 amount ) external view returns (address[] memory erc20s, uint256[] memory quantities) { - // directly after upgrade the primeNonce will be 0, which is not a valid value - require(primeNonce > 0, "primeNonce uninitialized"); require(basketNonces.length == portions.length, "portions does not mirror basketNonces"); IERC20[] memory erc20sAll = new IERC20[](assetRegistry.size()); @@ -409,10 +402,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // Calculate the linear combination basket for (uint48 i = 0; i < basketNonces.length; ++i) { - require( - basketNonces[i] >= primeNonce && basketNonces[i] <= nonce, - "invalid basketNonce" - ); // will always revert directly after setPrimeBasket() + require(basketNonces[i] <= nonce, "invalid basketNonce"); Basket storage b = basketHistory[basketNonces[i]]; // Add-in refAmts contribution from historical basket diff --git a/test/Main.test.ts b/test/Main.test.ts index 7aea0cb6f3..72ebec5310 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1888,7 +1888,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { // Set basket await expect(basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1')])) .to.emit(basketHandler, 'PrimeBasketSet') - .withArgs(2, [token0.address], [fp('1')], [ethers.utils.formatBytes32String('USD')]) + .withArgs([token0.address], [fp('1')], [ethers.utils.formatBytes32String('USD')]) }) it('Should revert if target has been changed in asset registry', async () => { diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 87e2321f0b..1642cd35c0 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -1112,36 +1112,36 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect(rToken.connect(addr1).redeem(bn(redeemAmount))).to.not.be.reverted }) - it('Should revert while rebalancing after prime basket change', async function () { + it('Should behave correctly after basket change', async function () { // New prime basket await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) expect(await basketHandler.fullyCollateralized()).to.equal(true) // ref basket hasn't changed - // Should still redeem through the main flow; not custom flow - await rToken.connect(addr1).redeem(1) - await expect( - rToken - .connect(addr1) - .redeemCustom(addr1.address, 1, [await basketHandler.nonce()], [fp('1')], [], []) - ).to.be.revertedWith('invalid basketNonce') + // Should be able to redeem through both main flow and custom flow + await rToken.connect(addr1).redeem(fp('1')) + await rToken + .connect(addr1) + .redeemCustom(addr1.address, fp('1'), [await basketHandler.nonce()], [fp('1')], [], []) // New reference basket await basketHandler.refreshBasket() expect(await basketHandler.fullyCollateralized()).to.equal(false) - // Before the first auction is completed, BOTH redemption methods are bricked + // Custom redemption should not revert since there is collateral overlap await expect(rToken.connect(addr1).redeem(1)).to.be.revertedWith( 'partial redemption; use redeemCustom' ) const nonce = await basketHandler.nonce() + await rToken.connect(addr1).redeemCustom(addr1.address, fp('1'), [nonce], [fp('1')], [], []) + + // Previous basket nonce should be redeemable + await rToken + .connect(addr1) + .redeemCustom(addr1.address, fp('1'), [nonce - 1], [fp('1')], [], []) + + // Future basket nonce should not be redeemable await expect( - rToken.connect(addr1).redeemCustom(addr1.address, 1, [nonce], [fp('1')], [], []) - ).to.be.revertedWith('empty redemption') - await expect( - rToken.connect(addr1).redeemCustom(addr1.address, 1, [nonce - 1], [fp('1')], [], []) - ).to.be.revertedWith('invalid basketNonce') - await expect( - rToken.connect(addr1).redeemCustom(addr1.address, 1, [nonce + 1], [fp('1')], [], []) + rToken.connect(addr1).redeemCustom(addr1.address, fp('1'), [nonce + 1], [fp('1')], [], []) ).to.be.revertedWith('invalid basketNonce') }) @@ -1720,38 +1720,6 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.totalSupply()).to.equal(issueAmount.div(2)) }) - it('Should enforce primeNonce <-> reference nonce relationship #fast', async function () { - const portions = [fp('1')] - await expect( - rToken - .connect(addr1) - .redeemCustom(addr1.address, issueAmount.div(2), [2], portions, [], []) - ).to.be.revertedWith('invalid basketNonce') - - // Bump primeNonce - await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) - - // Old basket is no longer redeemable - await expect( - rToken - .connect(addr1) - .redeemCustom(addr1.address, issueAmount.div(2), [1], portions, [], []) - ).to.be.revertedWith('invalid basketNonce') - - // Nonce 2 still doesn't have a reference basket yet - await expect( - rToken - .connect(addr1) - .redeemCustom(addr1.address, issueAmount.div(2), [2], portions, [], []) - ).to.be.revertedWith('invalid basketNonce') - - // Refresh reference basket - await basketHandler.connect(owner).refreshBasket() - await rToken - .connect(addr1) - .redeemCustom(addr1.address, issueAmount.div(2), [2], portions, [], []) - }) - it('Should prorate redemption if basket is DISABLED from fallen refPerTok() #fast', async function () { // Default immediately await token2.setExchangeRate(fp('0.1')) // 90% decrease From af4bff7f8b40732da4a5a1b702ba4e5ffbe0747b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 11:57:34 -0400 Subject: [PATCH 258/462] final answer: 33%/66% split with 18.6 minute auctions --- common/numbers.ts | 2 ++ contracts/plugins/trading/DutchTrade.sol | 31 +++++++++---------- docs/system-design.md | 4 +-- .../deployment/phase3-rtoken/rTokenConfig.ts | 6 ++-- test/Recollateralization.test.ts | 2 +- test/Revenues.test.ts | 5 ++- test/fixtures.ts | 2 +- test/integration/fixtures.ts | 2 +- .../aave/ATokenFiatCollateral.test.ts | 2 +- .../compoundv2/CTokenFiatCollateral.test.ts | 2 +- test/utils/trades.ts | 14 ++++----- 11 files changed, 35 insertions(+), 37 deletions(-) diff --git a/common/numbers.ts b/common/numbers.ts index 716a65c3cb..6d53f464d1 100644 --- a/common/numbers.ts +++ b/common/numbers.ts @@ -33,6 +33,8 @@ export const fp = (x: BigNumberish): BigNumber => { export const divFloor = (x: BigNumber, y: BigNumber): BigNumber => div(x, y, RoundingMode.FLOOR) +export const divRound = (x: BigNumber, y: BigNumber): BigNumber => div(x, y, RoundingMode.ROUND) + export const divCeil = (x: BigNumber, y: BigNumber): BigNumber => div(x, y, RoundingMode.CEIL) export function div(x: BigNumber, y: BigNumber, rnd: RoundingMode) { diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 376adfa058..b3ade0fede 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -7,9 +7,8 @@ import "../../libraries/Fixed.sol"; import "../../interfaces/IAsset.sol"; import "../../interfaces/ITrade.sol"; -uint192 constant THIRTY_PERCENT = 3e17; // {1} 30% -uint192 constant FIFTY_PERCENT = 5e17; // {1} 50% -uint192 constant SEVENTY_PERCENT = 7e17; // {1} 70% +uint192 constant ONE_THIRD = FIX_ONE / 3; // {1} 1/3 +uint192 constant TWO_THIRDS = ONE_THIRD * 2; // {1} 2/3 uint192 constant MAX_EXP = 31 * FIX_ONE; // {1} (5/4)^31 = 1009 // by using 4/5 as the base of the price exponential, the avg loss due to precision is exactly 10% @@ -18,11 +17,11 @@ uint192 constant BASE = 8e17; // {1} (4/5) /** * @title DutchTrade * @notice Implements a wholesale dutch auction via a piecewise falling-price mechansim. - * Over the first 30% of the auction the price falls from ~1000x the best plausible price + * Over the first third of the auction the price falls from ~1000x the best plausible price * down to the best expected price in a geometric series. The price decreases by 20% each time. * This period DOES NOT expect to receive a bid; it defends against manipulated prices. * - * Over the last 70% of the auction the price falls from the best expected price to the worst + * Over the last 2/3 of the auction the price falls from the best expected price to the worst * price, linearly. The worst price is further discounted by the maxTradeSlippage as a fraction * of how far from minTradeVolume to maxTradeVolume the trade lies. * At maxTradeVolume, no further discount is applied. @@ -74,10 +73,6 @@ contract DutchTrade is ITrade { /// @param timestamp {s} The block timestamp to get price for /// @return {qBuyTok} The amount of buy tokens required to purchase the lot function bidAmount(uint48 timestamp) public view returns (uint256) { - /// Price Curve: - /// - first 30%: exponentially 4/5ths the price from 1009x the middlePrice to 1x - /// - last 70%: decrease linearly from middlePrice to lowPrice - require(timestamp >= startTime, "cannot bid block auction was created"); require(timestamp <= endTime, "auction over"); @@ -230,22 +225,24 @@ contract DutchTrade is ITrade { /// @param timestamp {s} The block timestamp /// @return {buyTok/sellTok} function _price(uint48 timestamp) private view returns (uint192) { + /// Price Curve: + /// - first 1/3%: exponentially 4/5ths the price from 1009x the middlePrice to 1x + /// - last 2/3: decrease linearly from middlePrice to lowPrice + uint192 progression = divuu(timestamp - startTime, endTime - startTime); // {1} - // Fast geometric decay -- 30th percentile case - if (progression < THIRTY_PERCENT) { - uint192 exp = MAX_EXP.mulDiv(THIRTY_PERCENT - progression, THIRTY_PERCENT); + // Fast geometric decay -- 0%-33% of auction + if (progression < ONE_THIRD) { + uint192 exp = MAX_EXP.mulDiv(ONE_THIRD - progression, ONE_THIRD, ROUND); // middlePrice * ((5/4) ^ exp) = middlePrice / ((4/5) ^ exp) // safe uint48 downcast: exp is at-most 31 // {buyTok/sellTok} = {buyTok/sellTok} / {1} ^ {1} - return middlePrice.div(BASE.powu(uint48(exp.toUint())), CEIL); + return middlePrice.div(BASE.powu(uint48(exp.toUint(ROUND))), CEIL); // this reverts for middlePrice >= 6.21654046e36 * FIX_ONE } - // Slow linear decay -- 70th percentile case - return - middlePrice - - (middlePrice - lowPrice).mulDiv(progression - THIRTY_PERCENT, SEVENTY_PERCENT); + // Slow linear decay -- 33%-100% of auction + return middlePrice - (middlePrice - lowPrice).mulDiv(progression - ONE_THIRD, TWO_THIRDS); } } diff --git a/docs/system-design.md b/docs/system-design.md index ea7e42927b..03d96a9b7c 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -232,8 +232,8 @@ Dimension: `{seconds}` The dutch auction length is how many seconds long falling-price dutch auctions should be. A longer period will result in less slippage due to better price granularity. -Default value: `600` = 10 minutes -Mainnet reasonable range: 120 to 3600 +Default value: `1116` = 18.6 minutes - this value has been specifically chosen as a function of the dutch auction price curve. Increasing this value slightly will not have much benefit, and decreasing it at all will have significant cost. Changes to this value only make sense if they are substantial. 9.8 minutes is the other obvious attractor if looking for a shorter auction. +Mainnet reasonable range: 300 to 3600 ### `backingBuffer` diff --git a/scripts/deployment/phase3-rtoken/rTokenConfig.ts b/scripts/deployment/phase3-rtoken/rTokenConfig.ts index c9f6967537..e426ef7f5a 100644 --- a/scripts/deployment/phase3-rtoken/rTokenConfig.ts +++ b/scripts/deployment/phase3-rtoken/rTokenConfig.ts @@ -23,7 +23,7 @@ export const rTokenConfig: { [key: string]: IRToken } = { warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('14400'), // (the delay _after_ default has been confirmed) 4 hours batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('600'), // 10 minutes + dutchAuctionLength: bn('1116'), // 18.6 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days @@ -62,7 +62,7 @@ export const rTokenConfig: { [key: string]: IRToken } = { warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('14400'), // (the delay _after_ default has been confirmed) 4 hours batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('600'), // 10 minutes + dutchAuctionLength: bn('1116'), // 18.6 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days @@ -100,7 +100,7 @@ export const rTokenConfig: { [key: string]: IRToken } = { warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('600'), // 10 minutes + dutchAuctionLength: bn('1116'), // 18.6 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index b66b175bc7..352ef5ff82 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -3141,7 +3141,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) context('DutchTrade', () => { - const auctionLength = 1240 // 20.66 minutes + const auctionLength = 1116 // 18.6 minutes beforeEach(async () => { await broker.connect(owner).setDutchAuctionLength(auctionLength) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 21db1801c3..fb6081c2af 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2210,7 +2210,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) context('DutchTrade', () => { - const auctionLength = 300 + const auctionLength = 1116 // 18.6 minutes beforeEach(async () => { await broker.connect(owner).setDutchAuctionLength(auctionLength) }) @@ -2251,7 +2251,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) it('Should quote piecewise-falling price correctly throughout entirety of auction', async () => { - await broker.connect(owner).setDutchAuctionLength(1240) // 20.66 minutes issueAmount = issueAmount.div(10000) await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) @@ -2265,7 +2264,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const end = await trade.endTime() await advanceToTimestamp(start) - // Simulate 20 minutes of blocks, should swap at right price each time + // Simulate 18.6 minutes of blocks, should swap at right price each time for (let now = await getLatestBlockTimestamp(); now <= end; now += 12) { const actual = await trade.connect(addr1).bidAmount(now) console.log(actual) diff --git a/test/fixtures.ts b/test/fixtures.ts index 2aaed4b32f..620c1ae5a5 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -442,7 +442,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('1240'), // 20.66 minutes + dutchAuctionLength: bn('1116'), // 18.6 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index aa9b5e43fe..5e3f257aa8 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -650,7 +650,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('600'), // 10 minutes + dutchAuctionLength: bn('1116'), // 18.6 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index 2b32897bff..e1b1325ad9 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -120,7 +120,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi withdrawalLeak: fp('0'), // 0%; always refresh tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('600'), // 10 minutes + dutchAuctionLength: bn('1116'), // 18.6 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index fa67c78178..d1ec8e4eca 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -121,7 +121,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('600'), // 10 minutes + dutchAuctionLength: bn('1116'), // 18.6 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { diff --git a/test/utils/trades.ts b/test/utils/trades.ts index e1c72fffd3..8d51902350 100644 --- a/test/utils/trades.ts +++ b/test/utils/trades.ts @@ -3,7 +3,7 @@ import { BigNumber } from 'ethers' import { ethers } from 'hardhat' import { expect } from 'chai' import { TestITrading, GnosisTrade } from '../../typechain' -import { bn, fp, divCeil } from '../../common/numbers' +import { bn, fp, divCeil, divRound } from '../../common/numbers' export const expectTrade = async (trader: TestITrading, auctionInfo: Partial) => { if (!auctionInfo.sell) throw new Error('Must provide sell token to find trade') @@ -107,17 +107,17 @@ export const dutchBuyAmount = async ( const lowPrice = sellLow.mul(fp('1').sub(slippage)).div(buyHigh) const middlePrice = divCeil(sellHigh.mul(fp('1')), buyLow) + const ONE_THIRD = fp('1').div(3) // 0.33333 + const TWO_THIRDS = ONE_THIRD.mul(2) // 0.66666 + let price: BigNumber - if (progression.lt(fp('0.30'))) { - const exp = bn('31').mul(fp('0.30').sub(progression)).div(fp('0.30')) + if (progression.lt(ONE_THIRD)) { + const exp = divRound(bn('31').mul(ONE_THIRD.sub(progression)), ONE_THIRD) const divisor = new Decimal('4').div(5).pow(exp.toString()) price = divCeil(middlePrice.mul(fp('1')), fp(divisor.toString())) } else { price = middlePrice.sub( - middlePrice - .sub(lowPrice) - .mul(progression.sub(fp('0.30'))) - .div(fp('0.70')) + middlePrice.sub(lowPrice).mul(progression.sub(ONE_THIRD)).div(TWO_THIRDS) ) } return divCeil(outAmount.mul(price), fp('1')) From a39beea938afee363b79b45f40bd6d1f33e81b8f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 12:30:18 -0400 Subject: [PATCH 259/462] new gas snapshots --- contracts/plugins/trading/DutchTrade.sol | 4 +--- test/Recollateralization.test.ts | 22 ++++++++++++++----- .../Recollateralization.test.ts.snap | 8 ++++--- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index b3ade0fede..4b1949457d 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -73,7 +73,7 @@ contract DutchTrade is ITrade { /// @param timestamp {s} The block timestamp to get price for /// @return {qBuyTok} The amount of buy tokens required to purchase the lot function bidAmount(uint48 timestamp) public view returns (uint256) { - require(timestamp >= startTime, "cannot bid block auction was created"); + require(timestamp >= startTime, "auction not started"); require(timestamp <= endTime, "auction over"); // {buyTok/sellTok} @@ -104,8 +104,6 @@ contract DutchTrade is ITrade { ); // misuse by caller // Only start dutch auctions under well-defined prices - // - // may end up recalculating the RToken price (uint192 sellLow, uint192 sellHigh) = sell_.price(); // {UoA/sellTok} (uint192 buyLow, uint192 buyHigh) = buy_.price(); // {UoA/buyTok} require(sellLow > 0 && sellHigh < FIX_MAX, "bad sell pricing"); diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 352ef5ff82..d952d7e383 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -5076,23 +5076,35 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Will sell all balance of token2 await expect(backingManager.settleTrade(token2.address)).to.be.revertedWith('no trade open') await snapshotGasCost(backingManager.rebalance(TradeKind.DUTCH_AUCTION)) + expect(await backingManager.tradesOpen()).to.equal(1) // Another call should not create any new auctions if still ongoing await expect(backingManager.settleTrade(token2.address)).to.be.revertedWith( 'cannot settle yet' ) - // Bid + settle DutchTrade - const tradeAddr = await backingManager.trades(token2.address) - const trade = await ethers.getContractAt('DutchTrade', tradeAddr) + // Bid + settle DutchTrade in final block at floor price + let tradeAddr = await backingManager.trades(token2.address) + let trade = await ethers.getContractAt('DutchTrade', tradeAddr) await backupToken1.connect(addr1).approve(trade.address, initialBal) + await advanceToTimestamp((await trade.endTime()) - 1) await snapshotGasCost(trade.connect(addr1).bid()) - // Expect new trade started + // Expect new trade started -- bid in first block at ~1000x price expect(await backingManager.tradesOpen()).to.equal(1) expect(await backingManager.trades(token2.address)).to.equal(ZERO_ADDRESS) expect(await backingManager.trades(rsr.address)).to.not.equal(ZERO_ADDRESS) - await expect(backingManager.settleTrade(rsr.address)).to.be.revertedWith('cannot settle yet') + tradeAddr = await backingManager.trades(rsr.address) + trade = await ethers.getContractAt('DutchTrade', tradeAddr) + await backupToken1.connect(addr1).approve(trade.address, initialBal) + await advanceToTimestamp((await trade.startTime()) - 1) + await snapshotGasCost(trade.connect(addr1).bid()) + + // No new trade + expect(await backingManager.tradesOpen()).to.equal(0) + expect(await backingManager.trades(token2.address)).to.equal(ZERO_ADDRESS) + expect(await backingManager.trades(rsr.address)).equal(ZERO_ADDRESS) + expect(await basketHandler.fullyCollateralized()).to.equal(true) }) }) }) diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 59bed92f22..c1da32ae13 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,8 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1387984`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1389252`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1514304`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1522139`; + +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `636401`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1755363`; @@ -12,6 +14,6 @@ exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1790090`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1789734`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `212916`; From e590d4ab1ddb80f17cef60186ce4f2458c781c98 Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Thu, 18 May 2023 12:50:06 -0400 Subject: [PATCH 260/462] Correct vendor files (#826) * move plugin files to vendor folders if needed. * fix compilation. --- contracts/plugins/assets/aave/{ => vendor}/ERC20.sol | 0 contracts/plugins/assets/aave/{ => vendor}/IAToken.sol | 0 .../assets/aave/{ => vendor}/IAaveIncentivesController.sol | 0 contracts/plugins/assets/aave/{ => vendor}/IStaticATokenLM.sol | 0 .../plugins/assets/aave/{ => vendor}/RayMathNoRounding.sol | 0 contracts/plugins/assets/aave/{ => vendor}/ReentrancyGuard.sol | 0 .../plugins/assets/aave/{ => vendor}/StaticATokenErrors.sol | 0 contracts/plugins/assets/aave/{ => vendor}/StaticATokenLM.sol | 0 contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol | 2 +- contracts/plugins/assets/ankr/{ => vendor}/IAnkrETH.sol | 0 .../plugins/assets/compoundv3/{ => vendor}/CometExtMock.sol | 2 +- contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol | 2 +- .../plugins/assets/frax-eth/{ => vendor}/IfrxEthMinter.sol | 0 contracts/plugins/assets/frax-eth/{ => vendor}/IsfrxEth.sol | 0 contracts/plugins/assets/lido/LidoStakedEthCollateral.sol | 2 +- contracts/plugins/assets/lido/{ => vendor}/ISTETH.sol | 0 contracts/plugins/assets/lido/{ => vendor}/IWSTETH.sol | 0 contracts/plugins/assets/rocket-eth/RethCollateral.sol | 2 +- contracts/plugins/assets/rocket-eth/{ => vendor}/IReth.sol | 0 .../assets/rocket-eth/{ => vendor}/IRocketNetworkBalances.sol | 0 .../plugins/assets/rocket-eth/{ => vendor}/IRocketStorage.sol | 0 21 files changed, 5 insertions(+), 5 deletions(-) rename contracts/plugins/assets/aave/{ => vendor}/ERC20.sol (100%) rename contracts/plugins/assets/aave/{ => vendor}/IAToken.sol (100%) rename contracts/plugins/assets/aave/{ => vendor}/IAaveIncentivesController.sol (100%) rename contracts/plugins/assets/aave/{ => vendor}/IStaticATokenLM.sol (100%) rename contracts/plugins/assets/aave/{ => vendor}/RayMathNoRounding.sol (100%) rename contracts/plugins/assets/aave/{ => vendor}/ReentrancyGuard.sol (100%) rename contracts/plugins/assets/aave/{ => vendor}/StaticATokenErrors.sol (100%) rename contracts/plugins/assets/aave/{ => vendor}/StaticATokenLM.sol (100%) rename contracts/plugins/assets/ankr/{ => vendor}/IAnkrETH.sol (100%) rename contracts/plugins/assets/compoundv3/{ => vendor}/CometExtMock.sol (86%) rename contracts/plugins/assets/frax-eth/{ => vendor}/IfrxEthMinter.sol (100%) rename contracts/plugins/assets/frax-eth/{ => vendor}/IsfrxEth.sol (100%) rename contracts/plugins/assets/lido/{ => vendor}/ISTETH.sol (100%) rename contracts/plugins/assets/lido/{ => vendor}/IWSTETH.sol (100%) rename contracts/plugins/assets/rocket-eth/{ => vendor}/IReth.sol (100%) rename contracts/plugins/assets/rocket-eth/{ => vendor}/IRocketNetworkBalances.sol (100%) rename contracts/plugins/assets/rocket-eth/{ => vendor}/IRocketStorage.sol (100%) diff --git a/contracts/plugins/assets/aave/ERC20.sol b/contracts/plugins/assets/aave/vendor/ERC20.sol similarity index 100% rename from contracts/plugins/assets/aave/ERC20.sol rename to contracts/plugins/assets/aave/vendor/ERC20.sol diff --git a/contracts/plugins/assets/aave/IAToken.sol b/contracts/plugins/assets/aave/vendor/IAToken.sol similarity index 100% rename from contracts/plugins/assets/aave/IAToken.sol rename to contracts/plugins/assets/aave/vendor/IAToken.sol diff --git a/contracts/plugins/assets/aave/IAaveIncentivesController.sol b/contracts/plugins/assets/aave/vendor/IAaveIncentivesController.sol similarity index 100% rename from contracts/plugins/assets/aave/IAaveIncentivesController.sol rename to contracts/plugins/assets/aave/vendor/IAaveIncentivesController.sol diff --git a/contracts/plugins/assets/aave/IStaticATokenLM.sol b/contracts/plugins/assets/aave/vendor/IStaticATokenLM.sol similarity index 100% rename from contracts/plugins/assets/aave/IStaticATokenLM.sol rename to contracts/plugins/assets/aave/vendor/IStaticATokenLM.sol diff --git a/contracts/plugins/assets/aave/RayMathNoRounding.sol b/contracts/plugins/assets/aave/vendor/RayMathNoRounding.sol similarity index 100% rename from contracts/plugins/assets/aave/RayMathNoRounding.sol rename to contracts/plugins/assets/aave/vendor/RayMathNoRounding.sol diff --git a/contracts/plugins/assets/aave/ReentrancyGuard.sol b/contracts/plugins/assets/aave/vendor/ReentrancyGuard.sol similarity index 100% rename from contracts/plugins/assets/aave/ReentrancyGuard.sol rename to contracts/plugins/assets/aave/vendor/ReentrancyGuard.sol diff --git a/contracts/plugins/assets/aave/StaticATokenErrors.sol b/contracts/plugins/assets/aave/vendor/StaticATokenErrors.sol similarity index 100% rename from contracts/plugins/assets/aave/StaticATokenErrors.sol rename to contracts/plugins/assets/aave/vendor/StaticATokenErrors.sol diff --git a/contracts/plugins/assets/aave/StaticATokenLM.sol b/contracts/plugins/assets/aave/vendor/StaticATokenLM.sol similarity index 100% rename from contracts/plugins/assets/aave/StaticATokenLM.sol rename to contracts/plugins/assets/aave/vendor/StaticATokenLM.sol diff --git a/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol b/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol index efa7c05245..01cdb42dd5 100644 --- a/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol +++ b/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol @@ -5,7 +5,7 @@ import "@openzeppelin/contracts/utils/math/Math.sol"; import "../../../libraries/Fixed.sol"; import "../AppreciatingFiatCollateral.sol"; import "../OracleLib.sol"; -import "./IAnkrETH.sol"; +import "./vendor/IAnkrETH.sol"; /** * @title Ankr Staked Eth Collateral diff --git a/contracts/plugins/assets/ankr/IAnkrETH.sol b/contracts/plugins/assets/ankr/vendor/IAnkrETH.sol similarity index 100% rename from contracts/plugins/assets/ankr/IAnkrETH.sol rename to contracts/plugins/assets/ankr/vendor/IAnkrETH.sol diff --git a/contracts/plugins/assets/compoundv3/CometExtMock.sol b/contracts/plugins/assets/compoundv3/vendor/CometExtMock.sol similarity index 86% rename from contracts/plugins/assets/compoundv3/CometExtMock.sol rename to contracts/plugins/assets/compoundv3/vendor/CometExtMock.sol index 535edc4ee8..ba4e7fa5db 100644 --- a/contracts/plugins/assets/compoundv3/CometExtMock.sol +++ b/contracts/plugins/assets/compoundv3/vendor/CometExtMock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.17; -import "./vendor/CometCore.sol"; +import "./CometCore.sol"; contract CometExtMock is CometCore { function setBaseSupplyIndex(uint64 newIndex) external { diff --git a/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol b/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol index 101380373a..ee50f90eb3 100644 --- a/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol +++ b/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol @@ -5,7 +5,7 @@ import "@openzeppelin/contracts/utils/math/Math.sol"; import "../../../libraries/Fixed.sol"; import "../AppreciatingFiatCollateral.sol"; import "../OracleLib.sol"; -import "./IsfrxEth.sol"; +import "./vendor/IsfrxEth.sol"; /** * @title SFraxEthCollateral diff --git a/contracts/plugins/assets/frax-eth/IfrxEthMinter.sol b/contracts/plugins/assets/frax-eth/vendor/IfrxEthMinter.sol similarity index 100% rename from contracts/plugins/assets/frax-eth/IfrxEthMinter.sol rename to contracts/plugins/assets/frax-eth/vendor/IfrxEthMinter.sol diff --git a/contracts/plugins/assets/frax-eth/IsfrxEth.sol b/contracts/plugins/assets/frax-eth/vendor/IsfrxEth.sol similarity index 100% rename from contracts/plugins/assets/frax-eth/IsfrxEth.sol rename to contracts/plugins/assets/frax-eth/vendor/IsfrxEth.sol diff --git a/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol b/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol index 56a9d975a3..575f94d2b6 100644 --- a/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol +++ b/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol @@ -5,7 +5,7 @@ import "@openzeppelin/contracts/utils/math/Math.sol"; import "../../../libraries/Fixed.sol"; import "../AppreciatingFiatCollateral.sol"; import "../OracleLib.sol"; -import "./IWSTETH.sol"; +import "./vendor/IWSTETH.sol"; /** * @title Lido Staked Eth Collateral diff --git a/contracts/plugins/assets/lido/ISTETH.sol b/contracts/plugins/assets/lido/vendor/ISTETH.sol similarity index 100% rename from contracts/plugins/assets/lido/ISTETH.sol rename to contracts/plugins/assets/lido/vendor/ISTETH.sol diff --git a/contracts/plugins/assets/lido/IWSTETH.sol b/contracts/plugins/assets/lido/vendor/IWSTETH.sol similarity index 100% rename from contracts/plugins/assets/lido/IWSTETH.sol rename to contracts/plugins/assets/lido/vendor/IWSTETH.sol diff --git a/contracts/plugins/assets/rocket-eth/RethCollateral.sol b/contracts/plugins/assets/rocket-eth/RethCollateral.sol index 99a873af30..6c8708271f 100644 --- a/contracts/plugins/assets/rocket-eth/RethCollateral.sol +++ b/contracts/plugins/assets/rocket-eth/RethCollateral.sol @@ -5,7 +5,7 @@ import "@openzeppelin/contracts/utils/math/Math.sol"; import "../../../libraries/Fixed.sol"; import "../AppreciatingFiatCollateral.sol"; import "../OracleLib.sol"; -import "./IReth.sol"; +import "./vendor/IReth.sol"; /** * @title RethCollateral diff --git a/contracts/plugins/assets/rocket-eth/IReth.sol b/contracts/plugins/assets/rocket-eth/vendor/IReth.sol similarity index 100% rename from contracts/plugins/assets/rocket-eth/IReth.sol rename to contracts/plugins/assets/rocket-eth/vendor/IReth.sol diff --git a/contracts/plugins/assets/rocket-eth/IRocketNetworkBalances.sol b/contracts/plugins/assets/rocket-eth/vendor/IRocketNetworkBalances.sol similarity index 100% rename from contracts/plugins/assets/rocket-eth/IRocketNetworkBalances.sol rename to contracts/plugins/assets/rocket-eth/vendor/IRocketNetworkBalances.sol diff --git a/contracts/plugins/assets/rocket-eth/IRocketStorage.sol b/contracts/plugins/assets/rocket-eth/vendor/IRocketStorage.sol similarity index 100% rename from contracts/plugins/assets/rocket-eth/IRocketStorage.sol rename to contracts/plugins/assets/rocket-eth/vendor/IRocketStorage.sol From 924f518e1961e5e8853cd00d02ec9b87f6aff256 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 12:54:22 -0400 Subject: [PATCH 261/462] backwards compatible FacadeRead/Act (#825) * backwards-compatible facades * goerli phase1 deployments * add Multicall to FacadeAct and test * goerli FacadeAct with multicall * revert if neither 3.0.0 or <=2.1.0 interfaces work * fix forwardRevenue step * update goerli FacadeAct --- .openzeppelin/goerli.json | 3107 +++++++++++++++++ common/configuration.ts | 9 + contracts/facade/FacadeAct.sol | 37 +- contracts/facade/FacadeRead.sol | 46 +- scripts/addresses/5-RTKN-tmp-deployments.json | 19 - .../addresses/5-tmp-assets-collateral.json | 28 - scripts/addresses/5-tmp-deployments.json | 46 +- scripts/deployment/utils.ts | 1 - test/FacadeRead.test.ts | 14 +- 9 files changed, 3221 insertions(+), 86 deletions(-) create mode 100644 .openzeppelin/goerli.json delete mode 100644 scripts/addresses/5-RTKN-tmp-deployments.json delete mode 100644 scripts/addresses/5-tmp-assets-collateral.json diff --git a/.openzeppelin/goerli.json b/.openzeppelin/goerli.json new file mode 100644 index 0000000000..0269997ae9 --- /dev/null +++ b/.openzeppelin/goerli.json @@ -0,0 +1,3107 @@ +{ + "manifestVersion": "3.2", + "proxies": [], + "impls": { + "0a3d69633295294413b230ba2d6283346d99c68e60382c0eea41d4446d9a7a9b": { + "address": "0x63be601cDE1121C987B885cc319b44e0a9d707a2", + "txHash": "0xf00fe4b1293b566775cbfeadc63c5f6c924aa717d6b4600168799bea90cb0ae5", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC165Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41" + }, + { + "label": "_roles", + "offset": 0, + "slot": "101", + "type": "t_mapping(t_bytes32,t_struct(RoleData)80_storage)", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:61" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:259" + }, + { + "label": "longFreezes", + "offset": 0, + "slot": "151", + "type": "t_mapping(t_address,t_uint256)", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:36" + }, + { + "label": "unfreezeAt", + "offset": 0, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:38" + }, + { + "label": "shortFreeze", + "offset": 6, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:39" + }, + { + "label": "longFreeze", + "offset": 12, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:40" + }, + { + "label": "tradingPaused", + "offset": 18, + "slot": "152", + "type": "t_bool", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:45", + "renamedFrom": "paused" + }, + { + "label": "issuancePaused", + "offset": 19, + "slot": "152", + "type": "t_bool", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:46" + }, + { + "label": "__gap", + "offset": 0, + "slot": "153", + "type": "t_array(t_uint256)48_storage", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:225" + }, + { + "label": "rToken", + "offset": 0, + "slot": "201", + "type": "t_contract(IRToken)21842", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:34" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "202", + "type": "t_contract(IStRSR)22145", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:42" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "203", + "type": "t_contract(IAssetRegistry)19927", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:50" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "204", + "type": "t_contract(IBasketHandler)20248", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:58" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "205", + "type": "t_contract(IBackingManager)19989", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:66" + }, + { + "label": "distributor", + "offset": 0, + "slot": "206", + "type": "t_contract(IDistributor)20695", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:74" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "207", + "type": "t_contract(IRevenueTrader)21931", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:82" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "208", + "type": "t_contract(IRevenueTrader)21931", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:90" + }, + { + "label": "furnace", + "offset": 0, + "slot": "209", + "type": "t_contract(IFurnace)21173", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:98" + }, + { + "label": "broker", + "offset": 0, + "slot": "210", + "type": "t_contract(IBroker)20368", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:106" + }, + { + "label": "__gap", + "offset": 0, + "slot": "211", + "type": "t_array(t_uint256)40_storage", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:119" + }, + { + "label": "__gap", + "offset": 0, + "slot": "251", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "301", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "rsr", + "offset": 0, + "slot": "351", + "type": "t_contract(IERC20)11487", + "contract": "MainP1", + "src": "contracts/p1/Main.sol:19" + }, + { + "label": "__gap", + "offset": 0, + "slot": "352", + "type": "t_array(t_uint256)49_storage", + "contract": "MainP1", + "src": "contracts/p1/Main.sol:71" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)40_storage": { + "label": "uint256[40]", + "numberOfBytes": "1280" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)19927": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)19989": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)20248": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IBroker)20368": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)20695": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)11487": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)21173": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IRToken)21842": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)21931": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)22145": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)80_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(RoleData)80_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "89c46a2196e154cda2814859756d0888652971de5405a6347b8e460a22d876a9": { + "address": "0xCBE084C44e7A2223F76362Dcc4EbDacA5Fb1cbA7", + "txHash": "0xc4fa5cdeec161eb820c38d9d2724fd58fbad753cc3d3cdca1e4b9f29484b2793", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)21620", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "201", + "type": "t_contract(IBasketHandler)20248", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:18" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "202", + "type": "t_contract(IBackingManager)19989", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:19" + }, + { + "label": "_erc20s", + "offset": 0, + "slot": "203", + "type": "t_struct(AddressSet)17735_storage", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:22" + }, + { + "label": "assets", + "offset": 0, + "slot": "205", + "type": "t_mapping(t_contract(IERC20)11487,t_contract(IAsset)19684)", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:25" + }, + { + "label": "lastRefresh", + "offset": 0, + "slot": "206", + "type": "t_uint48", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "207", + "type": "t_array(t_uint256)46_storage", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:219" + } + ], + "types": { + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAsset)19684": { + "label": "contract IAsset", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)19989": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)20248": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IERC20)11487": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)21620": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)11487,t_contract(IAsset)19684)": { + "label": "mapping(contract IERC20 => contract IAsset)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)17735_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)17434_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Set)17434_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "4c293ed877185022a6a06a1b852a1f7a5c202dc4cd0e58c1bc905f51d2230e94": { + "address": "0xAdfB9BCdA981136c83076a52Ef8fE4D8B2b520e7", + "txHash": "0xf51f60e7fdbef443e9f7e5755e26315209de2738b3e7db65be16a2f100a394cf", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)21620", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_status", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:74" + }, + { + "label": "broker", + "offset": 0, + "slot": "251", + "type": "t_contract(IBroker)20368", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:27" + }, + { + "label": "trades", + "offset": 0, + "slot": "252", + "type": "t_mapping(t_contract(IERC20)11487,t_contract(ITrade)22280)", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:30" + }, + { + "label": "tradesOpen", + "offset": 0, + "slot": "253", + "type": "t_uint48", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:31" + }, + { + "label": "maxTradeSlippage", + "offset": 6, + "slot": "253", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:34" + }, + { + "label": "minTradeVolume", + "offset": 0, + "slot": "254", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "255", + "type": "t_array(t_uint256)46_storage", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:175" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "301", + "type": "t_contract(IAssetRegistry)19927", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:25" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "302", + "type": "t_contract(IBasketHandler)20248", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:26" + }, + { + "label": "distributor", + "offset": 0, + "slot": "303", + "type": "t_contract(IDistributor)20695", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:27" + }, + { + "label": "rToken", + "offset": 0, + "slot": "304", + "type": "t_contract(IRToken)21842", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:28" + }, + { + "label": "rsr", + "offset": 0, + "slot": "305", + "type": "t_contract(IERC20)11487", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:29" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "306", + "type": "t_contract(IStRSR)22145", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:30" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "307", + "type": "t_contract(IRevenueTrader)21931", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:31" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "308", + "type": "t_contract(IRevenueTrader)21931", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:32" + }, + { + "label": "tradingDelay", + "offset": 20, + "slot": "308", + "type": "t_uint48", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:36" + }, + { + "label": "backingBuffer", + "offset": 0, + "slot": "309", + "type": "t_uint192", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:37" + }, + { + "label": "furnace", + "offset": 0, + "slot": "310", + "type": "t_contract(IFurnace)21173", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:40" + }, + { + "label": "tradeEnd", + "offset": 0, + "slot": "311", + "type": "t_mapping(t_enum(TradeKind)20270,t_uint48)", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:41" + }, + { + "label": "__gap", + "offset": 0, + "slot": "312", + "type": "t_array(t_uint256)39_storage", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:305" + } + ], + "types": { + "t_array(t_uint256)39_storage": { + "label": "uint256[39]", + "numberOfBytes": "1248" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IAssetRegistry)19927": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)20248": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IBroker)20368": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)20695": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)11487": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)21173": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)21620": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)21842": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)21931": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)22145": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_contract(ITrade)22280": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_enum(TradeKind)20270": { + "label": "enum TradeKind", + "members": [ + "DUTCH_AUCTION", + "BATCH_AUCTION" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_contract(IERC20)11487,t_contract(ITrade)22280)": { + "label": "mapping(contract IERC20 => contract ITrade)", + "numberOfBytes": "32" + }, + "t_mapping(t_enum(TradeKind)20270,t_uint48)": { + "label": "mapping(enum TradeKind => uint48)", + "numberOfBytes": "32" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "441df6ba2fefbb543ea8c0ced4b94f4c08bd3a52eb4573fa54a74e6046fd6555": { + "address": "0xC9c37FC53682207844B058026024853A9C0b8c7B", + "txHash": "0x4dc06484bb51a990dd079c491ceef359fd0755a8b2c9c0827ea0e32c22717bd8", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)11446", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "201", + "type": "t_contract(IAssetRegistry)10118", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:31" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "202", + "type": "t_contract(IBackingManager)10180", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:32" + }, + { + "label": "rsr", + "offset": 0, + "slot": "203", + "type": "t_contract(IERC20)5616", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:33" + }, + { + "label": "rToken", + "offset": 0, + "slot": "204", + "type": "t_contract(IRToken)11668", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:34" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "205", + "type": "t_contract(IStRSR)11971", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:35" + }, + { + "label": "config", + "offset": 0, + "slot": "206", + "type": "t_struct(BasketConfig)20719_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:39" + }, + { + "label": "basket", + "offset": 0, + "slot": "210", + "type": "t_struct(Basket)20729_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:43" + }, + { + "label": "nonce", + "offset": 0, + "slot": "212", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:45" + }, + { + "label": "timestamp", + "offset": 6, + "slot": "212", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:46" + }, + { + "label": "disabled", + "offset": 12, + "slot": "212", + "type": "t_bool", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:50" + }, + { + "label": "_targetNames", + "offset": 0, + "slot": "213", + "type": "t_struct(Bytes32Set)7580_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:56" + }, + { + "label": "_newBasket", + "offset": 0, + "slot": "215", + "type": "t_struct(Basket)20729_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:57" + }, + { + "label": "warmupPeriod", + "offset": 0, + "slot": "217", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:63" + }, + { + "label": "lastStatusTimestamp", + "offset": 6, + "slot": "217", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:67" + }, + { + "label": "lastStatus", + "offset": 12, + "slot": "217", + "type": "t_enum(CollateralStatus)9907", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:68" + }, + { + "label": "primeNonce", + "offset": 13, + "slot": "217", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:75" + }, + { + "label": "basketHistory", + "offset": 0, + "slot": "218", + "type": "t_mapping(t_uint48,t_struct(Basket)20729_storage)", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:78" + }, + { + "label": "_targetAmts", + "offset": 0, + "slot": "219", + "type": "t_struct(Bytes32ToUintMap)7192_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:81" + }, + { + "label": "__gap", + "offset": 0, + "slot": "222", + "type": "t_array(t_uint256)37_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:689" + } + ], + "types": { + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_contract(IERC20)5616)dyn_storage": { + "label": "contract IERC20[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)37_storage": { + "label": "uint256[37]", + "numberOfBytes": "1184" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)10118": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)10180": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IERC20)5616": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)11446": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)11668": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)11971": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_enum(CollateralStatus)9907": { + "label": "enum CollateralStatus", + "members": [ + "SOUND", + "IFFY", + "DISABLED" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_bytes32,t_bytes32)": { + "label": "mapping(bytes32 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(BackupConfig)20699_storage)": { + "label": "mapping(bytes32 => struct BackupConfig)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)5616,t_bytes32)": { + "label": "mapping(contract IERC20 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)5616,t_uint192)": { + "label": "mapping(contract IERC20 => uint192)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint48,t_struct(Basket)20729_storage)": { + "label": "mapping(uint48 => struct Basket)", + "numberOfBytes": "32" + }, + "t_struct(BackupConfig)20699_storage": { + "label": "struct BackupConfig", + "members": [ + { + "label": "max", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)5616)dyn_storage", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Basket)20729_storage": { + "label": "struct Basket", + "members": [ + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)5616)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "refAmts", + "type": "t_mapping(t_contract(IERC20)5616,t_uint192)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(BasketConfig)20719_storage": { + "label": "struct BasketConfig", + "members": [ + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)5616)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "targetAmts", + "type": "t_mapping(t_contract(IERC20)5616,t_uint192)", + "offset": 0, + "slot": "1" + }, + { + "label": "targetNames", + "type": "t_mapping(t_contract(IERC20)5616,t_bytes32)", + "offset": 0, + "slot": "2" + }, + { + "label": "backups", + "type": "t_mapping(t_bytes32,t_struct(BackupConfig)20699_storage)", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_struct(Bytes32Set)7580_storage": { + "label": "struct EnumerableSet.Bytes32Set", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)7386_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Bytes32ToBytes32Map)6269_storage": { + "label": "struct EnumerableMap.Bytes32ToBytes32Map", + "members": [ + { + "label": "_keys", + "type": "t_struct(Bytes32Set)7580_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_values", + "type": "t_mapping(t_bytes32,t_bytes32)", + "offset": 0, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(Bytes32ToUintMap)7192_storage": { + "label": "struct EnumerableMap.Bytes32ToUintMap", + "members": [ + { + "label": "_inner", + "type": "t_struct(Bytes32ToBytes32Map)6269_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "96" + }, + "t_struct(Set)7386_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "52214281036f88bf40991db96a9d697f072c00392e337b7a77dc2588f06154d3": { + "address": "0x8Af118a89c5023Bb2B03C70f70c8B396aE71963D", + "txHash": "0xf543fe674e0facfbcf7c4d79fe17524be2f4a30d587f9d9e5545aac3647e07fc", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)21620", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "201", + "type": "t_contract(IBackingManager)19989", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:29" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "202", + "type": "t_contract(IRevenueTrader)21931", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:30" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "203", + "type": "t_contract(IRevenueTrader)21931", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:31" + }, + { + "label": "batchTradeImplementation", + "offset": 0, + "slot": "204", + "type": "t_contract(ITrade)22280", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:35", + "renamedFrom": "tradeImplementation" + }, + { + "label": "gnosis", + "offset": 0, + "slot": "205", + "type": "t_contract(IGnosis)21273", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:38" + }, + { + "label": "batchAuctionLength", + "offset": 20, + "slot": "205", + "type": "t_uint48", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:42", + "renamedFrom": "auctionLength" + }, + { + "label": "disabled", + "offset": 26, + "slot": "205", + "type": "t_bool", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:46" + }, + { + "label": "trades", + "offset": 0, + "slot": "206", + "type": "t_mapping(t_address,t_bool)", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:49" + }, + { + "label": "dutchTradeImplementation", + "offset": 0, + "slot": "207", + "type": "t_contract(ITrade)22280", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:54" + }, + { + "label": "dutchAuctionLength", + "offset": 20, + "slot": "207", + "type": "t_uint48", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:57" + }, + { + "label": "__gap", + "offset": 0, + "slot": "208", + "type": "t_array(t_uint256)43_storage", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:234" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IBackingManager)19989": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IGnosis)21273": { + "label": "contract IGnosis", + "numberOfBytes": "20" + }, + "t_contract(IMain)21620": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)21931": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(ITrade)22280": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "ed0f3c3c2593439410d8c721479c8521d5fd0daff42cb96e9eda880135593706": { + "address": "0xD31eEc6679Dd18D5D42A92F32f01Ed98d4e91941", + "txHash": "0x60a81639c1827c76a3718ce12e2684d5dabfdb15d37f0286b91cf9971d988666", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)21620", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "destinations", + "offset": 0, + "slot": "201", + "type": "t_struct(AddressSet)17735_storage", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:17" + }, + { + "label": "distribution", + "offset": 0, + "slot": "203", + "type": "t_mapping(t_address,t_struct(RevenueShare)20633_storage)", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:18" + }, + { + "label": "rsr", + "offset": 0, + "slot": "204", + "type": "t_contract(IERC20)11487", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:36" + }, + { + "label": "rToken", + "offset": 0, + "slot": "205", + "type": "t_contract(IERC20)11487", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:37" + }, + { + "label": "furnace", + "offset": 0, + "slot": "206", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:38" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "207", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:39" + }, + { + "label": "__gap", + "offset": 0, + "slot": "208", + "type": "t_array(t_uint256)46_storage", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:190" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IERC20)11487": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)21620": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_struct(RevenueShare)20633_storage)": { + "label": "mapping(address => struct RevenueShare)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)17735_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)17434_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(RevenueShare)20633_storage": { + "label": "struct RevenueShare", + "members": [ + { + "label": "rTokenDist", + "type": "t_uint16", + "offset": 0, + "slot": "0" + }, + { + "label": "rsrDist", + "type": "t_uint16", + "offset": 2, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Set)17434_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "08aacf85d9f9a6a9da0e1bf1c919b6e64b9e1e0240ff7aa834555df038ad9aa0": { + "address": "0xFdb9F465C56933ab91f341C966DB517f975de5c1", + "txHash": "0x3750e25140f229b2b7dc95f0d5c57aa3e64da24b4b125ecf913aec5caece8938", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)21620", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "rToken", + "offset": 0, + "slot": "201", + "type": "t_contract(IRToken)21842", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:18" + }, + { + "label": "ratio", + "offset": 0, + "slot": "202", + "type": "t_uint192", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:21" + }, + { + "label": "lastPayout", + "offset": 24, + "slot": "202", + "type": "t_uint48", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:24" + }, + { + "label": "lastPayoutBal", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:25" + }, + { + "label": "__gap", + "offset": 0, + "slot": "204", + "type": "t_array(t_uint256)47_storage", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:98" + } + ], + "types": { + "t_array(t_uint256)47_storage": { + "label": "uint256[47]", + "numberOfBytes": "1504" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IMain)21620": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)21842": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "c0c0a3e591da8dfab7da5971f8203651cdc00afc45c05a42b1aa098c65b93d4a": { + "address": "0x18a26902126154437322fe01fBa04A36b093906f", + "txHash": "0xa64a8e6e0984a3a6f8e05805e163583c3750a2fe9b40dc434bb1a1833f1e7eca", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)21620", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_status", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:74" + }, + { + "label": "broker", + "offset": 0, + "slot": "251", + "type": "t_contract(IBroker)20368", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:27" + }, + { + "label": "trades", + "offset": 0, + "slot": "252", + "type": "t_mapping(t_contract(IERC20)11487,t_contract(ITrade)22280)", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:30" + }, + { + "label": "tradesOpen", + "offset": 0, + "slot": "253", + "type": "t_uint48", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:31" + }, + { + "label": "maxTradeSlippage", + "offset": 6, + "slot": "253", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:34" + }, + { + "label": "minTradeVolume", + "offset": 0, + "slot": "254", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "255", + "type": "t_array(t_uint256)46_storage", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:175" + }, + { + "label": "tokenToBuy", + "offset": 0, + "slot": "301", + "type": "t_contract(IERC20)11487", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:19" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "302", + "type": "t_contract(IAssetRegistry)19927", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:20" + }, + { + "label": "distributor", + "offset": 0, + "slot": "303", + "type": "t_contract(IDistributor)20695", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:21" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "304", + "type": "t_contract(IBackingManager)19989", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:22" + }, + { + "label": "furnace", + "offset": 0, + "slot": "305", + "type": "t_contract(IFurnace)21173", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:23" + }, + { + "label": "rToken", + "offset": 0, + "slot": "306", + "type": "t_contract(IRToken)21842", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "307", + "type": "t_array(t_uint256)44_storage", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:144" + } + ], + "types": { + "t_array(t_uint256)44_storage": { + "label": "uint256[44]", + "numberOfBytes": "1408" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IAssetRegistry)19927": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)19989": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBroker)20368": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)20695": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)11487": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)21173": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)21620": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)21842": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(ITrade)22280": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_mapping(t_contract(IERC20)11487,t_contract(ITrade)22280)": { + "label": "mapping(contract IERC20 => contract ITrade)", + "numberOfBytes": "32" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "f9210bd8391f5a9d4ded100c433ad1820ade9285c1164d64581480d678996098": { + "address": "0x0240E29Be6cBbB178543fF27EA4AaC8F8b870b44", + "txHash": "0x37c16a97f4b45c3d1a482425e9b0e9a107a3e844d6db13dde3124f36265cfc12", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)11446", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_balances", + "offset": 0, + "slot": "201", + "type": "t_mapping(t_address,t_uint256)", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:37" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "202", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:39" + }, + { + "label": "_totalSupply", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:41" + }, + { + "label": "_name", + "offset": 0, + "slot": "204", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:43" + }, + { + "label": "_symbol", + "offset": 0, + "slot": "205", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:44" + }, + { + "label": "__gap", + "offset": 0, + "slot": "206", + "type": "t_array(t_uint256)45_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:394" + }, + { + "label": "_HASHED_NAME", + "offset": 0, + "slot": "251", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:32" + }, + { + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "252", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:33" + }, + { + "label": "__gap", + "offset": 0, + "slot": "253", + "type": "t_array(t_uint256)50_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:120" + }, + { + "label": "_nonces", + "offset": 0, + "slot": "303", + "type": "t_mapping(t_address,t_struct(Counter)2208_storage)", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:37" + }, + { + "label": "_PERMIT_TYPEHASH_DEPRECATED_SLOT", + "offset": 0, + "slot": "304", + "type": "t_bytes32", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:51", + "renamedFrom": "_PERMIT_TYPEHASH" + }, + { + "label": "__gap", + "offset": 0, + "slot": "305", + "type": "t_array(t_uint256)48_storage", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:129" + }, + { + "label": "mandate", + "offset": 0, + "slot": "353", + "type": "t_string_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:44" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "354", + "type": "t_contract(IAssetRegistry)10118", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:47" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "355", + "type": "t_contract(IBasketHandler)10439", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:48" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "356", + "type": "t_contract(IBackingManager)10180", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:49" + }, + { + "label": "furnace", + "offset": 0, + "slot": "357", + "type": "t_contract(IFurnace)10999", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:50" + }, + { + "label": "basketsNeeded", + "offset": 0, + "slot": "358", + "type": "t_uint192", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:55" + }, + { + "label": "issuanceThrottle", + "offset": 0, + "slot": "359", + "type": "t_struct(Throttle)14022_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:58" + }, + { + "label": "redemptionThrottle", + "offset": 0, + "slot": "363", + "type": "t_struct(Throttle)14022_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:59" + }, + { + "label": "__gap", + "offset": 0, + "slot": "367", + "type": "t_array(t_uint256)42_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:526" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)42_storage": { + "label": "uint256[42]", + "numberOfBytes": "1344" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)10118": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)10180": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)10439": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)10999": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)11446": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Counter)2208_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Counter)2208_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Params)14014_storage": { + "label": "struct ThrottleLib.Params", + "members": [ + { + "label": "amtRate", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "pctRate", + "type": "t_uint192", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Throttle)14022_storage": { + "label": "struct ThrottleLib.Throttle", + "members": [ + { + "label": "params", + "type": "t_struct(Params)14014_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "lastTimestamp", + "type": "t_uint48", + "offset": 0, + "slot": "2" + }, + { + "label": "lastAvailable", + "type": "t_uint256", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "7c5f0cd72cfc5dbb78865b7128109fc33d0b45bff718bda1abd88d0d91aea4cd": { + "address": "0x27F672aAf061cb0b2640a4DFCCBd799cD1a7309A", + "txHash": "0x7e0e0117a9a7a2489490b1b18183d5f492b9de0ab26b8b63eafc4fa123aab3a8", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)11446", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_HASHED_NAME", + "offset": 0, + "slot": "201", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:32" + }, + { + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "202", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:33" + }, + { + "label": "__gap", + "offset": 0, + "slot": "203", + "type": "t_array(t_uint256)50_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:120" + }, + { + "label": "name", + "offset": 0, + "slot": "253", + "type": "t_string_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:43" + }, + { + "label": "symbol", + "offset": 0, + "slot": "254", + "type": "t_string_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:44" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "255", + "type": "t_contract(IAssetRegistry)10118", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:49" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "256", + "type": "t_contract(IBackingManager)10180", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:50" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "257", + "type": "t_contract(IBasketHandler)10439", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:51" + }, + { + "label": "rsr", + "offset": 0, + "slot": "258", + "type": "t_contract(IERC20)5616", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:52" + }, + { + "label": "era", + "offset": 0, + "slot": "259", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:57" + }, + { + "label": "stakes", + "offset": 0, + "slot": "260", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:61" + }, + { + "label": "totalStakes", + "offset": 0, + "slot": "261", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:62" + }, + { + "label": "stakeRSR", + "offset": 0, + "slot": "262", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:63" + }, + { + "label": "stakeRate", + "offset": 0, + "slot": "263", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:64" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "264", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_mapping(t_address,t_uint256)))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:69" + }, + { + "label": "draftEra", + "offset": 0, + "slot": "265", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:74" + }, + { + "label": "draftQueues", + "offset": 0, + "slot": "266", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(CumulativeDraft)17765_storage)dyn_storage))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:82" + }, + { + "label": "firstRemainingDraft", + "offset": 0, + "slot": "267", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:83" + }, + { + "label": "totalDrafts", + "offset": 0, + "slot": "268", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:84" + }, + { + "label": "draftRSR", + "offset": 0, + "slot": "269", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:85" + }, + { + "label": "draftRate", + "offset": 0, + "slot": "270", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:86" + }, + { + "label": "_nonces", + "offset": 0, + "slot": "271", + "type": "t_mapping(t_address,t_struct(Counter)2208_storage)", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:124" + }, + { + "label": "_delegationNonces", + "offset": 0, + "slot": "272", + "type": "t_mapping(t_address,t_struct(Counter)2208_storage)", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:126" + }, + { + "label": "unstakingDelay", + "offset": 0, + "slot": "273", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:136" + }, + { + "label": "rewardRatio", + "offset": 6, + "slot": "273", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:137" + }, + { + "label": "payoutLastPaid", + "offset": 0, + "slot": "274", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:148" + }, + { + "label": "rsrRewardsAtLastPayout", + "offset": 0, + "slot": "275", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:151" + }, + { + "label": "leaked", + "offset": 0, + "slot": "276", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:157" + }, + { + "label": "lastWithdrawRefresh", + "offset": 24, + "slot": "276", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:158" + }, + { + "label": "withdrawalLeak", + "offset": 0, + "slot": "277", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:159" + }, + { + "label": "__gap", + "offset": 0, + "slot": "278", + "type": "t_array(t_uint256)28_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:950" + }, + { + "label": "_delegates", + "offset": 0, + "slot": "306", + "type": "t_mapping(t_address,t_address)", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:31" + }, + { + "label": "_eras", + "offset": 0, + "slot": "307", + "type": "t_array(t_struct(Checkpoint)19952_storage)dyn_storage", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:34" + }, + { + "label": "_checkpoints", + "offset": 0, + "slot": "308", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(Checkpoint)19952_storage)dyn_storage))", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:38" + }, + { + "label": "_totalSupplyCheckpoints", + "offset": 0, + "slot": "309", + "type": "t_mapping(t_uint256,t_array(t_struct(Checkpoint)19952_storage)dyn_storage)", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:40" + }, + { + "label": "__gap", + "offset": 0, + "slot": "310", + "type": "t_array(t_uint256)46_storage", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:243" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_struct(Checkpoint)19952_storage)dyn_storage": { + "label": "struct StRSRP1Votes.Checkpoint[]", + "numberOfBytes": "32" + }, + "t_array(t_struct(CumulativeDraft)17765_storage)dyn_storage": { + "label": "struct StRSRP1.CumulativeDraft[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)28_storage": { + "label": "uint256[28]", + "numberOfBytes": "896" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)10118": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)10180": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)10439": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IERC20)5616": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)11446": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_address)": { + "label": "mapping(address => address)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_array(t_struct(Checkpoint)19952_storage)dyn_storage)": { + "label": "mapping(address => struct StRSRP1Votes.Checkpoint[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_array(t_struct(CumulativeDraft)17765_storage)dyn_storage)": { + "label": "mapping(address => struct StRSRP1.CumulativeDraft[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Counter)2208_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_array(t_struct(Checkpoint)19952_storage)dyn_storage)": { + "label": "mapping(uint256 => struct StRSRP1Votes.Checkpoint[])", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(Checkpoint)19952_storage)dyn_storage))": { + "label": "mapping(uint256 => mapping(address => struct StRSRP1Votes.Checkpoint[]))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(CumulativeDraft)17765_storage)dyn_storage))": { + "label": "mapping(uint256 => mapping(address => struct StRSRP1.CumulativeDraft[]))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_mapping(t_address,t_uint256)))": { + "label": "mapping(uint256 => mapping(address => mapping(address => uint256)))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_uint256))": { + "label": "mapping(uint256 => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Checkpoint)19952_storage": { + "label": "struct StRSRP1Votes.Checkpoint", + "members": [ + { + "label": "fromBlock", + "type": "t_uint48", + "offset": 0, + "slot": "0" + }, + { + "label": "val", + "type": "t_uint224", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Counter)2208_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(CumulativeDraft)17765_storage": { + "label": "struct StRSRP1.CumulativeDraft", + "members": [ + { + "label": "drafts", + "type": "t_uint176", + "offset": 0, + "slot": "0" + }, + { + "label": "availableAt", + "type": "t_uint64", + "offset": 22, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_uint176": { + "label": "uint176", + "numberOfBytes": "22" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint224": { + "label": "uint224", + "numberOfBytes": "28" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + } + } +} diff --git a/common/configuration.ts b/common/configuration.ts index f291146471..1c8cdf1d7b 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -273,6 +273,15 @@ export const networkConfig: { [key: string]: INetworkConfig } = { WBTC: '0x528FdEd7CC39209ed67B4edA11937A9ABe1f6249', EURT: '0xD6da5A7ADE2a906d9992612752A339E3485dB508', RSR: '0xB58b5530332D2E9e15bfd1f2525E6fD84e830307', + CVX: '0x7f7B77e49d5b30445f222764a794AFE14af062eB', + CRV: '0xaA91d24c2F7DBb6487f61869cD8cd8aFd5c5Cab2', + ankrETH: '0xE2b16e14dB6216e33082D5A8Be1Ef01DF7511bBb', + frxETH: '0xe0E1d3c6f09DA01399e84699722B11308607BBfC', + sfrxETH: '0x291ed25eB61fcc074156eE79c5Da87e5DA94198F', + stETH: '0x97F9d5ed17A0C99B279887caD5254d15fb1B619B', + wstETH: '0xd000a79BD2a07EB6D2e02ECAd73437De40E52d69', + rETH: '0x2304E98cD1E2F0fd3b4E30A1Bc6E9594dE2ea9b7', + cUSDCv3: '0x719fbae9e2Dcd525bCf060a8D5DBC6C9fE104A50', }, chainlinkFeeds: { ETH: '0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e', // canonical chainlink diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 4e0b1181e0..c6211528e6 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/utils/Multicall.sol"; import "../interfaces/IFacadeAct.sol"; /** @@ -9,7 +10,7 @@ import "../interfaces/IFacadeAct.sol"; * @notice A Facade to help batch compound actions that cannot be done from an EOA, solely. * For use with ^3.0.0 RTokens. */ -contract FacadeAct is IFacadeAct { +contract FacadeAct is IFacadeAct, Multicall { function claimRewards(IRToken rToken) public { IMain main = rToken.main(); main.backingManager().claimRewards(); @@ -38,12 +39,42 @@ contract FacadeAct is IFacadeAct { revenueTrader.settleTrade(toSettle[i]); } + // solhint-disable avoid-low-level-calls + // Transfer revenue backingManager -> revenueTrader - revenueTrader.main().backingManager().forwardRevenue(toStart); + { + address bm = address(revenueTrader.main().backingManager()); + + // 3.0.0 BackingManager interface + (bool success, ) = bm.call{ value: 0 }( + abi.encodeWithSignature("forwardRevenue(address[])", toStart) + ); + + // Fallback to <=2.1.0 interface + if (!success) { + (success, ) = bm.call{ value: 0 }( + abi.encodeWithSignature("manageTokens(address[])", toStart) + ); + require(success, "failed to forward revenue"); + } + } // Start auctions + address rt = address(revenueTrader); for (uint256 i = 0; i < toStart.length; ++i) { - revenueTrader.manageToken(toStart[i], kind); + // 3.0.0 RevenueTrader interface + (bool success, ) = rt.call{ value: 0 }( + abi.encodeWithSignature("manageToken(address,uint8)", toStart[i], kind) + ); + + // Fallback to <=2.1.0 interface + if (!success) { + (success, ) = rt.call{ value: 0 }( + abi.encodeWithSignature("manageToken(address)", toStart[i]) + ); + require(success, "failed to start revenue auction"); + } } + // solhint-enable avoid-low-level-calls } } diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index f041af0879..c7537ab17a 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -278,8 +278,25 @@ contract FacadeRead is IFacadeRead { uint192 minTradeVolume = revenueTrader.minTradeVolume(); // {UoA} Registry memory reg = revenueTrader.main().assetRegistry().getRegistry(); + // solhint-disable avoid-low-level-calls + // Forward ALL revenue - revenueTrader.main().backingManager().forwardRevenue(reg.erc20s); + { + address bm = address(revenueTrader.main().backingManager()); + + // First try 3.0.0 interface + (bool success, ) = bm.call{ value: 0 }( + abi.encodeWithSignature("forwardRevenue(address[])", reg.erc20s) + ); + + // Fallback to <=2.1.0 interface + if (!success) { + (success, ) = bm.call{ value: 0 }( + abi.encodeWithSignature("manageTokens(address[])", reg.erc20s) + ); + require(success, "failed to forward revenue"); + } + } erc20s = new IERC20[](reg.erc20s.length); canStart = new bool[](reg.erc20s.length); @@ -305,14 +322,29 @@ contract FacadeRead is IFacadeRead { ); if (reg.erc20s[i].balanceOf(address(revenueTrader)) > minTradeAmounts[i]) { - try revenueTrader.manageToken(reg.erc20s[i], TradeKind.DUTCH_AUCTION) { - if (revenueTrader.tradesOpen() - tradesOpen > 0) { - canStart[i] = true; - } - // solhint-disable-next-line no-empty-blocks - } catch {} + // 3.0.0 RevenueTrader interface + (bool success, ) = address(revenueTrader).call{ value: 0 }( + abi.encodeWithSignature( + "manageToken(address,uint8)", + erc20s[i], + TradeKind.DUTCH_AUCTION + ) + ); + + // Fallback to <=2.1.0 interface + if (!success) { + (success, ) = address(revenueTrader).call{ value: 0 }( + abi.encodeWithSignature("manageToken(address)", erc20s[i]) + ); + require(success, "failed to start revenue auction"); + } + + if (revenueTrader.tradesOpen() - tradesOpen > 0) { + canStart[i] = true; + } } } + // solhint-enable avoid-low-level-calls } // === Views === diff --git a/scripts/addresses/5-RTKN-tmp-deployments.json b/scripts/addresses/5-RTKN-tmp-deployments.json deleted file mode 100644 index 9b4133f562..0000000000 --- a/scripts/addresses/5-RTKN-tmp-deployments.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "facadeWrite": "0x97C75046CE7Ea5253d20A35B3138699865E8813f", - "main": "0x490EF0b754Cca0dcEDA78af5D92765342e5FCA42", - "components": { - "assetRegistry": "0x35Cf8a802A6711DAab423175bB64Fa021d5E27c2", - "backingManager": "0x43E0c22ccC8a05aC0c1e6FE25E88fdac57BDE838", - "basketHandler": "0x4581214366e7366877601fA15Bc5cF2d6E056412", - "broker": "0xbC94E8dcD4A01AA433cafdfAc4173De8292297cA", - "distributor": "0x1D707b61F2D1a8549965080684742Df3665bc6F9", - "furnace": "0xbB09FD74f12fB76412F4ceca00692D94C38d8bd6", - "rsrTrader": "0xAe80c30a8E3E07141d9763B114aD374ee94031AD", - "rTokenTrader": "0x2d4466e7e91aB81526b9D8FE56E9C05d1e693AB8", - "rToken": "0x191Ab2A0100625d5a880c365b3F43c1F59aba36D", - "stRSR": "0xb9E760c02424212BDF13DE7e349Ded974E09aB4d" - }, - "rTokenAsset": "0x2b5Ee8791f7Fb9E9d0C05BE97DDa33c3CCEc7D3b", - "governance": "0x2e830fD6397A4b26b5ec9fd156ba049D7015787D", - "timelock": "0xA69F005479e82d84Dba805F47b4ED8DFcF58c804" -} \ No newline at end of file diff --git a/scripts/addresses/5-tmp-assets-collateral.json b/scripts/addresses/5-tmp-assets-collateral.json deleted file mode 100644 index 720128656b..0000000000 --- a/scripts/addresses/5-tmp-assets-collateral.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "assets": { - "stkAAVE": "0x3ddc60c2dFa57F78972466ca805C9dA69795FdB7", - "COMP": "0x0F875eE2b36a7B6BdF6c9cb5f2f608E287C3d619" - }, - "collateral": { - "DAI": "0xA6e159b274e00848322B9Fa89F0783876884CeDD", - "USDC": "0xF8Ad2522C049A03C5E6AB56427449162c0502f02", - "USDT": "0xeB1A036E83aD95f0a28d0c8E2F20bf7f1B299F05", - "USDP": "0x80A574cC2B369dc496af6655f57a16a4f180BfAF", - "TUSD": "0x9E8C96d86F1c85BC43200B8093159cf47e0CB921", - "BUSD": "0x440A634DdcFb890BCF8b0Bf07Ef2AaBB37dd5F8C", - "aDAI": "0x50a9d529EA175CdE72525Eaa809f5C3c47dAA1bB", - "aUSDC": "0x5757fF814da66a2B4f9D11d48570d742e246CfD9", - "aUSDT": "0x99bD63BF7e2a69822cD73A82d42cF4b5501e5E50", - "aBUSD": "0x688c95461d611Ecfc423A8c87caCE163C6B40384", - "aUSDP": "0x357d4dB0c2179886334cC33B8528048F7E1D3Fe3", - "cDAI": "0x87A959e0377C68A50b08a91ae5ab3aFA7F41ACA4", - "cUSDC": "0x6B87142C7e6cA80aa3E6ead0351673C45c8990e3", - "cUSDT": "0xEBD07CE38e2f46031c982136012472A4D24AE070", - "cUSDP": "0xC1E16AD7844Da1AEFFa6c3932AD02b823DE12d3F", - "cWBTC": "0x0E6D6cBdA4629Fb2D82b4b4Af0D5c887f21F3BC7", - "cETH": "0x6D05CB2CB647B58189FA16f81784C05B4bcd4fe9", - "WBTC": "0x8A9F74d40c5323E73B63a80c4282658fD43F3AA2", - "WETH": "0xF73EB45d83AC86f8a6F75a6252ca1a59a9A3aED3", - "EURT": "0x714341800AD1913B5FCCBFd5d136553Ad1C314d6" - } -} \ No newline at end of file diff --git a/scripts/addresses/5-tmp-deployments.json b/scripts/addresses/5-tmp-deployments.json index ae3fa1caf9..eeafe862be 100644 --- a/scripts/addresses/5-tmp-deployments.json +++ b/scripts/addresses/5-tmp-deployments.json @@ -4,28 +4,32 @@ "RSR_FEED": "0x905084691C2c7505b5FC63229621621b616bbbFe", "GNOSIS_EASY_AUCTION": "0x1fbab40c338e2e7243da945820ba680c92ef8281" }, - "tradingLib": "0xCFA67f42A0fDe4F0Fb612ea5e66170B0465B84c1", - "facadeRead": "0x3C8cD9FCa9925780598eB097D9718dF3da482C2F", - "facadeAct": "0xafd16aFdE22D42038223A6FfDF00ee49c8fDa985", - "facadeWriteLib": "0xCBcd605088D5A5Da9ceEb3618bc01BFB87387423", - "facadeWrite": "0x97C75046CE7Ea5253d20A35B3138699865E8813f", - "deployer": "0x0F53Aba2a7354C86B64dcaEe0ab9BF852846bAa5", - "rsrAsset": "0x2f98bA77a8ca1c630255c4517b1b3878f6e60C89", + "tradingLib": "0x3BECE5EC596331033726E5C6C188c313Ff4E3fE5", + "cvxMiningLib": "0x890FAa00C16EAD6AA76F18A1A7fe9C40838F9122", + "facadeRead": "0xe8461dB45A7430AA7aB40346E68821284980FdFD", + "facadeAct": "0x103BAFaB86f37C407f2C4813ee343020b66b062f", + "facadeWriteLib": "0x1bc463270b8d3D797F59Fe639eDF5ae130f35FF3", + "basketLib": "0x5A4f2FfC4aD066152B344Ceb2fc2275275b1a9C7", + "facadeWrite": "0xdEBe74dc2A415e00bE8B4b9d1e6e0007153D006a", + "deployer": "0x14c443d8BdbE9A65F3a23FA4e199d8741D5B38Fa", + "rsrAsset": "0xC87CDFFD680D57BF50De4C364BF4277B8A90098E", "implementations": { - "main": "0x5a5eb5d26871e26645bD6d006671ec0887aeca69", - "trade": "0x45B950AF443281c5F67c2c7A1d9bBc325ECb8eEA", + "main": "0x63be601cDE1121C987B885cc319b44e0a9d707a2", + "trading": { + "gnosisTrade": "0x9FF9c353136e86EFe02ADD177E7c9769f8a5A77F", + "dutchTrade": "0xc9291eF2f81dBc9B412381aBe83b28954220565E" + }, "components": { - "assetRegistry": "0x4Cc7eCbc901910186A6bA8ee6cB2649c93c5646e", - "backingManager": "0x4024c00bBD0C420E719527D88781bc1543e63dd5", - "basketHandler": "0xDeC1B73754449166cB270AC83F4b536e738b1351", - "broker": "0xf7d1C6eE4C0D84C6B530D53A897daa1E9eB56833", - "distributor": "0xb3Be23A0cEFfd1814DC4F1FdcDc1200b39922bCc", - "furnace": "0xBE9D23040fe22E8Bd8A88BF5101061557355cA04", - "rsrTrader": "0x72BA23683CBc1a3Fa5b3129b1335327A32c2CE8C", - "rTokenTrader": "0x72BA23683CBc1a3Fa5b3129b1335327A32c2CE8C", - "rToken": "0x58D7bF13D3572b08dE5d96373b8097d94B1325ad", - "stRSR": "0x70c635Bf4972259F2358Db5e431DB9592a2745a2" + "assetRegistry": "0xCBE084C44e7A2223F76362Dcc4EbDacA5Fb1cbA7", + "backingManager": "0xAdfB9BCdA981136c83076a52Ef8fE4D8B2b520e7", + "basketHandler": "0xC9c37FC53682207844B058026024853A9C0b8c7B", + "broker": "0x8Af118a89c5023Bb2B03C70f70c8B396aE71963D", + "distributor": "0xD31eEc6679Dd18D5D42A92F32f01Ed98d4e91941", + "furnace": "0xFdb9F465C56933ab91f341C966DB517f975de5c1", + "rsrTrader": "0x18a26902126154437322fe01fBa04A36b093906f", + "rTokenTrader": "0x18a26902126154437322fe01fBa04A36b093906f", + "rToken": "0x0240E29Be6cBbB178543fF27EA4AaC8F8b870b44", + "stRSR": "0x27F672aAf061cb0b2640a4DFCCBd799cD1a7309A" } - }, - "facadeMonitor": "0x256b89658bD831CC40283F42e85B1fa8973Db0c9" + } } \ No newline at end of file diff --git a/scripts/deployment/utils.ts b/scripts/deployment/utils.ts index 8954dacbed..e5450ae9b4 100644 --- a/scripts/deployment/utils.ts +++ b/scripts/deployment/utils.ts @@ -163,7 +163,6 @@ export const getEmptyDeployment = (): IDeployments => { facadeRead: '', facadeWriteLib: '', cvxMiningLib: '', - facadeMonitor: '', facadeWrite: '', facadeAct: '', deployer: '', diff --git a/test/FacadeRead.test.ts b/test/FacadeRead.test.ts index 642ffb3952..33d53bc5fb 100644 --- a/test/FacadeRead.test.ts +++ b/test/FacadeRead.test.ts @@ -429,7 +429,6 @@ describe('FacadeRead contract', () => { expect(canStart[i]).to.equal(false) expect(surpluses[i]).to.equal(0) } - const asset = await ethers.getContractAt('IAsset', await assetRegistry.toAsset(erc20s[i])) const [low] = await asset.price() expect(minTradeAmounts[i]).to.equal( @@ -437,13 +436,14 @@ describe('FacadeRead contract', () => { ) // 1% oracleError } - // Run revenue auctions - await facadeAct.runRevenueAuctions( - trader.address, - [], - erc20sToStart, - TradeKind.DUTCH_AUCTION + // Run revenue auctions via multicall + const funcSig = ethers.utils.id('runRevenueAuctions(address,address[],address[],uint8)') + const args = ethers.utils.defaultAbiCoder.encode( + ['address', 'address[]', 'address[]', 'uint8'], + [trader.address, [], erc20sToStart, TradeKind.DUTCH_AUCTION] ) + const data = funcSig.substring(0, 10) + args.slice(2) + await expect(facadeAct.multicall([data])).to.emit(trader, 'TradeStarted') // Nothing should be settleable expect((await facade.auctionsSettleable(trader.address)).length).to.equal(0) From dacb34807f95c322360412205fc96d19f75aaf15 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 13:01:32 -0400 Subject: [PATCH 262/462] fix failing tests --- test/Broker.test.ts | 8 ++++---- test/FacadeRead.test.ts | 13 +++++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 7accd108fb..d97c78fa81 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -29,7 +29,7 @@ import { IMPLEMENTATION, } from './fixtures' import snapshotGasCost from './utils/snapshotGasCost' -import { advanceTime, getLatestBlockTimestamp } from './utils/time' +import { advanceTime, advanceToTimestamp, getLatestBlockTimestamp } from './utils/time' import { ITradeRequest } from './utils/trades' import { useEnv } from '#/utils/env' @@ -923,7 +923,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ) // Advance time till trade can be settled - await advanceTime(config.batchAuctionLength.add(100).toString()) + await advanceToTimestamp(await trade.endTime()) // Settle trade await whileImpersonating(backingManager.address, async (bmSigner) => { @@ -992,9 +992,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(token1.address) expect(await trade.sellAmount()).to.equal(amount) - expect(await trade.startTime()).to.equal(await getLatestBlockTimestamp()) + expect(await trade.startTime()).to.equal((await getLatestBlockTimestamp()) + 12) expect(await trade.endTime()).to.equal( - (await getLatestBlockTimestamp()) + Number(config.dutchAuctionLength) + (await trade.startTime()) + config.dutchAuctionLength.toNumber() ) const [sellLow, sellHigh] = await collateral0.price() const [buyLow, buyHigh] = await collateral1.price() diff --git a/test/FacadeRead.test.ts b/test/FacadeRead.test.ts index 642ffb3952..d56870d046 100644 --- a/test/FacadeRead.test.ts +++ b/test/FacadeRead.test.ts @@ -410,7 +410,7 @@ describe('FacadeRead contract', () => { const trader = traders[traderIndex] const minTradeVolume = await trader.minTradeVolume() - const auctionLength = await broker.batchAuctionLength() + const auctionLength = await broker.dutchAuctionLength() const tokenSurplus = bn('0.5e18') await token.connect(addr1).transfer(trader.address, tokenSurplus) @@ -438,18 +438,15 @@ describe('FacadeRead contract', () => { } // Run revenue auctions - await facadeAct.runRevenueAuctions( - trader.address, - [], - erc20sToStart, - TradeKind.DUTCH_AUCTION - ) + await expect( + facadeAct.runRevenueAuctions(trader.address, [], erc20sToStart, TradeKind.DUTCH_AUCTION) + ).to.emit(trader, 'TradeStarted') // Nothing should be settleable expect((await facade.auctionsSettleable(trader.address)).length).to.equal(0) // Advance time till auction ended - await advanceTime(auctionLength + 100) + await advanceTime(auctionLength + 13) // Now should be settleable const settleable = await facade.auctionsSettleable(trader.address) From 403615bfbaf70f46a7152c75e0b33f3672f32f16 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 13:22:38 -0400 Subject: [PATCH 263/462] docs/system-design.md --- docs/system-design.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/system-design.md b/docs/system-design.md index 03d96a9b7c..b240405085 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -232,7 +232,9 @@ Dimension: `{seconds}` The dutch auction length is how many seconds long falling-price dutch auctions should be. A longer period will result in less slippage due to better price granularity. -Default value: `1116` = 18.6 minutes - this value has been specifically chosen as a function of the dutch auction price curve. Increasing this value slightly will not have much benefit, and decreasing it at all will have significant cost. Changes to this value only make sense if they are substantial. 9.8 minutes is the other obvious attractor if looking for a shorter auction. +In general, the dutchAuctionLength should be a multiple of the blocktime. This is not enforced at a smart-contract level. + +Default value: `1116` = 18.6 minutes - this value has been specifically chosen as a function of the dutch auction price curve. Increasing this value slightly will not have much benefit, and decreasing it at all results in meaningfully worse precision. Changes to this value only make sense if the change is substantial. It should be kept a multiple of the blocktime. Mainnet reasonable range: 300 to 3600 ### `backingBuffer` From 9f2cf7e6583a52c2170fbdf22cd0d7a23a56b947 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 14:56:04 -0400 Subject: [PATCH 264/462] fix Broker test --- test/Broker.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index d97c78fa81..94e5ffd6df 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -1076,7 +1076,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ) // Advance time till trade can be settled - await advanceTime(config.batchAuctionLength.add(100).toString()) + await advanceTime(config.dutchAuctionLength.add(100).toString()) // Settle trade await whileImpersonating(backingManager.address, async (bmSigner) => { From 14ea9cff0560f164d0f5f6e7b08252325b504927 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 16:28:48 -0400 Subject: [PATCH 265/462] system-design.md: withdrawalLeak --- docs/system-design.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/system-design.md b/docs/system-design.md index ea7e42927b..d862b2bad4 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -271,6 +271,17 @@ The number of seconds a long freeze lasts. Long freezes can be disabled by remov Default value: `604800` = 7 days Mainnet reasonable range: 86400 to 31536000 (1 day to 1 year) +### `withdrawalLeak` + +Dimension: `{1}` + +The fraction of RSR stake that should be permitted to withdraw without a refresh. When cumulative withdrawals (or a single withdrawal) exceed this fraction, gas must be paid to refresh all assets. + +Setting this number larger allows unstakers to save more on gas at the cost of allowing more RSR to exit improperly prior to a default. + +Default value: `0.005e18` = 0.5% +Mainnet reasonable range: 0 to 0.1e18 (0 to 10%) + ### `RToken Supply Throttles` In order to restrict the system to organic patterns of behavior, we maintain two supply throttles, one for net issuance and one for net redemption. When a supply change occurs, a check is performed to ensure this does not move the supply more than an acceptable range over a period; a period is fixed to be an hour. The acceptable range (per throttle) is a function of the `amtRate` and `pctRate` variables. **It is the maximum of whichever variable provides the larger rate.** From 28532d154e20a499ab4192fede5e7d48d00e3a70 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 18:35:12 -0400 Subject: [PATCH 266/462] cache quantities only --- contracts/interfaces/IBasketHandler.sol | 5 - contracts/p0/BasketHandler.sol | 47 +++---- contracts/p1/BasketHandler.sol | 42 +++--- .../p1/mixins/RecollateralizationLib.sol | 126 +++++++----------- contracts/plugins/assets/RTokenAsset.sol | 51 ++----- test/Main.test.ts | 17 +-- .../Recollateralization.test.ts.snap | 10 +- 7 files changed, 111 insertions(+), 187 deletions(-) diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 59ea67ce68..30a9c355d6 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -146,11 +146,6 @@ interface IBasketHandler is IComponent { /// @return lotHigh {UoA/tok} The upper end of the lot price estimate function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh); - /// Returns both the price & lotPrice at once, for gas optimization - /// @return price_ {UoA/tok} The low and high price estimate of an RToken - /// @return lotPrice_ {UoA/tok} The low and high lotprice of an RToken - function prices() external view returns (Price memory price_, Price memory lotPrice_); - /// @return timestamp The timestamp at which the basket was last set function timestamp() external view returns (uint48); diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 7cf607fd29..ea6ca7ffaa 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -368,53 +368,46 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } /// Should not revert - /// @return {UoA/BU} The lower end of the price estimate - /// @return {UoA/BU} The upper end of the price estimate + /// @return low {UoA/BU} The lower end of the price estimate + /// @return high {UoA/BU} The upper end of the price estimate // returns sum(quantity(erc20) * price(erc20) for erc20 in basket.erc20s) - function price() external view returns (uint192, uint192) { - (Price memory p, ) = prices(); - return (p.low, p.high); + function price() external view returns (uint192 low, uint192 high) { + return _price(false); } /// Should not revert /// lowLow should be nonzero when the asset might be worth selling - /// @return {UoA/BU} The lower end of the lot price estimate - /// @return {UoA/BU} The upper end of the lot price estimate + /// @return lotLow {UoA/BU} The lower end of the lot price estimate + /// @return lotHigh {UoA/BU} The upper end of the lot price estimate // returns sum(quantity(erc20) * lotPrice(erc20) for erc20 in basket.erc20s) - function lotPrice() external view returns (uint192, uint192) { - (, Price memory lotP) = prices(); - return (lotP.low, lotP.high); + function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh) { + return _price(true); } - /// Returns both the price() & lotPrice() at once, for gas optimization - /// @return price_ {UoA/tok} The low and high price estimate of an RToken - /// @return lotPrice_ {UoA/tok} The low and high lotprice of an RToken - function prices() public view returns (Price memory price_, Price memory lotPrice_) { + /// Returns the price of a BU, using the lot prices if `useLotPrice` is true + /// @return low {UoA/BU} The lower end of the lot price estimate + /// @return high {UoA/BU} The upper end of the lot price estimate + function _price(bool useLotPrice) internal view returns (uint192 low, uint192 high) { + IAssetRegistry reg = main.assetRegistry(); + uint256 low256; uint256 high256; - uint256 lotLow256; - uint256 lotHigh256; - uint256 len = basket.erc20s.length; - for (uint256 i = 0; i < len; ++i) { + for (uint256 i = 0; i < basket.erc20s.length; i++) { uint192 qty = quantity(basket.erc20s[i]); if (qty == 0) continue; - IAsset asset = main.assetRegistry().toAsset(basket.erc20s[i]); - (uint192 lowP, uint192 highP) = asset.price(); - (uint192 lotLowP, uint192 lotHighP) = asset.lotPrice(); + (uint192 lowP, uint192 highP) = useLotPrice + ? reg.toAsset(basket.erc20s[i]).lotPrice() + : reg.toAsset(basket.erc20s[i]).price(); low256 += qty.safeMul(lowP, RoundingMode.FLOOR); high256 += qty.safeMul(highP, RoundingMode.CEIL); - lotLow256 += qty.safeMul(lotLowP, RoundingMode.FLOOR); - lotHigh256 += qty.safeMul(lotHighP, RoundingMode.CEIL); } // safe downcast: FIX_MAX is type(uint192).max - price_.low = low256 >= FIX_MAX ? FIX_MAX : uint192(low256); - price_.high = high256 >= FIX_MAX ? FIX_MAX : uint192(high256); - lotPrice_.low = lotLow256 >= FIX_MAX ? FIX_MAX : uint192(lotLow256); - lotPrice_.high = lotHigh256 >= FIX_MAX ? FIX_MAX : uint192(lotHigh256); + low = low256 >= FIX_MAX ? FIX_MAX : uint192(low256); + high = high256 >= FIX_MAX ? FIX_MAX : uint192(high256); } /// Return the current issuance/redemption value of `amount` BUs diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 32a8c5dfef..767c6c93c9 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -303,53 +303,45 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } /// Should not revert - /// @return {UoA/BU} The lower end of the price estimate - /// @return {UoA/BU} The upper end of the price estimate + /// @return low {UoA/BU} The lower end of the price estimate + /// @return high {UoA/BU} The upper end of the price estimate // returns sum(quantity(erc20) * price(erc20) for erc20 in basket.erc20s) - function price() external view returns (uint192, uint192) { - (Price memory p, ) = prices(); - return (p.low, p.high); + function price() external view returns (uint192 low, uint192 high) { + return _price(false); } /// Should not revert /// lowLow should be nonzero when the asset might be worth selling - /// @return {UoA/BU} The lower end of the lot price estimate - /// @return {UoA/BU} The upper end of the lot price estimate + /// @return lotLow {UoA/BU} The lower end of the lot price estimate + /// @return lotHigh {UoA/BU} The upper end of the lot price estimate // returns sum(quantity(erc20) * lotPrice(erc20) for erc20 in basket.erc20s) - function lotPrice() external view returns (uint192, uint192) { - (, Price memory lotP) = prices(); - return (lotP.low, lotP.high); + function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh) { + return _price(true); } - /// Returns both the price() & lotPrice() at once, for gas optimization - /// @return price_ {UoA/tok} The low and high price estimate of an RToken - /// @return lotPrice_ {UoA/tok} The low and high lotprice of an RToken - function prices() public view returns (Price memory price_, Price memory lotPrice_) { + /// Returns the price of a BU, using the lot prices if `useLotPrice` is true + /// @return low {UoA/BU} The lower end of the price estimate + /// @return high {UoA/BU} The upper end of the price estimate + function _price(bool useLotPrice) internal view returns (uint192 low, uint192 high) { uint256 low256; uint256 high256; - uint256 lotLow256; - uint256 lotHigh256; uint256 len = basket.erc20s.length; for (uint256 i = 0; i < len; ++i) { uint192 qty = quantity(basket.erc20s[i]); if (qty == 0) continue; - IAsset asset = assetRegistry.toAsset(basket.erc20s[i]); - (uint192 lowP, uint192 highP) = asset.price(); - (uint192 lotLowP, uint192 lotHighP) = asset.lotPrice(); + (uint192 lowP, uint192 highP) = useLotPrice + ? assetRegistry.toAsset(basket.erc20s[i]).lotPrice() + : assetRegistry.toAsset(basket.erc20s[i]).price(); low256 += qty.safeMul(lowP, RoundingMode.FLOOR); high256 += qty.safeMul(highP, RoundingMode.CEIL); - lotLow256 += qty.safeMul(lotLowP, RoundingMode.FLOOR); - lotHigh256 += qty.safeMul(lotHighP, RoundingMode.CEIL); } // safe downcast: FIX_MAX is type(uint192).max - price_.low = low256 >= FIX_MAX ? FIX_MAX : uint192(low256); - price_.high = high256 >= FIX_MAX ? FIX_MAX : uint192(high256); - lotPrice_.low = lotLow256 >= FIX_MAX ? FIX_MAX : uint192(lotLow256); - lotPrice_.high = lotHigh256 >= FIX_MAX ? FIX_MAX : uint192(lotHigh256); + low = low256 >= FIX_MAX ? FIX_MAX : uint192(low256); + high = high256 >= FIX_MAX ? FIX_MAX : uint192(high256); } /// Return the current issuance/redemption value of `amount` BUs diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index 4ca6eab1c3..255e00ba8c 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -20,6 +20,7 @@ struct TradingContext { // Components IBackingManager bm; + IBasketHandler bh; IAssetRegistry ar; IStRSR stRSR; IERC20 rsr; @@ -27,14 +28,8 @@ struct TradingContext { // Gov Vars uint192 minTradeVolume; // {UoA} uint192 maxTradeSlippage; // {1} -} - -// All the extra information nextTradePair() needs; necessary to stay under stack limit -struct NextTradePairContext { + // Cached values uint192[] quantities; // {tok/BU} basket quantities - BasketRange range; - Price rTokenPrice; - Price rTokenLotPrice; } /** @@ -66,48 +61,35 @@ library RecollateralizationLibP1 { view returns (bool doTrade, TradeRequest memory req) { - NextTradePairContext memory ntpCtx; + IMain main = bm.main(); - // === Prepare cached values === + // === Prepare TradingContext cache === + TradingContext memory ctx; - IMain main = bm.main(); - TradingContext memory ctx = TradingContext({ - basketsHeld: basketsHeld, - bm: bm, - ar: main.assetRegistry(), - stRSR: main.stRSR(), - rsr: main.rsr(), - rToken: main.rToken(), - minTradeVolume: bm.minTradeVolume(), - maxTradeSlippage: bm.maxTradeSlippage() - }); - Registry memory reg = ctx.ar.getRegistry(); + ctx.basketsHeld = basketsHeld; + ctx.bm = bm; + ctx.bh = main.basketHandler(); + ctx.ar = main.assetRegistry(); + ctx.stRSR = main.stRSR(); + ctx.rsr = main.rsr(); + ctx.rToken = main.rToken(); + ctx.minTradeVolume = bm.minTradeVolume(); + ctx.maxTradeSlippage = bm.maxTradeSlippage(); - // Calculate quantities up-front for re-use - IBasketHandler bh = main.basketHandler(); - ntpCtx.quantities = new uint192[](reg.erc20s.length); + // Calculate quantities + Registry memory reg = ctx.ar.getRegistry(); + ctx.quantities = new uint192[](reg.erc20s.length); for (uint256 i = 0; i < reg.erc20s.length; ++i) { - ntpCtx.quantities[i] = bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + ctx.quantities[i] = ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); } // ============================ - // Compute BU price - (Price memory buPrice, Price memory buLotPrice) = bh.prices(); - // Compute a target basket range for trading - {BU} - ntpCtx.range = basketRange(ctx, reg, ntpCtx.quantities, buPrice); - - // Compute RToken price - RTokenAsset rTokenAsset = RTokenAsset(address(ctx.ar.toAsset(IERC20(address(ctx.rToken))))); - (ntpCtx.rTokenPrice, ntpCtx.rTokenLotPrice) = rTokenAsset.prices( - ntpCtx.range, - buPrice, - buLotPrice - ); + BasketRange memory range = basketRange(ctx, reg); // Select a pair to trade next, if one exists - TradeInfo memory trade = nextTradePair(ctx, reg, ntpCtx); + TradeInfo memory trade = nextTradePair(ctx, reg, range); // Don't trade if no pair is selected if (address(trade.sell) == address(0) || address(trade.buy) == address(0)) { @@ -167,13 +149,13 @@ library RecollateralizationLibP1 { // - range.bottom = min(rToken.basketsNeeded, basketsHeld.bottom + least baskets purchaseable) // where "least baskets purchaseable" involves trading at unfavorable prices, // incurring maxTradeSlippage, and taking up to a minTradeVolume loss due to dust. - function basketRange( - TradingContext memory ctx, - Registry memory reg, - uint192[] memory quantities, - Price memory buPrice - ) internal view returns (BasketRange memory range) { - uint192 basketsNeeded = ctx.rToken.basketsNeeded(); + function basketRange(TradingContext memory ctx, Registry memory reg) + internal + view + returns (BasketRange memory range) + { + (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.price(); // {UoA/BU} + uint192 basketsNeeded = ctx.rToken.basketsNeeded(); // {BU} // Cap ctx.basketsHeld.top if (ctx.basketsHeld.top > basketsNeeded) { @@ -207,7 +189,7 @@ library RecollateralizationLibP1 { // Intentionally include value of IFFY/DISABLED collateral if ( - quantities[i] == 0 && + ctx.quantities[i] == 0 && !TradeLib.isEnoughToSell(reg.assets[i], bal, lotLow, ctx.minTradeVolume) ) continue; } @@ -220,20 +202,20 @@ library RecollateralizationLibP1 { // if in surplus relative to ctx.basketsHeld.top: add-in surplus baskets { // {tok} = {tok/BU} * {BU} - uint192 anchor = quantities[i].mul(ctx.basketsHeld.top, CEIL); + uint192 anchor = ctx.quantities[i].mul(ctx.basketsHeld.top, CEIL); if (anchor > bal) { // deficit: deduct optimistic estimate of baskets missing // {BU} = {UoA/tok} * {tok} / {UoA/BU} - deltaTop -= int256(uint256(low.mulDiv(anchor - bal, buPrice.high, FLOOR))); + deltaTop -= int256(uint256(low.mulDiv(anchor - bal, buPriceHigh, FLOOR))); // does not need underflow protection: using low price of asset } else { // surplus: add-in optimistic estimate of baskets purchaseable // {BU} = {UoA/tok} * {tok} / {UoA/BU} deltaTop += int256( - uint256(ctx.bm.safeMulDivCeil(high, bal - anchor, buPrice.low)) + uint256(ctx.bm.safeMulDivCeil(high, bal - anchor, buPriceLow)) ); // needs overflow protection: using high price of asset which can be FIX_MAX } @@ -243,7 +225,7 @@ library RecollateralizationLibP1 { // add-in surplus baskets relative to ctx.basketsHeld.bottom { // {tok} = {tok/BU} * {BU} - uint192 anchor = quantities[i].mul(ctx.basketsHeld.bottom, FLOOR); + uint192 anchor = ctx.quantities[i].mul(ctx.basketsHeld.bottom, FLOOR); // (1) Sell tokens at low price // {UoA} = {UoA/tok} * {tok} @@ -261,11 +243,7 @@ library RecollateralizationLibP1 { // (3) Buy BUs at their high price with the remaining value // (4) Assume maximum slippage in trade // {BU} = {UoA} * {1} / {UoA/BU} - range.bottom += val.mulDiv( - FIX_ONE.minus(ctx.maxTradeSlippage), - buPrice.high, - FLOOR - ); + range.bottom += val.mulDiv(FIX_ONE.minus(ctx.maxTradeSlippage), buPriceHigh, FLOOR); } } @@ -334,7 +312,7 @@ library RecollateralizationLibP1 { function nextTradePair( TradingContext memory ctx, Registry memory reg, - NextTradePairContext memory ntpCtx + BasketRange memory range ) private view returns (TradeInfo memory trade) { MaxSurplusDeficit memory maxes; maxes.surplusStatus = CollateralStatus.IFFY; // least-desirable sell status @@ -348,7 +326,7 @@ library RecollateralizationLibP1 { // {tok} = {BU} * {tok/BU} // needed(Top): token balance needed for range.top baskets: quantity(e) * range.top - uint192 needed = ntpCtx.range.top.mul(ntpCtx.quantities[i], CEIL); // {tok} + uint192 needed = range.top.mul(ctx.quantities[i], CEIL); // {tok} if (bal.gt(needed)) { uint192 low; // {UoA/sellTok} @@ -393,7 +371,7 @@ library RecollateralizationLibP1 { } } else { // needed(Bottom): token balance needed at bottom of the basket range - needed = ntpCtx.range.bottom.mul(ntpCtx.quantities[i], CEIL); // {buyTok}; + needed = range.bottom.mul(ctx.quantities[i], CEIL); // {buyTok}; if (bal.lt(needed)) { uint192 amtShort = needed.minus(bal); // {buyTok} @@ -414,30 +392,24 @@ library RecollateralizationLibP1 { } } - // Special-case RToken - { + // Use RToken if needed + if (address(trade.sell) == address(0) && address(trade.buy) != address(0)) { RTokenAsset rTokenAsset = RTokenAsset( address(ctx.ar.toAsset(IERC20(address(ctx.rToken)))) ); uint192 bal = rTokenAsset.bal(address(ctx.bm)); - if ( - ntpCtx.rTokenPrice.high > 0 && - isBetterSurplus( - maxes, - CollateralStatus.SOUND, - bal.mul(ntpCtx.rTokenLotPrice.low, FLOOR) - ) && - TradeLib.isEnoughToSell( - rTokenAsset, - bal, - ntpCtx.rTokenLotPrice.low, - ctx.minTradeVolume - ) - ) { - trade.sell = rTokenAsset; - trade.sellAmount = bal; - trade.sellPrice = ntpCtx.rTokenPrice.low; + if (bal > 0) { + (uint192 low, uint192 high) = rTokenAsset.price(); // {UoA/tok} + (uint192 lotLow, ) = rTokenAsset.lotPrice(); // {UoA/tok} + if ( + high > 0 && + TradeLib.isEnoughToSell(rTokenAsset, bal, lotLow, ctx.minTradeVolume) + ) { + trade.sell = rTokenAsset; + trade.sellAmount = bal; + trade.sellPrice = low; + } } } diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index 22c0ef9fb9..f01c9bd1d4 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -39,33 +39,6 @@ contract RTokenAsset is IAsset, VersionedAsset { maxTradeVolume = maxTradeVolume_; } - /// Calculates price() & lotPrice() in a gas-optimized way using a cached BasketRange - /// Used by RecollateralizationLib for efficient price calculation - /// @param buRange {BU} The top and bottom of the bu band; how many BUs we expect to hold - /// @param buPrice {UoA/BU} The low and high price estimate of a basket unit - /// @param buLotPrice {UoA/BU} The low and high lotprice of a basket unit - /// @return price_ {UoA/tok} The low and high price estimate of an RToken - /// @return lotPrice_ {UoA/tok} The low and high lotprice of an RToken - function prices( - BasketRange memory buRange, - Price memory buPrice, - Price memory buLotPrice - ) public view returns (Price memory price_, Price memory lotPrice_) { - // Here we take advantage of the fact that we know RToken has 18 decimals - // to convert between uint256 an uint192. Fits due to assumed max totalSupply. - uint192 supply = _safeWrap(IRToken(address(erc20)).totalSupply()); - - if (supply == 0) return (buPrice, buLotPrice); - - // {UoA/tok} = {BU} * {UoA/BU} / {tok} - price_.low = buRange.bottom.mulDiv(buPrice.low, supply, FLOOR); - price_.high = buRange.top.mulDiv(buPrice.high, supply, CEIL); - lotPrice_.low = buRange.bottom.mulDiv(buLotPrice.low, supply, FLOOR); - lotPrice_.high = buRange.top.mulDiv(buLotPrice.high, supply, CEIL); - assert(price_.low <= price_.high); - assert(lotPrice_.low <= lotPrice_.high); - } - /// 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 @@ -159,9 +132,6 @@ contract RTokenAsset is IAsset, VersionedAsset { /// Computationally expensive basketRange calculation; used in price() & lotPrice() function basketRange() private view returns (BasketRange memory range) { - Price memory buPrice; - (buPrice.low, buPrice.high) = basketHandler.price(); // {UoA/BU} - BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(backingManager)); uint192 basketsNeeded = IRToken(address(erc20)).basketsNeeded(); // {BU} @@ -176,26 +146,27 @@ contract RTokenAsset is IAsset, VersionedAsset { // should switch over to an asset with a price feed. IMain main = backingManager.main(); + Registry memory reg = assetRegistry.getRegistry(); + + uint192[] memory quantities = new uint192[](reg.erc20s.length); + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + quantities[i] = basketHandler.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + } TradingContext memory ctx = TradingContext({ basketsHeld: basketsHeld, bm: backingManager, - ar: main.assetRegistry(), + bh: basketHandler, + ar: assetRegistry, stRSR: main.stRSR(), rsr: main.rsr(), rToken: main.rToken(), minTradeVolume: backingManager.minTradeVolume(), - maxTradeSlippage: backingManager.maxTradeSlippage() + maxTradeSlippage: backingManager.maxTradeSlippage(), + quantities: quantities }); - Registry memory reg = assetRegistry.getRegistry(); - - uint192[] memory quantities = new uint192[](reg.erc20s.length); - for (uint256 i = 0; i < reg.erc20s.length; ++i) { - quantities[i] = basketHandler.quantityUnsafe(reg.erc20s[i], reg.assets[i]); - } - // will exclude UoA value from RToken balances at BackingManager - range = RecollateralizationLibP1.basketRange(ctx, reg, quantities, buPrice); + range = RecollateralizationLibP1.basketRange(ctx, reg); } } } diff --git a/test/Main.test.ts b/test/Main.test.ts index 72ebec5310..f7c7f4b811 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -2604,7 +2604,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(highPrice).to.equal(MAX_UINT192) }) - it('Should distinguish between price/lotPrice - prices()', async () => { + it('Should distinguish between price/lotPrice', async () => { // Set basket with single collateral await basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1')]) await basketHandler.refreshBasket() @@ -2613,13 +2613,14 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const [low, high] = await collateral0.price() await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) // oracle error - const [price, lotPrice] = await basketHandler.prices() - expect(price.low).to.equal(0) - expect(price.high).to.equal(MAX_UINT192) - expect(lotPrice.low).to.be.closeTo(low, low.div(bn('1e5'))) // small decay - expect(lotPrice.low).to.be.lt(low) - expect(lotPrice.high).to.be.closeTo(high, high.div(bn('1e5'))) // small decay - expect(lotPrice.high).to.be.lt(high) + const [lowPrice, highPrice] = await basketHandler.price() + const [lotLowPrice, lotHighPrice] = await basketHandler.lotPrice() + expect(lowPrice).to.equal(0) + expect(highPrice).to.equal(MAX_UINT192) + expect(lotLowPrice).to.be.closeTo(low, low.div(bn('1e5'))) // small decay + expect(lotLowPrice).to.be.lt(low) + expect(lotHighPrice).to.be.closeTo(high, high.div(bn('1e5'))) // small decay + expect(lotHighPrice).to.be.lt(high) }) it('Should disable basket on asset deregistration + return quantities correctly', async () => { diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 59bed92f22..6cb625c1b2 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1387984`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1332440`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1514304`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `2230209`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1755363`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1689530`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1699806`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1633814`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1790090`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `2664701`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `212916`; From 053c1a9a1eacee7f731eb5a21cb2e15c1329a035 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 18:56:01 -0400 Subject: [PATCH 267/462] skip over RToken dust balances without computing price --- contracts/interfaces/IDistributor.sol | 3 +++ contracts/p0/mixins/TradingLib.sol | 24 ++++++++++++++++++- .../p1/mixins/RecollateralizationLib.sol | 13 ++++++---- .../Recollateralization.test.ts.snap | 4 ++-- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/contracts/interfaces/IDistributor.sol b/contracts/interfaces/IDistributor.sol index 730b560640..3d0696e286 100644 --- a/contracts/interfaces/IDistributor.sol +++ b/contracts/interfaces/IDistributor.sol @@ -15,6 +15,9 @@ struct RevenueTotals { uint24 rsrTotal; // {revShare} } +// The maximum value of rTokenTotal and rsrTotal; up to 1024 entries, each up to 10,000 +uint256 constant MAX_REVENUE_TOTALS = 1024 * 1e4; + /** * @title IDistributor * @notice The Distributor Component maintains a revenue distribution table that dictates diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index 8afe65f660..8f201dec87 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -423,7 +423,7 @@ library TradingLibP0 { // No space on the stack to cache erc20s.length for (uint256 i = 0; i < erc20s.length; ++i) { - if (erc20s[i] == ctx.rsr) continue; + if (erc20s[i] == ctx.rsr || address(erc20s[i]) == address(ctx.rToken)) continue; IAsset asset = ctx.reg.toAsset(erc20s[i]); @@ -487,6 +487,28 @@ library TradingLibP0 { } } + // Use RToken if needed + if (address(trade.sell) == address(0) && address(trade.buy) != address(0)) { + IAsset rTokenAsset = IAsset(address(ctx.reg.toAsset(IERC20(address(ctx.rToken))))); + uint192 bal = rTokenAsset.bal(address(ctx.bm)); + + // Only price RToken if it is large enough to be worth it + if (bal > MAX_REVENUE_TOTALS) { + // Context: The Distributor leaves small balances behind. It is a non-UoA measure. + // MAX_REVENUE_TOTALS is about 1e7, so on a minTradeVolume of $1000 it would + // require 1 whole RToken to be worth 100 trillion dollars to be a mistake. + // So, it seems like a safe heuristic to use to avoid looking up RToken price. + + (uint192 low, uint192 high) = rTokenAsset.price(); // {UoA/tok} + (uint192 lotLow, ) = rTokenAsset.lotPrice(); // {UoA/tok} + if (high > 0 && isEnoughToSell(rTokenAsset, bal, lotLow, ctx.minTradeVolume)) { + trade.sell = rTokenAsset; + trade.sellAmount = bal; + trade.sellPrice = low; + } + } + } + // Use RSR if needed if (address(trade.sell) == address(0) && address(trade.buy) != address(0)) { IAsset rsrAsset = ctx.reg.toAsset(ctx.rsr); diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index 255e00ba8c..a3e594f981 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -5,7 +5,6 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../../interfaces/IAsset.sol"; import "../../interfaces/IAssetRegistry.sol"; import "../../interfaces/IBackingManager.sol"; -import "../../plugins/assets/RTokenAsset.sol"; import "../../libraries/Fixed.sol"; import "./TradeLib.sol"; @@ -394,12 +393,16 @@ library RecollateralizationLibP1 { // Use RToken if needed if (address(trade.sell) == address(0) && address(trade.buy) != address(0)) { - RTokenAsset rTokenAsset = RTokenAsset( - address(ctx.ar.toAsset(IERC20(address(ctx.rToken)))) - ); + IAsset rTokenAsset = ctx.ar.toAsset(IERC20(address(ctx.rToken))); uint192 bal = rTokenAsset.bal(address(ctx.bm)); - if (bal > 0) { + // Only price RToken if it is large enough to be worth it + if (bal > MAX_REVENUE_TOTALS) { + // Context: The Distributor leaves small balances behind. It is a non-UoA measure. + // MAX_REVENUE_TOTALS is about 1e7, so on a minTradeVolume of $1000 it would + // require 1 whole RToken to be worth 100 trillion dollars to be a mistake. + // So, it seems like a safe heuristic to use to avoid looking up RToken price. + (uint192 low, uint192 high) = rTokenAsset.price(); // {UoA/tok} (uint192 lotLow, ) = rTokenAsset.lotPrice(); // {UoA/tok} if ( diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 6cb625c1b2..522081eb58 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -2,7 +2,7 @@ exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1332440`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `2230209`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1453082`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1689530`; @@ -12,6 +12,6 @@ exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `2664701`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1731105`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `212916`; From 0af3dc41350d12f95358b179e35c95c6e4bed38a Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 19:07:25 -0400 Subject: [PATCH 268/462] add test for trading RToken last --- test/Recollateralization.test.ts | 192 +++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 48ab75a45f..8a9452bd97 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -2024,6 +2024,198 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) }) + it('Should recollateralize correctly when switching basket - Using RToken for remainder', async () => { + // Send it all the RToken at the start -- should be ignored + await rToken.connect(addr1).transfer(backingManager.address, issueAmount) + + // Set prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(3, [token1.address], [fp('1')], false) + + // Check state remains SOUND + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken -- retains price because of + // over-collateralization + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Trigger recollateralization + const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) + .to.emit(backingManager, 'TradeStarted') + .withArgs( + anyValue, + token0.address, + token1.address, + sellAmt, + toBNDecimals(minBuyAmt, 6).add(1) + ) + + let auctionTimestamp: number = await getLatestBlockTimestamp() + + // Check auction registered + // Token0 -> Token1 Auction + await expectTrade(backingManager, { + sell: token0.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) + + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + // Asset value is zero, everything was moved to the Market + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check price in USD of the current RToken -- retains price because of + // over-collateralization + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Check Gnosis + expect(await token0.balanceOf(gnosis.address)).to.equal(issueAmount) + + // Perform Mock Bids for the new Token (addr1 has balance) + // Get fair price - minBuyAmt + await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6).add(1)) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: toBNDecimals(minBuyAmt, 6).add(1), + }) + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction, should start a new one to sell a new revenue token instead of RSR + // About 3e18 Tokens left to buy - Sets Buy amount as independent value + const buyAmtBidRevToken: BigNumber = sellAmt.sub(minBuyAmt) + + // Send the excess revenue tokens to backing manager - should be used instead of RSR + // Set price = $1 as expected + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [ + anyValue, + token0.address, + token1.address, + sellAmt, + toBNDecimals(minBuyAmt, 6).add(1), + ], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + args: [ + anyValue, + rToken.address, + token1.address, + anyValue, + toBNDecimals(buyAmtBidRevToken, 6).add(1), + ], + emitted: true, + }, + ]) + + auctionTimestamp = await getLatestBlockTimestamp() + + // RToken -> Token1 Auction + await expectTrade(backingManager, { + sell: rToken.address, + buy: token1.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('1'), + }) + + const t = await getTrade(backingManager, rToken.address) + const sellAmtRevToken = await t.initBal() + expect(toBNDecimals(buyAmtBidRevToken, 6).add(1)).to.equal( + toBNDecimals(await toMinBuyAmt(sellAmtRevToken, fp('1'), fp('1')), 6).add(1) + ) + + // Check state + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal( + toBNDecimals(minBuyAmt, 6).add(1) + ) + expect(await rToken.balanceOf(backingManager.address)).to.equal( + issueAmount.sub(sellAmtRevToken) + ) + expect(await rToken.totalSupply()).to.equal(issueAmount) + + // Check Gnosis + expect(await rToken.balanceOf(gnosis.address)).to.equal(sellAmtRevToken) + + // Perform Mock Bids for the new Token (addr1 has balance) + // Cover buyAmtBidRevToken which is all the amount required + await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmtRevToken, 6)) + await gnosis.placeBid(1, { + bidder: addr1.address, + sellAmount: sellAmtRevToken, + buyAmount: toBNDecimals(buyAmtBidRevToken, 6).add(1), + }) + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // End current auction + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: backingManager, + name: 'TradeSettled', + args: [ + anyValue, + rToken.address, + token1.address, + sellAmtRevToken, + toBNDecimals(buyAmtBidRevToken, 6).add(1), + ], + emitted: true, + }, + { + contract: backingManager, + name: 'TradeStarted', + emitted: false, + }, + ]) + + // Check state - Order restablished + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(true) + expect(await token0.balanceOf(backingManager.address)).to.equal(0) + expect(await token1.balanceOf(backingManager.address)).to.equal( + toBNDecimals(issueAmount, 6).add(1) + ) + expect(await rToken.balanceOf(backingManager.address)).to.be.closeTo(bn('0'), 100) // distributor leaves some + expect(await rToken.totalSupply()).to.be.closeTo(issueAmount, fp('0.000001')) // we have a bit more + + // Check price in USD of the current RToken + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + + // Stakes unchanged + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) + }) + it('Should recollateralize correctly in case of default - Using RSR for remainder', async () => { // Register Collateral await assetRegistry.connect(owner).register(backupCollateral1.address) From 7b6ed642355056fdaa5f41c08c1d29d0c9198c59 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 19:22:13 -0400 Subject: [PATCH 269/462] gas snapshots --- contracts/plugins/assets/RTokenAsset.sol | 32 +++++++++---------- test/__snapshots__/FacadeWrite.test.ts.snap | 2 +- test/__snapshots__/Main.test.ts.snap | 8 ++--- test/__snapshots__/RToken.test.ts.snap | 6 ++-- .../Recollateralization.test.ts.snap | 4 +-- test/__snapshots__/Revenues.test.ts.snap | 2 +- test/__snapshots__/ZZStRSR.test.ts.snap | 4 +-- .../__snapshots__/MaxBasketSize.test.ts.snap | 16 +++++----- 8 files changed, 37 insertions(+), 37 deletions(-) diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index f01c9bd1d4..87645cc6d0 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -146,24 +146,24 @@ contract RTokenAsset is IAsset, VersionedAsset { // should switch over to an asset with a price feed. IMain main = backingManager.main(); - Registry memory reg = assetRegistry.getRegistry(); - - uint192[] memory quantities = new uint192[](reg.erc20s.length); + TradingContext memory ctx; + + ctx.basketsHeld = basketsHeld; + ctx.bm = backingManager; + ctx.bh = basketHandler; + ctx.ar = assetRegistry; + ctx.stRSR = main.stRSR(); + ctx.rsr = main.rsr(); + ctx.rToken = main.rToken(); + ctx.minTradeVolume = backingManager.minTradeVolume(); + ctx.maxTradeSlippage = backingManager.maxTradeSlippage(); + + // Calculate quantities + Registry memory reg = ctx.ar.getRegistry(); + ctx.quantities = new uint192[](reg.erc20s.length); for (uint256 i = 0; i < reg.erc20s.length; ++i) { - quantities[i] = basketHandler.quantityUnsafe(reg.erc20s[i], reg.assets[i]); + ctx.quantities[i] = ctx.bh.quantityUnsafe(reg.erc20s[i], reg.assets[i]); } - TradingContext memory ctx = TradingContext({ - basketsHeld: basketsHeld, - bm: backingManager, - bh: basketHandler, - ar: assetRegistry, - stRSR: main.stRSR(), - rsr: main.rsr(), - rToken: main.rToken(), - minTradeVolume: backingManager.minTradeVolume(), - maxTradeSlippage: backingManager.maxTradeSlippage(), - quantities: quantities - }); // will exclude UoA value from RToken balances at BackingManager range = RecollateralizationLibP1.basketRange(ctx, reg); diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index b5b5e716b1..e7f3bf509d 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `9202775`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `9069178`; exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464736`; diff --git a/test/__snapshots__/Main.test.ts.snap b/test/__snapshots__/Main.test.ts.snap index 5d8cce6b50..1861862025 100644 --- a/test/__snapshots__/Main.test.ts.snap +++ b/test/__snapshots__/Main.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `341691`; +exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `341625`; exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `192993`; exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `192993`; -exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `164122`; +exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `164144`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80394`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80416`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `69906`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `69928`; diff --git a/test/__snapshots__/RToken.test.ts.snap b/test/__snapshots__/RToken.test.ts.snap index d884307c95..f3c5d0ddb5 100644 --- a/test/__snapshots__/RToken.test.ts.snap +++ b/test/__snapshots__/RToken.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `772417`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `772308`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `610624`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `610515`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `586825`; +exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `573888`; exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56570`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 522081eb58..1fd6e7b0e3 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -2,7 +2,7 @@ exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1332440`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1453082`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1465975`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1689530`; @@ -12,6 +12,6 @@ exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1731105`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1731461`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `212916`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index 189c3841f3..c2cca65c5c 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -14,7 +14,7 @@ exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212382`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `758101`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1288954`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1150171`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `320785`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index 107dfcea64..b779667f61 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -14,6 +14,6 @@ exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `222609`; exports[`StRSRP1 contract Gas Reporting Unstake 2`] = `139758`; -exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `555770`; +exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `555639`; -exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `509774`; +exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `509643`; diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index 56b7e55d3a..fa053cc38c 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `11745798`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `11745689`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9481844`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9481800`; exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2292984`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `12996783`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `12996739`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `25811511`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `25176105`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10752586`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10752477`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8471544`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8471500`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4535205`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4535170`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `17426207`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `17613771`; From 6008aad63502087279b1e1d1e4100e873f0d2a15 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 19:43:01 -0400 Subject: [PATCH 270/462] delete console.log --- test/Revenues.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index fb6081c2af..8a6063101f 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2267,7 +2267,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Simulate 18.6 minutes of blocks, should swap at right price each time for (let now = await getLatestBlockTimestamp(); now <= end; now += 12) { const actual = await trade.connect(addr1).bidAmount(now) - console.log(actual) const expected = await dutchBuyAmount( fp(now - start).div(end - start), rTokenAsset.address, From b94fd68e4cdce1dacab965183b6e69f017adc754 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 18 May 2023 20:03:48 -0400 Subject: [PATCH 271/462] mainnet facadeAct/facadeRead addresses --- .../mainnet-3.0.0/1-tmp-deployments.json | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json diff --git a/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json b/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json new file mode 100644 index 0000000000..4f97046bea --- /dev/null +++ b/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json @@ -0,0 +1,35 @@ +{ + "prerequisites": { + "RSR": "0x320623b8e4ff03373931769a31fc52a4e78b5d70", + "RSR_FEED": "0x759bBC1be8F90eE6457C44abc7d443842a976d02", + "GNOSIS_EASY_AUCTION": "0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101" + }, + "tradingLib": "", + "cvxMiningLib": "", + "facadeRead": "0xad0BFAEE863B1102e9fD4e6330A02B08d885C715", + "facadeAct": "0xBe70970a10C186185b1bc1bE980eA09BD68fD97A", + "facadeWriteLib": "", + "basketLib": "", + "facadeWrite": "", + "deployer": "", + "rsrAsset": "0x9cd0F8387672fEaaf7C269b62c34C53590d7e948", + "implementations": { + "main": "", + "trading": { + "gnosisTrade": "", + "dutchTrade": "" + }, + "components": { + "assetRegistry": "", + "backingManager": "", + "basketHandler": "", + "broker": "", + "distributor": "", + "furnace": "", + "rsrTrader": "", + "rTokenTrader": "", + "rToken": "", + "stRSR": "" + } + } +} \ No newline at end of file From 85d5263d43381a74ad22bb007a2e9c7d914f4663 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 11:53:15 -0400 Subject: [PATCH 272/462] fix distributor internal consistency + add clearer comment --- contracts/interfaces/IDistributor.sol | 9 +++++---- contracts/p0/Distributor.sol | 2 +- contracts/p0/mixins/TradingLib.sol | 3 ++- contracts/p1/Distributor.sol | 2 +- contracts/p1/mixins/RecollateralizationLib.sol | 3 ++- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/contracts/interfaces/IDistributor.sol b/contracts/interfaces/IDistributor.sol index 3d0696e286..05c490a716 100644 --- a/contracts/interfaces/IDistributor.sol +++ b/contracts/interfaces/IDistributor.sol @@ -4,20 +4,21 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./IComponent.sol"; +uint256 constant MAX_DISTRIBUTION = 1e4; // 10,000 +uint8 constant MAX_DESTINATIONS = 100; + struct RevenueShare { uint16 rTokenDist; // {revShare} A value between [0, 10,000] uint16 rsrDist; // {revShare} A value between [0, 10,000] } -/// Assumes no more than 1024 independent distributions. +/// Assumes no more than MAX_DESTINATIONS independent distributions. +/// Each total can only be at most MAX_DISTRIBUTION * MAX_DESTINATIONS struct RevenueTotals { uint24 rTokenTotal; // {revShare} uint24 rsrTotal; // {revShare} } -// The maximum value of rTokenTotal and rsrTotal; up to 1024 entries, each up to 10,000 -uint256 constant MAX_REVENUE_TOTALS = 1024 * 1e4; - /** * @title IDistributor * @notice The Distributor Component maintains a revenue distribution table that dictates diff --git a/contracts/p0/Distributor.sol b/contracts/p0/Distributor.sol index 3eaae7dffb..af36733c14 100644 --- a/contracts/p0/Distributor.sol +++ b/contracts/p0/Distributor.sol @@ -21,7 +21,7 @@ contract DistributorP0 is ComponentP0, IDistributor { address public constant FURNACE = address(1); address public constant ST_RSR = address(2); - uint8 public constant MAX_DESTINATIONS_ALLOWED = 100; + uint8 public constant MAX_DESTINATIONS_ALLOWED = MAX_DESTINATIONS; function init(IMain main_, RevenueShare memory dist) public initializer { __Component_init(main_); diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index 8f201dec87..b9c397ae5d 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -493,7 +493,8 @@ library TradingLibP0 { uint192 bal = rTokenAsset.bal(address(ctx.bm)); // Only price RToken if it is large enough to be worth it - if (bal > MAX_REVENUE_TOTALS) { + // Heuristic: If the distributor would skip it, it's smaller than minTradeVolume + if (bal >= MAX_DISTRIBUTION * MAX_DESTINATIONS) { // Context: The Distributor leaves small balances behind. It is a non-UoA measure. // MAX_REVENUE_TOTALS is about 1e7, so on a minTradeVolume of $1000 it would // require 1 whole RToken to be worth 100 trillion dollars to be a mistake. diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index 8dccce968e..3e0017f53e 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -31,7 +31,7 @@ contract DistributorP1 is ComponentP1, IDistributor { address public constant FURNACE = address(1); address public constant ST_RSR = address(2); - uint8 public constant MAX_DESTINATIONS_ALLOWED = 100; + uint8 public constant MAX_DESTINATIONS_ALLOWED = MAX_DESTINATIONS; IERC20 private rsr; IERC20 private rToken; diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index a3e594f981..a490cdae0e 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -397,7 +397,8 @@ library RecollateralizationLibP1 { uint192 bal = rTokenAsset.bal(address(ctx.bm)); // Only price RToken if it is large enough to be worth it - if (bal > MAX_REVENUE_TOTALS) { + // Heuristic: If the distributor would skip it, it's smaller than minTradeVolume + if (bal >= MAX_DISTRIBUTION * MAX_DESTINATIONS) { // Context: The Distributor leaves small balances behind. It is a non-UoA measure. // MAX_REVENUE_TOTALS is about 1e7, so on a minTradeVolume of $1000 it would // require 1 whole RToken to be worth 100 trillion dollars to be a mistake. From db2466d81b4323dcd24cefbf0cdb611e795936b2 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 11:56:48 -0400 Subject: [PATCH 273/462] more comment clarifications --- contracts/p0/mixins/TradingLib.sol | 6 +++--- contracts/p1/mixins/RecollateralizationLib.sol | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index b9c397ae5d..ed0efbdf8d 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -496,9 +496,9 @@ library TradingLibP0 { // Heuristic: If the distributor would skip it, it's smaller than minTradeVolume if (bal >= MAX_DISTRIBUTION * MAX_DESTINATIONS) { // Context: The Distributor leaves small balances behind. It is a non-UoA measure. - // MAX_REVENUE_TOTALS is about 1e7, so on a minTradeVolume of $1000 it would - // require 1 whole RToken to be worth 100 trillion dollars to be a mistake. - // So, it seems like a safe heuristic to use to avoid looking up RToken price. + // This product is 1e6, so on a minTradeVolume of $1000 it would + // require 1 whole RToken to be worth 1 quadrillion dollars to be a mistake. + // So, it is a pretty safe heuristic to use to avoid looking up RToken price. (uint192 low, uint192 high) = rTokenAsset.price(); // {UoA/tok} (uint192 lotLow, ) = rTokenAsset.lotPrice(); // {UoA/tok} diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index a490cdae0e..086d57bcac 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -400,9 +400,9 @@ library RecollateralizationLibP1 { // Heuristic: If the distributor would skip it, it's smaller than minTradeVolume if (bal >= MAX_DISTRIBUTION * MAX_DESTINATIONS) { // Context: The Distributor leaves small balances behind. It is a non-UoA measure. - // MAX_REVENUE_TOTALS is about 1e7, so on a minTradeVolume of $1000 it would - // require 1 whole RToken to be worth 100 trillion dollars to be a mistake. - // So, it seems like a safe heuristic to use to avoid looking up RToken price. + // This product is 1e6, so on a minTradeVolume of $1000 it would + // require 1 whole RToken to be worth 1 quadrillion dollars to be a mistake. + // So, it is a pretty safe heuristic to use to avoid looking up RToken price. (uint192 low, uint192 high) = rTokenAsset.price(); // {UoA/tok} (uint192 lotLow, ) = rTokenAsset.lotPrice(); // {UoA/tok} From 782277117d38824a901e177f1936c9b9bcb95172 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 14:28:55 -0400 Subject: [PATCH 274/462] switch from trading RToken last to virtually redeeming it first --- contracts/interfaces/IDistributor.sol | 1 - contracts/interfaces/IRToken.sol | 4 + contracts/p0/BackingManager.sol | 7 + contracts/p0/Distributor.sol | 4 +- contracts/p0/RToken.sol | 48 +++-- contracts/p0/mixins/TradingLib.sol | 23 --- contracts/p1/BackingManager.sol | 7 + contracts/p1/Distributor.sol | 8 +- contracts/p1/RToken.sol | 46 +++-- .../p1/mixins/RecollateralizationLib.sol | 26 --- test/RToken.test.ts | 4 + test/Recollateralization.test.ts | 169 ++---------------- 12 files changed, 101 insertions(+), 246 deletions(-) diff --git a/contracts/interfaces/IDistributor.sol b/contracts/interfaces/IDistributor.sol index 05c490a716..459d849352 100644 --- a/contracts/interfaces/IDistributor.sol +++ b/contracts/interfaces/IDistributor.sol @@ -13,7 +13,6 @@ struct RevenueShare { } /// Assumes no more than MAX_DESTINATIONS independent distributions. -/// Each total can only be at most MAX_DISTRIBUTION * MAX_DESTINATIONS struct RevenueTotals { uint24 rTokenTotal; // {revShare} uint24 rsrTotal; // {revShare} diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index 004a7c9161..f51882a301 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -119,6 +119,10 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea /// @param amount {qRTok} The amount to be melted function melt(uint256 amount) external; + /// Giveup an amount of RToken from caller's account and scales basketsNeeded down + /// Callable only by backingManager + function giveup(uint256 amount) external; + /// Set the number of baskets needed directly, callable only by the BackingManager /// @param basketsNeeded {BU} The number of baskets to target /// needed range: pretty interesting diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 1473d43b68..346d0956c9 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -91,6 +91,11 @@ contract BackingManagerP0 is TradingP0, IBackingManager { ); require(!main.basketHandler().fullyCollateralized(), "already collateralized"); + // First giveup any held RToken balance (no token transfers required) + uint256 balance = main.rToken().balanceOf(address(this)); + if (balance > 0) main.rToken().giveup(balance); + if (main.basketHandler().fullyCollateralized()) return; // return if now capitalized + /* * Recollateralization * @@ -202,6 +207,8 @@ contract BackingManagerP0 is TradingP0, IBackingManager { } } + // === Private === + /// Compromise on how many baskets are needed in order to recollateralize-by-accounting /// @param wholeBasketsHeld {BU} The number of full basket units held by the BackingManager function compromiseBasketsNeeded(uint192 wholeBasketsHeld) private { diff --git a/contracts/p0/Distributor.sol b/contracts/p0/Distributor.sol index af36733c14..8d02be6c67 100644 --- a/contracts/p0/Distributor.sol +++ b/contracts/p0/Distributor.sol @@ -94,8 +94,8 @@ contract DistributorP0 is ComponentP0, IDistributor { ); if (dest == FURNACE) require(share.rsrDist == 0, "Furnace must get 0% of RSR"); if (dest == ST_RSR) require(share.rTokenDist == 0, "StRSR must get 0% of RToken"); - require(share.rsrDist <= 10000, "RSR distribution too high"); - require(share.rTokenDist <= 10000, "RToken distribution too high"); + require(share.rsrDist <= MAX_DISTRIBUTION, "RSR distribution too high"); + require(share.rTokenDist <= MAX_DISTRIBUTION, "RToken distribution too high"); if (share.rsrDist == 0 && share.rTokenDist == 0) { destinations.remove(dest); diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 0e28e83a83..a2305127d0 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -152,9 +152,8 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { issuanceThrottle.useAvailable(totalSupply(), -int256(amount)); redemptionThrottle.useAvailable(totalSupply(), int256(amount)); // reverts on overuse - // {BU} = {BU} * {qRTok} / {qRTok} - uint192 basketsRedeemed = basketsNeeded.muluDivu(amount, totalSupply()); - assert(basketsRedeemed.lte(basketsNeeded)); + // {BU} + uint192 basketsRedeemed = _redeem(_msgSender(), amount); emit Redemption(_msgSender(), recipient, amount, basketsRedeemed); (address[] memory erc20s, uint256[] memory amounts) = main.basketHandler().quote( @@ -162,9 +161,6 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { FLOOR ); - emit BasketsNeededChanged(basketsNeeded, basketsNeeded.minus(basketsRedeemed)); - basketsNeeded = basketsNeeded.minus(basketsRedeemed); - // ==== Send out balances ==== for (uint256 i = 0; i < erc20s.length; i++) { // Send withdrawal @@ -176,9 +172,6 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { ); } } - - // Accept and burn RToken, reverts if not enough balance - _burn(_msgSender(), amount); } /// Redeem RToken for a linear combination of historical baskets, to a particular recipient @@ -212,9 +205,8 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { issuanceThrottle.useAvailable(totalSupply(), -int256(amount)); redemptionThrottle.useAvailable(totalSupply(), int256(amount)); // reverts on overuse - // {BU} = {BU} * {qRTok} / {qRTok} - uint192 basketsRedeemed = basketsNeeded.muluDivu(amount, totalSupply()); - assert(basketsRedeemed.lte(basketsNeeded)); + // {BU} + uint192 basketsRedeemed = _redeem(_msgSender(), amount); emit Redemption(_msgSender(), recipient, amount, basketsRedeemed); // === Get basket redemption amounts === @@ -233,9 +225,6 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { basketsRedeemed ); - emit BasketsNeededChanged(basketsNeeded, basketsNeeded.minus(basketsRedeemed)); - basketsNeeded = basketsNeeded.minus(basketsRedeemed); - // === Save initial recipient balances === uint256[] memory pastBals = new uint256[](expectedERC20sOut.length); @@ -269,9 +258,6 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { if (allZero) revert("empty redemption"); } - // Accept and burn RToken, reverts if not enough balance - _burn(_msgSender(), amount); - // === Post-checks === // Check post-balances @@ -304,6 +290,15 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { emit Melted(amount); } + /// Giveup an amount of RToken from caller's account and scales basketsNeeded down + /// Callable only by backingManager + /// @param amount {qRTok} + /// @custom:protected + function giveup(uint256 amount) external notTradingPausedOrFrozen exchangeRateIsValidAfter { + require(_msgSender() == address(main.backingManager()), "not backing manager"); + _redeem(_msgSender(), amount); + } + /// An affordance of last resort for Main in order to ensure re-capitalization /// @custom:protected function setBasketsNeeded(uint192 basketsNeeded_) @@ -368,6 +363,23 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { // === Private === + /// Redeem an amount of RToken from an account for basket units, without transferring tokens + /// @param account The address to redeem RTokens from + /// @param amount {qRTok} The amount of RToken to be redeemed + /// @param basketsRedeemed {BU} The number of baskets redeemed + function _redeem(address account, uint256 amount) private returns (uint192 basketsRedeemed) { + if (amount == 0) return 0; + + // {BU} = {BU} * {qRTok} / {qRTok} + basketsRedeemed = basketsNeeded.muluDivu(amount, totalSupply()); // FLOOR + assert(basketsRedeemed.lte(basketsNeeded)); + emit BasketsNeededChanged(basketsNeeded, basketsNeeded.minus(basketsRedeemed)); + basketsNeeded = basketsNeeded.minus(basketsRedeemed); + + // Burn RToken from account; reverts if not enough balance + _burn(account, amount); + } + /** * @dev Hook that is called before any transfer of tokens. This includes * minting and burning. diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index ed0efbdf8d..3de3f0f0d7 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -487,29 +487,6 @@ library TradingLibP0 { } } - // Use RToken if needed - if (address(trade.sell) == address(0) && address(trade.buy) != address(0)) { - IAsset rTokenAsset = IAsset(address(ctx.reg.toAsset(IERC20(address(ctx.rToken))))); - uint192 bal = rTokenAsset.bal(address(ctx.bm)); - - // Only price RToken if it is large enough to be worth it - // Heuristic: If the distributor would skip it, it's smaller than minTradeVolume - if (bal >= MAX_DISTRIBUTION * MAX_DESTINATIONS) { - // Context: The Distributor leaves small balances behind. It is a non-UoA measure. - // This product is 1e6, so on a minTradeVolume of $1000 it would - // require 1 whole RToken to be worth 1 quadrillion dollars to be a mistake. - // So, it is a pretty safe heuristic to use to avoid looking up RToken price. - - (uint192 low, uint192 high) = rTokenAsset.price(); // {UoA/tok} - (uint192 lotLow, ) = rTokenAsset.lotPrice(); // {UoA/tok} - if (high > 0 && isEnoughToSell(rTokenAsset, bal, lotLow, ctx.minTradeVolume)) { - trade.sell = rTokenAsset; - trade.sellAmount = bal; - trade.sellPrice = low; - } - } - } - // Use RSR if needed if (address(trade.sell) == address(0) && address(trade.buy) != address(0)) { IAsset rsrAsset = ctx.reg.toAsset(ctx.rsr); diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 872d8202af..c89be020e1 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -124,6 +124,11 @@ contract BackingManagerP1 is TradingP1, IBackingManager { require(basketsHeld.bottom < rToken.basketsNeeded(), "already collateralized"); // require(!basketHandler.fullyCollateralized()) + // First giveup any held RToken balance (no token transfers required) + uint256 balance = main.rToken().balanceOf(address(this)); + if (balance > 0) main.rToken().giveup(balance); + if (basketsHeld.bottom >= rToken.basketsNeeded()) return; // return if now capitalized + /* * Recollateralization * @@ -268,6 +273,8 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // It's okay if there is leftover dust for RToken or a surplus asset (not RSR) } + // === Private === + /// Compromise on how many baskets are needed in order to recollateralize-by-accounting /// @param basketsHeldBottom {BU} The number of full basket units held by the BackingManager function compromiseBasketsNeeded(uint192 basketsHeldBottom) private { diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index 3e0017f53e..4dce4ceb6e 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -23,7 +23,7 @@ contract DistributorP1 is ComponentP1, IDistributor { // distribution[FURNACE].rsrDist == 0 // distribution[ST_RSR].rTokenDist == 0 // distribution has no more than MAX_DESTINATIONS_ALLOWED key-value entries - // all distribution-share values are <= 10000 + // all distribution-share values are <= MAX_DISTRIBUTION // ==== destinations: // distribution[dest] != (0,0) if and only if dest in destinations @@ -149,7 +149,7 @@ contract DistributorP1 is ComponentP1, IDistributor { // checks: // distribution'[FURNACE].rsrDist == 0 // distribution'[ST_RSR].rTokenDist == 0 - // share.rsrDist <= 10000 + // share.rsrDist <= MAX_DISTRIBUTION // size(destinations') <= MAX_DESTINATIONS_ALLOWED // effects: // destinations' = destinations.add(dest) @@ -162,8 +162,8 @@ contract DistributorP1 is ComponentP1, IDistributor { ); if (dest == FURNACE) require(share.rsrDist == 0, "Furnace must get 0% of RSR"); if (dest == ST_RSR) require(share.rTokenDist == 0, "StRSR must get 0% of RToken"); - require(share.rsrDist <= 10000, "RSR distribution too high"); - require(share.rTokenDist <= 10000, "RToken distribution too high"); + require(share.rsrDist <= MAX_DISTRIBUTION, "RSR distribution too high"); + require(share.rTokenDist <= MAX_DISTRIBUTION, "RToken distribution too high"); if (share.rsrDist == 0 && share.rTokenDist == 0) { destinations.remove(dest); diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index f7cc0ab447..a31debc2c8 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -223,18 +223,13 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { uint256 supply = totalSupply(); - // Accept and burn RToken, reverts if not enough balance to burn - _burn(_msgSender(), amount); - // Revert if redemption exceeds either supply throttle issuanceThrottle.useAvailable(supply, -int256(amount)); redemptionThrottle.useAvailable(supply, int256(amount)); // reverts on over-redemption - // D18{BU} = D18{BU} * {qRTok} / {qRTok} - uint192 basketsRedeemed = basketsNeeded.muluDivu(amount, supply); // FLOOR + // {BU} + uint192 basketsRedeemed = _redeem(_msgSender(), amount); emit Redemption(_msgSender(), recipient, amount, basketsRedeemed); - emit BasketsNeededChanged(basketsNeeded, basketsNeeded - basketsRedeemed); - basketsNeeded = basketsNeeded - basketsRedeemed; (address[] memory erc20s, uint256[] memory amounts) = basketHandler.quote( basketsRedeemed, @@ -314,18 +309,13 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { uint256 supply = totalSupply(); - // Accept and burn RToken, reverts if not enough balance to burn - _burn(_msgSender(), amount); - // Revert if redemption exceeds either supply throttle issuanceThrottle.useAvailable(supply, -int256(amount)); redemptionThrottle.useAvailable(supply, int256(amount)); // reverts on over-redemption - // D18{BU} = D18{BU} * {qRTok} / {qRTok} - uint192 basketsRedeemed = basketsNeeded.muluDivu(amount, supply); // FLOOR + // {BU} + uint192 basketsRedeemed = _redeem(_msgSender(), amount); emit Redemption(_msgSender(), recipient, amount, basketsRedeemed); - emit BasketsNeededChanged(basketsNeeded, basketsNeeded - basketsRedeemed); - basketsNeeded = basketsNeeded - basketsRedeemed; // === Get basket redemption amounts === @@ -420,11 +410,21 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // `else` branch of `exchangeRateIsValidAfter` (ie. revert) shows as uncovered // but it is fully covered for `melt` (limitations of coverage plugin) function melt(uint256 amtRToken) external exchangeRateIsValidAfter { - require(_msgSender() == address(furnace), "furnace only"); + address caller = _msgSender(); + require(caller == address(furnace) || caller == address(backingManager), "furnace only"); _burn(_msgSender(), amtRToken); emit Melted(amtRToken); } + /// Giveup an amount of RToken from caller's account and scales basketsNeeded down + /// Callable only by backingManager + /// @param amount {qRTok} + /// @custom:proctected + function giveup(uint256 amount) external notTradingPausedOrFrozen exchangeRateIsValidAfter { + require(_msgSender() == address(backingManager), "not backing manager"); + _redeem(_msgSender(), amount); + } + /// An affordance of last resort for Main in order to ensure re-capitalization /// @custom:protected // checks: trading unpaused; unfrozen; caller is backingManager @@ -498,6 +498,22 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // ==== Private ==== + /// Redeem an amount of RToken from an account for basket units, without transferring tokens + /// @param account The address to redeem RTokens from + /// @param amount {qRTok} The amount of RToken to be redeemed + /// @param basketsRedeemed {BU} The number of baskets redeemed + function _redeem(address account, uint256 amount) private returns (uint192 basketsRedeemed) { + if (amount == 0) return 0; + + // D18{BU} = D18{BU} * {qRTok} / {qRTok} + basketsRedeemed = basketsNeeded.muluDivu(amount, totalSupply()); // FLOOR + emit BasketsNeededChanged(basketsNeeded, basketsNeeded - basketsRedeemed); + basketsNeeded = basketsNeeded - basketsRedeemed; + + // Burn RToken from account; reverts if not enough balance + _burn(account, amount); + } + /** * @dev Hook that is called before any transfer of tokens. This includes * minting and burning. diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index 086d57bcac..73281db5c0 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -391,32 +391,6 @@ library RecollateralizationLibP1 { } } - // Use RToken if needed - if (address(trade.sell) == address(0) && address(trade.buy) != address(0)) { - IAsset rTokenAsset = ctx.ar.toAsset(IERC20(address(ctx.rToken))); - uint192 bal = rTokenAsset.bal(address(ctx.bm)); - - // Only price RToken if it is large enough to be worth it - // Heuristic: If the distributor would skip it, it's smaller than minTradeVolume - if (bal >= MAX_DISTRIBUTION * MAX_DESTINATIONS) { - // Context: The Distributor leaves small balances behind. It is a non-UoA measure. - // This product is 1e6, so on a minTradeVolume of $1000 it would - // require 1 whole RToken to be worth 1 quadrillion dollars to be a mistake. - // So, it is a pretty safe heuristic to use to avoid looking up RToken price. - - (uint192 low, uint192 high) = rTokenAsset.price(); // {UoA/tok} - (uint192 lotLow, ) = rTokenAsset.lotPrice(); // {UoA/tok} - if ( - high > 0 && - TradeLib.isEnoughToSell(rTokenAsset, bal, lotLow, ctx.minTradeVolume) - ) { - trade.sell = rTokenAsset; - trade.sellAmount = bal; - trade.sellPrice = low; - } - } - } - // Use RSR if needed if (address(trade.sell) == address(0) && address(trade.buy) != address(0)) { IAsset rsrAsset = ctx.ar.toAsset(ctx.rsr); diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 1642cd35c0..8d3f793638 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -172,6 +172,10 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) describe('Configuration #fast', () => { + it('Should allow to giveup RTokens only from BackingManager', async () => { + await expect(rToken.connect(owner).giveup(fp('1'))).to.be.revertedWith('not backing manager') + }) + it('Should allow to set basketsNeeded only from BackingManager', async () => { // Check initial status expect(await rToken.basketsNeeded()).to.equal(0) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 8a9452bd97..219cecdd15 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -2024,8 +2024,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) }) - it('Should recollateralize correctly when switching basket - Using RToken for remainder', async () => { - // Send it all the RToken at the start -- should be ignored + it('Should dissolve held RToken to recapitalize, when possible', async () => { + // Send all RToken to BackingManager await rToken.connect(addr1).transfer(backingManager.address, issueAmount) // Set prime basket @@ -2048,172 +2048,27 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // over-collateralization await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - // Trigger recollateralization - const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) - const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) - - await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) - .to.emit(backingManager, 'TradeStarted') - .withArgs( - anyValue, - token0.address, - token1.address, - sellAmt, - toBNDecimals(minBuyAmt, 6).add(1) - ) - - let auctionTimestamp: number = await getLatestBlockTimestamp() - - // Check auction registered - // Token0 -> Token1 Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: token1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), - }) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - // Asset value is zero, everything was moved to the Market - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check price in USD of the current RToken -- retains price because of - // over-collateralization - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - - // Check Gnosis - expect(await token0.balanceOf(gnosis.address)).to.equal(issueAmount) - - // Perform Mock Bids for the new Token (addr1 has balance) - // Get fair price - minBuyAmt - await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmt, 6).add(1)) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: toBNDecimals(minBuyAmt, 6).add(1), - }) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // End current auction, should start a new one to sell a new revenue token instead of RSR - // About 3e18 Tokens left to buy - Sets Buy amount as independent value - const buyAmtBidRevToken: BigNumber = sellAmt.sub(minBuyAmt) - - // Send the excess revenue tokens to backing manager - should be used instead of RSR - // Set price = $1 as expected - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [ - anyValue, - token0.address, - token1.address, - sellAmt, - toBNDecimals(minBuyAmt, 6).add(1), - ], - emitted: true, - }, + // Trigger recollateralization -- should recapitalize by dissolving RToken + await expectEvents(backingManager.rebalance(TradeKind.BATCH_AUCTION), [ { contract: backingManager, name: 'TradeStarted', - args: [ - anyValue, - rToken.address, - token1.address, - anyValue, - toBNDecimals(buyAmtBidRevToken, 6).add(1), - ], - emitted: true, + emitted: false, }, - ]) - - auctionTimestamp = await getLatestBlockTimestamp() - - // RToken -> Token1 Auction - await expectTrade(backingManager, { - sell: rToken.address, - buy: token1.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('1'), - }) - - const t = await getTrade(backingManager, rToken.address) - const sellAmtRevToken = await t.initBal() - expect(toBNDecimals(buyAmtBidRevToken, 6).add(1)).to.equal( - toBNDecimals(await toMinBuyAmt(sellAmtRevToken, fp('1'), fp('1')), 6).add(1) - ) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal( - toBNDecimals(minBuyAmt, 6).add(1) - ) - expect(await rToken.balanceOf(backingManager.address)).to.equal( - issueAmount.sub(sellAmtRevToken) - ) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check Gnosis - expect(await rToken.balanceOf(gnosis.address)).to.equal(sellAmtRevToken) - - // Perform Mock Bids for the new Token (addr1 has balance) - // Cover buyAmtBidRevToken which is all the amount required - await token1.connect(addr1).approve(gnosis.address, toBNDecimals(sellAmtRevToken, 6)) - await gnosis.placeBid(1, { - bidder: addr1.address, - sellAmount: sellAmtRevToken, - buyAmount: toBNDecimals(buyAmtBidRevToken, 6).add(1), - }) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // End current auction - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ { - contract: backingManager, - name: 'TradeSettled', - args: [ - anyValue, - rToken.address, - token1.address, - sellAmtRevToken, - toBNDecimals(buyAmtBidRevToken, 6).add(1), - ], + contract: rToken, + name: 'BasketsNeededChanged', emitted: true, }, - { - contract: backingManager, - name: 'TradeStarted', - emitted: false, - }, ]) - // Check state - Order restablished + // Check fullyCollateralized and rest of state expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await token1.balanceOf(backingManager.address)).to.equal( - toBNDecimals(issueAmount, 6).add(1) - ) - expect(await rToken.balanceOf(backingManager.address)).to.be.closeTo(bn('0'), 100) // distributor leaves some - expect(await rToken.totalSupply()).to.be.closeTo(issueAmount, fp('0.000001')) // we have a bit more - - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - - // Stakes unchanged - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) - expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + expect(await rToken.totalSupply()).to.equal(0) }) it('Should recollateralize correctly in case of default - Using RSR for remainder', async () => { From 623f9a01c426a4985d84981f6a9cb223159786a3 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 14:30:20 -0400 Subject: [PATCH 275/462] remove MAX_DESTINATIONS --- contracts/interfaces/IDistributor.sol | 3 +-- contracts/p0/Distributor.sol | 2 +- contracts/p1/Distributor.sol | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/interfaces/IDistributor.sol b/contracts/interfaces/IDistributor.sol index 459d849352..fa2c3f5dff 100644 --- a/contracts/interfaces/IDistributor.sol +++ b/contracts/interfaces/IDistributor.sol @@ -5,14 +5,13 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./IComponent.sol"; uint256 constant MAX_DISTRIBUTION = 1e4; // 10,000 -uint8 constant MAX_DESTINATIONS = 100; struct RevenueShare { uint16 rTokenDist; // {revShare} A value between [0, 10,000] uint16 rsrDist; // {revShare} A value between [0, 10,000] } -/// Assumes no more than MAX_DESTINATIONS independent distributions. +/// Assumes no more than 100 independent distributions. struct RevenueTotals { uint24 rTokenTotal; // {revShare} uint24 rsrTotal; // {revShare} diff --git a/contracts/p0/Distributor.sol b/contracts/p0/Distributor.sol index 8d02be6c67..7e501de398 100644 --- a/contracts/p0/Distributor.sol +++ b/contracts/p0/Distributor.sol @@ -21,7 +21,7 @@ contract DistributorP0 is ComponentP0, IDistributor { address public constant FURNACE = address(1); address public constant ST_RSR = address(2); - uint8 public constant MAX_DESTINATIONS_ALLOWED = MAX_DESTINATIONS; + uint8 public constant MAX_DESTINATIONS_ALLOWED = 100; function init(IMain main_, RevenueShare memory dist) public initializer { __Component_init(main_); diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index 4dce4ceb6e..8f2b3f3ea2 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -31,7 +31,7 @@ contract DistributorP1 is ComponentP1, IDistributor { address public constant FURNACE = address(1); address public constant ST_RSR = address(2); - uint8 public constant MAX_DESTINATIONS_ALLOWED = MAX_DESTINATIONS; + uint8 public constant MAX_DESTINATIONS_ALLOWED = 100; IERC20 private rsr; IERC20 private rToken; From c0236758b1a79994f44839014aefb6ae8b7c14b6 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 15:45:13 -0400 Subject: [PATCH 276/462] add-back distributor-dust optimization + use dissolve() as function name --- contracts/interfaces/IDistributor.sol | 1 + contracts/interfaces/IRToken.sol | 8 +++++--- contracts/p0/BackingManager.sol | 5 +++-- contracts/p0/Distributor.sol | 2 +- contracts/p0/RToken.sol | 16 ++++------------ contracts/p1/BackingManager.sol | 5 +++-- contracts/p1/Distributor.sol | 2 +- contracts/p1/RToken.sol | 6 +++--- .../Recollateralization.test.ts.snap | 10 +++++----- 9 files changed, 26 insertions(+), 29 deletions(-) diff --git a/contracts/interfaces/IDistributor.sol b/contracts/interfaces/IDistributor.sol index fa2c3f5dff..19e69cbdbc 100644 --- a/contracts/interfaces/IDistributor.sol +++ b/contracts/interfaces/IDistributor.sol @@ -5,6 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./IComponent.sol"; uint256 constant MAX_DISTRIBUTION = 1e4; // 10,000 +uint8 constant MAX_DESTINATIONS = 100; // maximum number of RevenueShare destinations struct RevenueShare { uint16 rTokenDist; // {revShare} A value between [0, 10,000] diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index f51882a301..187d00c1f7 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -117,11 +117,13 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea /// Melt a quantity of RToken from the caller's account /// @param amount {qRTok} The amount to be melted + /// @custom:protected function melt(uint256 amount) external; - /// Giveup an amount of RToken from caller's account and scales basketsNeeded down - /// Callable only by backingManager - function giveup(uint256 amount) external; + /// Dissolve an amount of RToken from caller's account and scale basketsNeeded down + /// Callable only by BackingManager + /// @custom:protected + function dissolve(uint256 amount) external; /// Set the number of baskets needed directly, callable only by the BackingManager /// @param basketsNeeded {BU} The number of baskets to target diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 346d0956c9..b234ebf00b 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -91,9 +91,10 @@ contract BackingManagerP0 is TradingP0, IBackingManager { ); require(!main.basketHandler().fullyCollateralized(), "already collateralized"); - // First giveup any held RToken balance (no token transfers required) + // First dissolve any held RToken balance above Distributor-dust + // gas-optimization: 1 whole RToken must be worth 100 trillion dollars for this to skip $1 uint256 balance = main.rToken().balanceOf(address(this)); - if (balance > 0) main.rToken().giveup(balance); + if (balance >= MAX_DISTRIBUTION * MAX_DESTINATIONS) main.rToken().dissolve(balance); if (main.basketHandler().fullyCollateralized()) return; // return if now capitalized /* diff --git a/contracts/p0/Distributor.sol b/contracts/p0/Distributor.sol index 7e501de398..c58fe609db 100644 --- a/contracts/p0/Distributor.sol +++ b/contracts/p0/Distributor.sol @@ -21,7 +21,7 @@ contract DistributorP0 is ComponentP0, IDistributor { address public constant FURNACE = address(1); address public constant ST_RSR = address(2); - uint8 public constant MAX_DESTINATIONS_ALLOWED = 100; + uint8 public constant MAX_DESTINATIONS_ALLOWED = MAX_DESTINATIONS; // 100 function init(IMain main_, RevenueShare memory dist) public initializer { __Component_init(main_); diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index a2305127d0..059fff061e 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -273,11 +273,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// @param recipient The recipient of the newly minted RToken /// @param amount {qRTok} The amount to be minted /// @custom:protected - function mint(address recipient, uint256 amount) - external - notTradingPausedOrFrozen - exchangeRateIsValidAfter - { + function mint(address recipient, uint256 amount) external exchangeRateIsValidAfter { require(_msgSender() == address(main.backingManager()), "not backing manager"); _mint(recipient, amount); } @@ -290,22 +286,18 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { emit Melted(amount); } - /// Giveup an amount of RToken from caller's account and scales basketsNeeded down + /// Dissolve an amount of RToken from caller's account and scale basketsNeeded down /// Callable only by backingManager /// @param amount {qRTok} /// @custom:protected - function giveup(uint256 amount) external notTradingPausedOrFrozen exchangeRateIsValidAfter { + function dissolve(uint256 amount) external exchangeRateIsValidAfter { require(_msgSender() == address(main.backingManager()), "not backing manager"); _redeem(_msgSender(), amount); } /// An affordance of last resort for Main in order to ensure re-capitalization /// @custom:protected - function setBasketsNeeded(uint192 basketsNeeded_) - external - notTradingPausedOrFrozen - exchangeRateIsValidAfter - { + function setBasketsNeeded(uint192 basketsNeeded_) external exchangeRateIsValidAfter { require(_msgSender() == address(main.backingManager()), "not backing manager"); emit BasketsNeededChanged(basketsNeeded, basketsNeeded_); basketsNeeded = basketsNeeded_; diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index c89be020e1..836c59ed4b 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -124,9 +124,10 @@ contract BackingManagerP1 is TradingP1, IBackingManager { require(basketsHeld.bottom < rToken.basketsNeeded(), "already collateralized"); // require(!basketHandler.fullyCollateralized()) - // First giveup any held RToken balance (no token transfers required) + // First dissolve any held RToken balance (above Distributor-dust) + // gas-optimization: 1 whole RToken must be worth 100 trillion dollars for this to skip $1 uint256 balance = main.rToken().balanceOf(address(this)); - if (balance > 0) main.rToken().giveup(balance); + if (balance >= MAX_DISTRIBUTION * MAX_DESTINATIONS) main.rToken().dissolve(balance); if (basketsHeld.bottom >= rToken.basketsNeeded()) return; // return if now capitalized /* diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index 8f2b3f3ea2..585b6a2e9f 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -31,7 +31,7 @@ contract DistributorP1 is ComponentP1, IDistributor { address public constant FURNACE = address(1); address public constant ST_RSR = address(2); - uint8 public constant MAX_DESTINATIONS_ALLOWED = 100; + uint8 public constant MAX_DESTINATIONS_ALLOWED = MAX_DESTINATIONS; // 100 IERC20 private rsr; IERC20 private rToken; diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index a31debc2c8..fe47580728 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -416,11 +416,11 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { emit Melted(amtRToken); } - /// Giveup an amount of RToken from caller's account and scales basketsNeeded down + /// Dissolve an amount of RToken from caller's account and scale basketsNeeded down /// Callable only by backingManager /// @param amount {qRTok} - /// @custom:proctected - function giveup(uint256 amount) external notTradingPausedOrFrozen exchangeRateIsValidAfter { + /// @custom:protected + function dissolve(uint256 amount) external notTradingPausedOrFrozen exchangeRateIsValidAfter { require(_msgSender() == address(backingManager), "not backing manager"); _redeem(_msgSender(), amount); } diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 1fd6e7b0e3..4d1681a4f8 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1332440`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1338646`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1465975`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1451081`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1689530`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1695736`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1633814`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1640020`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1731461`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1729104`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `212916`; From a22f53f1b1be104289596cd81a64943d8733e29d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 15:47:03 -0400 Subject: [PATCH 277/462] fix typo --- contracts/p0/RToken.sol | 1 + contracts/p1/RToken.sol | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 059fff061e..4081a39248 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -280,6 +280,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// Melt a quantity of RToken from the caller's account, increasing the basket rate /// @param amount {qRTok} The amount to be melted + /// @custom:protected function melt(uint256 amount) external exchangeRateIsValidAfter { require(_msgSender() == address(main.furnace()), "furnace only"); _burn(_msgSender(), amount); diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index fe47580728..fd5ceab2b2 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -401,6 +401,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// Melt a quantity of RToken from the caller's account, increasing the basket rate /// @param amtRToken {qRTok} The amtRToken to be melted + /// @custom:protected // checks: not trading paused or frozen // effects: // bal'[caller] = bal[caller] - amtRToken @@ -410,8 +411,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // `else` branch of `exchangeRateIsValidAfter` (ie. revert) shows as uncovered // but it is fully covered for `melt` (limitations of coverage plugin) function melt(uint256 amtRToken) external exchangeRateIsValidAfter { - address caller = _msgSender(); - require(caller == address(furnace) || caller == address(backingManager), "furnace only"); + require(_msgSender() == address(furnace), "furnace only"); _burn(_msgSender(), amtRToken); emit Melted(amtRToken); } From 0ba27bf734cfd3dd33a12f8bcd6f7529afaa898d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 15:53:40 -0400 Subject: [PATCH 278/462] align P0 RToken with P1 --- contracts/p0/RToken.sol | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 4081a39248..a8bf22424a 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -273,14 +273,17 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// @param recipient The recipient of the newly minted RToken /// @param amount {qRTok} The amount to be minted /// @custom:protected - function mint(address recipient, uint256 amount) external exchangeRateIsValidAfter { + function mint(address recipient, uint256 amount) + external + notTradingPausedOrFrozen + exchangeRateIsValidAfter + { require(_msgSender() == address(main.backingManager()), "not backing manager"); _mint(recipient, amount); } /// Melt a quantity of RToken from the caller's account, increasing the basket rate /// @param amount {qRTok} The amount to be melted - /// @custom:protected function melt(uint256 amount) external exchangeRateIsValidAfter { require(_msgSender() == address(main.furnace()), "furnace only"); _burn(_msgSender(), amount); @@ -291,14 +294,18 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// Callable only by backingManager /// @param amount {qRTok} /// @custom:protected - function dissolve(uint256 amount) external exchangeRateIsValidAfter { + function dissolve(uint256 amount) external notTradingPausedOrFrozen exchangeRateIsValidAfter { require(_msgSender() == address(main.backingManager()), "not backing manager"); _redeem(_msgSender(), amount); } /// An affordance of last resort for Main in order to ensure re-capitalization /// @custom:protected - function setBasketsNeeded(uint192 basketsNeeded_) external exchangeRateIsValidAfter { + function setBasketsNeeded(uint192 basketsNeeded_) + external + notTradingPausedOrFrozen + exchangeRateIsValidAfter + { require(_msgSender() == address(main.backingManager()), "not backing manager"); emit BasketsNeededChanged(basketsNeeded, basketsNeeded_); basketsNeeded = basketsNeeded_; From 17d10bec3116538242f150f563ec59b301e849e1 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 16:03:21 -0400 Subject: [PATCH 279/462] fix RToken test --- test/RToken.test.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 8d3f793638..ac054d0b81 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -172,8 +172,25 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) describe('Configuration #fast', () => { - it('Should allow to giveup RTokens only from BackingManager', async () => { - await expect(rToken.connect(owner).giveup(fp('1'))).to.be.revertedWith('not backing manager') + it('Should allow to dissolve RTokens only from BackingManager and when unpaused/unfrozen', async () => { + await expect(rToken.connect(owner).dissolve(fp('1'))).to.be.revertedWith( + 'not backing manager' + ) + + await whileImpersonating(backingManager.address, async (bmSigner) => { + // Should revert if paused + await main.connect(owner).pauseTrading() + await expect(rToken.connect(bmSigner).dissolve(fp('1'))).to.be.revertedWith( + 'frozen or trading paused' + ) + + // Should revert if frozen + await main.connect(owner).unpauseTrading() + await main.connect(owner).freezeShort() + await expect(rToken.connect(bmSigner).dissolve(fp('1'))).to.be.revertedWith( + 'frozen or trading paused' + ) + }) }) it('Should allow to set basketsNeeded only from BackingManager', async () => { From de997b0e73ed596365c7359234bf9a13f868da84 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 16:17:50 -0400 Subject: [PATCH 280/462] remove unreachable line --- contracts/p0/RToken.sol | 2 -- contracts/p1/RToken.sol | 2 -- 2 files changed, 4 deletions(-) diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index a8bf22424a..dbbd79e62e 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -368,8 +368,6 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// @param amount {qRTok} The amount of RToken to be redeemed /// @param basketsRedeemed {BU} The number of baskets redeemed function _redeem(address account, uint256 amount) private returns (uint192 basketsRedeemed) { - if (amount == 0) return 0; - // {BU} = {BU} * {qRTok} / {qRTok} basketsRedeemed = basketsNeeded.muluDivu(amount, totalSupply()); // FLOOR assert(basketsRedeemed.lte(basketsNeeded)); diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index fd5ceab2b2..6b8dae3b60 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -503,8 +503,6 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// @param amount {qRTok} The amount of RToken to be redeemed /// @param basketsRedeemed {BU} The number of baskets redeemed function _redeem(address account, uint256 amount) private returns (uint192 basketsRedeemed) { - if (amount == 0) return 0; - // D18{BU} = D18{BU} * {qRTok} / {qRTok} basketsRedeemed = basketsNeeded.muluDivu(amount, totalSupply()); // FLOOR emit BasketsNeededChanged(basketsNeeded, basketsNeeded - basketsRedeemed); From 064f02f36ed2817dacc52b6af030f360d94defd1 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 16:31:34 -0400 Subject: [PATCH 281/462] add test nit --- test/RToken.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/RToken.test.ts b/test/RToken.test.ts index ac054d0b81..5c871137d5 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -190,6 +190,12 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect(rToken.connect(bmSigner).dissolve(fp('1'))).to.be.revertedWith( 'frozen or trading paused' ) + + // Should have a different outcome if unfrozen + await main.connect(owner).unfreeze() + await expect(rToken.connect(bmSigner).dissolve(fp('1'))).to.not.be.revertedWith( + 'frozen or trading paused' + ) }) }) From 2a7e75aa7654c28ddca59e225b6e268a2a5df50b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 19:40:24 -0400 Subject: [PATCH 282/462] simplify RToken/BackingManager relationship + remove redundant modifiers --- contracts/interfaces/IRToken.sol | 10 +-- contracts/p0/BackingManager.sol | 36 ++++------ contracts/p0/Broker.sol | 6 +- contracts/p0/Distributor.sol | 2 +- contracts/p0/RToken.sol | 70 +++++++++--------- contracts/p0/StRSR.sol | 2 +- contracts/p0/mixins/Trading.sol | 7 +- contracts/p1/BackingManager.sol | 106 ++++++++++++---------------- contracts/p1/Broker.sol | 8 +-- contracts/p1/Distributor.sol | 2 +- contracts/p1/RToken.sol | 117 ++++++++++++++----------------- contracts/p1/RevenueTrader.sol | 15 +++- contracts/p1/StRSR.sol | 10 ++- contracts/p1/mixins/Trading.sol | 14 +--- 14 files changed, 179 insertions(+), 226 deletions(-) diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index 187d00c1f7..fe6246923b 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -109,18 +109,18 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea uint256[] memory minAmounts ) external returns (address[] memory erc20sOut, uint256[] memory amountsOut); - /// Mints a quantity of RToken to the `recipient`, callable only by the BackingManager - /// @param recipient The recipient of the newly minted RToken - /// @param amount {qRTok} The amount to be minted + /// Mint an amount of RToken equivalent to baskets BUs, scaling basketsNeeded up + /// Callable only by BackingManager + /// @param baskets {BU} The number of baskets to mint RToken for /// @custom:protected - function mint(address recipient, uint256 amount) external; + function mint(uint192 baskets) external; /// Melt a quantity of RToken from the caller's account /// @param amount {qRTok} The amount to be melted /// @custom:protected function melt(uint256 amount) external; - /// Dissolve an amount of RToken from caller's account and scale basketsNeeded down + /// Burn an amount of RToken from caller's account and scale basketsNeeded down /// Callable only by BackingManager /// @custom:protected function dissolve(uint256 amount) external; diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index b234ebf00b..04f9104266 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -54,7 +54,12 @@ contract BackingManagerP0 is TradingP0, IBackingManager { /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) public override(ITrading, TradingP0) returns (ITrade trade) { + function settleTrade(IERC20 sell) + public + override(ITrading, TradingP0) + notTradingPausedOrFrozen + returns (ITrade trade) + { trade = super.settleTrade(sell); // if the settler is the trade contract itself, try chaining with another rebalance() @@ -70,9 +75,8 @@ contract BackingManagerP0 is TradingP0, IBackingManager { /// Apply the overall backing policy using the specified TradeKind, taking a haircut if unable /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION - /// @custom:interaction RCEI + /// @custom:interaction function rebalance(TradeKind kind) external notTradingPausedOrFrozen { - // == Refresh == main.assetRegistry().refresh(); main.furnace().melt(); @@ -125,9 +129,8 @@ contract BackingManagerP0 is TradingP0, IBackingManager { } // Execute Trade - tradeEnd[kind] = uint48(block.timestamp) + ONE_BLOCK; ITrade trade = tryTrade(kind, req); - if (trade.endTime() > tradeEnd[kind]) tradeEnd[kind] = trade.endTime(); + tradeEnd[kind] = trade.endTime(); } else { // Haircut time compromiseBasketsNeeded(basketsHeld.bottom); @@ -136,11 +139,10 @@ contract BackingManagerP0 is TradingP0, IBackingManager { /// Forward revenue to RevenueTraders; reverts if not fully collateralized /// @param erc20s The tokens to forward - /// @custom:interaction RCEI + /// @custom:interaction function forwardRevenue(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { require(ArrayLib.allUnique(erc20s), "duplicate tokens"); - // == Refresh == main.assetRegistry().refresh(); main.furnace().melt(); @@ -162,23 +164,11 @@ contract BackingManagerP0 is TradingP0, IBackingManager { } // Mint revenue RToken + // Keep backingBuffer worth of collateral before recognizing revenue uint192 needed = main.rToken().basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); // {BU} - { - IRToken rToken = main.rToken(); - if (basketsHeld.bottom.gt(needed)) { - int8 decimals = int8(rToken.decimals()); - uint192 totalSupply = shiftl_toFix(rToken.totalSupply(), -decimals); // {rTok} - - // {BU} = {BU} - {BU} - uint192 extraBUs = basketsHeld.bottom.minus(needed); - - // {qRTok: Fix} = {BU} * {qRTok / BU} (if needed == 0, conv rate is 1 qRTok/BU) - uint192 rTok = (needed > 0) ? extraBUs.mulDiv(totalSupply, needed) : extraBUs; - - rToken.mint(address(this), rTok); - rToken.setBasketsNeeded(basketsHeld.bottom); - needed = basketsHeld.bottom; - } + if (basketsHeld.bottom.gt(needed)) { + main.rToken().mint(basketsHeld.bottom.minus(needed)); + needed = main.rToken().basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); // keep buffer } // Handout excess assets above what is needed, including any newly minted RToken diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index cb43f30a27..fd4e8b9f77 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -58,11 +58,7 @@ contract BrokerP0 is ComponentP0, IBroker { /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @dev Requires setting an allowance in advance /// @custom:protected - function openTrade(TradeKind kind, TradeRequest memory req) - external - notTradingPausedOrFrozen - returns (ITrade) - { + function openTrade(TradeKind kind, TradeRequest memory req) external returns (ITrade) { require(!disabled, "broker disabled"); assert(req.sellAmount > 0); diff --git a/contracts/p0/Distributor.sol b/contracts/p0/Distributor.sol index c58fe609db..bea4059c0a 100644 --- a/contracts/p0/Distributor.sol +++ b/contracts/p0/Distributor.sol @@ -41,7 +41,7 @@ contract DistributorP0 is ComponentP0, IDistributor { /// Distribute revenue, in rsr or rtoken, per the distribution table. /// Requires that this contract has an allowance of at least /// `amount` tokens, from `from`, of the token at `erc20`. - function distribute(IERC20 erc20, uint256 amount) external notTradingPausedOrFrozen { + function distribute(IERC20 erc20, uint256 amount) external { IERC20 rsr = main.rsr(); require(erc20 == rsr || erc20 == IERC20(address(main.rToken())), "RSR or RToken"); diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index dbbd79e62e..9532f2a8b1 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -121,11 +121,8 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { IERC20(erc20s[i]).safeTransferFrom(issuer, address(main.backingManager()), deposits[i]); } - _mint(recipient, amount); + _scaleUp(recipient, baskets); emit Issuance(issuer, recipient, amount, baskets); - - emit BasketsNeededChanged(basketsNeeded, basketsNeeded.plus(baskets)); - basketsNeeded = basketsNeeded.plus(baskets); } /// Redeem RToken for basket collateral @@ -153,11 +150,11 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { redemptionThrottle.useAvailable(totalSupply(), int256(amount)); // reverts on overuse // {BU} - uint192 basketsRedeemed = _redeem(_msgSender(), amount); - emit Redemption(_msgSender(), recipient, amount, basketsRedeemed); + uint192 baskets = _scaleDown(_msgSender(), amount); + emit Redemption(_msgSender(), recipient, amount, baskets); (address[] memory erc20s, uint256[] memory amounts) = main.basketHandler().quote( - basketsRedeemed, + baskets, FLOOR ); @@ -206,7 +203,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { redemptionThrottle.useAvailable(totalSupply(), int256(amount)); // reverts on overuse // {BU} - uint192 basketsRedeemed = _redeem(_msgSender(), amount); + uint192 basketsRedeemed = _scaleDown(_msgSender(), amount); emit Redemption(_msgSender(), recipient, amount, basketsRedeemed); // === Get basket redemption amounts === @@ -269,17 +266,13 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { // === - /// Mint a quantity of RToken to the `recipient`, decreasing the basket rate - /// @param recipient The recipient of the newly minted RToken - /// @param amount {qRTok} The amount to be minted + /// Mint an amount of RToken equivalent to baskets BUs, scaling basketsNeeded up + /// Callable only by BackingManager + /// @param baskets {BU} The number of baskets to mint RToken for /// @custom:protected - function mint(address recipient, uint256 amount) - external - notTradingPausedOrFrozen - exchangeRateIsValidAfter - { + function mint(uint192 baskets) external exchangeRateIsValidAfter { require(_msgSender() == address(main.backingManager()), "not backing manager"); - _mint(recipient, amount); + _scaleUp(address(main.backingManager()), baskets); } /// Melt a quantity of RToken from the caller's account, increasing the basket rate @@ -294,18 +287,14 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// Callable only by backingManager /// @param amount {qRTok} /// @custom:protected - function dissolve(uint256 amount) external notTradingPausedOrFrozen exchangeRateIsValidAfter { + function dissolve(uint256 amount) external exchangeRateIsValidAfter { require(_msgSender() == address(main.backingManager()), "not backing manager"); - _redeem(_msgSender(), amount); + _scaleDown(_msgSender(), amount); } /// An affordance of last resort for Main in order to ensure re-capitalization /// @custom:protected - function setBasketsNeeded(uint192 basketsNeeded_) - external - notTradingPausedOrFrozen - exchangeRateIsValidAfter - { + function setBasketsNeeded(uint192 basketsNeeded_) external exchangeRateIsValidAfter { require(_msgSender() == address(main.backingManager()), "not backing manager"); emit BasketsNeededChanged(basketsNeeded, basketsNeeded_); basketsNeeded = basketsNeeded_; @@ -363,16 +352,29 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { // === Private === - /// Redeem an amount of RToken from an account for basket units, without transferring tokens - /// @param account The address to redeem RTokens from - /// @param amount {qRTok} The amount of RToken to be redeemed - /// @param basketsRedeemed {BU} The number of baskets redeemed - function _redeem(address account, uint256 amount) private returns (uint192 basketsRedeemed) { - // {BU} = {BU} * {qRTok} / {qRTok} - basketsRedeemed = basketsNeeded.muluDivu(amount, totalSupply()); // FLOOR - assert(basketsRedeemed.lte(basketsNeeded)); - emit BasketsNeededChanged(basketsNeeded, basketsNeeded.minus(basketsRedeemed)); - basketsNeeded = basketsNeeded.minus(basketsRedeemed); + /// Mint an amount of RToken equivalent to amtBaskets and scale basketsNeeded up + /// @param recipient The address to receive the RTokens + /// @param amtBaskets {BU} The number of amtBaskets to mint RToken for + function _scaleUp(address recipient, uint192 amtBaskets) private { + uint256 amtRToken = totalSupply() > 0 + ? amtBaskets.muluDivu(totalSupply(), uint256(basketsNeeded)) + : amtBaskets; // {rTok} + emit BasketsNeededChanged(basketsNeeded, basketsNeeded.plus(amtBaskets)); + basketsNeeded = basketsNeeded.plus(amtBaskets); + + // Mint RToken to recipient + _mint(recipient, amtRToken); // take advantage of 18 decimals in cast + } + + /// Burn an amount of RToken and scale basketsNeeded down + /// @param account The address to dissolve RTokens from + /// @param amount {qRTok} The amount of RToken to be dissolved + /// @return baskets {BU} The equivalent number of baskets dissolved + function _scaleDown(address account, uint256 amount) private returns (uint192 baskets) { + // D18{BU} = D18{BU} * {qRTok} / {qRTok} + baskets = basketsNeeded.muluDivu(amount, totalSupply()); // FLOOR + emit BasketsNeededChanged(basketsNeeded, basketsNeeded.minus(baskets)); + basketsNeeded = basketsNeeded.minus(baskets); // Burn RToken from account; reverts if not enough balance _burn(account, amount); diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index ea60d1ab8d..3652e818b4 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -228,7 +228,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { main.rsr().safeTransfer(account, total); } - function cancelUnstake(uint256 endId) external notTradingPausedOrFrozen { + function cancelUnstake(uint256 endId) external notFrozen { address account = _msgSender(); // IBasketHandler bh = main.basketHandler(); diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index 832561274d..1d2e9f22e0 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -43,12 +43,7 @@ abstract contract TradingP0 is RewardableP0, ITrading { /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) - public - virtual - notTradingPausedOrFrozen - returns (ITrade trade) - { + function settleTrade(IERC20 sell) public virtual returns (ITrade trade) { trade = trades[sell]; require(address(trade) != address(0), "no trade open"); require(trade.canSettle(), "cannot settle yet"); diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 836c59ed4b..a9a669e847 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -55,16 +55,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { __Component_init(main_); __Trading_init(main_, maxTradeSlippage_, minTradeVolume_); - assetRegistry = main_.assetRegistry(); - basketHandler = main_.basketHandler(); - distributor = main_.distributor(); - rsr = main_.rsr(); - rsrTrader = main_.rsrTrader(); - rTokenTrader = main_.rTokenTrader(); - rToken = main_.rToken(); - stRSR = main_.stRSR(); - furnace = main_.furnace(); - + cacheComponents(); setTradingDelay(tradingDelay_); setBackingBuffer(backingBuffer_); } @@ -82,11 +73,17 @@ contract BackingManagerP1 is TradingP1, IBackingManager { } /// Settle a single trade. If DUTCH_AUCTION, try rebalance() + /// While this function is not nonReentrant, its two subsets each individually are /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { - trade = super.settleTrade(sell); // modifier: notTradingPausedOrFrozen + function settleTrade(IERC20 sell) + public + override(ITrading, TradingP1) + notTradingPausedOrFrozen + returns (ITrade trade) + { + trade = super.settleTrade(sell); // nonReentrant // if the settler is the trade contract itself, try chaining with another rebalance() if (_msgSender() == address(trade)) { @@ -101,14 +98,12 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// Apply the overall backing policy using the specified TradeKind, taking a haircut if unable /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION - /// @custom:interaction RCEI - function rebalance(TradeKind kind) external notTradingPausedOrFrozen { + /// @custom:interaction not RCEI, nonReentrant + function rebalance(TradeKind kind) external nonReentrant notTradingPausedOrFrozen { // == Refresh == assetRegistry.refresh(); furnace.melt(); - // == Checks/Effects == - // DoS prevention: unless caller is self, require 1 empty block between like-kind auctions // Assumption: chain has <= 12s blocktimes require( @@ -147,11 +142,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { (bool doTrade, TradeRequest memory req) = RecollateralizationLibP1 .prepareRecollateralizationTrade(this, basketsHeld); - // == Interactions == - if (doTrade) { - tradeEnd[kind] = uint48(block.timestamp) + ONE_BLOCK; // reentrancy protection - // Seize RSR if needed if (req.sell.erc20() == rsr) { uint256 bal = req.sell.erc20().balanceOf(address(this)); @@ -160,8 +151,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // Execute Trade ITrade trade = tryTrade(kind, req); - uint48 endTime = trade.endTime(); - if (endTime > tradeEnd[kind]) tradeEnd[kind] = endTime; + tradeEnd[kind] = trade.endTime(); } else { // Haircut time compromiseBasketsNeeded(basketsHeld.bottom); @@ -170,11 +160,14 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// Forward revenue to RevenueTraders; reverts if not fully collateralized /// @param erc20s The tokens to forward - /// @custom:interaction RCEI - function forwardRevenue(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { + /// @custom:interaction not RCEI, nonReentrant + function forwardRevenue(IERC20[] calldata erc20s) + external + nonReentrant + notTradingPausedOrFrozen + { require(ArrayLib.allUnique(erc20s), "duplicate tokens"); - // == Refresh == assetRegistry.refresh(); furnace.melt(); @@ -210,34 +203,15 @@ contract BackingManagerP1 is TradingP1, IBackingManager { IERC20(address(rsr)).safeTransfer(address(stRSR), rsr.balanceOf(address(this))); } - // Mint revenue RToken and update `basketsNeeded` - // across this block: - // where rate(R) == R.basketsNeeded / R.totalSupply, - // rate(rToken') >== rate(rToken) - // (>== is "no less than, and nearly equal to") - // and rToken'.basketsNeeded <= basketsHeld.bottom - // and rToken'.totalSupply is maximal satisfying this. - + // Mint revenue RToken // Keep backingBuffer worth of collateral before recognizing revenue - uint192 needed = rToken.basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); // {BU} - - if (basketsHeld.bottom.gt(needed)) { - // gas-optimization: RToken is known to have 18 decimals, the same as FixLib - uint192 totalSupply = _safeWrap(rToken.totalSupply()); // {rTok} - - // {BU} = {BU} - {BU} - uint192 extraBUs = basketsHeld.bottom.minus(needed); - - // {rTok} = {BU} * {rTok / BU} (if needed == 0, conv rate is 1 rTok/BU) - uint192 rTok = (needed > 0) ? extraBUs.mulDiv(totalSupply, needed) : extraBUs; - - // gas-optimization: RToken is known to have 18 decimals, same as FixLib - rToken.mint(address(this), uint256(rTok)); - rToken.setBasketsNeeded(basketsHeld.bottom); - needed = basketsHeld.bottom; + uint192 needed = rToken.basketsNeeded().mul(FIX_ONE + backingBuffer); // {BU} + if (basketsHeld.bottom > needed) { + rToken.mint(basketsHeld.bottom - needed); + needed = rToken.basketsNeeded().mul(FIX_ONE + backingBuffer); // keep buffer } - // At this point, even though basketsNeeded may have changed: + // At this point, even though basketsNeeded may have changed, we are: // - We're fully collateralized // - The BU exchange rate {BU/rTok} did not decrease @@ -245,8 +219,6 @@ contract BackingManagerP1 is TradingP1, IBackingManager { uint256 length = erc20s.length; RevenueTotals memory totals = distributor.totals(); - uint256[] memory toRSR = new uint256[](length); - uint256[] memory toRToken = new uint256[](length); for (uint256 i = 0; i < length; ++i) { IAsset asset = assetRegistry.toAsset(erc20s[i]); @@ -257,20 +229,22 @@ contract BackingManagerP1 is TradingP1, IBackingManager { if (bal.gt(req)) { // delta: {qTok}, the excess quantity of this asset that we hold uint256 delta = bal.minus(req).shiftl_toUint(int8(asset.erc20Decimals())); + uint256 tokensPerShare = delta / (totals.rTokenTotal + totals.rsrTotal); // no div-by-0: Distributor guarantees (totals.rTokenTotal + totals.rsrTotal) > 0 // initial division is intentional here! We'd rather save the dust than be unfair - toRSR[i] = (delta / (totals.rTokenTotal + totals.rsrTotal)) * totals.rsrTotal; - toRToken[i] = (delta / (totals.rTokenTotal + totals.rsrTotal)) * totals.rTokenTotal; - } - } - // == Interactions == - for (uint256 i = 0; i < length; ++i) { - if (toRToken[i] > 0) erc20s[i].safeTransfer(address(rTokenTrader), toRToken[i]); - if (toRSR[i] > 0) erc20s[i].safeTransfer(address(rsrTrader), toRSR[i]); + if (totals.rsrTotal > 0) { + erc20s[i].safeTransfer(address(rsrTrader), tokensPerShare * totals.rsrTotal); + } + if (totals.rTokenTotal > 0) { + erc20s[i].safeTransfer( + address(rTokenTrader), + tokensPerShare * totals.rTokenTotal + ); + } + } } - // It's okay if there is leftover dust for RToken or a surplus asset (not RSR) } @@ -301,7 +275,15 @@ contract BackingManagerP1 is TradingP1, IBackingManager { } /// Call after upgrade to >= 3.0.0 - function cacheFurnace() public { + function cacheComponents() public { + assetRegistry = main.assetRegistry(); + basketHandler = main.basketHandler(); + distributor = main.distributor(); + rToken = main.rToken(); + rsr = main.rsr(); + stRSR = main.stRSR(); + rsrTrader = main.rsrTrader(); + rTokenTrader = main.rTokenTrader(); furnace = main.furnace(); } diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 308164c2a3..242d28358c 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -84,7 +84,7 @@ contract BrokerP1 is ComponentP1, IBroker { /// Handle a trade request by deploying a customized disposable trading contract /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @dev Requires setting an allowance in advance - /// @custom:interaction CEI + /// @custom:protected and @custom:interaction CEI // checks: // not disabled, paused (trading), or frozen // caller is a system Trader @@ -94,11 +94,7 @@ contract BrokerP1 is ComponentP1, IBroker { // actions: // Transfers req.sellAmount of req.sell.erc20 from caller to `trade` // Calls trade.init() with appropriate parameters - function openTrade(TradeKind kind, TradeRequest memory req) - external - notTradingPausedOrFrozen - returns (ITrade) - { + function openTrade(TradeKind kind, TradeRequest memory req) external returns (ITrade) { require(!disabled, "broker disabled"); address caller = _msgSender(); diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index 585b6a2e9f..21b973fcef 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -84,7 +84,7 @@ contract DistributorP1 is ComponentP1, IDistributor { // actions: // for dest where w[dest] != 0: // erc20.transferFrom(from, addrOf(dest), tokensPerShare * w[dest]) - function distribute(IERC20 erc20, uint256 amount) external notTradingPausedOrFrozen { + function distribute(IERC20 erc20, uint256 amount) external { require(erc20 == rsr || erc20 == rToken, "RSR or RToken"); bool isRSR = erc20 == rsr; // if false: isRToken uint256 tokensPerShare; diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 6b8dae3b60..c1f3d8be2a 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -113,14 +113,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// @param recipient The address to receive the issued RTokens /// @param amount {qRTok} The quantity of RToken to issue /// @custom:interaction - // untestable: - // `else` branch of `exchangeRateIsValidAfter` (ie. revert) - // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. - function issueTo(address recipient, uint256 amount) - public - notIssuancePausedOrFrozen - exchangeRateIsValidAfter - { + // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. + function issueTo(address recipient, uint256 amount) public notIssuancePausedOrFrozen { require(amount > 0, "Cannot issue zero"); // == Refresh == @@ -160,15 +154,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { CEIL ); - // Fixlib optimization: - // D18{BU} = D18{BU} + D18{BU}; uint192(+) is the same as Fix.plus - uint192 newBasketsNeeded = basketsNeeded + amtBaskets; - emit BasketsNeededChanged(basketsNeeded, newBasketsNeeded); - basketsNeeded = newBasketsNeeded; - - // == Interactions: mint + transfer tokens to BackingManager == - - _mint(recipient, amount); + // == Interactions: Create RToken + transfer tokens to BackingManager == + _scaleUp(recipient, amtBaskets, supply); for (uint256 i = 0; i < erc20s.length; ++i) { IERC20Upgradeable(erc20s[i]).safeTransferFrom( @@ -201,13 +188,11 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // for each token in erc20s: // let tokenAmt = (amount * basketsNeeded / totalSupply) current baskets // do token.transferFrom(backingManager, caller, tokenAmt) - // untestable: - // `else` branch of `exchangeRateIsValidAfter` (ie. revert) - // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. + // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem /// @custom:interaction RCEI - function redeemTo(address recipient, uint256 amount) public notFrozen exchangeRateIsValidAfter { + function redeemTo(address recipient, uint256 amount) public notFrozen { // == Refresh == assetRegistry.refresh(); @@ -228,13 +213,10 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { redemptionThrottle.useAvailable(supply, int256(amount)); // reverts on over-redemption // {BU} - uint192 basketsRedeemed = _redeem(_msgSender(), amount); - emit Redemption(_msgSender(), recipient, amount, basketsRedeemed); + uint192 baskets = _scaleDown(_msgSender(), amount); + emit Redemption(_msgSender(), recipient, amount, baskets); - (address[] memory erc20s, uint256[] memory amounts) = basketHandler.quote( - basketsRedeemed, - FLOOR - ); + (address[] memory erc20s, uint256[] memory amounts) = basketHandler.quote(baskets, FLOOR); // === Interactions === @@ -267,9 +249,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // let tokenAmt = (amount * basketsNeeded / totalSupply) custom baskets // let prorataAmt = (amount / totalSupply) * token.balanceOf(backingManager) // do token.transferFrom(backingManager, caller, min(tokenAmt, prorataAmt)) - // untestable: - // `else` branch of `exchangeRateIsValidAfter` (ie. revert) - // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. + // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. /// @dev Allows partial redemptions up to the minAmounts /// @param recipient The address to receive the backing collateral tokens /// @param amount {qRTok} The quantity {qRToken} of RToken to redeem @@ -285,12 +265,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { uint192[] memory portions, address[] memory expectedERC20sOut, uint256[] memory minAmounts - ) - external - notFrozen - exchangeRateIsValidAfter - returns (address[] memory erc20sOut, uint256[] memory amountsOut) - { + ) external notFrozen returns (address[] memory erc20sOut, uint256[] memory amountsOut) { // == Refresh == assetRegistry.refresh(); @@ -314,15 +289,15 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { redemptionThrottle.useAvailable(supply, int256(amount)); // reverts on over-redemption // {BU} - uint192 basketsRedeemed = _redeem(_msgSender(), amount); - emit Redemption(_msgSender(), recipient, amount, basketsRedeemed); + uint192 baskets = _scaleDown(_msgSender(), amount); + emit Redemption(_msgSender(), recipient, amount, baskets); // === Get basket redemption amounts === (erc20sOut, amountsOut) = basketHandler.quoteCustomRedemption( basketNonces, portions, - basketsRedeemed + baskets ); // ==== Prorate redemption ==== @@ -378,25 +353,22 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { } } - /// Mint a quantity of RToken to the `recipient`, decreasing the basket rate - /// @param recipient The recipient of the newly minted RToken - /// @param amtRToken {qRTok} The amtRToken to be minted + /// Mint an amount of RToken equivalent to baskets BUs, scaling basketsNeeded up + /// Callable only by BackingManager + /// @param baskets {BU} The number of baskets to mint RToken for /// @custom:protected // checks: unpaused; unfrozen; caller is backingManager // effects: // bal'[recipient] = bal[recipient] + amtRToken // totalSupply' = totalSupply + amtRToken + // basketsNeeded' = basketsNeeded + baskets // // untestable: // `else` branch of `exchangeRateIsValidAfter` (ie. revert) shows as uncovered // but it is fully covered for `mint` (limitations of coverage plugin) - function mint(address recipient, uint256 amtRToken) - external - notTradingPausedOrFrozen - exchangeRateIsValidAfter - { + function mint(uint192 baskets) external exchangeRateIsValidAfter { require(_msgSender() == address(backingManager), "not backing manager"); - _mint(recipient, amtRToken); + _scaleUp(address(backingManager), baskets, totalSupply()); } /// Melt a quantity of RToken from the caller's account, increasing the basket rate @@ -416,13 +388,14 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { emit Melted(amtRToken); } - /// Dissolve an amount of RToken from caller's account and scale basketsNeeded down + /// Burn an amount of RToken from caller's account and scale basketsNeeded down /// Callable only by backingManager /// @param amount {qRTok} /// @custom:protected - function dissolve(uint256 amount) external notTradingPausedOrFrozen exchangeRateIsValidAfter { + // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. + function dissolve(uint256 amount) external { require(_msgSender() == address(backingManager), "not backing manager"); - _redeem(_msgSender(), amount); + _scaleDown(_msgSender(), amount); } /// An affordance of last resort for Main in order to ensure re-capitalization @@ -433,11 +406,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // untestable: // `else` branch of `exchangeRateIsValidAfter` (ie. revert) shows as uncovered // but it is fully covered for `setBasketsNeeded` (limitations of coverage plugin) - function setBasketsNeeded(uint192 basketsNeeded_) - external - notTradingPausedOrFrozen - exchangeRateIsValidAfter - { + function setBasketsNeeded(uint192 basketsNeeded_) external exchangeRateIsValidAfter { require(_msgSender() == address(backingManager), "not backing manager"); emit BasketsNeededChanged(basketsNeeded, basketsNeeded_); basketsNeeded = basketsNeeded_; @@ -498,15 +467,35 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // ==== Private ==== - /// Redeem an amount of RToken from an account for basket units, without transferring tokens - /// @param account The address to redeem RTokens from - /// @param amount {qRTok} The amount of RToken to be redeemed - /// @param basketsRedeemed {BU} The number of baskets redeemed - function _redeem(address account, uint256 amount) private returns (uint192 basketsRedeemed) { + /// Mint an amount of RToken equivalent to amtBaskets and scale basketsNeeded up + /// @param recipient The address to receive the RTokens + /// @param amtBaskets {BU} The number of amtBaskets to mint RToken for + /// @param totalSupply {qRTok} The current totalSupply + function _scaleUp( + address recipient, + uint192 amtBaskets, + uint256 totalSupply + ) private { + // take advantage of 18 decimals during casting + uint256 amtRToken = totalSupply > 0 + ? amtBaskets.muluDivu(totalSupply, basketsNeeded) // {rTok} = {BU} * {qRTok} * {qRTok} + : amtBaskets; // {rTok} + emit BasketsNeededChanged(basketsNeeded, basketsNeeded + amtBaskets); + basketsNeeded += amtBaskets; + + // Mint RToken to recipient + _mint(recipient, amtRToken); + } + + /// Burn an amount of RToken and scale basketsNeeded down + /// @param account The address to dissolve RTokens from + /// @param amount {qRTok} The amount of RToken to be dissolved + /// @return baskets {BU} The equivalent number of baskets dissolved + function _scaleDown(address account, uint256 amount) private returns (uint192 baskets) { // D18{BU} = D18{BU} * {qRTok} / {qRTok} - basketsRedeemed = basketsNeeded.muluDivu(amount, totalSupply()); // FLOOR - emit BasketsNeededChanged(basketsNeeded, basketsNeeded - basketsRedeemed); - basketsNeeded = basketsNeeded - basketsRedeemed; + baskets = basketsNeeded.muluDivu(amount, totalSupply()); // FLOOR + emit BasketsNeededChanged(basketsNeeded, basketsNeeded - baskets); + basketsNeeded -= baskets; // Burn RToken from account; reverts if not enough balance _burn(account, amount); diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index cf7af8559a..73f45d72f7 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -40,8 +40,13 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { - trade = super.settleTrade(sell); // modifier: notTradingPausedOrFrozen + function settleTrade(IERC20 sell) + public + override(ITrading, TradingP1) + notTradingPausedOrFrozen + returns (ITrade trade) + { + trade = super.settleTrade(sell); // nonReentrant distributeTokenToBuy(); // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } @@ -73,7 +78,11 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { // actions: // tryTrade(kind, prepareTradeSell(toAsset(erc20), toAsset(tokenToBuy), bal)) // (i.e, start a trade, selling as much of our bal of erc20 as we can, to buy tokenToBuy) - function manageToken(IERC20 erc20, TradeKind kind) external notTradingPausedOrFrozen { + function manageToken(IERC20 erc20, TradeKind kind) + external + nonReentrant + notTradingPausedOrFrozen + { if (erc20 == tokenToBuy) { distributeTokenToBuy(); return; diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 99d66d70d6..744e600b24 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -198,7 +198,8 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab /// Assign reward payouts to the staker pool /// @custom:refresher - function payoutRewards() external notFrozen { + function payoutRewards() external { + requireNotFrozen(); _payoutRewards(); } @@ -333,8 +334,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab } function cancelUnstake(uint256 endId) external { - requireNotTradingPausedOrFrozen(); - + requireNotFrozen(); address account = _msgSender(); // We specifically allow unstaking when under collateralized @@ -700,6 +700,10 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // solhint-disable-next-line no-empty-blocks function requireNotTradingPausedOrFrozen() private notTradingPausedOrFrozen {} + // contract-size-saver + // solhint-disable-next-line no-empty-blocks + function requireNotFrozen() private notFrozen {} + // ==== ERC20 ==== // This section extracted from ERC20; adjusted to work with stakes/eras // name(), symbol(), and decimals() are all auto-generated diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 5e2e25d177..ff1754fa96 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -83,13 +83,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // tradesOpen' = tradesOpen - 1 // untested: // OZ nonReentrant line is assumed to be working. cost/benefit of direct testing is high - function settleTrade(IERC20 sell) - public - virtual - notTradingPausedOrFrozen - nonReentrant - returns (ITrade trade) - { + function settleTrade(IERC20 sell) public virtual nonReentrant returns (ITrade trade) { trade = trades[sell]; require(address(trade) != address(0), "no trade open"); require(trade.canSettle(), "cannot settle yet"); @@ -121,11 +115,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // This is reentrancy-safe because we're using the `nonReentrant` modifier on every method of // this contract that changes state this function refers to. // slither-disable-next-line reentrancy-vulnerabilities-1 - function tryTrade(TradeKind kind, TradeRequest memory req) - internal - nonReentrant - returns (ITrade trade) - { + function tryTrade(TradeKind kind, TradeRequest memory req) internal returns (ITrade trade) { /* */ IERC20 sell = req.sell.erc20(); assert(address(trades[sell]) == address(0)); From 1379fcf158f73462f2819682772e51ef846aca0b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 19:49:33 -0400 Subject: [PATCH 283/462] tests --- contracts/p1/RToken.sol | 2 + test/Broker.test.ts | 42 ----------- test/RToken.test.ts | 125 +++---------------------------- test/Recollateralization.test.ts | 2 +- test/Revenues.test.ts | 24 +----- 5 files changed, 18 insertions(+), 177 deletions(-) diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index c1f3d8be2a..da64929516 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -471,6 +471,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// @param recipient The address to receive the RTokens /// @param amtBaskets {BU} The number of amtBaskets to mint RToken for /// @param totalSupply {qRTok} The current totalSupply + // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. function _scaleUp( address recipient, uint192 amtBaskets, @@ -491,6 +492,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// @param account The address to dissolve RTokens from /// @param amount {qRTok} The amount of RToken to be dissolved /// @return baskets {BU} The equivalent number of baskets dissolved + // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. function _scaleDown(address account, uint256 amount) private returns (uint192 baskets) { // D18{BU} = D18{BU} * {qRTok} / {qRTok} baskets = basketsNeeded.muluDivu(amount, totalSupply()); // FLOOR diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 7accd108fb..b7714afdef 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -384,48 +384,6 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) }) - it('Should not allow to open trade if trading paused', async () => { - await main.connect(owner).pauseTrading() - - // Attempt to open trade - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: bn('100e18'), - minBuyAmount: bn('0'), - } - - await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect( - broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) - ).to.be.revertedWith('frozen or trading paused') - await expect( - broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) - ).to.be.revertedWith('frozen or trading paused') - }) - }) - - it('Should not allow to open trade if frozen', async () => { - await main.connect(owner).freezeShort() - - // Attempt to open trade - const tradeRequest: ITradeRequest = { - sell: collateral0.address, - buy: collateral1.address, - sellAmount: bn('100e18'), - minBuyAmount: bn('0'), - } - - await whileImpersonating(backingManager.address, async (bmSigner) => { - await expect( - broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) - ).to.be.revertedWith('frozen or trading paused') - await expect( - broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) - ).to.be.revertedWith('frozen or trading paused') - }) - }) - it('Should only allow to open trade if a trader', async () => { const amount: BigNumber = bn('100e18') diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 5c871137d5..b786db61c3 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -172,31 +172,10 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) describe('Configuration #fast', () => { - it('Should allow to dissolve RTokens only from BackingManager and when unpaused/unfrozen', async () => { + it('Should allow to dissolve RTokens only from BackingManager', async () => { await expect(rToken.connect(owner).dissolve(fp('1'))).to.be.revertedWith( 'not backing manager' ) - - await whileImpersonating(backingManager.address, async (bmSigner) => { - // Should revert if paused - await main.connect(owner).pauseTrading() - await expect(rToken.connect(bmSigner).dissolve(fp('1'))).to.be.revertedWith( - 'frozen or trading paused' - ) - - // Should revert if frozen - await main.connect(owner).unpauseTrading() - await main.connect(owner).freezeShort() - await expect(rToken.connect(bmSigner).dissolve(fp('1'))).to.be.revertedWith( - 'frozen or trading paused' - ) - - // Should have a different outcome if unfrozen - await main.connect(owner).unfreeze() - await expect(rToken.connect(bmSigner).dissolve(fp('1'))).to.not.be.revertedWith( - 'frozen or trading paused' - ) - }) }) it('Should allow to set basketsNeeded only from BackingManager', async () => { @@ -2267,27 +2246,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect(rToken.connect(addr1).melt(issueAmount)).to.be.revertedWith('furnace only') }) - it('Should not allow mint/transfer/transferFrom to address(this)', async () => { - // mint - await whileImpersonating(backingManager.address, async (signer) => { - await expect(rToken.connect(signer).mint(rToken.address, 1)).to.be.revertedWith( - 'RToken transfer to self' - ) - }) - - // transfer - await expect(rToken.connect(addr1).transfer(rToken.address, 1)).to.be.revertedWith( - 'RToken transfer to self' - ) - - // transferFrom - await rToken.connect(addr1).approve(addr2.address, 1) - await expect( - rToken.connect(addr2).transferFrom(addr1.address, rToken.address, 1) - ).to.be.revertedWith('RToken transfer to self') - }) - - it('Should allow to mint tokens when called by backing manager', async () => { + it('Should only allow to mint tokens when called by backing manager', async () => { // Mint tokens const mintAmount: BigNumber = bn('10e18') @@ -2295,45 +2254,23 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.totalSupply()).to.equal(issueAmount) await whileImpersonating(backingManager.address, async (signer) => { - await rToken.connect(signer).mint(addr1.address, mintAmount) + await rToken.connect(signer).mint(mintAmount) }) - expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount.add(mintAmount)) + expect(await rToken.balanceOf(backingManager.address)).to.equal(mintAmount) expect(await rToken.totalSupply()).to.equal(issueAmount.add(mintAmount)) // Trying to mint with another account will fail - await expect(rToken.connect(other).mint(addr1.address, mintAmount)).to.be.revertedWith( - 'not backing manager' - ) + await expect(rToken.connect(other).mint(mintAmount)).to.be.revertedWith('not backing manager') // Trying to mint from a non-backing manager component should fail await whileImpersonating(basketHandler.address, async (signer) => { - await expect(rToken.connect(signer).mint(addr1.address, mintAmount)).to.be.revertedWith( + await expect(rToken.connect(signer).mint(mintAmount)).to.be.revertedWith( 'not backing manager' ) }) }) - it('Should not mint if paused', async () => { - await main.connect(owner).pauseTrading() - - await whileImpersonating(backingManager.address, async (signer) => { - await expect(rToken.connect(signer).mint(addr1.address, bn('10e18'))).to.be.revertedWith( - 'frozen or trading paused' - ) - }) - }) - - it('Should not mint if frozen', async () => { - await main.connect(owner).freezeShort() - - await whileImpersonating(backingManager.address, async (signer) => { - await expect(rToken.connect(signer).mint(addr1.address, bn('10e18'))).to.be.revertedWith( - 'frozen or trading paused' - ) - }) - }) - it('Should not allow setBasketsNeeded to set BU exchange rate to outside [1e-9, 1e9]', async () => { // setBasketsNeeded() await whileImpersonating(backingManager.address, async (signer) => { @@ -2348,53 +2285,13 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) }) - it('Should not allow setBasketsNeeded if paused', async () => { - // Check initial status - expect(await rToken.basketsNeeded()).to.equal(issueAmount) - - // Pause Main - await main.connect(owner).pauseTrading() - - // Try to set baskets needed - await whileImpersonating(backingManager.address, async (bhSigner) => { - await expect(rToken.connect(bhSigner).setBasketsNeeded(fp('1'))).to.be.revertedWith( - 'frozen or trading paused' - ) - }) - - // Check value remains the same - expect(await rToken.basketsNeeded()).to.equal(issueAmount) - }) - - it('Should not allow setBasketsNeeded if frozen', async () => { - // Check initial status - expect(await rToken.basketsNeeded()).to.equal(issueAmount) - - // Freeze Main - await main.connect(owner).freezeShort() - - // Try to set baskets needed - await whileImpersonating(backingManager.address, async (bhSigner) => { - await expect(rToken.connect(bhSigner).setBasketsNeeded(fp('1'))).to.be.revertedWith( - 'frozen or trading paused' - ) - }) - - // Check value remains the same - expect(await rToken.basketsNeeded()).to.equal(issueAmount) - }) - - it('Should not allow mint to set BU exchange rate to above 1e9', async () => { + it('mint() should not change the BU exchange rate', async () => { // mint() await whileImpersonating(backingManager.address, async (signer) => { - await expect( - rToken - .connect(signer) - .mint(addr1.address, issueAmount.mul(bn('1e9')).add(1).sub(issueAmount)) - ).to.be.revertedWith('BU rate out of range') - await rToken - .connect(signer) - .mint(addr1.address, issueAmount.mul(bn('1e9')).sub(issueAmount)) + await rToken.connect(signer).mint(issueAmount.mul(bn('1e9')).add(1).sub(issueAmount)) + const basketsNeeded = await rToken.basketsNeeded() + const supply = await rToken.totalSupply() + expect(basketsNeeded.mul(fp('1')).div(supply)).to.equal(fp('1')) }) }) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 219cecdd15..608da7a042 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -2601,7 +2601,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { const supply = bonus.sub(bonus.mul(config.backingBuffer).div(fp('1'))) // Should mint the excess in order to re-handout to RToken holders and stakers - expect(await rToken.totalSupply()).to.be.closeTo(supply, supply.div(bn('1e6'))) + expect(await rToken.totalSupply()).to.be.closeTo(supply, supply.div(bn('1e4'))) expect(await rToken.totalSupply()).to.be.gte(supply) // Check price in USD of the current RToken - overcollateralized and still targeting 1 diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 37866e8fc1..09152136c5 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -1548,24 +1548,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) }) - it('Should not distribute if paused or frozen', async () => { - const distAmount: BigNumber = bn('100e18') - - await main.connect(owner).pauseTrading() - - await expect(distributor.distribute(rsr.address, distAmount)).to.be.revertedWith( - 'frozen or trading paused' - ) - - await main.connect(owner).unpauseTrading() - - await main.connect(owner).freezeShort() - - await expect(distributor.distribute(rsr.address, distAmount)).to.be.revertedWith( - 'frozen or trading paused' - ) - }) - it('Should allow anyone to call distribute', async () => { const distAmount: BigNumber = bn('100e18') @@ -3107,7 +3089,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await rsr.connect(owner).mint(addr1.address, initialBal) }) - it('Should be unable to handout excess assets', async () => { + it('Should be able to forwardRevenue without changing BU exchange rate', async () => { // Check Price and Assets value await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) @@ -3122,7 +3104,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await token2.connect(owner).mint(backingManager.address, mintAmt) await token3.connect(owner).mint(backingManager.address, mintAmt) - await expect(backingManager.forwardRevenue([])).revertedWith('BU rate out of range') + await expect(backingManager.forwardRevenue([])).to.emit(rToken, 'Transfer') + expect(await rToken.totalSupply()).to.equal(mintAmt.mul(2)) + expect(await rToken.basketsNeeded()).to.equal(mintAmt.mul(2)) }) }) }) From 15faffc6e5c419a1e63e4d8a25d79c2d2d7b16fc Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 19:49:40 -0400 Subject: [PATCH 284/462] gas snapshots --- test/__snapshots__/Broker.test.ts.snap | 14 ++++---- test/__snapshots__/FacadeWrite.test.ts.snap | 6 ++-- test/__snapshots__/Furnace.test.ts.snap | 32 +++++++++---------- test/__snapshots__/RToken.test.ts.snap | 12 +++---- .../Recollateralization.test.ts.snap | 10 +++--- test/__snapshots__/Revenues.test.ts.snap | 12 +++---- test/__snapshots__/ZZStRSR.test.ts.snap | 4 +-- .../__snapshots__/MaxBasketSize.test.ts.snap | 12 +++---- 8 files changed, 51 insertions(+), 51 deletions(-) diff --git a/test/__snapshots__/Broker.test.ts.snap b/test/__snapshots__/Broker.test.ts.snap index d894fc2cd7..0d19437393 100644 --- a/test/__snapshots__/Broker.test.ts.snap +++ b/test/__snapshots__/Broker.test.ts.snap @@ -1,21 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `233100`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `233035`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `350227`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `337662`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `352276`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `339776`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `354414`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `341914`; exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63429`; exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Initialize Trade 1`] = `451427`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 1`] = `552471`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 1`] = `539971`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 2`] = `540309`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 2`] = `527809`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 3`] = `542447`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 3`] = `529947`; exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Settle Trade 1`] = `113056`; diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index e7f3bf509d..63df8acefd 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `9069178`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `9070064`; -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464736`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464714`; -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Without governance 1`] = `114917`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Without governance 1`] = `114895`; diff --git a/test/__snapshots__/Furnace.test.ts.snap b/test/__snapshots__/Furnace.test.ts.snap index 9cdf75c39e..df1a557300 100644 --- a/test/__snapshots__/Furnace.test.ts.snap +++ b/test/__snapshots__/Furnace.test.ts.snap @@ -1,35 +1,35 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 1`] = `83816`; +exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 1`] = `83925`; -exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 2`] = `92565`; +exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 2`] = `92751`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 1`] = `83816`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 1`] = `83925`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 2`] = `81048`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 2`] = `81234`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 3`] = `81048`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 3`] = `81234`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 4`] = `81048`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 4`] = `81234`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 5`] = `81048`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 5`] = `81234`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 6`] = `81048`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 6`] = `81234`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 7`] = `81048`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 7`] = `81234`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 8`] = `81048`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 8`] = `81234`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 9`] = `81048`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 9`] = `81234`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 10`] = `81048`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 10`] = `81234`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 11`] = `81048`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 11`] = `81234`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 1`] = `63916`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 1`] = `64025`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 2`] = `80548`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 2`] = `80657`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 3`] = `81048`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 3`] = `81234`; exports[`FurnaceP1 contract Gas Reporting Melt - One period 4`] = `40755`; diff --git a/test/__snapshots__/RToken.test.ts.snap b/test/__snapshots__/RToken.test.ts.snap index f3c5d0ddb5..45f7c82958 100644 --- a/test/__snapshots__/RToken.test.ts.snap +++ b/test/__snapshots__/RToken.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `772308`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `770967`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `610515`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `597971`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `573888`; +exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `573851`; -exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56570`; +exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56658`; -exports[`RTokenP1 contract Gas Reporting Transfer 2`] = `34670`; +exports[`RTokenP1 contract Gas Reporting Transfer 2`] = `34758`; -exports[`RTokenP1 contract Gas Reporting Transfer 3`] = `51770`; +exports[`RTokenP1 contract Gas Reporting Transfer 3`] = `51858`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 4d1681a4f8..018e1c87cc 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1338646`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1334404`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1451081`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1459732`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1695736`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1691559`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1640020`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1635487`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1729104`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1725283`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `212916`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index c2cca65c5c..7e156a57b1 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -12,14 +12,14 @@ exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229482`; exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212382`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `758101`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `754579`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1150171`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1146627`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `320785`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `317989`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `273846`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `271345`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `741001`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `737479`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `251645`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `248849`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index b779667f61..d3c38d5241 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -14,6 +14,6 @@ exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `222609`; exports[`StRSRP1 contract Gas Reporting Unstake 2`] = `139758`; -exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `555639`; +exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `555617`; -exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `509643`; +exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `509621`; diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index fa053cc38c..0e0cede769 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `11745689`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `11745060`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9481800`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9481763`; exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2292984`; exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `12996739`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `25176105`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `25285711`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10752477`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10751848`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8471500`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8471463`; exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4535170`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `17613771`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `17723377`; From 6eda68b6dbb3f99b443d309dd77f8761353ae74f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 20:06:44 -0400 Subject: [PATCH 285/462] polish off RToken + final snapshots --- contracts/p0/RToken.sol | 14 ++-- contracts/p1/RToken.sol | 83 ++++++++++--------- test/__snapshots__/Furnace.test.ts.snap | 24 +++--- .../Recollateralization.test.ts.snap | 2 +- test/__snapshots__/ZZStRSR.test.ts.snap | 2 +- 5 files changed, 63 insertions(+), 62 deletions(-) diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 9532f2a8b1..e04825248c 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -368,16 +368,16 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// Burn an amount of RToken and scale basketsNeeded down /// @param account The address to dissolve RTokens from - /// @param amount {qRTok} The amount of RToken to be dissolved - /// @return baskets {BU} The equivalent number of baskets dissolved - function _scaleDown(address account, uint256 amount) private returns (uint192 baskets) { + /// @param amtRToken {qRTok} The amount of RToken to be dissolved + /// @return amtBaskets {BU} The equivalent number of baskets dissolved + function _scaleDown(address account, uint256 amtRToken) private returns (uint192 amtBaskets) { // D18{BU} = D18{BU} * {qRTok} / {qRTok} - baskets = basketsNeeded.muluDivu(amount, totalSupply()); // FLOOR - emit BasketsNeededChanged(basketsNeeded, basketsNeeded.minus(baskets)); - basketsNeeded = basketsNeeded.minus(baskets); + amtBaskets = basketsNeeded.muluDivu(amtRToken, totalSupply()); // FLOOR + emit BasketsNeededChanged(basketsNeeded, basketsNeeded.minus(amtBaskets)); + basketsNeeded = basketsNeeded.minus(amtBaskets); // Burn RToken from account; reverts if not enough balance - _burn(account, amount); + _burn(account, amtRToken); } /** diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index da64929516..4a4a7ebcdd 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -86,22 +86,6 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { redemptionThrottle.lastTimestamp = uint48(block.timestamp); } - /// after fn(), assert exchangeRate in [MIN_EXCHANGE_RATE, MAX_EXCHANGE_RATE] - modifier exchangeRateIsValidAfter() { - _; - uint256 supply = totalSupply(); - if (supply == 0) return; - - // Note: These are D18s, even though they are uint256s. This is because - // we cannot assume we stay inside our valid range here, as that is what - // we are checking in the first place - uint256 low = (FIX_ONE_256 * basketsNeeded) / supply; // D18{BU/rTok} - uint256 high = (FIX_ONE_256 * basketsNeeded + (supply - 1)) / supply; // D18{BU/rTok} - - // here we take advantage of an implicit upcast from uint192 exchange rates - require(low >= MIN_EXCHANGE_RATE && high <= MAX_EXCHANGE_RATE, "BU rate out of range"); - } - /// Issue an RToken on current the basket /// @param amount {qTok} The quantity of RToken to issue /// @custom:interaction nearly CEI, but see comments around handling of refunds @@ -357,16 +341,13 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// Callable only by BackingManager /// @param baskets {BU} The number of baskets to mint RToken for /// @custom:protected - // checks: unpaused; unfrozen; caller is backingManager + // checks: caller is backingManager // effects: // bal'[recipient] = bal[recipient] + amtRToken // totalSupply' = totalSupply + amtRToken // basketsNeeded' = basketsNeeded + baskets - // - // untestable: - // `else` branch of `exchangeRateIsValidAfter` (ie. revert) shows as uncovered - // but it is fully covered for `mint` (limitations of coverage plugin) - function mint(uint192 baskets) external exchangeRateIsValidAfter { + // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. + function mint(uint192 baskets) external { require(_msgSender() == address(backingManager), "not backing manager"); _scaleUp(address(backingManager), baskets, totalSupply()); } @@ -374,15 +355,13 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// Melt a quantity of RToken from the caller's account, increasing the basket rate /// @param amtRToken {qRTok} The amtRToken to be melted /// @custom:protected - // checks: not trading paused or frozen + // checks: caller is furnace // effects: // bal'[caller] = bal[caller] - amtRToken // totalSupply' = totalSupply - amtRToken - // - // untestable: - // `else` branch of `exchangeRateIsValidAfter` (ie. revert) shows as uncovered - // but it is fully covered for `melt` (limitations of coverage plugin) - function melt(uint256 amtRToken) external exchangeRateIsValidAfter { + // BU exchange rate cannot decrease + // BU exchange rate CAN increase, but we already trust furnace to do this slowly + function melt(uint256 amtRToken) external { require(_msgSender() == address(furnace), "furnace only"); _burn(_msgSender(), amtRToken); emit Melted(amtRToken); @@ -392,6 +371,11 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// Callable only by backingManager /// @param amount {qRTok} /// @custom:protected + // checks: caller is backingManager + // effects: + // bal'[recipient] = bal[recipient] - amtRToken + // totalSupply' = totalSupply - amtRToken + // basketsNeeded' = basketsNeeded - baskets // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. function dissolve(uint256 amount) external { require(_msgSender() == address(backingManager), "not backing manager"); @@ -400,16 +384,25 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// An affordance of last resort for Main in order to ensure re-capitalization /// @custom:protected - // checks: trading unpaused; unfrozen; caller is backingManager + // checks: caller is backingManager // effects: basketsNeeded' = basketsNeeded_ - // - // untestable: - // `else` branch of `exchangeRateIsValidAfter` (ie. revert) shows as uncovered - // but it is fully covered for `setBasketsNeeded` (limitations of coverage plugin) - function setBasketsNeeded(uint192 basketsNeeded_) external exchangeRateIsValidAfter { + function setBasketsNeeded(uint192 basketsNeeded_) external { require(_msgSender() == address(backingManager), "not backing manager"); emit BasketsNeededChanged(basketsNeeded, basketsNeeded_); basketsNeeded = basketsNeeded_; + + // Ensure exchange rate is valid + uint256 supply = totalSupply(); + assert(supply > 0); + + // Note: These are D18s, even though they are uint256s. This is because + // we cannot assume we stay inside our valid range here, as that is what + // we are checking in the first place + uint256 low = (FIX_ONE_256 * basketsNeeded) / supply; // D18{BU/rTok} + uint256 high = (FIX_ONE_256 * basketsNeeded + (supply - 1)) / supply; // D18{BU/rTok} + + // here we take advantage of an implicit upcast from uint192 exchange rates + require(low >= MIN_EXCHANGE_RATE && high <= MAX_EXCHANGE_RATE, "BU rate out of range"); } /// Sends all token balance of erc20 (if it is registered) to the BackingManager @@ -471,6 +464,10 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// @param recipient The address to receive the RTokens /// @param amtBaskets {BU} The number of amtBaskets to mint RToken for /// @param totalSupply {qRTok} The current totalSupply + // effects: + // bal'[recipient] = bal[recipient] + amtRToken + // totalSupply' = totalSupply + amtRToken + // basketsNeeded' = basketsNeeded + amtBaskets // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. function _scaleUp( address recipient, @@ -490,17 +487,21 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// Burn an amount of RToken and scale basketsNeeded down /// @param account The address to dissolve RTokens from - /// @param amount {qRTok} The amount of RToken to be dissolved - /// @return baskets {BU} The equivalent number of baskets dissolved + /// @param amtRToken {qRTok} The amount of RToken to be dissolved + /// @return amtBaskets {BU} The equivalent number of baskets dissolved + // effects: + // bal'[recipient] = bal[recipient] - amtRToken + // totalSupply' = totalSupply - amtRToken + // basketsNeeded' = basketsNeeded - amtBaskets // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. - function _scaleDown(address account, uint256 amount) private returns (uint192 baskets) { + function _scaleDown(address account, uint256 amtRToken) private returns (uint192 amtBaskets) { // D18{BU} = D18{BU} * {qRTok} / {qRTok} - baskets = basketsNeeded.muluDivu(amount, totalSupply()); // FLOOR - emit BasketsNeededChanged(basketsNeeded, basketsNeeded - baskets); - basketsNeeded -= baskets; + amtBaskets = basketsNeeded.muluDivu(amtRToken, totalSupply()); // FLOOR + emit BasketsNeededChanged(basketsNeeded, basketsNeeded - amtBaskets); + basketsNeeded -= amtBaskets; // Burn RToken from account; reverts if not enough balance - _burn(account, amount); + _burn(account, amtRToken); } /** diff --git a/test/__snapshots__/Furnace.test.ts.snap b/test/__snapshots__/Furnace.test.ts.snap index df1a557300..fbfe5b16dd 100644 --- a/test/__snapshots__/Furnace.test.ts.snap +++ b/test/__snapshots__/Furnace.test.ts.snap @@ -2,34 +2,34 @@ exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 1`] = `83925`; -exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 2`] = `92751`; +exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 2`] = `89814`; exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 1`] = `83925`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 2`] = `81234`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 2`] = `78297`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 3`] = `81234`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 3`] = `78297`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 4`] = `81234`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 4`] = `78297`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 5`] = `81234`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 5`] = `78297`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 6`] = `81234`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 6`] = `78297`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 7`] = `81234`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 7`] = `78297`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 8`] = `81234`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 8`] = `78297`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 9`] = `81234`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 9`] = `78297`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 10`] = `81234`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 10`] = `78297`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 11`] = `81234`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 11`] = `78297`; exports[`FurnaceP1 contract Gas Reporting Melt - One period 1`] = `64025`; exports[`FurnaceP1 contract Gas Reporting Melt - One period 2`] = `80657`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 3`] = `81234`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 3`] = `78297`; exports[`FurnaceP1 contract Gas Reporting Melt - One period 4`] = `40755`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 018e1c87cc..dd746f6f0d 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -8,7 +8,7 @@ exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1635487`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1635843`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184816`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index d3c38d5241..0fc4f146ac 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -2,7 +2,7 @@ exports[`StRSRP1 contract Gas Reporting Stake 1`] = `152134`; -exports[`StRSRP1 contract Gas Reporting Stake 2`] = `147334`; +exports[`StRSRP1 contract Gas Reporting Stake 2`] = `164488`; exports[`StRSRP1 contract Gas Reporting Transfer 1`] = `63389`; From 9bdb1e3102b5ea83de1b200f0429df49c1c19095 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 20:14:07 -0400 Subject: [PATCH 286/462] keep notTradingPausedOrFrozen on setBasketsNeeded --- contracts/p0/RToken.sol | 6 +++++- contracts/p1/RToken.sol | 4 ++-- test/RToken.test.ts | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index e04825248c..e6714282b3 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -294,7 +294,11 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { /// An affordance of last resort for Main in order to ensure re-capitalization /// @custom:protected - function setBasketsNeeded(uint192 basketsNeeded_) external exchangeRateIsValidAfter { + function setBasketsNeeded(uint192 basketsNeeded_) + external + notTradingPausedOrFrozen + exchangeRateIsValidAfter + { require(_msgSender() == address(main.backingManager()), "not backing manager"); emit BasketsNeededChanged(basketsNeeded, basketsNeeded_); basketsNeeded = basketsNeeded_; diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 4a4a7ebcdd..46b455d90b 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -386,14 +386,14 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// @custom:protected // checks: caller is backingManager // effects: basketsNeeded' = basketsNeeded_ - function setBasketsNeeded(uint192 basketsNeeded_) external { + function setBasketsNeeded(uint192 basketsNeeded_) external notTradingPausedOrFrozen { require(_msgSender() == address(backingManager), "not backing manager"); emit BasketsNeededChanged(basketsNeeded, basketsNeeded_); basketsNeeded = basketsNeeded_; // Ensure exchange rate is valid uint256 supply = totalSupply(); - assert(supply > 0); + assert(supply > 0); // BackingManager should not be calling // Note: These are D18s, even though they are uint256s. This is because // we cannot assume we stay inside our valid range here, as that is what diff --git a/test/RToken.test.ts b/test/RToken.test.ts index b786db61c3..1f49aded31 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -2285,6 +2285,42 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) }) + it('Should not allow setBasketsNeeded if paused', async () => { + // Check initial status + expect(await rToken.basketsNeeded()).to.equal(issueAmount) + + // Pause Main + await main.connect(owner).pauseTrading() + + // Try to set baskets needed + await whileImpersonating(backingManager.address, async (bhSigner) => { + await expect(rToken.connect(bhSigner).setBasketsNeeded(fp('1'))).to.be.revertedWith( + 'frozen or trading paused' + ) + }) + + // Check value remains the same + expect(await rToken.basketsNeeded()).to.equal(issueAmount) + }) + + it('Should not allow setBasketsNeeded if frozen', async () => { + // Check initial status + expect(await rToken.basketsNeeded()).to.equal(issueAmount) + + // Freeze Main + await main.connect(owner).freezeShort() + + // Try to set baskets needed + await whileImpersonating(backingManager.address, async (bhSigner) => { + await expect(rToken.connect(bhSigner).setBasketsNeeded(fp('1'))).to.be.revertedWith( + 'frozen or trading paused' + ) + }) + + // Check value remains the same + expect(await rToken.basketsNeeded()).to.equal(issueAmount) + }) + it('mint() should not change the BU exchange rate', async () => { // mint() await whileImpersonating(backingManager.address, async (signer) => { From 6b58e9fc8a1ebb5f4e2b2db36bbef51f31394f04 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 20:27:26 -0400 Subject: [PATCH 287/462] re-order tryTrade and document nonReentrant testatibility --- contracts/p1/BackingManager.sol | 4 ++++ contracts/p1/RevenueTrader.sol | 2 ++ contracts/p1/mixins/Trading.sol | 13 ++++--------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index a9a669e847..5451b51bdf 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -99,6 +99,8 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// Apply the overall backing policy using the specified TradeKind, taking a haircut if unable /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @custom:interaction not RCEI, nonReentrant + // untested: + // OZ nonReentrant line is assumed to be working. cost/benefit of direct testing is high function rebalance(TradeKind kind) external nonReentrant notTradingPausedOrFrozen { // == Refresh == assetRegistry.refresh(); @@ -161,6 +163,8 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// Forward revenue to RevenueTraders; reverts if not fully collateralized /// @param erc20s The tokens to forward /// @custom:interaction not RCEI, nonReentrant + // untested: + // OZ nonReentrant line is assumed to be working. cost/benefit of direct testing is high function forwardRevenue(IERC20[] calldata erc20s) external nonReentrant diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 73f45d72f7..9ee3c5b6a2 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -78,6 +78,8 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { // actions: // tryTrade(kind, prepareTradeSell(toAsset(erc20), toAsset(tokenToBuy), bal)) // (i.e, start a trade, selling as much of our bal of erc20 as we can, to buy tokenToBuy) + // untested: + // OZ nonReentrant line is assumed to be working. cost/benefit of direct testing is high function manageToken(IERC20 erc20, TradeKind kind) external nonReentrant diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index ff1754fa96..5bc6f4ada2 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -109,23 +109,18 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // effects: // trades' = trades.set(req.sell, tradeID) // tradesOpen' = tradesOpen + 1 - // - // untested: - // OZ nonReentrant line is assumed to be working. cost/benefit of direct testing is high - // This is reentrancy-safe because we're using the `nonReentrant` modifier on every method of - // this contract that changes state this function refers to. - // slither-disable-next-line reentrancy-vulnerabilities-1 function tryTrade(TradeKind kind, TradeRequest memory req) internal returns (ITrade trade) { /* */ IERC20 sell = req.sell.erc20(); assert(address(trades[sell]) == address(0)); + trades[sell] = trade; + tradesOpen++; + IERC20Upgradeable(address(sell)).safeApprove(address(broker), 0); IERC20Upgradeable(address(sell)).safeApprove(address(broker), req.sellAmount); - trade = broker.openTrade(kind, req); - trades[sell] = trade; - tradesOpen++; + emit TradeStarted(trade, sell, req.buy.erc20(), req.sellAmount, req.minBuyAmount); } From 7265e24014fabdce748d3380c5a2f4d732eaaaff Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 21:28:34 -0400 Subject: [PATCH 288/462] fix RTokenP0 --- contracts/p0/RToken.sol | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index dbbd79e62e..03b9b2c81d 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -201,9 +201,11 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { // Call collective state keepers. main.poke(); + uint256 supply = totalSupply(); + // Revert if redemption exceeds either supply throttle - issuanceThrottle.useAvailable(totalSupply(), -int256(amount)); - redemptionThrottle.useAvailable(totalSupply(), int256(amount)); // reverts on overuse + issuanceThrottle.useAvailable(supply, -int256(amount)); + redemptionThrottle.useAvailable(supply, int256(amount)); // reverts on overuse // {BU} uint192 basketsRedeemed = _redeem(_msgSender(), amount); @@ -237,12 +239,12 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { bool allZero = true; // Bound each withdrawal by the prorata share, in case currently under-collateralized for (uint256 i = 0; i < erc20sOut.length; i++) { - uint256 bal = IERC20Upgradeable(erc20sOut[i]).balanceOf( - address(main.backingManager()) - ); // {qTok} - // {qTok} = {qTok} * {qRTok} / {qRTok} - uint256 prorata = mulDiv256(bal, amount, totalSupply()); // FLOOR + uint256 prorata = mulDiv256( + IERC20(erc20sOut[i]).balanceOf(address(main.backingManager())), + amount, + supply + ); // FLOOR if (prorata < amountsOut[i]) amountsOut[i] = prorata; // Send withdrawal From cb6c04522d8365e3fd568df9ce545b5b2ed74c4b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 21:29:34 -0400 Subject: [PATCH 289/462] fix integration test inconsistency in CI --- test/integration/mainnet-test/StaticATokens.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/mainnet-test/StaticATokens.test.ts b/test/integration/mainnet-test/StaticATokens.test.ts index f19d60937c..efc3bdadad 100644 --- a/test/integration/mainnet-test/StaticATokens.test.ts +++ b/test/integration/mainnet-test/StaticATokens.test.ts @@ -366,7 +366,7 @@ describeFork(`Static ATokens - Mainnet Check - Mainnet Forking P${IMPLEMENTATION }) // Wrap aBusd - Underlying = false - expect(await aBusd.balanceOf(addr1.address)).to.equal(initialBal) + expect(await aBusd.balanceOf(addr1.address)).to.be.closeTo(initialBal, 1) expect(await stataBusd.balanceOf(addr1.address)).to.equal(0) // Wrap aBUSD into a staticaBUSD From de6f2da7bfe16d8bd7862c7e2d9a711a5883e387 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 21:41:05 -0400 Subject: [PATCH 290/462] fix P1 tests --- contracts/p0/RToken.sol | 1 + contracts/p1/RToken.sol | 2 +- test/RToken.test.ts | 39 ++++++--------------------------------- 3 files changed, 8 insertions(+), 34 deletions(-) diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 716f704e8f..390d99e617 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -302,6 +302,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { exchangeRateIsValidAfter { require(_msgSender() == address(main.backingManager()), "not backing manager"); + require(totalSupply() > 0, "0 supply"); emit BasketsNeededChanged(basketsNeeded, basketsNeeded_); basketsNeeded = basketsNeeded_; } diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 46b455d90b..d4aa0bcb71 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -393,7 +393,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // Ensure exchange rate is valid uint256 supply = totalSupply(); - assert(supply > 0); // BackingManager should not be calling + require(supply > 0, "0 supply"); // Note: These are D18s, even though they are uint256s. This is because // we cannot assume we stay inside our valid range here, as that is what diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 1f49aded31..43e5dfef55 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -192,18 +192,15 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { 'not backing manager' ) }) + }) - // Check value not updated - expect(await rToken.basketsNeeded()).to.equal(0) - + it('Should not allow to setBasketsNeeded at 0 supply', async () => { + // Should not be able to setBasketsNeeded at 0 supply await whileImpersonating(backingManager.address, async (bhSigner) => { - await expect(rToken.connect(bhSigner).setBasketsNeeded(fp('1'))) - .to.emit(rToken, 'BasketsNeededChanged') - .withArgs(0, fp('1')) + await expect(rToken.connect(bhSigner).setBasketsNeeded(fp('1'))).to.be.revertedWith( + '0 supply' + ) }) - - // Check updated value - expect(await rToken.basketsNeeded()).to.equal(fp('1')) }) it('Should allow to update issuance throttle if Owner and perform validations', async () => { @@ -2330,30 +2327,6 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(basketsNeeded.mul(fp('1')).div(supply)).to.equal(fp('1')) }) }) - - it('Should not allow melt to set BU exchange rate to below 1e-9', async () => { - await rToken.setIssuanceThrottleParams({ amtRate: bn('1e28'), pctRate: fp('1') }) - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 3600) - const largeIssueAmt = bn('1e28') - - // Issue more RTokens - await Promise.all( - tokens.map((t) => t.connect(owner).mint(addr1.address, largeIssueAmt.sub(issueAmount))) - ) - await Promise.all( - tokens.map((t) => t.connect(addr1).approve(rToken.address, largeIssueAmt.sub(issueAmount))) - ) - await rToken.connect(addr1).issue(largeIssueAmt.sub(issueAmount)) - await rToken.connect(addr1).transfer(furnace.address, largeIssueAmt) - - // melt() - await whileImpersonating(furnace.address, async (signer) => { - await expect( - rToken.connect(signer).melt(largeIssueAmt.sub(largeIssueAmt.div(bn('1e9'))).add(1)) - ).to.be.revertedWith('BU rate out of range') - await rToken.connect(signer).melt(largeIssueAmt.sub(largeIssueAmt.div(bn('1e9')))) - }) - }) }) describe('Transfers #fast', () => { From ac26373c4dc67bb5122b27ac11a1d03a2ddf4d39 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 19 May 2023 23:40:37 -0400 Subject: [PATCH 291/462] TraderP1: fix ordering and label nonReentrant requirement --- contracts/p1/mixins/Trading.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 5bc6f4ada2..1e6c800aef 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -99,7 +99,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl /// Try to initiate a trade with a trading partner provided by the broker /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @return trade The trade contract created - /// @custom:interaction (only reads or writes `trades`, and is marked `nonReentrant`) + /// @custom:interaction Assumption: Caller is nonReentrant // checks: // (not external, so we don't need auth or pause checks) // trades[req.sell] == 0 @@ -114,12 +114,12 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl IERC20 sell = req.sell.erc20(); assert(address(trades[sell]) == address(0)); - trades[sell] = trade; - tradesOpen++; - IERC20Upgradeable(address(sell)).safeApprove(address(broker), 0); IERC20Upgradeable(address(sell)).safeApprove(address(broker), req.sellAmount); + trade = broker.openTrade(kind, req); + trades[sell] = trade; + tradesOpen++; emit TradeStarted(trade, sell, req.buy.erc20(), req.sellAmount, req.minBuyAmount); } From 659d3c8c7d1a5716f0d597ba15ea65abe0952c32 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 20 May 2023 11:03:10 -0400 Subject: [PATCH 292/462] test cacheComponents() doesn't break anything --- contracts/interfaces/IBackingManager.sol | 2 ++ contracts/interfaces/IRevenueTrader.sol | 2 ++ contracts/p1/RToken.sol | 2 +- test/Recollateralization.test.ts | 1 + test/Revenues.test.ts | 2 ++ 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/contracts/interfaces/IBackingManager.sol b/contracts/interfaces/IBackingManager.sol index e1161079d6..f9b91c6f69 100644 --- a/contracts/interfaces/IBackingManager.sol +++ b/contracts/interfaces/IBackingManager.sol @@ -61,4 +61,6 @@ interface TestIBackingManager is IBackingManager, TestITrading { function setTradingDelay(uint48 val) external; function setBackingBuffer(uint192 val) external; + + function cacheComponents() external; } diff --git a/contracts/interfaces/IRevenueTrader.sol b/contracts/interfaces/IRevenueTrader.sol index 7f90f20b89..12a5f8f4f3 100644 --- a/contracts/interfaces/IRevenueTrader.sol +++ b/contracts/interfaces/IRevenueTrader.sol @@ -36,4 +36,6 @@ interface IRevenueTrader is IComponent, ITrading { // solhint-disable-next-line no-empty-blocks interface TestIRevenueTrader is IRevenueTrader, TestITrading { function tokenToBuy() external view returns (IERC20); + + function cacheComponents() external; } diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index d4aa0bcb71..e73e405c1a 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -391,7 +391,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { emit BasketsNeededChanged(basketsNeeded, basketsNeeded_); basketsNeeded = basketsNeeded_; - // Ensure exchange rate is valid + // == P0 exchangeRateIsValidAfter modifier == uint256 supply = totalSupply(); require(supply > 0, "0 supply"); diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 017ed1b754..50732ef6ee 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -201,6 +201,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await backupToken1.connect(owner).mint(addr2.address, initialBal) await backupToken2.connect(owner).mint(addr2.address, initialBal) + await backingManager.cacheComponents() } beforeEach(async () => { diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index eeaf433390..5ea240feae 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -189,6 +189,8 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Mint initial balances initialBal = bn('1000000e18') await mintCollaterals(owner, [addr1, addr2], initialBal, basket) + await rTokenTrader.cacheComponents() + await rsrTrader.cacheComponents() }) describe('Deployment', () => { From 38b86cd0d4a1203bb0dac5387a772136e3b6363a Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 20 May 2023 11:03:53 -0400 Subject: [PATCH 293/462] make auction algorithm far more general; switch to 40%/60% 30m --- contracts/plugins/trading/DutchTrade.sol | 50 +++++++++++-------- docs/system-design.md | 6 ++- .../deployment/phase3-rtoken/rTokenConfig.ts | 6 +-- test/Recollateralization.test.ts | 7 ++- test/Revenues.test.ts | 6 +-- test/fixtures.ts | 2 +- test/integration/fixtures.ts | 2 +- .../aave/ATokenFiatCollateral.test.ts | 2 +- .../compoundv2/CTokenFiatCollateral.test.ts | 2 +- test/utils/trades.ts | 12 ++--- 10 files changed, 52 insertions(+), 43 deletions(-) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 4b1949457d..630d026922 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -7,28 +7,34 @@ import "../../libraries/Fixed.sol"; import "../../interfaces/IAsset.sol"; import "../../interfaces/ITrade.sol"; -uint192 constant ONE_THIRD = FIX_ONE / 3; // {1} 1/3 -uint192 constant TWO_THIRDS = ONE_THIRD * 2; // {1} 2/3 +uint192 constant FORTY_PERCENT = 4e17; // {1} 0.4 +uint192 constant SIXTY_PERCENT = 6e17; // {1} 0.6 -uint192 constant MAX_EXP = 31 * FIX_ONE; // {1} (5/4)^31 = 1009 -// by using 4/5 as the base of the price exponential, the avg loss due to precision is exactly 10% -uint192 constant BASE = 8e17; // {1} (4/5) +// A geometric price decay with base (999999/1000000). Price starts at 1000x and decays to 1x +// (1000000/999999)^6907752 = ~1000x +// A 30-minute auction on a chain with a 12-second blocktime has a ~10.87% price drop per block +// during the geometric period and a 0.05% drop during the linear period of the auction. +// This is the recommended length of auction for a chain with 12 +uint192 constant MAX_EXP = 6907752 * FIX_ONE; // {1} +uint192 constant BASE = 999999e12; // {1} (999999/1000000) /** * @title DutchTrade * @notice Implements a wholesale dutch auction via a piecewise falling-price mechansim. - * Over the first third of the auction the price falls from ~1000x the best plausible price - * down to the best expected price in a geometric series. The price decreases by 20% each time. - * This period DOES NOT expect to receive a bid; it defends against manipulated prices. + * Over the first 40% of the auction the price falls from ~1000x the best plausible price + * down to the best plausible price in a geometric series. The price decreases by the same % + * each time. At 30 minutes the decreases are 10.87% per block. Longer auctions have + * smaller price decreases, and shorter auctions have larger price decreases. + * This period DOES NOT expect to receive a bid; it just defends against manipulated prices. * - * Over the last 2/3 of the auction the price falls from the best expected price to the worst + * Over the last 60% of the auction the price falls from the best plausible price to the worst * price, linearly. The worst price is further discounted by the maxTradeSlippage as a fraction * of how far from minTradeVolume to maxTradeVolume the trade lies. * At maxTradeVolume, no further discount is applied. * * To bid: * - Call `bidAmount()` view to check prices at various timestamps - * - Wait until desirable a block is reached + * - Wait until desirable a block is reached (hopefully not in the first 40% of the auction) * - Provide approval of buy tokens and call bid(). The swap will be atomic */ contract DutchTrade is ITrade { @@ -50,7 +56,7 @@ contract DutchTrade is ITrade { uint48 public startTime; // {s} when the dutch auction begins (1 block after init()) uint48 public endTime; // {s} when the dutch auction ends if no bids are received - // highPrice is always 8192x the middlePrice, so we don't need to track it explicitly + // highPrice is always 1000x the middlePrice, so we don't need to track it explicitly uint192 public middlePrice; // {buyTok/sellTok} The price at which the function is piecewise uint192 public lowPrice; // {buyTok/sellTok} The price the auction ends at @@ -219,28 +225,30 @@ contract DutchTrade is ITrade { ); } - /// Return the price of the auction based on a particular % progression + /// Return the price of the auction at a particular timestamp /// @param timestamp {s} The block timestamp /// @return {buyTok/sellTok} function _price(uint48 timestamp) private view returns (uint192) { /// Price Curve: - /// - first 1/3%: exponentially 4/5ths the price from 1009x the middlePrice to 1x - /// - last 2/3: decrease linearly from middlePrice to lowPrice + /// - first 40%: geometrically decrease the price from 1000x the middlePrice to 1x + /// - last 60: decrease linearly from middlePrice to lowPrice uint192 progression = divuu(timestamp - startTime, endTime - startTime); // {1} - // Fast geometric decay -- 0%-33% of auction - if (progression < ONE_THIRD) { - uint192 exp = MAX_EXP.mulDiv(ONE_THIRD - progression, ONE_THIRD, ROUND); + // Fast geometric decay -- 0%-40% of auction + if (progression < FORTY_PERCENT) { + uint192 exp = MAX_EXP.mulDiv(FORTY_PERCENT - progression, FORTY_PERCENT, ROUND); - // middlePrice * ((5/4) ^ exp) = middlePrice / ((4/5) ^ exp) - // safe uint48 downcast: exp is at-most 31 + // middlePrice * ((1000000/999999) ^ exp) = middlePrice / ((999999/1000000) ^ exp) + // safe uint48 downcast: exp is at-most 69075 // {buyTok/sellTok} = {buyTok/sellTok} / {1} ^ {1} return middlePrice.div(BASE.powu(uint48(exp.toUint(ROUND))), CEIL); // this reverts for middlePrice >= 6.21654046e36 * FIX_ONE } - // Slow linear decay -- 33%-100% of auction - return middlePrice - (middlePrice - lowPrice).mulDiv(progression - ONE_THIRD, TWO_THIRDS); + // Slow linear decay -- 40%-100% of auction + return + middlePrice - + (middlePrice - lowPrice).mulDiv(progression - FORTY_PERCENT, SIXTY_PERCENT); } } diff --git a/docs/system-design.md b/docs/system-design.md index a93602a000..3f0c7543ed 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -230,13 +230,15 @@ Mainnet reasonable range: 60 to 3600 Dimension: `{seconds}` -The dutch auction length is how many seconds long falling-price dutch auctions should be. A longer period will result in less slippage due to better price granularity. +The dutch auction length is how many seconds long falling-price dutch auctions should be. A longer period will result in less slippage due to better price granularity, and a shorter period will result in more slippage. In general, the dutchAuctionLength should be a multiple of the blocktime. This is not enforced at a smart-contract level. -Default value: `1116` = 18.6 minutes - this value has been specifically chosen as a function of the dutch auction price curve. Increasing this value slightly will not have much benefit, and decreasing it at all results in meaningfully worse precision. Changes to this value only make sense if the change is substantial. It should be kept a multiple of the blocktime. +Default value: `1800` = 30 minutes Mainnet reasonable range: 300 to 3600 +At 30 minutes, a 12-second blocktime chain would have 10.87% price drops during the first 40% of the auction, and 0.055% price drops during the second 60%. + ### `backingBuffer` Dimension: `{1}` diff --git a/scripts/deployment/phase3-rtoken/rTokenConfig.ts b/scripts/deployment/phase3-rtoken/rTokenConfig.ts index e426ef7f5a..dfc14ca28a 100644 --- a/scripts/deployment/phase3-rtoken/rTokenConfig.ts +++ b/scripts/deployment/phase3-rtoken/rTokenConfig.ts @@ -23,7 +23,7 @@ export const rTokenConfig: { [key: string]: IRToken } = { warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('14400'), // (the delay _after_ default has been confirmed) 4 hours batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('1116'), // 18.6 minutes + dutchAuctionLength: bn('1800'), // 30 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days @@ -62,7 +62,7 @@ export const rTokenConfig: { [key: string]: IRToken } = { warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('14400'), // (the delay _after_ default has been confirmed) 4 hours batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('1116'), // 18.6 minutes + dutchAuctionLength: bn('1800'), // 30 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days @@ -100,7 +100,7 @@ export const rTokenConfig: { [key: string]: IRToken } = { warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('1116'), // 18.6 minutes + dutchAuctionLength: bn('1800'), // 30 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% shortFreeze: bn('259200'), // 3 days diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 8bbac393ea..752b86321f 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -3188,7 +3188,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) context('DutchTrade', () => { - const auctionLength = 1116 // 18.6 minutes + const auctionLength = 1800 // 30 minutes beforeEach(async () => { await broker.connect(owner).setDutchAuctionLength(auctionLength) @@ -3239,7 +3239,6 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) it('Should quote piecewise-falling price correctly throughout entirety of auction', async () => { - // issueAmount = issueAmount.div(10000) await backingManager.rebalance(TradeKind.DUTCH_AUCTION) const trade = await ethers.getContractAt( 'DutchTrade', @@ -3251,7 +3250,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { const end = await trade.endTime() await advanceToTimestamp(start) - // Simulate 20 minutes of blocks, should swap at right price each time + // Simulate 30 minutes of blocks, should swap at right price each time for (let now = await getLatestBlockTimestamp(); now <= end; now += 12) { const actual = await trade.connect(addr1).bidAmount(now) const expected = divCeil( @@ -3265,7 +3264,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ), bn('1e12') // fix for decimals ) - expect(actual).to.equal(expected) + expect(actual).to.be.closeTo(expected, expected.div(bn('1e15'))) const staticResult = await trade.connect(addr1).callStatic.bid() expect(staticResult).to.equal(expected) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 8a6063101f..a7c43823d0 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2210,7 +2210,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) context('DutchTrade', () => { - const auctionLength = 1116 // 18.6 minutes + const auctionLength = 1800 // 30 minutes beforeEach(async () => { await broker.connect(owner).setDutchAuctionLength(auctionLength) }) @@ -2264,7 +2264,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const end = await trade.endTime() await advanceToTimestamp(start) - // Simulate 18.6 minutes of blocks, should swap at right price each time + // Simulate 30 minutes of blocks, should swap at right price each time for (let now = await getLatestBlockTimestamp(); now <= end; now += 12) { const actual = await trade.connect(addr1).bidAmount(now) const expected = await dutchBuyAmount( @@ -2275,7 +2275,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { config.minTradeVolume, config.maxTradeSlippage ) - expect(actual).to.equal(expected) + expect(actual).to.be.closeTo(expected, expected.div(bn('1e15'))) const staticResult = await trade.connect(addr1).callStatic.bid() expect(staticResult).to.equal(actual) diff --git a/test/fixtures.ts b/test/fixtures.ts index 620c1ae5a5..fdecb5940d 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -442,7 +442,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('1116'), // 18.6 minutes + dutchAuctionLength: bn('1800'), // 30 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index 5e3f257aa8..c11414f9a6 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -650,7 +650,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('1116'), // 18.6 minutes + dutchAuctionLength: bn('1800'), // 30 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index e1b1325ad9..5b3eeea84e 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -120,7 +120,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi withdrawalLeak: fp('0'), // 0%; always refresh tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('1116'), // 18.6 minutes + dutchAuctionLength: bn('1800'), // 30 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index d1ec8e4eca..c40542d338 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -121,7 +121,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) batchAuctionLength: bn('900'), // 15 minutes - dutchAuctionLength: bn('1116'), // 18.6 minutes + dutchAuctionLength: bn('1800'), // 30 minutes backingBuffer: fp('0.0001'), // 0.01% maxTradeSlippage: fp('0.01'), // 1% issuanceThrottle: { diff --git a/test/utils/trades.ts b/test/utils/trades.ts index 8d51902350..7302e0489f 100644 --- a/test/utils/trades.ts +++ b/test/utils/trades.ts @@ -107,17 +107,17 @@ export const dutchBuyAmount = async ( const lowPrice = sellLow.mul(fp('1').sub(slippage)).div(buyHigh) const middlePrice = divCeil(sellHigh.mul(fp('1')), buyLow) - const ONE_THIRD = fp('1').div(3) // 0.33333 - const TWO_THIRDS = ONE_THIRD.mul(2) // 0.66666 + const FORTY_PERCENT = fp('0.4') // 40% + const SIXTY_PERCENT = fp('0.6') // 60% let price: BigNumber - if (progression.lt(ONE_THIRD)) { - const exp = divRound(bn('31').mul(ONE_THIRD.sub(progression)), ONE_THIRD) - const divisor = new Decimal('4').div(5).pow(exp.toString()) + if (progression.lt(FORTY_PERCENT)) { + const exp = divRound(bn('6907752').mul(FORTY_PERCENT.sub(progression)), FORTY_PERCENT) + const divisor = new Decimal('999999').div('1000000').pow(exp.toString()) price = divCeil(middlePrice.mul(fp('1')), fp(divisor.toString())) } else { price = middlePrice.sub( - middlePrice.sub(lowPrice).mul(progression.sub(ONE_THIRD)).div(TWO_THIRDS) + middlePrice.sub(lowPrice).mul(progression.sub(FORTY_PERCENT)).div(SIXTY_PERCENT) ) } return divCeil(outAmount.mul(price), fp('1')) From 215bedd010d45b2ec7d093e8e926dcbf892a1206 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 20 May 2023 12:02:55 -0400 Subject: [PATCH 294/462] cache components P1 only --- contracts/interfaces/IBackingManager.sol | 2 -- test/Recollateralization.test.ts | 6 +++++- test/Revenues.test.ts | 7 +++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/contracts/interfaces/IBackingManager.sol b/contracts/interfaces/IBackingManager.sol index f9b91c6f69..e1161079d6 100644 --- a/contracts/interfaces/IBackingManager.sol +++ b/contracts/interfaces/IBackingManager.sol @@ -61,6 +61,4 @@ interface TestIBackingManager is IBackingManager, TestITrading { function setTradingDelay(uint48 val) external; function setBackingBuffer(uint192 val) external; - - function cacheComponents() external; } diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 50732ef6ee..feefb189a8 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -17,6 +17,7 @@ import { bn, fp, pow10, toBNDecimals, divCeil } from '../common/numbers' import { Asset, ATokenFiatCollateral, + BackingManagerP1, CTokenMock, DutchTrade, CTokenVaultMock, @@ -201,7 +202,10 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await backupToken1.connect(owner).mint(addr2.address, initialBal) await backupToken2.connect(owner).mint(addr2.address, initialBal) - await backingManager.cacheComponents() + + if (IMPLEMENTATION === Implementation.P1) { + await (backingManager).cacheComponents() + } } beforeEach(async () => { diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 5ea240feae..b0d45d04f1 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -27,6 +27,7 @@ import { IAssetRegistry, InvalidATokenFiatCollateralMock, MockV3Aggregator, + RevenueTraderP1, RTokenAsset, StaticATokenMock, TestIBackingManager, @@ -189,8 +190,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Mint initial balances initialBal = bn('1000000e18') await mintCollaterals(owner, [addr1, addr2], initialBal, basket) - await rTokenTrader.cacheComponents() - await rsrTrader.cacheComponents() + if (IMPLEMENTATION === Implementation.P1) { + await (rTokenTrader).cacheComponents() + await (rsrTrader).cacheComponents() + } }) describe('Deployment', () => { From 9be87097158123edf0510ceed60da728aed474d0 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 20 May 2023 12:09:24 -0400 Subject: [PATCH 295/462] snipe in last possible second in tests --- test/Recollateralization.test.ts | 2 +- test/Revenues.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 752b86321f..6127e19ae7 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -3317,7 +3317,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { const expected = divCeil( await dutchBuyAmount( - fp('300').div(300), // after all txs so far, at 300/300s + fp(auctionLength).div(auctionLength), // last possible second collateral0.address, collateral1.address, issueAmount, diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index a7c43823d0..7d379889d0 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2326,7 +2326,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await token0.balanceOf(addr1.address)).to.equal(initialBal.sub(issueAmount.div(4))) const expected = await dutchBuyAmount( - fp('300').div(300), + fp(auctionLength).div(auctionLength), // last possible second rTokenAsset.address, collateral0.address, issueAmount, From 2bb2e132c91abe5d1fbc206d05380794367f9ae3 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 20 May 2023 12:09:28 -0400 Subject: [PATCH 296/462] updated gas snapshots --- test/__snapshots__/Recollateralization.test.ts.snap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 3d2defa04a..740399a874 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1339914`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1340369`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1471809`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1472971`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `636277`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `646632`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1695736`; From 352ea80f31fcb6f83f594a554d70e62774443a8d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 20 May 2023 12:32:32 -0400 Subject: [PATCH 297/462] cacheComponents the right way --- contracts/interfaces/IRevenueTrader.sol | 2 -- test/Recollateralization.test.ts | 5 +++-- test/Revenues.test.ts | 5 ++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/contracts/interfaces/IRevenueTrader.sol b/contracts/interfaces/IRevenueTrader.sol index 12a5f8f4f3..7f90f20b89 100644 --- a/contracts/interfaces/IRevenueTrader.sol +++ b/contracts/interfaces/IRevenueTrader.sol @@ -36,6 +36,4 @@ interface IRevenueTrader is IComponent, ITrading { // solhint-disable-next-line no-empty-blocks interface TestIRevenueTrader is IRevenueTrader, TestITrading { function tokenToBuy() external view returns (IERC20); - - function cacheComponents() external; } diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index feefb189a8..f9ac3d9ef8 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -17,7 +17,6 @@ import { bn, fp, pow10, toBNDecimals, divCeil } from '../common/numbers' import { Asset, ATokenFiatCollateral, - BackingManagerP1, CTokenMock, DutchTrade, CTokenVaultMock, @@ -204,7 +203,9 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await backupToken2.connect(owner).mint(addr2.address, initialBal) if (IMPLEMENTATION === Implementation.P1) { - await (backingManager).cacheComponents() + await ( + await ethers.getContractAt('BackingManagerP1', backingManager.address) + ).cacheComponents() } } diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index b0d45d04f1..db643404e7 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -27,7 +27,6 @@ import { IAssetRegistry, InvalidATokenFiatCollateralMock, MockV3Aggregator, - RevenueTraderP1, RTokenAsset, StaticATokenMock, TestIBackingManager, @@ -191,8 +190,8 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { initialBal = bn('1000000e18') await mintCollaterals(owner, [addr1, addr2], initialBal, basket) if (IMPLEMENTATION === Implementation.P1) { - await (rTokenTrader).cacheComponents() - await (rsrTrader).cacheComponents() + await (await ethers.getContractAt('RevenueTraderP1', rTokenTrader.address)).cacheComponents() + await (await ethers.getContractAt('RevenueTraderP1', rsrTrader.address)).cacheComponents() } }) From b096416bf088aefc769b11cee71a0e8682146ed1 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 20 May 2023 14:24:17 -0400 Subject: [PATCH 298/462] updated gas snapshots --- test/__snapshots__/Broker.test.ts.snap | 10 +++++----- test/__snapshots__/Recollateralization.test.ts.snap | 8 +++++--- test/__snapshots__/ZZStRSR.test.ts.snap | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/test/__snapshots__/Broker.test.ts.snap b/test/__snapshots__/Broker.test.ts.snap index 0d19437393..8189fb7987 100644 --- a/test/__snapshots__/Broker.test.ts.snap +++ b/test/__snapshots__/Broker.test.ts.snap @@ -1,14 +1,14 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `233035`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `233492`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `337662`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `338119`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `339776`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `340233`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `341914`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `342371`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63429`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63421`; exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Initialize Trade 1`] = `451427`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index dd746f6f0d..e71935ad25 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,14 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1334404`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1336127`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1459732`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1468729`; + +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `649733`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1691559`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1635843`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1635487`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184816`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index 0fc4f146ac..d3c38d5241 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -2,7 +2,7 @@ exports[`StRSRP1 contract Gas Reporting Stake 1`] = `152134`; -exports[`StRSRP1 contract Gas Reporting Stake 2`] = `164488`; +exports[`StRSRP1 contract Gas Reporting Stake 2`] = `147334`; exports[`StRSRP1 contract Gas Reporting Transfer 1`] = `63389`; From 12812c31662f48c0935859788e830fb9d7f0e65f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 20 May 2023 18:54:42 -0400 Subject: [PATCH 299/462] nit over DutchTrade comments --- contracts/plugins/trading/DutchTrade.sol | 25 ++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 630d026922..3260d05c38 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -10,12 +10,13 @@ import "../../interfaces/ITrade.sol"; uint192 constant FORTY_PERCENT = 4e17; // {1} 0.4 uint192 constant SIXTY_PERCENT = 6e17; // {1} 0.6 -// A geometric price decay with base (999999/1000000). Price starts at 1000x and decays to 1x -// (1000000/999999)^6907752 = ~1000x +// Exponential price decay with base (999999/1000000). Price starts at 1000x and decays to 1x // A 30-minute auction on a chain with a 12-second blocktime has a ~10.87% price drop per block -// during the geometric period and a 0.05% drop during the linear period of the auction. -// This is the recommended length of auction for a chain with 12 -uint192 constant MAX_EXP = 6907752 * FIX_ONE; // {1} +// during the geometric/exponential period and a 0.05% drop during the linear period. +// 30-minutes is the recommended length of auction for a chain with 12-second blocktimes, but +// longer and shorter times can be used as well. The pricing algorithm does not degrade +// beyond the degree to which less overall blocktime means necessarily larger price drops. +uint192 constant MAX_EXP = 6907752 * FIX_ONE; // {1} (1000000/999999)^6907752 = ~1000x uint192 constant BASE = 999999e12; // {1} (999999/1000000) /** @@ -34,7 +35,7 @@ uint192 constant BASE = 999999e12; // {1} (999999/1000000) * * To bid: * - Call `bidAmount()` view to check prices at various timestamps - * - Wait until desirable a block is reached (hopefully not in the first 40% of the auction) + * - Wait until a desirable block is reached (hopefully not in the first 40% of the auction) * - Provide approval of buy tokens and call bid(). The swap will be atomic */ contract DutchTrade is ITrade { @@ -45,7 +46,7 @@ contract DutchTrade is ITrade { TradeStatus public status; // reentrancy protection - ITrading public origin; // initializer + ITrading public origin; // the address that initialized the contract // === Auction === IERC20Metadata public sell; @@ -53,7 +54,7 @@ contract DutchTrade is ITrade { uint192 public sellAmount; // {sellTok} // The auction runs from [startTime, endTime], inclusive - uint48 public startTime; // {s} when the dutch auction begins (1 block after init()) + uint48 public startTime; // {s} when the dutch auction begins (12s after init()) uint48 public endTime; // {s} when the dutch auction ends if no bids are received // highPrice is always 1000x the middlePrice, so we don't need to track it explicitly @@ -85,7 +86,7 @@ contract DutchTrade is ITrade { // {buyTok/sellTok} uint192 price = _price(timestamp); - // {qBuyTok} = {sellTok} * {buyTok/sellTok} + // {qBuyTok} = {sellTok} * {buyTok/sellTok} * {qBuyTok/buyTok} return sellAmount.mul(price, CEIL).shiftl_toUint(int8(buy.decimals()), CEIL); } @@ -131,10 +132,10 @@ contract DutchTrade is ITrade { fixMin(sell_.maxTradeVolume(), buy_.maxTradeVolume()) // maxTradeVolume ); - // {buyTok/sellTok} = {1} * {UoA/sellTok} / {UoA/buyTok} + // {buyTok/sellTok} = {UoA/sellTok} * {1} / {UoA/buyTok} lowPrice = sellLow.mulDiv(FIX_ONE - slippage, buyHigh, FLOOR); middlePrice = sellHigh.div(buyLow, CEIL); // no additional slippage - // highPrice = 1.5 * middlePrice + // highPrice = 1000 * middlePrice assert(lowPrice <= middlePrice); } @@ -240,7 +241,7 @@ contract DutchTrade is ITrade { uint192 exp = MAX_EXP.mulDiv(FORTY_PERCENT - progression, FORTY_PERCENT, ROUND); // middlePrice * ((1000000/999999) ^ exp) = middlePrice / ((999999/1000000) ^ exp) - // safe uint48 downcast: exp is at-most 69075 + // safe uint48 downcast: exp is at-most 6907752 // {buyTok/sellTok} = {buyTok/sellTok} / {1} ^ {1} return middlePrice.div(BASE.powu(uint48(exp.toUint(ROUND))), CEIL); // this reverts for middlePrice >= 6.21654046e36 * FIX_ONE From f4beb6011efbdb323ebbc0a5fcbe3a448ef84634 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 20 May 2023 19:09:18 -0400 Subject: [PATCH 300/462] a few more comments --- contracts/p1/BackingManager.sol | 6 +++--- contracts/p1/RToken.sol | 2 +- contracts/p1/RevenueTrader.sol | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 5451b51bdf..3d03294a98 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -72,7 +72,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { IERC20(address(erc20)).safeApprove(address(main.rToken()), type(uint256).max); } - /// Settle a single trade. If DUTCH_AUCTION, try rebalance() + /// Settle a single trade. If the caller is the trade, try chaining into rebalance() /// While this function is not nonReentrant, its two subsets each individually are /// @param sell The sell token in the trade /// @return trade The ITrade contract settled @@ -98,7 +98,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// Apply the overall backing policy using the specified TradeKind, taking a haircut if unable /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION - /// @custom:interaction not RCEI, nonReentrant + /// @custom:interaction not RCEI; nonReentrant // untested: // OZ nonReentrant line is assumed to be working. cost/benefit of direct testing is high function rebalance(TradeKind kind) external nonReentrant notTradingPausedOrFrozen { @@ -162,7 +162,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// Forward revenue to RevenueTraders; reverts if not fully collateralized /// @param erc20s The tokens to forward - /// @custom:interaction not RCEI, nonReentrant + /// @custom:interaction not RCEI; nonReentrant // untested: // OZ nonReentrant line is assumed to be working. cost/benefit of direct testing is high function forwardRevenue(IERC20[] calldata erc20s) diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index e73e405c1a..507398cf58 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -181,7 +181,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { assetRegistry.refresh(); // solhint-disable-next-line no-empty-blocks - try main.furnace().melt() {} catch {} + try main.furnace().melt() {} catch {} // nice for the redeemer, but not necessary // == Checks and Effects == diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 9ee3c5b6a2..849b06ebd8 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -64,7 +64,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { /// If erc20 is tokenToBuy, distribute it; else, sell it for tokenToBuy /// @dev Intended to be used with multicall /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION - /// @custom:interaction RCEI + /// @custom:interaction RCEI and nonReentrant // let bal = this contract's balance of erc20 // checks: !paused (trading), !frozen // does nothing if erc20 == addr(0) or bal == 0 From 3d6f0971752394931817e7cd38b8789f898aa20d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 20 May 2023 21:47:04 -0400 Subject: [PATCH 301/462] Facades: terser way to try-catch 3.0.0/2.1.0 implementations --- contracts/facade/FacadeAct.sol | 25 ++++++++----------------- contracts/facade/FacadeRead.sol | 26 +++++++------------------- 2 files changed, 15 insertions(+), 36 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index c6211528e6..d54fc7eb8f 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -43,16 +43,12 @@ contract FacadeAct is IFacadeAct, Multicall { // Transfer revenue backingManager -> revenueTrader { - address bm = address(revenueTrader.main().backingManager()); + IBackingManager bm = revenueTrader.main().backingManager(); - // 3.0.0 BackingManager interface - (bool success, ) = bm.call{ value: 0 }( - abi.encodeWithSignature("forwardRevenue(address[])", toStart) - ); - - // Fallback to <=2.1.0 interface - if (!success) { - (success, ) = bm.call{ value: 0 }( + // 3.0.0 interface + try bm.forwardRevenue(toStart) {} catch { + // try 2.1.0 interface + (bool success, ) = address(bm).call{ value: 0 }( abi.encodeWithSignature("manageTokens(address[])", toStart) ); require(success, "failed to forward revenue"); @@ -60,16 +56,11 @@ contract FacadeAct is IFacadeAct, Multicall { } // Start auctions - address rt = address(revenueTrader); for (uint256 i = 0; i < toStart.length; ++i) { // 3.0.0 RevenueTrader interface - (bool success, ) = rt.call{ value: 0 }( - abi.encodeWithSignature("manageToken(address,uint8)", toStart[i], kind) - ); - - // Fallback to <=2.1.0 interface - if (!success) { - (success, ) = rt.call{ value: 0 }( + try revenueTrader.manageToken(toStart[i], kind) {} catch { + // Fallback to <=2.1.0 interface + (bool success, ) = address(revenueTrader).call{ value: 0 }( abi.encodeWithSignature("manageToken(address)", toStart[i]) ); require(success, "failed to start revenue auction"); diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index c7537ab17a..85ae35c963 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -282,16 +282,12 @@ contract FacadeRead is IFacadeRead { // Forward ALL revenue { - address bm = address(revenueTrader.main().backingManager()); + IBackingManager bm = revenueTrader.main().backingManager(); // First try 3.0.0 interface - (bool success, ) = bm.call{ value: 0 }( - abi.encodeWithSignature("forwardRevenue(address[])", reg.erc20s) - ); - - // Fallback to <=2.1.0 interface - if (!success) { - (success, ) = bm.call{ value: 0 }( + try bm.forwardRevenue(reg.erc20s) {} catch { + // try 2.1.0 interface + (bool success, ) = address(bm).call{ value: 0 }( abi.encodeWithSignature("manageTokens(address[])", reg.erc20s) ); require(success, "failed to forward revenue"); @@ -323,17 +319,9 @@ contract FacadeRead is IFacadeRead { if (reg.erc20s[i].balanceOf(address(revenueTrader)) > minTradeAmounts[i]) { // 3.0.0 RevenueTrader interface - (bool success, ) = address(revenueTrader).call{ value: 0 }( - abi.encodeWithSignature( - "manageToken(address,uint8)", - erc20s[i], - TradeKind.DUTCH_AUCTION - ) - ); - - // Fallback to <=2.1.0 interface - if (!success) { - (success, ) = address(revenueTrader).call{ value: 0 }( + try revenueTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch { + // try 2.1.0 interface + (bool success, ) = address(revenueTrader).call{ value: 0 }( abi.encodeWithSignature("manageToken(address)", erc20s[i]) ); require(success, "failed to start revenue auction"); From 4e7697a097f96e1bfa64e8b12f48f1b8f2d290a6 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 20 May 2023 21:53:43 -0400 Subject: [PATCH 302/462] FacadeRead: remove basketNonce from redeem function to match RToken.redeem --- contracts/facade/FacadeAct.sol | 2 ++ contracts/facade/FacadeRead.sol | 9 +++------ contracts/interfaces/IFacadeRead.sol | 6 +----- test/FacadeRead.test.ts | 12 ++---------- test/RToken.test.ts | 3 --- 5 files changed, 8 insertions(+), 24 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index d54fc7eb8f..4fde0dea10 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -46,6 +46,7 @@ contract FacadeAct is IFacadeAct, Multicall { IBackingManager bm = revenueTrader.main().backingManager(); // 3.0.0 interface + // solhint-disable-next-line no-empty-blocks try bm.forwardRevenue(toStart) {} catch { // try 2.1.0 interface (bool success, ) = address(bm).call{ value: 0 }( @@ -58,6 +59,7 @@ contract FacadeAct is IFacadeAct, Multicall { // Start auctions for (uint256 i = 0; i < toStart.length; ++i) { // 3.0.0 RevenueTrader interface + // solhint-disable-next-line no-empty-blocks try revenueTrader.manageToken(toStart[i], kind) {} catch { // Fallback to <=2.1.0 interface (bool success, ) = address(revenueTrader).call{ value: 0 }( diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 85ae35c963..946b5ceb2b 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -86,11 +86,7 @@ contract FacadeRead is IFacadeRead { /// @return withdrawals The balances necessary to issue `amount` RToken /// @return isProrata True if the redemption is prorata and not full /// @custom:static-call - function redeem( - IRToken rToken, - uint256 amount, - uint48 basketNonce - ) + function redeem(IRToken rToken, uint256 amount) external returns ( address[] memory tokens, @@ -103,7 +99,6 @@ contract FacadeRead is IFacadeRead { IRToken rTok = rToken; IBasketHandler bh = main.basketHandler(); uint256 supply = rTok.totalSupply(); - require(bh.nonce() == basketNonce, "non-current basket nonce"); // D18{BU} = D18{BU} * {qRTok} / {qRTok} uint192 basketsRedeemed = rTok.basketsNeeded().muluDivu(amount, supply); @@ -285,6 +280,7 @@ contract FacadeRead is IFacadeRead { IBackingManager bm = revenueTrader.main().backingManager(); // First try 3.0.0 interface + // solhint-disable-next-line no-empty-blocks try bm.forwardRevenue(reg.erc20s) {} catch { // try 2.1.0 interface (bool success, ) = address(bm).call{ value: 0 }( @@ -319,6 +315,7 @@ contract FacadeRead is IFacadeRead { if (reg.erc20s[i].balanceOf(address(revenueTrader)) > minTradeAmounts[i]) { // 3.0.0 RevenueTrader interface + // solhint-disable-next-line no-empty-blocks try revenueTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch { // try 2.1.0 interface (bool success, ) = address(revenueTrader).call{ value: 0 }( diff --git a/contracts/interfaces/IFacadeRead.sol b/contracts/interfaces/IFacadeRead.sol index 5150d57e77..4b05a6fa39 100644 --- a/contracts/interfaces/IFacadeRead.sol +++ b/contracts/interfaces/IFacadeRead.sol @@ -35,11 +35,7 @@ interface IFacadeRead { /// @return withdrawals The balances necessary to issue `amount` RToken /// @return isProrata True if the redemption is prorata and not full /// @custom:static-call - function redeem( - IRToken rToken, - uint256 amount, - uint48 basketNonce - ) + function redeem(IRToken rToken, uint256 amount) external returns ( address[] memory tokens, diff --git a/test/FacadeRead.test.ts b/test/FacadeRead.test.ts index f73678279a..953cac74dd 100644 --- a/test/FacadeRead.test.ts +++ b/test/FacadeRead.test.ts @@ -195,11 +195,9 @@ describe('FacadeRead contract', () => { }) it('Should return redeemable quantities correctly', async () => { - const nonce = await basketHandler.nonce() const [toks, quantities, isProrata] = await facade.callStatic.redeem( rToken.address, - issueAmount, - nonce + issueAmount ) expect(toks.length).to.equal(4) expect(toks[0]).to.equal(token.address) @@ -216,17 +214,11 @@ describe('FacadeRead contract', () => { await token.burn(await main.backingManager(), issueAmount.div(8)) const [newToks, newQuantities, newIsProrata] = await facade.callStatic.redeem( rToken.address, - issueAmount, - nonce + issueAmount ) expect(newToks[0]).to.equal(token.address) expect(newQuantities[0]).to.equal(issueAmount.div(8)) expect(newIsProrata).to.equal(true) - - // Wrong nonce - await expect( - facade.callStatic.redeem(rToken.address, issueAmount, nonce - 1) - ).to.be.revertedWith('non-current basket nonce') }) it('Should return backingOverview correctly', async () => { diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 43e5dfef55..d5f08270c5 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -28,7 +28,6 @@ import { StaticATokenMock, TestIBackingManager, TestIBasketHandler, - TestIFurnace, TestIMain, TestIRToken, USDCMock, @@ -97,7 +96,6 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager let basketHandler: TestIBasketHandler - let furnace: TestIFurnace beforeEach(async () => { ;[owner, addr1, addr2, other] = await ethers.getSigners() @@ -113,7 +111,6 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { main, rToken, rTokenAsset, - furnace, } = await loadFixture(defaultFixture)) // Get assets and tokens From 40de8d1867d1d077c85d6bf7f981d227f503fc3c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 20 May 2023 21:59:33 -0400 Subject: [PATCH 303/462] FacadeRead.nextRecollateralizationAuction: make backwards compatible with 2.1.0 --- contracts/facade/FacadeRead.sol | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 946b5ceb2b..fa42b3946c 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -239,19 +239,26 @@ contract FacadeRead is IFacadeRead { // If no auctions ongoing, try to find a new auction to start if (bm.tradesOpen() == 0) { // Try to launch auctions - try bm.rebalance(TradeKind.DUTCH_AUCTION) { - // Find the started auction - for (uint256 i = 0; i < erc20s.length; ++i) { - DutchTrade trade = DutchTrade(address(bm.trades(erc20s[i]))); - if (address(trade) != address(0)) { - canStart = true; - sell = trade.sell(); - buy = trade.buy(); - sellAmount = trade.sellAmount(); - } + // solhint-disable-next-line no-empty-blocks + try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch { + // try 2.1.0 interface + IERC20[] memory emptyERC20s = new IERC20[](0); + (bool success, ) = address(bm).call{ value: 0 }( + abi.encodeWithSignature("manageTokens(address[])", emptyERC20s) + ); + require(success, "failed to launch rebalance"); + } + + // Find the started auction + for (uint256 i = 0; i < erc20s.length; ++i) { + DutchTrade trade = DutchTrade(address(bm.trades(erc20s[i]))); + if (address(trade) != address(0)) { + canStart = true; + sell = trade.sell(); + buy = trade.buy(); + sellAmount = trade.sellAmount(); } - // solhint-disable-next-line no-empty-blocks - } catch {} + } } } From 205befdd8c58928258aaa59db9a2e817a92ee0c5 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sat, 20 May 2023 22:02:53 -0400 Subject: [PATCH 304/462] fix compile --- contracts/facade/FacadeAct.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 4fde0dea10..3028d1264a 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/Multicall.sol"; +import "../interfaces/IBackingManager.sol"; import "../interfaces/IFacadeAct.sol"; /** From 2831935659112675f21ba5db153ce4cfd4a737a9 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sun, 21 May 2023 16:09:59 -0400 Subject: [PATCH 305/462] docs/system-design.md: document dutch auctions --- docs/system-design.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/system-design.md b/docs/system-design.md index 3f0c7543ed..527858df36 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -132,15 +132,29 @@ Design intentions: The Reserve Protocol makes a few different types of trades: -- from collateral to RSR or RToken, in order to distribute collateral yields. These happen often. -- from reward tokens to RSR or RToken, in order to distribute tokens rewards from collateral. These also happen often. -- collateral to collateral, in order to change the distribution of collateral due to a basket change. Basket changes should be rare, happening only when governance changes the basket, or when some collateral token defaults. -- RSR to collateral, in order to recollateralize the protocol from stRSR over-collateralization, after a basket change. These auctions should be even rarer, happening when there's a basket change and insufficient capital to achieve recollateralization without using the over-collateralization buffer. +- from collateral to RSR or RToken, in order to distribute collateral yields. These happen often in a RevenueTrader. +- from reward tokens to RSR or RToken, in order to distribute tokens rewards from collateral. These also happen often in a RevenueTrader. +- collateral to collateral, in order to change the distribution of collateral due to a basket change. Basket changes should be rare, happening only when governance changes the basket, or when some collateral token defaults. This only happens in the BackingManager. +- RSR to collateral, in order to recollateralize the protocol from stRSR over-collateralization, after a basket change. These auctions should be even rarer, happening when there's a basket change and insufficient capital to achieve recollateralization without using the over-collateralization buffer. These auctions also happen in the BackingManager. -Each type of trade can currently happen in only one way; the protocol launches a Gnosis EasyAuction. The Reserve Protocol is designed to make it easy to add other trading methods, but none others are currently supported. +Each type of trade can happen two ways: either by a falling-price ductch auction (DutchTrade) or by a batch auction via Gnosis EasyAuction (GnosisTrade). More trading methods can be added in the future. + +### Gnosis EasyAuction Batch Auctions (GnosisTrade) A good explainer for how Gnosis auctions work can be found (on their github)[https://github.com/gnosis/ido-contracts]. +### Dutch Auctions (DutchTrade) + +The Dutch auction occurs in two phases: + +Geometric/Exponential Phase (first 40% of auction): The price starts at about 1000x the best plausible price and decays down to the best plausible price following a geometric/exponential series. The price decreases by the same percentage each time. This phase is primarily defensive, and it's not expected to receive a bid; it merely protects against manipulated prices. + +Linear Phase (last 60% of auction): During this phase, the price decreases linearly from the best plausible price to the worst plausible price. The worst price is further discounted based on maxTradeSlippage, which considers how far from minTradeVolume to maxTradeVolume the trade lies. No further discount is applied at maxTradeVolume. + +The `dutchAuctionLength` can be configured to be any value. The suggested default is 30 minutes for a blockchain with a 12-second blocktime. At this ratio of blocktime to auction length, there is a 10.87% price drop per block during the geometric/exponential period and a 0.05% drop during the linear period. The duration of the auction can be adjusted, which will impact the size of the price decreases per block. + +The "best plausible price" is equal to the exchange rate at the high price of the sell token and the low price of the buy token. The "worst-case price" is equal to the exchange rate at the low price of the sell token and the high price of the sell token, plus an additional discount ranging from 0 to `maxTradeSlippage()`. At minimum auction size the full `maxTradeSlippage()` is applied, while at max auction size no further discount is applied. + ## Deployment Parameters ### `dist` (revenue split) From 58d6795d5f5a7ec87f232dbeba8715f6d3d440bc Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sun, 21 May 2023 16:21:22 -0400 Subject: [PATCH 306/462] docs/system-design.md: document all the places we make a blocktime assumption --- contracts/interfaces/IMain.sol | 3 ++- docs/system-design.md | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/contracts/interfaces/IMain.sol b/contracts/interfaces/IMain.sol index 9b08fd6c7d..fbdb421f18 100644 --- a/contracts/interfaces/IMain.sol +++ b/contracts/interfaces/IMain.sol @@ -16,7 +16,8 @@ import "./IStRSR.sol"; import "./ITrading.sol"; import "./IVersioned.sol"; -// Warning, assumption: Chain must have blocktimes >= 12s +// Warning, assumption: Chain should have blocktimes of 12s +// See docs/system-design.md for discussion of handling longer or shorter times uint48 constant ONE_BLOCK = 12; //{s} // === Auth roles === diff --git a/docs/system-design.md b/docs/system-design.md index 527858df36..9d9d6bef75 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -37,6 +37,22 @@ Some of the core contracts in our system regularly own ERC20 tokens. In each cas 2. At vesting time, the `RToken` contract mints new RToken to the recipient and transfers the held collateral to the `BackingManager`. If the `BasketHandler` has updated the basket since issuance began, then the collateral is instead returned to the recipient and no RToken is minted. 3. During redemption, RToken is burnt from the redeemer's account and they are transferred a prorata share of backing collateral from the `BackingManager`. +## Protocol Assumptions + +### Blocktime = 12s + +The protocol (weakly) assumes a 12-second blocktime. This section documents the places where this assumption is made and whether changes would be required if blocktime were different. + +#### Should-be-changed if blocktime different + +- The `Furnace` melts RToken in periods of 12 seconds. If the protocol is deployed to a chain with shorter blocktime, it is possible it may be rational to issue right before melting and redeem directly after, in order to selfishly benefit. The `Furnace` shouild be updated to melt more often. + +#### Probably fine if blocktime different + +- `DutchTrade` price curve can handle 1s blocktimes as-is, as well as longer blocktimes +- The `StRSR` contract hands out RSR rewards in periods of 12 seconds. Since the unstaking period is usually much larger than this, it is fine to deploy StRSR to another chain without changing anything, with shorter or longer blocktimes +- `BackingManager` spaces out same-kind auctions by 12s. No change is required is blocktime is less; some change required is blocktime is longer + ## Some Monetary Units Our system refers to units of financial value in a handful of different ways, and treats them as different dimensions. Some of these distinctions may seem like splitting hairs if you're just thinking about one or two example RTokens, but the differences are crucial to understanding how the protocol works in a wide variety of different settings. From fec188cad3254441bab266739cc5ee8615528617 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sun, 21 May 2023 16:21:40 -0400 Subject: [PATCH 307/462] CHANGELOG.md: partially complete --- CHANGELOG.md | 146 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 138 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f7269bc5c..6aa1494f13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,7 +106,7 @@ Candidate release for the "all clear" milestone. There wasn't any real usage of ## 2.1.0 -#### Core protocol contracts +### Core protocol contracts - `BasketHandler` - Bugfix for `getPrimeBasket()` view @@ -120,7 +120,7 @@ Candidate release for the "all clear" milestone. There wasn't any real usage of - `StRSR` - Expose RSR variables via `getDraftRSR()`, `getStakeRSR()`, and `getTotalDrafts()` views -#### Facades +### Facades - `FacadeRead` - Extend `issue()` to return the estimated USD value of deposits as `depositsUoA` @@ -131,14 +131,13 @@ Candidate release for the "all clear" milestone. There wasn't any real usage of - `FacadeAct` - Add `runRevenueAuctions()` -#### Assets - -- Deploy CRV + CVX plugins +### Plugins -#### Collateral +#### Assets and Collateral Across all collateral, `tryPrice()` was updated to exclude revenueHiding considerations +- Deploy CRV + CVX plugins - Add `AnkrStakedEthCollateral` + tests + deployment/verification scripts for ankrETH - Add FluxFinance collateral tests + deployment/verification scripts for fUSDC, fUSDT, fDAI, and fFRAX - Add CompoundV3 `CTokenV3Collateral` + tests + deployment/verification scripts for cUSDCV3 @@ -150,13 +149,144 @@ Across all collateral, `tryPrice()` was updated to exclude revenueHiding conside - Add Lido `LidoStakedEthCollateral` + tests + deployment/verification scripts for wstETH - Add RocketPool `RethCollateral` + tests + deployment/verification scripts for rETH -#### Testing +### Testing - Add generic collateral testing suite at `test/plugins/individual-collateral/collateralTests.ts` - Add EasyAuction regression test for Broker false positive (observed during USDC de-peg) - Add EasyAuction extreme tests -#### Documentation +### Documentation - Add `docs/plugin-addresses.md` as well as accompanying script for generation at `scripts/collateral-params.ts` - Add `docs/exhaustive-tests.md` to document running exhaustive tests on GCP + +## 3.0.0 + +#### Core Protocol Contracts + +- `AssetRegistry` [+1 slot] + - Add last refresh timestamp tracking and expose via `lastRefresh()` getter + - Add `size()` getter for number of registered assets +- `BackingManager` [+2 slots] + + - Remove `delegatecall` during reward claiming + - Modify `settleTrade(IERC20 sell)` to call `rebalance()` when caller is a trade it deployed. + - Remove `manageTokensSortedOrder(IERC20[] memory erc20s)` + - Replace `manageTokens(IERC20[] memory erc20s)` with: + - `rebalance() ` - the argument-agnostic trading part of the logic + - Modify trading algorithm to not trade RToken, and instead dissolve it when it has a balance above ~1e6. "dissolve" = melt() with a basketsNeeded change, like redemption. + - Add significant caching of asset state to `RecollateralizationLibP1` in addition to removing RToken trading (due to dissolve) + - `forwardRevenue(IERC20[] memory erc20s)` - the revenue distributing part + - Modify backingBuffer logic to keep the backing buffer in collateral tokens only. Fix subtle and inconsequential bug that resulted in not maximizing RToken minting locally. + - Use `nonReentrant` over CEI pattern for gas improvement. related to discussion of [this](https://github.com/code-423n4/2023-01-reserve-findings/issues/347) cross-contract reentrancy risk + - move `nonReentrant` up outside `tryTrade` internal helper + - Functions now revert on unproductive executions, instead of no-op + - Do not trade until a warmupPeriod (last time SOUND was newly attained) has passed + - Add `cacheComponents()` refresher to be called on upgrade + - Bugfix: consider `maxTradeVolume()` from both assets on a trade, not just 1 + +- `BasketHandler` [+5 slots] + + - Add new gov param: `warmupPeriod`. Has `setWarmupPeriod(..)` + - Add `isReady()` view + - Extract basket switching logic out into external library `BasketLibP1` + - Enforce `setPrimeBasket()` does not change the net value of a basket in terms of its target units + - Add `quoteCustomRedemption(uint48[] basketNonces, uint192[] memory portions, ..)` to quote a linear combination of current-or-previous baskets. + - Add `getHistoricalBasket(uint48 basketNonce)` view + +- `Broker` [+1 slot] + + - Add `TradeKind` enum to track multiple trading types + - Add new dutch auction `DutchTrade` + - Add minimum auction length of 24s; applies to all auction types + - Add `setDutchAuctionLength(..)` governance setter + - Modify `openTrade(TradeRequest memory reg)` -> `openTrade(TradeKind kind, TradeRequest memory req)` + +- `Deployer` [+0 slots] + - Modify to handle new gov params: `warmupPeriod`, `dutchAuctionLength`, and `withdrawalLeak` + - Do not grant OWNER any of the roles other than ownership +- `Distributor` [+0 slots] + - Remove `notPausedOrFrozen` modifier from `distribute()`; caller only hurts themselves +- `Furnace` [+0 slots] + - Modify `melt()` modifier: `notPausedOrFrozen` -> `notFrozen` +- `Main` [+0 slots] + + - Break `paused` into `issuancePaused` and `tradingPaused` + - `pause()` -> `pauseTrading()` and `pauseIssuance()` + - `unpause()` -> `unpauseTrading()` and `unpauseIssuance()` + - `pausedOrFrozen()` -> `tradingPausedOrFrozen()` and `issuancePausedOrFrozen()` + +- `RevenueTrader` [+3 slots] + + - Remove `delegatecall` during reward claiming + - Add `cacheComponents()` refresher to be called on upgrade + - `manageToken(IERC20 sell)` -> `manageToken(IERC20 sell, TradeKind kind)` + - Allow `manageToken(..)` to open dust auctions + - Revert on 0 balance or collision auction, instead of no-op + - Refresh buy and sell asset + +- `RToken` [+0 slots] + - Remove `exchangeRateIsValidAfter` modifier from all functions except `setBasketsNeeded()` + - Modify `issueTo()` to revert before `warmupPeriod` + - Remove `redeem(uint256 amount, uint48 basketNonce)` and `redeemTo(address recipient, uint256 amount, uint48 basketNonce)` + - Add `redeem(uint256 amount)` and `redeemTo(address recipient, uint256 amount)` - always on current basket nonce; reverts on partial redemption + - Add new `redeemCustom(.., uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, ..)` function to allow redemption from a linear combination of current and previous baskets. All non-standard possibly lossy redemptions must go through this function + - `mint(address recipient, uint256 amtRToken)` -> `mint(uint256 amtRToken)`, since recipient is _always_ BackingManager. Expand scope to include adjustments to `basketsNeeded` + - Add `dissolve(uint256 amount)`: burns RToken and reduces `basketsNeeded`, similar to redemption. Only callable by BackingManager + - Modify `setBasketsNeeded(..)` to revert when supply is 0 +- `StRSR` [+2 slots] + - Remove duplicate `stakeRate()` getter (it's 1 / exchangeRate()) + - Add `withdrawalLeak` gov param, with `setWithdrawalLeak(..)` setter and `leakyRefresh()` helper + - Modify `withdraw()` to allow a small % of RSR too exit without paying to refresh all assets + - Modify `withdraw()` to check for `warmupPeriod` + - Add ability to re-stake during a withdrawal via `cancelUnstake(uint256 endId)` +- `StRSRVotes` [+0 slots] + - Add `stakeAndDelegate(uint256 rsrAmount, address delegate)` function, to encourage people to receive voting weight upon staking + +#### Facades + +- `FacadeAct` + - Remove `getActCalldata(..)` + - Modify `runRevenueAuctions(..)` to work with both 3.0.0 and 2.1.0 interfaces +- `FacadeRead` + - Remove `basketNonce` from `redeem(.., uint48 basketNonce)` + - Remove `traderBalances(..)` + - `balancesAcrossAllTraders(IBackingManager) returns (IERC20[] memory erc20s, uint256[] memory balances, uint256[] memory balancesNeededByBackingManager)` + - Add `nextRecollateralizationAuction(..) returns (bool canStart, IERC20 sell, IERC20 buy, uint256 sellAmount)` + - Add `revenueOverview(IRevenueTrader) returns ( IERC20[] memory erc20s, bool[] memory canStart, uint256[] memory surpluses, uint256[] memory minTradeAmounts)` +- Remove `FacadeMonitor` + +### Plugins + +#### DutchTrade + +Implements a new, simpler, trading method. Intended to be the new dominant trading method, with GnosisTrade (batch auctions) available as a faster-but-more-gas-expensive backup option. + +DutchTrade implements a two-stage, single-lot, falling price dutch auction. In the first 40% of the auction, the price falls from 1000x to the best-case price in a geometric/exponential decay as a price manipulation defense mechanism. Bids are not expected to occur (but note: unlike the GnosisTrade batch auction, this mechanism is not resistant to _arbitrary_ price manipulation). + +Over the last 60% of the auction, the price falls linearly from the best-case price to the worst-case price. Only a single bidder can bid fill the auction, and settlement is atomic. If no bids are received, the capital cycles back to the BackingManager and no loss is taken. + +Duration: 30 min (default) + +#### Assets and Collateral + +- Add `version() return (string)` getter to pave way for separation of asset versioning and core protocol versioning +- Remove expectation of `delegatecall` during `claimRewards()` call, though assets can still do it and it won't break anything + +TODO + +# Links + + + + +- [[3.0.0]](https://github.com/reserve-protocol/protocol/releases/tag/3.0.0) + - https://github.com/reserve-protocol/protocol/compare/2.1.0-rc4...3.0.0 +- [[2.1.0]](https://github.com/reserve-protocol/protocol/releases/tag/2.1.0-rc4) + - https://github.com/reserve-protocol/protocol/compare/2.0.0-candidate-4...2.1.0-rc4 +- [[2.0.0]](https://github.com/reserve-protocol/protocol/releases/tag/2.0.0-candidate-4) + - https://github.com/reserve-protocol/protocol/compare/1.1.0...2.0.0-candidate-4 +- [[1.1.0]](https://github.com/reserve-protocol/protocol/releases/tag/1.1.0) + - https://github.com/reserve-protocol/protocol/compare/1.0.0...1.1.0 +- [[1.0.0]](https://github.com/reserve-protocol/protocol/releases/tag/1.0.0) + - https://github.com/reserve-protocol/protocol/releases/tag/1.0.0 From 40078c089c96a7df0b8f599dab369ed1cf04f7bb Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Sun, 21 May 2023 17:36:23 -0400 Subject: [PATCH 308/462] invert changelog --- CHANGELOG.md | 320 +++++++++++++++++++++++++-------------------------- 1 file changed, 160 insertions(+), 160 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6aa1494f13..3091117fda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,165 +1,5 @@ # Changelog -## 1.0.0 - -(This release is the one from the canonical lauch onstage in Bogota. We were missing semantic versioning at the time, but we call this the 1.0.0 release retroactively.) - -Deploy commit [eda322894a5ed379bbda2b399c9d1cc65aa8c132](https://github.com/reserve-protocol/protocol/commit/eda322894a5ed379bbda2b399c9d1cc65aa8c132) - -## 1.1.0 - -- Introduce semantic versioning to the Deployer and RToken -- `RTokenCreated` event: added `version` argument - -``` -event RTokenCreated( - IMain indexed main, - IRToken indexed rToken, - IStRSR stRSR, - address indexed owner - ); - -``` - -=> - -``` -event RTokenCreated( - IMain indexed main, - IRToken indexed rToken, - IStRSR stRSR, - address indexed owner, - string version - ); - -``` - -- Add `version()` getter on Deployer, Main, and all Components, via mix-in. To be updated with each subsequent release. - -Deploy commit [d757d3a5a6097ae42c71fc03a7c787ec001d2efc](https://github.com/reserve-protocol/protocol/commit/d757d3a5a6097ae42c71fc03a7c787ec001d2efc) - -## 2.0.0 - -Candidate release for the "all clear" milestone. There wasn't any real usage of the 1.0.0/1.1.0 releases; this is the first release that we are going to spend real effort to remain backwards compatible with. - -- Bump solidity version to 0.8.17 -- Support multiple beneficiaries via the [`FacadeWrite`](contracts/facade/FacadeWrite.sol) -- Add `RToken.issueTo(address recipient, uint256 amount, ..)` and `RToken.redeemTo(address recipient, uint256 amount, ..)` to support issuance/redemption to a different address than `msg.sender` -- Add `RToken.redeem*(.., uint256 basketNonce)` to enable msg sender to control expectations around partial redemptions -- Add `RToken.issuanceAvailable()` + `RToken.redemptionAvailable()` -- Add `FacadeRead.primeBasket()` + `FacadeRead.backupConfig()` views -- Many external libs moved to internal -- Switch from price point estimates to price ranges; all prices now have a `low` and `high`. Impacted interface functions: - - `IAsset.price()` - - `IBasketHandler.price()` -- Replace `IAsset.fallbackPrice()` with `IAsset.lotPrice()`. The lot price is the current price when available, and a fallback price otherwise. -- Introduce decaying fallback prices. Over a finite period of time the fallback price goes to zero, linearly. -- Remove `IAsset.pricePerTarget()` from asset interface -- Remove rewards earning and sweeping from RToken -- Add `safeMulDivCeil()` to `ITrading` traders. Use when overflow is possible from 2 locations: - - [RecollateralizationLib.sol:L271](contracts/p1/mixins/RecollateralizationLib.sol) - - [TradeLib.sol:L59](contracts/p1/mixins/TradeLib.sol) -- Introduce config struct to encapsulate Collateral constructor params more neatly -- In general it should be easier to write Collateral plugins. Implementors should _only_ ever have to override 4 functions: `tryPrice()`, `refPerTok()`, `targetPerRef()`, and `claimRewards()`. -- Add `.div(1 - maxTradeSlippage)` to calculation of `shortfallSlippage` in [RecollateralizationLib.sol:L188](contracts/p1/mixins/RecollateralizationLib.sol). -- FacadeRead: - - remove `.pendingIssuances()` + `.endIdForVest()` - - refactor calculations in `basketBreakdown()` -- Bugfix: Fix claim rewards from traders in `FacadeAct` -- Bugfix: Do not handout RSR rewards when no one is staked -- Bugfix: Support small redemptions even when the RToken supply is massive -- Bump component-wide `version()` getter to 2.0.0 -- Remove non-atomic issuance -- Replace redemption battery with issuance and redemption throttles - - `amtRate` valid range: `[1e18, 1e48]` - - `pctRate` valid range: `[0, 1e18]` -- Fix Furnace/StRSR reward period to 12 seconds -- Gov params: - - --`rewardPeriod` - - --`issuanceRate` - - ++`issuanceThrottle` - - ++`redemptionThrottle` -- Events: - - --`RToken.IssuanceStarted` - - --`RToken.IssuancesCompleted` - - --`RToken.IssuancesCanceled` - - `Issuance( address indexed recipient, uint256 indexed amount, uint192 baskets )` -> `Issuance( address indexed issuer, address indexed recipient, uint256 indexed amount, uint192 baskets )` - - `Redemption(address indexed recipient, uint256 indexed amount, uint192 baskets )` -> `Redemption(address indexed redeemer, address indexed recipient, uint256 indexed amount, uint192 baskets )` - - ++`RToken.IssuanceThrottleSet` - - ++`RToken.RedemptionThrottleSet` -- Allow redemption while DISABLED -- Allow `grantRTokenAllowances()` while paused -- Add `RToken.monetizeDonations()` escape hatch for accidentally donated tokens -- Collateral default threshold: 5% -> 1% (+ include oracleError) -- RecollateralizationLib: Tighter basket range during recollateralization. Will now do `minTradeVolume`-size auctions to fill in dust rather than haircut. -- Remove StRSR.setName()/setSymbol() -- Prevent RToken exchange rate manipulation at low supply -- Prevent StRSR exchange rate manipulation at low supply -- Forward RSR directly to StRSR, bypassing RSRTrader -- Accumulate melting on `Furnace.setRatio()` -- Payout RSR rewards on `StRSR.setRatio()` -- Distinguish oracle timeouts when dealing with multiple oracles in one plugin -- Add safety during asset degregistration to ensure it is always possible to unregister an infinite-looping asset -- Fix `StRSR`/`RToken` EIP712 typehash to use release version instead of "1" -- Add `FacadeRead.redeem(IRToken rToken, uint256 amount, uint48 basketNonce)` to return the expected redemption quantities on the basketNonce, or revert -- Integrate with OZ 4.7.3 Governance (changes to `quorum()`/t`proposalThreshold()`) - -## 2.1.0 - -### Core protocol contracts - -- `BasketHandler` - - Bugfix for `getPrimeBasket()` view - - Minor change to `_price()` rounding - - Minor natspec improvement to `refreshBasket()` -- `Broker` - - Fix `GnosisTrade` trade implemention to treat defensive rounding by EasyAuction correctly - - Add `setGnosis()` and `setTradeImplementation()` governance functions -- `RToken` - - Minor gas optimization added to `redeemTo` to use saved `assetRegistry` variable -- `StRSR` - - Expose RSR variables via `getDraftRSR()`, `getStakeRSR()`, and `getTotalDrafts()` views - -### Facades - -- `FacadeRead` - - Extend `issue()` to return the estimated USD value of deposits as `depositsUoA` - - Add `traderBalances()` - - Add `auctionsSettleable()` - - Add `nextRecollateralizationAuction()` - - Modify `backingOverview() to handle unpriced cases` -- `FacadeAct` - - Add `runRevenueAuctions()` - -### Plugins - -#### Assets and Collateral - -Across all collateral, `tryPrice()` was updated to exclude revenueHiding considerations - -- Deploy CRV + CVX plugins -- Add `AnkrStakedEthCollateral` + tests + deployment/verification scripts for ankrETH -- Add FluxFinance collateral tests + deployment/verification scripts for fUSDC, fUSDT, fDAI, and fFRAX -- Add CompoundV3 `CTokenV3Collateral` + tests + deployment/verification scripts for cUSDCV3 -- Add Convex `CvxStableCollateral` + tests + deployment/verification scripts for 3Pool -- Add Convex `CvxVolatileCollateral` + tests + deployment/verification scripts for Tricrypto -- Add Convex `CvxStableMetapoolCollateral` + tests + deployment/verification scripts for MIM/3Pool -- Add Convex `CvxStableRTokenMetapoolCollateral` + tests + deployment/verification scripts for eUSD/fraxBP -- Add Frax `SFraxEthCollateral` + tests + deployment/verification scripts for sfrxETH -- Add Lido `LidoStakedEthCollateral` + tests + deployment/verification scripts for wstETH -- Add RocketPool `RethCollateral` + tests + deployment/verification scripts for rETH - -### Testing - -- Add generic collateral testing suite at `test/plugins/individual-collateral/collateralTests.ts` -- Add EasyAuction regression test for Broker false positive (observed during USDC de-peg) -- Add EasyAuction extreme tests - -### Documentation - -- Add `docs/plugin-addresses.md` as well as accompanying script for generation at `scripts/collateral-params.ts` -- Add `docs/exhaustive-tests.md` to document running exhaustive tests on GCP - ## 3.0.0 #### Core Protocol Contracts @@ -273,8 +113,168 @@ Duration: 30 min (default) - Add `version() return (string)` getter to pave way for separation of asset versioning and core protocol versioning - Remove expectation of `delegatecall` during `claimRewards()` call, though assets can still do it and it won't break anything +## 2.1.0 + +### Core protocol contracts + +- `BasketHandler` + - Bugfix for `getPrimeBasket()` view + - Minor change to `_price()` rounding + - Minor natspec improvement to `refreshBasket()` +- `Broker` + - Fix `GnosisTrade` trade implemention to treat defensive rounding by EasyAuction correctly + - Add `setGnosis()` and `setTradeImplementation()` governance functions +- `RToken` + - Minor gas optimization added to `redeemTo` to use saved `assetRegistry` variable +- `StRSR` + - Expose RSR variables via `getDraftRSR()`, `getStakeRSR()`, and `getTotalDrafts()` views + +### Facades + +- `FacadeRead` + - Extend `issue()` to return the estimated USD value of deposits as `depositsUoA` + - Add `traderBalances()` + - Add `auctionsSettleable()` + - Add `nextRecollateralizationAuction()` + - Modify `backingOverview() to handle unpriced cases` +- `FacadeAct` + - Add `runRevenueAuctions()` + +### Plugins + +#### Assets and Collateral + +Across all collateral, `tryPrice()` was updated to exclude revenueHiding considerations + +- Deploy CRV + CVX plugins +- Add `AnkrStakedEthCollateral` + tests + deployment/verification scripts for ankrETH +- Add FluxFinance collateral tests + deployment/verification scripts for fUSDC, fUSDT, fDAI, and fFRAX +- Add CompoundV3 `CTokenV3Collateral` + tests + deployment/verification scripts for cUSDCV3 +- Add Convex `CvxStableCollateral` + tests + deployment/verification scripts for 3Pool +- Add Convex `CvxVolatileCollateral` + tests + deployment/verification scripts for Tricrypto +- Add Convex `CvxStableMetapoolCollateral` + tests + deployment/verification scripts for MIM/3Pool +- Add Convex `CvxStableRTokenMetapoolCollateral` + tests + deployment/verification scripts for eUSD/fraxBP +- Add Frax `SFraxEthCollateral` + tests + deployment/verification scripts for sfrxETH +- Add Lido `LidoStakedEthCollateral` + tests + deployment/verification scripts for wstETH +- Add RocketPool `RethCollateral` + tests + deployment/verification scripts for rETH + +### Testing + +- Add generic collateral testing suite at `test/plugins/individual-collateral/collateralTests.ts` +- Add EasyAuction regression test for Broker false positive (observed during USDC de-peg) +- Add EasyAuction extreme tests + +### Documentation + +- Add `docs/plugin-addresses.md` as well as accompanying script for generation at `scripts/collateral-params.ts` +- Add `docs/exhaustive-tests.md` to document running exhaustive tests on GCP + +## 2.0.0 + +Candidate release for the "all clear" milestone. There wasn't any real usage of the 1.0.0/1.1.0 releases; this is the first release that we are going to spend real effort to remain backwards compatible with. + +- Bump solidity version to 0.8.17 +- Support multiple beneficiaries via the [`FacadeWrite`](contracts/facade/FacadeWrite.sol) +- Add `RToken.issueTo(address recipient, uint256 amount, ..)` and `RToken.redeemTo(address recipient, uint256 amount, ..)` to support issuance/redemption to a different address than `msg.sender` +- Add `RToken.redeem*(.., uint256 basketNonce)` to enable msg sender to control expectations around partial redemptions +- Add `RToken.issuanceAvailable()` + `RToken.redemptionAvailable()` +- Add `FacadeRead.primeBasket()` + `FacadeRead.backupConfig()` views +- Many external libs moved to internal +- Switch from price point estimates to price ranges; all prices now have a `low` and `high`. Impacted interface functions: + - `IAsset.price()` + - `IBasketHandler.price()` +- Replace `IAsset.fallbackPrice()` with `IAsset.lotPrice()`. The lot price is the current price when available, and a fallback price otherwise. +- Introduce decaying fallback prices. Over a finite period of time the fallback price goes to zero, linearly. +- Remove `IAsset.pricePerTarget()` from asset interface +- Remove rewards earning and sweeping from RToken +- Add `safeMulDivCeil()` to `ITrading` traders. Use when overflow is possible from 2 locations: + - [RecollateralizationLib.sol:L271](contracts/p1/mixins/RecollateralizationLib.sol) + - [TradeLib.sol:L59](contracts/p1/mixins/TradeLib.sol) +- Introduce config struct to encapsulate Collateral constructor params more neatly +- In general it should be easier to write Collateral plugins. Implementors should _only_ ever have to override 4 functions: `tryPrice()`, `refPerTok()`, `targetPerRef()`, and `claimRewards()`. +- Add `.div(1 - maxTradeSlippage)` to calculation of `shortfallSlippage` in [RecollateralizationLib.sol:L188](contracts/p1/mixins/RecollateralizationLib.sol). +- FacadeRead: + - remove `.pendingIssuances()` + `.endIdForVest()` + - refactor calculations in `basketBreakdown()` +- Bugfix: Fix claim rewards from traders in `FacadeAct` +- Bugfix: Do not handout RSR rewards when no one is staked +- Bugfix: Support small redemptions even when the RToken supply is massive +- Bump component-wide `version()` getter to 2.0.0 +- Remove non-atomic issuance +- Replace redemption battery with issuance and redemption throttles + - `amtRate` valid range: `[1e18, 1e48]` + - `pctRate` valid range: `[0, 1e18]` +- Fix Furnace/StRSR reward period to 12 seconds +- Gov params: + - --`rewardPeriod` + - --`issuanceRate` + - ++`issuanceThrottle` + - ++`redemptionThrottle` +- Events: + - --`RToken.IssuanceStarted` + - --`RToken.IssuancesCompleted` + - --`RToken.IssuancesCanceled` + - `Issuance( address indexed recipient, uint256 indexed amount, uint192 baskets )` -> `Issuance( address indexed issuer, address indexed recipient, uint256 indexed amount, uint192 baskets )` + - `Redemption(address indexed recipient, uint256 indexed amount, uint192 baskets )` -> `Redemption(address indexed redeemer, address indexed recipient, uint256 indexed amount, uint192 baskets )` + - ++`RToken.IssuanceThrottleSet` + - ++`RToken.RedemptionThrottleSet` +- Allow redemption while DISABLED +- Allow `grantRTokenAllowances()` while paused +- Add `RToken.monetizeDonations()` escape hatch for accidentally donated tokens +- Collateral default threshold: 5% -> 1% (+ include oracleError) +- RecollateralizationLib: Tighter basket range during recollateralization. Will now do `minTradeVolume`-size auctions to fill in dust rather than haircut. +- Remove StRSR.setName()/setSymbol() +- Prevent RToken exchange rate manipulation at low supply +- Prevent StRSR exchange rate manipulation at low supply +- Forward RSR directly to StRSR, bypassing RSRTrader +- Accumulate melting on `Furnace.setRatio()` +- Payout RSR rewards on `StRSR.setRatio()` +- Distinguish oracle timeouts when dealing with multiple oracles in one plugin +- Add safety during asset degregistration to ensure it is always possible to unregister an infinite-looping asset +- Fix `StRSR`/`RToken` EIP712 typehash to use release version instead of "1" +- Add `FacadeRead.redeem(IRToken rToken, uint256 amount, uint48 basketNonce)` to return the expected redemption quantities on the basketNonce, or revert +- Integrate with OZ 4.7.3 Governance (changes to `quorum()`/t`proposalThreshold()`) + TODO +## 1.1.0 + +- Introduce semantic versioning to the Deployer and RToken +- `RTokenCreated` event: added `version` argument + +``` +event RTokenCreated( + IMain indexed main, + IRToken indexed rToken, + IStRSR stRSR, + address indexed owner + ); + +``` + +=> + +``` +event RTokenCreated( + IMain indexed main, + IRToken indexed rToken, + IStRSR stRSR, + address indexed owner, + string version + ); + +``` + +- Add `version()` getter on Deployer, Main, and all Components, via mix-in. To be updated with each subsequent release. + +Deploy commit [d757d3a5a6097ae42c71fc03a7c787ec001d2efc](https://github.com/reserve-protocol/protocol/commit/d757d3a5a6097ae42c71fc03a7c787ec001d2efc) + +## 1.0.0 + +(This release is the one from the canonical lauch onstage in Bogota. We were missing semantic versioning at the time, but we call this the 1.0.0 release retroactively.) + +Deploy commit [eda322894a5ed379bbda2b399c9d1cc65aa8c132](https://github.com/reserve-protocol/protocol/commit/eda322894a5ed379bbda2b399c9d1cc65aa8c132) + # Links From 3a85b2145fa6d0eba6293ac4259928e54792e436 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 22 May 2023 11:13:55 -0300 Subject: [PATCH 309/462] fix call to settle trade --- contracts/facade/FacadeRead.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index c7537ab17a..b8690752fb 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -235,7 +235,7 @@ contract FacadeRead is IFacadeRead { for (uint256 i = 0; i < erc20s.length; ++i) { ITrade trade = bm.trades(erc20s[i]); if (address(trade) != address(0) && trade.canSettle()) { - trade.settle(); + bm.settleTrade(erc20s[i]); break; // backingManager can only have 1 trade open at a time } } From 59156a91365338c558c95947fb2b43f54f9a7d3f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 11:45:35 -0400 Subject: [PATCH 310/462] bump Versioned mixin --- contracts/mixins/Versioned.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mixins/Versioned.sol b/contracts/mixins/Versioned.sol index b4dd024758..bd57e4bd0d 100644 --- a/contracts/mixins/Versioned.sol +++ b/contracts/mixins/Versioned.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.17; import "../interfaces/IVersioned.sol"; // This value should be updated on each release -string constant VERSION = "2.1.0"; +string constant VERSION = "3.0.0"; /** * @title Versioned From 830d0709bb750b310a266407338bf0b0922136f6 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 11:45:55 -0400 Subject: [PATCH 311/462] FacadeRead/Act: parse version and make version-specific call --- contracts/facade/FacadeAct.sol | 28 ++++++++++------- contracts/facade/FacadeRead.sol | 45 ++++++++++++++++++---------- contracts/interfaces/IFacadeRead.sol | 4 +++ 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 3028d1264a..545a382f99 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -5,6 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/Multicall.sol"; import "../interfaces/IBackingManager.sol"; import "../interfaces/IFacadeAct.sol"; +import "../interfaces/IFacadeRead.sol"; /** * @title Facade @@ -45,28 +46,35 @@ contract FacadeAct is IFacadeAct, Multicall { // Transfer revenue backingManager -> revenueTrader { IBackingManager bm = revenueTrader.main().backingManager(); + bytes1 majorVersion = bytes(bm.version())[0]; - // 3.0.0 interface - // solhint-disable-next-line no-empty-blocks - try bm.forwardRevenue(toStart) {} catch { - // try 2.1.0 interface + if (majorVersion == MAJOR_VERSION_3) { + // solhint-disable-next-line no-empty-blocks + try bm.forwardRevenue(toStart) {} catch {} + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_2) { (bool success, ) = address(bm).call{ value: 0 }( abi.encodeWithSignature("manageTokens(address[])", toStart) ); - require(success, "failed to forward revenue"); + success = success; // hush warning + } else { + revert("unrecognized version"); } } // Start auctions for (uint256 i = 0; i < toStart.length; ++i) { - // 3.0.0 RevenueTrader interface - // solhint-disable-next-line no-empty-blocks - try revenueTrader.manageToken(toStart[i], kind) {} catch { - // Fallback to <=2.1.0 interface + bytes1 majorVersion = bytes(revenueTrader.version())[0]; + + if (majorVersion == MAJOR_VERSION_3) { + // solhint-disable-next-line no-empty-blocks + try revenueTrader.manageToken(toStart[i], kind) {} catch {} + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_2) { (bool success, ) = address(revenueTrader).call{ value: 0 }( abi.encodeWithSignature("manageToken(address)", toStart[i]) ); - require(success, "failed to start revenue auction"); + success = success; // hush warning + } else { + revert("unrecognized version"); } } // solhint-enable avoid-low-level-calls diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 518435f752..be5182a2a4 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -238,15 +238,19 @@ contract FacadeRead is IFacadeRead { // If no auctions ongoing, try to find a new auction to start if (bm.tradesOpen() == 0) { - // Try to launch auctions - // solhint-disable-next-line no-empty-blocks - try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch { - // try 2.1.0 interface + bytes1 majorVersion = bytes(bm.version())[0]; + + if (majorVersion == MAJOR_VERSION_3) { + // solhint-disable-next-line no-empty-blocks + try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch {} + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_2) { IERC20[] memory emptyERC20s = new IERC20[](0); (bool success, ) = address(bm).call{ value: 0 }( abi.encodeWithSignature("manageTokens(address[])", emptyERC20s) ); - require(success, "failed to launch rebalance"); + success = success; // hush warning + } else { + revert("unrecognized version"); } // Find the started auction @@ -285,15 +289,18 @@ contract FacadeRead is IFacadeRead { // Forward ALL revenue { IBackingManager bm = revenueTrader.main().backingManager(); + bytes1 majorVersion = bytes(bm.version())[0]; - // First try 3.0.0 interface - // solhint-disable-next-line no-empty-blocks - try bm.forwardRevenue(reg.erc20s) {} catch { - // try 2.1.0 interface + if (majorVersion == MAJOR_VERSION_3) { + // solhint-disable-next-line no-empty-blocks + try bm.forwardRevenue(reg.erc20s) {} catch {} + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_2) { (bool success, ) = address(bm).call{ value: 0 }( abi.encodeWithSignature("manageTokens(address[])", reg.erc20s) ); - require(success, "failed to forward revenue"); + success = success; // hush warning + } else { + revert("unrecognized version"); } } @@ -320,15 +327,21 @@ contract FacadeRead is IFacadeRead { int8(reg.assets[i].erc20Decimals()) ); - if (reg.erc20s[i].balanceOf(address(revenueTrader)) > minTradeAmounts[i]) { - // 3.0.0 RevenueTrader interface - // solhint-disable-next-line no-empty-blocks - try revenueTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch { - // try 2.1.0 interface + bytes1 majorVersion = bytes(revenueTrader.version())[0]; + if ( + reg.erc20s[i].balanceOf(address(revenueTrader)) > minTradeAmounts[i] && + revenueTrader.trades(reg.erc20s[i]) == ITrade(address(0)) + ) { + if (majorVersion == MAJOR_VERSION_3) { + // solhint-disable-next-line no-empty-blocks + try revenueTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_2) { (bool success, ) = address(revenueTrader).call{ value: 0 }( abi.encodeWithSignature("manageToken(address)", erc20s[i]) ); - require(success, "failed to start revenue auction"); + success = success; // hush warning + } else { + revert("unrecognized version"); } if (revenueTrader.tradesOpen() - tradesOpen > 0) { diff --git a/contracts/interfaces/IFacadeRead.sol b/contracts/interfaces/IFacadeRead.sol index 4b05a6fa39..70f65c257d 100644 --- a/contracts/interfaces/IFacadeRead.sol +++ b/contracts/interfaces/IFacadeRead.sol @@ -5,6 +5,10 @@ import "../p1/RToken.sol"; import "./IRToken.sol"; import "./IStRSR.sol"; +bytes1 constant MAJOR_VERSION_1 = bytes1("1"); +bytes1 constant MAJOR_VERSION_2 = bytes1("2"); +bytes1 constant MAJOR_VERSION_3 = bytes1("3"); + /** * @title IFacade * @notice A UX-friendly layer for non-governance protocol interactions From d0147777817e7f8300d603d9838f3792d1b87458 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 11:47:29 -0400 Subject: [PATCH 312/462] fix typo --- contracts/facade/FacadeAct.sol | 4 ++-- contracts/facade/FacadeRead.sol | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 545a382f99..0017863c90 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -51,7 +51,7 @@ contract FacadeAct is IFacadeAct, Multicall { if (majorVersion == MAJOR_VERSION_3) { // solhint-disable-next-line no-empty-blocks try bm.forwardRevenue(toStart) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_2) { + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { (bool success, ) = address(bm).call{ value: 0 }( abi.encodeWithSignature("manageTokens(address[])", toStart) ); @@ -68,7 +68,7 @@ contract FacadeAct is IFacadeAct, Multicall { if (majorVersion == MAJOR_VERSION_3) { // solhint-disable-next-line no-empty-blocks try revenueTrader.manageToken(toStart[i], kind) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_2) { + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { (bool success, ) = address(revenueTrader).call{ value: 0 }( abi.encodeWithSignature("manageToken(address)", toStart[i]) ); diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index be5182a2a4..46a987b818 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -243,7 +243,7 @@ contract FacadeRead is IFacadeRead { if (majorVersion == MAJOR_VERSION_3) { // solhint-disable-next-line no-empty-blocks try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_2) { + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { IERC20[] memory emptyERC20s = new IERC20[](0); (bool success, ) = address(bm).call{ value: 0 }( abi.encodeWithSignature("manageTokens(address[])", emptyERC20s) @@ -294,7 +294,7 @@ contract FacadeRead is IFacadeRead { if (majorVersion == MAJOR_VERSION_3) { // solhint-disable-next-line no-empty-blocks try bm.forwardRevenue(reg.erc20s) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_2) { + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { (bool success, ) = address(bm).call{ value: 0 }( abi.encodeWithSignature("manageTokens(address[])", reg.erc20s) ); @@ -335,7 +335,7 @@ contract FacadeRead is IFacadeRead { if (majorVersion == MAJOR_VERSION_3) { // solhint-disable-next-line no-empty-blocks try revenueTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_2) { + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { (bool success, ) = address(revenueTrader).call{ value: 0 }( abi.encodeWithSignature("manageToken(address)", erc20s[i]) ); From f4254954e02a5890a668462c29f5919d431d96f7 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 13:17:21 -0400 Subject: [PATCH 313/462] lint clean --- contracts/facade/FacadeAct.sol | 5 ++--- contracts/facade/FacadeRead.sol | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 0017863c90..049de85de1 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -41,8 +41,6 @@ contract FacadeAct is IFacadeAct, Multicall { revenueTrader.settleTrade(toSettle[i]); } - // solhint-disable avoid-low-level-calls - // Transfer revenue backingManager -> revenueTrader { IBackingManager bm = revenueTrader.main().backingManager(); @@ -52,6 +50,7 @@ contract FacadeAct is IFacadeAct, Multicall { // solhint-disable-next-line no-empty-blocks try bm.forwardRevenue(toStart) {} catch {} } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + // solhint-disable-next-line avoid-low-level-calls (bool success, ) = address(bm).call{ value: 0 }( abi.encodeWithSignature("manageTokens(address[])", toStart) ); @@ -69,6 +68,7 @@ contract FacadeAct is IFacadeAct, Multicall { // solhint-disable-next-line no-empty-blocks try revenueTrader.manageToken(toStart[i], kind) {} catch {} } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + // solhint-disable-next-line avoid-low-level-calls (bool success, ) = address(revenueTrader).call{ value: 0 }( abi.encodeWithSignature("manageToken(address)", toStart[i]) ); @@ -77,6 +77,5 @@ contract FacadeAct is IFacadeAct, Multicall { revert("unrecognized version"); } } - // solhint-enable avoid-low-level-calls } } diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 46a987b818..d4c51e8042 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -245,6 +245,7 @@ contract FacadeRead is IFacadeRead { try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch {} } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { IERC20[] memory emptyERC20s = new IERC20[](0); + // solhint-disable-next-line avoid-low-level-calls (bool success, ) = address(bm).call{ value: 0 }( abi.encodeWithSignature("manageTokens(address[])", emptyERC20s) ); @@ -284,8 +285,6 @@ contract FacadeRead is IFacadeRead { uint192 minTradeVolume = revenueTrader.minTradeVolume(); // {UoA} Registry memory reg = revenueTrader.main().assetRegistry().getRegistry(); - // solhint-disable avoid-low-level-calls - // Forward ALL revenue { IBackingManager bm = revenueTrader.main().backingManager(); @@ -295,6 +294,7 @@ contract FacadeRead is IFacadeRead { // solhint-disable-next-line no-empty-blocks try bm.forwardRevenue(reg.erc20s) {} catch {} } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + // solhint-disable-next-line avoid-low-level-calls (bool success, ) = address(bm).call{ value: 0 }( abi.encodeWithSignature("manageTokens(address[])", reg.erc20s) ); @@ -336,6 +336,7 @@ contract FacadeRead is IFacadeRead { // solhint-disable-next-line no-empty-blocks try revenueTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + // solhint-disable-next-line avoid-low-level-calls (bool success, ) = address(revenueTrader).call{ value: 0 }( abi.encodeWithSignature("manageToken(address)", erc20s[i]) ); @@ -349,7 +350,6 @@ contract FacadeRead is IFacadeRead { } } } - // solhint-enable avoid-low-level-calls } // === Views === From 1fe413d071b51b2aeda8aadbf5868a27e4009009 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 13:28:15 -0400 Subject: [PATCH 314/462] add back tests for transfer/transferFrom to RToken --- test/RToken.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/RToken.test.ts b/test/RToken.test.ts index d5f08270c5..1e018b4024 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -2240,6 +2240,19 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await expect(rToken.connect(addr1).melt(issueAmount)).to.be.revertedWith('furnace only') }) + it('Should not allow transfer/transferFrom to address(this)', async () => { + // transfer + await expect(rToken.connect(addr1).transfer(rToken.address, 1)).to.be.revertedWith( + 'RToken transfer to self' + ) + + // transferFrom + await rToken.connect(addr1).approve(addr2.address, 1) + await expect( + rToken.connect(addr2).transferFrom(addr1.address, rToken.address, 1) + ).to.be.revertedWith('RToken transfer to self') + }) + it('Should only allow to mint tokens when called by backing manager', async () => { // Mint tokens const mintAmount: BigNumber = bn('10e18') From c29455f4762b1d19a9fc556aa0c73ffcf936d7cd Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 14:19:29 -0400 Subject: [PATCH 315/462] bump suggested withdrawalLeak to 1% --- docs/system-design.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/system-design.md b/docs/system-design.md index 9d9d6bef75..4a49ba503e 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -45,7 +45,7 @@ The protocol (weakly) assumes a 12-second blocktime. This section documents the #### Should-be-changed if blocktime different -- The `Furnace` melts RToken in periods of 12 seconds. If the protocol is deployed to a chain with shorter blocktime, it is possible it may be rational to issue right before melting and redeem directly after, in order to selfishly benefit. The `Furnace` shouild be updated to melt more often. +- The `Furnace` melts RToken in periods of 12 seconds. If the protocol is deployed to a chain with shorter blocktime, it is possible it may be rational to issue right before melting and redeem directly after, in order to selfishly benefit. The `Furnace` shouild be updated to melt more often. #### Probably fine if blocktime different @@ -313,8 +313,8 @@ The fraction of RSR stake that should be permitted to withdraw without a refresh Setting this number larger allows unstakers to save more on gas at the cost of allowing more RSR to exit improperly prior to a default. -Default value: `0.005e18` = 0.5% -Mainnet reasonable range: 0 to 0.1e18 (0 to 10%) +Default value: `1e16` = 1% +Mainnet reasonable range: 0 to 1e17 (0 to 10%) ### `RToken Supply Throttles` From 6db28bdc236f4df61f83e03eb4136b8131384a10 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 14:22:28 -0400 Subject: [PATCH 316/462] bump suggested withdrawalLeak to 5% --- docs/system-design.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/system-design.md b/docs/system-design.md index 4a49ba503e..21ae313e5c 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -313,8 +313,8 @@ The fraction of RSR stake that should be permitted to withdraw without a refresh Setting this number larger allows unstakers to save more on gas at the cost of allowing more RSR to exit improperly prior to a default. -Default value: `1e16` = 1% -Mainnet reasonable range: 0 to 1e17 (0 to 10%) +Default value: `5e16` = 5% +Mainnet reasonable range: 0 to 25e16 (0 to 25%) ### `RToken Supply Throttles` From e0b66e46ca58fbd0d033afbf39e55ebab7086954 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Tue, 23 May 2023 00:28:42 +0530 Subject: [PATCH 317/462] nitpicks --- contracts/mixins/Versioned.sol | 2 +- contracts/p1/BasketHandler.sol | 1 + contracts/p1/RToken.sol | 2 +- contracts/p1/StRSR.sol | 16 +++++++++------- contracts/p1/mixins/Trading.sol | 3 ++- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/contracts/mixins/Versioned.sol b/contracts/mixins/Versioned.sol index b4dd024758..bd57e4bd0d 100644 --- a/contracts/mixins/Versioned.sol +++ b/contracts/mixins/Versioned.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.17; import "../interfaces/IVersioned.sol"; // This value should be updated on each release -string constant VERSION = "2.1.0"; +string constant VERSION = "3.0.0"; /** * @title Versioned diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 767c6c93c9..d89f911322 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -20,6 +20,7 @@ import "./mixins/Component.sol"; contract BasketHandlerP1 is ComponentP1, IBasketHandler { using BasketLibP1 for Basket; using EnumerableMap for EnumerableMap.Bytes32ToUintMap; + using EnumerableSet for EnumerableSet.Bytes32Set; using CollateralStatusComparator for CollateralStatus; using FixLib for uint192; diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 507398cf58..3119702d62 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -86,7 +86,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { redemptionThrottle.lastTimestamp = uint48(block.timestamp); } - /// Issue an RToken on current the basket + /// Issue an RToken on the current basket /// @param amount {qTok} The quantity of RToken to issue /// @custom:interaction nearly CEI, but see comments around handling of refunds function issue(uint256 amount) public { diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 744e600b24..339ddb4665 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -158,6 +158,8 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab uint48 private lastWithdrawRefresh; // {s} timestamp of last refresh() during withdraw() uint192 public withdrawalLeak; // {1} gov param -- % RSR that can be withdrawn without refresh + // NOTE for future: Do not add a uint48 (or smaller) here + // ====================== // init() can only be called once (initializer) @@ -704,6 +706,10 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // solhint-disable-next-line no-empty-blocks function requireNotFrozen() private notFrozen {} + // contract-size-saver + // solhint-disable-next-line no-empty-blocks + function requireGovernanceOnly() private governance {} + // ==== ERC20 ==== // This section extracted from ERC20; adjusted to work with stakes/eras // name(), symbol(), and decimals() are all auto-generated @@ -919,7 +925,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab /// @custom:governance function setUnstakingDelay(uint48 val) public { - governanceOnly(); + requireGovernanceOnly(); require(val > MIN_UNSTAKING_DELAY && val <= MAX_UNSTAKING_DELAY, "invalid unstakingDelay"); emit UnstakingDelaySet(unstakingDelay, val); unstakingDelay = val; @@ -927,7 +933,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab /// @custom:governance function setRewardRatio(uint192 val) public { - governanceOnly(); + requireGovernanceOnly(); if (!main.frozen()) _payoutRewards(); require(val <= MAX_REWARD_RATIO, "invalid rewardRatio"); emit RewardRatioSet(rewardRatio, val); @@ -936,16 +942,12 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab /// @custom:governance function setWithdrawalLeak(uint192 val) public { - governanceOnly(); + requireGovernanceOnly(); require(val <= MAX_WITHDRAWAL_LEAK, "invalid withdrawalLeak"); emit WithdrawalLeakSet(withdrawalLeak, val); withdrawalLeak = val; } - // contract-size-saver - // solhint-disable-next-line no-empty-blocks - function governanceOnly() private governance {} - /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 1e6c800aef..258c7cd342 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -12,7 +12,7 @@ import "./Component.sol"; import "./RewardableLib.sol"; /// Abstract trading mixin for BackingManager + RevenueTrader. -/// @dev The use of Multicall here instead of MulticallUpgradeable cannot be changed +/// @dev The use of Multicall here instead of MulticallUpgradeable cannot be /// changed without breaking <3.0.0 RTokens. The only difference in /// MulticallUpgradeable is the 50 slot storage gap and an empty constructor. /// It should be fine to leave the non-upgradeable Multicall here permanently. @@ -76,6 +76,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // checks: // !paused (trading), !frozen // trade[sell].canSettle() + // (see override) // actions: // trade[sell].settle() // effects: From 80a3a933155f14aa486da7a97e00c03b07443a67 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 15:25:10 -0400 Subject: [PATCH 318/462] use 3.0.0 in tests for Permit approvals, etc --- test/fixtures.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures.ts b/test/fixtures.ts index fdecb5940d..ac8db13cce 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -78,7 +78,7 @@ export const ORACLE_ERROR = fp('0.01') // 1% oracle error export const REVENUE_HIDING = fp('0') // no revenue hiding by default; test individually // This will have to be updated on each release -export const VERSION = '2.1.0' +export const VERSION = '3.0.0' export type Collateral = | FiatCollateral From da6d2ce9d612a616ae302943a9810f5dc02d0585 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Tue, 23 May 2023 00:58:11 +0530 Subject: [PATCH 319/462] Remove comment --- contracts/p1/StRSR.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 339ddb4665..be30a77c6b 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -158,8 +158,6 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab uint48 private lastWithdrawRefresh; // {s} timestamp of last refresh() during withdraw() uint192 public withdrawalLeak; // {1} gov param -- % RSR that can be withdrawn without refresh - // NOTE for future: Do not add a uint48 (or smaller) here - // ====================== // init() can only be called once (initializer) From b72d3f6bad65e5b4a5c5445bc9fe28a918f31e37 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 20:45:58 -0400 Subject: [PATCH 320/462] document StRSR.cancelUnstake --- contracts/p1/StRSR.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index be30a77c6b..03938d4e88 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -233,6 +233,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab /// Begins a delayed unstaking for `amount` StRSR /// @param stakeAmount {qStRSR} + /// @custom:interaction // checks: // not paused (trading) or frozen // 0 < stakeAmount <= bal[caller] @@ -333,6 +334,8 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab require(basketHandler.isReady(), "basket not ready"); } + /// Cancel an ongoing unstaking; resume staking + /// @custom:interaction CEI function cancelUnstake(uint256 endId) external { requireNotFrozen(); address account = _msgSender(); @@ -408,7 +411,6 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // // other properties: // seized >= rsrAmount, which should be a logical consequence of the above effects - function seizeRSR(uint256 rsrAmount) external { requireNotTradingPausedOrFrozen(); From ab35193147b410eb7d17297f2f00e95ecb28a81c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 20:52:19 -0400 Subject: [PATCH 321/462] remove all mention of manageTokens --- contracts/interfaces/IBackingManager.sol | 7 ++---- contracts/p1/BackingManager.sol | 2 -- docs/recollateralization.md | 2 +- docs/solidity-style.md | 5 +++- .../testing/upgrade-checker-utils/rewards.ts | 7 +++--- .../testing/upgrade-checker-utils/rtokens.ts | 12 ++++------ .../upgrade-checker-utils/upgrades/2_1_0.ts | 24 +++++++++---------- test/Revenues.test.ts | 2 +- 8 files changed, 27 insertions(+), 34 deletions(-) diff --git a/contracts/interfaces/IBackingManager.sol b/contracts/interfaces/IBackingManager.sol index e1161079d6..982a79aa9e 100644 --- a/contracts/interfaces/IBackingManager.sol +++ b/contracts/interfaces/IBackingManager.sol @@ -10,12 +10,9 @@ import "./ITrading.sol"; * @title IBackingManager * @notice The BackingManager handles changes in the ERC20 balances that back an RToken. * - It computes which trades to perform, if any, and initiates these trades with the Broker. + * - rebalance() * - If already collateralized, excess assets are transferred to RevenueTraders. - * - * `manageTokens(erc20s)` and `manageTokensSortedOrder(erc20s)` are handles for getting at the - * same underlying functionality. The former allows an ERC20 list in any order, while the - * latter requires a sorted array, and executes in O(n) rather than O(n^2) time. In the - * vast majority of cases we expect the the O(n^2) function to be acceptable. + * - forwardRevenue(IERC20[] calldata erc20s) */ interface IBackingManager is IComponent, ITrading { /// Emitted when the trading delay is changed diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 3d03294a98..386a26fbfc 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -42,8 +42,6 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // ==== Invariants ==== // tradingDelay <= MAX_TRADING_DELAY and backingBuffer <= MAX_BACKING_BUFFER - // - // ... and the *much* more complicated temporal properties for _manageTokens() function init( IMain main_, diff --git a/docs/recollateralization.md b/docs/recollateralization.md index 510fff640d..3a7dde392e 100644 --- a/docs/recollateralization.md +++ b/docs/recollateralization.md @@ -1,6 +1,6 @@ # Recollateralization Trading Algorithm -Recollateralization takes place in the central loop of [`BackingManager.manageTokens()`](../contracts/p1/BackingManager). Since the BackingManager can only have open 1 trade at a time, it needs to know which tokens to try to trade and how much. This algorithm should not be gameable and should not result in unnecessary losses. +Recollateralization takes place in the central loop of [`BackingManager.rebalance()`](../contracts/p1/BackingManager). Since the BackingManager can only have open 1 trade at a time, it needs to know which tokens to try to trade and how much. This algorithm should not be gameable and should not result in unnecessary losses. ```solidity (bool doTrade, TradeRequest memory req) = RecollateralizationLibP1.prepareRecollateralizationTrade(...); diff --git a/docs/solidity-style.md b/docs/solidity-style.md index 69f23d42c9..4c8be4c18a 100644 --- a/docs/solidity-style.md +++ b/docs/solidity-style.md @@ -153,12 +153,15 @@ For each `external` or `public` function, one of these tags MUST be in the corre - stRSR.stake() - stRSR.unstake() +- stRSR.cancelUnstaking() +- stRSR.withdraw() - rToken.issue() - rToken.redeem() - {rsrTrader,rTokenTrader,backingManager}.claimRewards() - {rsrTrader,rTokenTrader,backingManager}.settleTrade() - backingManager.grantRTokenAllowances() -- backingManager.manageTokens\*() +- backingManager.rebalance\*() +- backingManager.forwardRevenue\*() - {rsrTrader,rTokenTrader}.manageToken() ### `@custom:governance` diff --git a/tasks/testing/upgrade-checker-utils/rewards.ts b/tasks/testing/upgrade-checker-utils/rewards.ts index efe8106e81..4e69385cd8 100644 --- a/tasks/testing/upgrade-checker-utils/rewards.ts +++ b/tasks/testing/upgrade-checker-utils/rewards.ts @@ -1,4 +1,5 @@ import { fp } from '#/common/numbers' +import { TradeKind } from '#/common/constants' import { whileImpersonating } from '#/utils/impersonation' import { advanceBlocks, advanceTime } from '#/utils/time' import { IRewardable } from '@typechain/IRewardable' @@ -32,7 +33,7 @@ export const claimRsrRewards = async (hre: HardhatRuntimeEnvironment, rtokenAddr const rsrRatePre = await strsr.exchangeRate() const rewards = await claimRewards(backingManager) - await backingManager.manageTokens(rewards) + await backingManager.forwardRevenue(rewards) const comp = '0xc00e94Cb662C3520282E6f5717214004A7f26888' const compContract = await hre.ethers.getContractAt('ERC20Mock', comp) @@ -41,9 +42,9 @@ export const claimRsrRewards = async (hre: HardhatRuntimeEnvironment, rtokenAddr await compContract.connect(compWhale).transfer(rsrTrader.address, fp('1e5')) }) - await rsrTrader.manageToken(comp) + await rsrTrader.manageToken(comp, TradeKind.BATCH_AUCTION) await runTrade(hre, rsrTrader, comp, false) - await rsrTrader.manageToken(rsr.address) + await rsrTrader.manageToken(rsr.address, TradeKind.BATCH_AUCTION) await strsr.payoutRewards() await advanceBlocks(hre, 100) await advanceTime(hre, 1200) diff --git a/tasks/testing/upgrade-checker-utils/rtokens.ts b/tasks/testing/upgrade-checker-utils/rtokens.ts index ef3588a193..6573909e88 100644 --- a/tasks/testing/upgrade-checker-utils/rtokens.ts +++ b/tasks/testing/upgrade-checker-utils/rtokens.ts @@ -1,4 +1,5 @@ import { bn } from '#/common/numbers' +import { TradeKind } from '#/common/constants' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { BigNumber } from 'ethers' import { Interface, LogDescription, formatEther } from 'ethers/lib/utils' @@ -53,7 +54,7 @@ export const redeemRTokens = async ( const preRedeemRTokenBal = await rToken.balanceOf(user.address) const preRedeemErc20Bals = await getAccountBalances(hre, user.address, expectedTokens) - await rToken.connect(user).redeem(redeemAmount, await basketHandler.nonce()) + await rToken.connect(user).redeem(redeemAmount) const postRedeemRTokenBal = await rToken.balanceOf(user.address) const postRedeemErc20Bals = await getAccountBalances(hre, user.address, expectedTokens) @@ -81,10 +82,6 @@ export const recollateralize = async (hre: HardhatRuntimeEnvironment, rtokenAddr console.log(`\n\n* * * * * Recollateralizing RToken ${rtokenAddress}...`) const rToken = await hre.ethers.getContractAt('RTokenP1', rtokenAddress) const main = await hre.ethers.getContractAt('IMain', await rToken.main()) - const assetRegistry = await hre.ethers.getContractAt( - 'AssetRegistryP1', - await main.assetRegistry() - ) const backingManager = await hre.ethers.getContractAt( 'BackingManagerP1', await main.backingManager() @@ -94,8 +91,7 @@ export const recollateralize = async (hre: HardhatRuntimeEnvironment, rtokenAddr await main.basketHandler() ) - const registeredERC20s = await assetRegistry.erc20s() - let r = await backingManager.manageTokens(registeredERC20s) + let r = await backingManager.rebalance(TradeKind.BATCH_AUCTION) const iface: Interface = backingManager.interface let tradesRemain = true @@ -119,7 +115,7 @@ export const recollateralize = async (hre: HardhatRuntimeEnvironment, rtokenAddr await runTrade(hre, backingManager, parsedLog.args.sell, false) } } - r = await backingManager.manageTokens(registeredERC20s) + r = await backingManager.rebalance(TradeKind.BATCH_AUCTION) } const basketStatus = await basketHandler.status() diff --git a/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts b/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts index b986fa09f6..ae507db89a 100644 --- a/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts +++ b/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts @@ -2,15 +2,15 @@ import { whileImpersonating } from '#/utils/impersonation' import { HardhatRuntimeEnvironment } from 'hardhat/types' import { ProposalBuilder, buildProposal } from '../governance' import { Proposal } from '#/utils/subgraph' -import { overrideOracle, pushOracleForward, pushOraclesForward } from '../oracles' +import { overrideOracle, pushOracleForward } from '../oracles' import { networkConfig } from '#/common/configuration' import { recollateralize } from '../rtokens' +import { TradeKind } from '#/common/constants' import { bn, fp } from '#/common/numbers' import { advanceBlocks, advanceTime, getLatestBlockTimestamp } from '#/utils/time' import { LogDescription, Interface } from 'ethers/lib/utils' import { logToken } from '../logs' -import { runTrade } from '../trades' -import { CollateralStatus, QUEUE_START } from '#/common/constants' +import { QUEUE_START } from '#/common/constants' import { getTrade } from '#/utils/trades' import { whales } from '../constants' import { BigNumber } from 'ethers' @@ -34,7 +34,7 @@ export default async ( // check Broker updates const broker = await hre.ethers.getContractAt('BrokerP1', await main.broker()) const preGnosis = await broker.gnosis() - const preTrade = await broker.tradeImplementation() + const preTrade = await broker.batchTradeImplementation() const gnosisFactory = await hre.ethers.getContractFactory('EasyAuction') const newGnosis = await gnosisFactory.deploy() @@ -43,11 +43,11 @@ export default async ( await whileImpersonating(hre, timelock.address, async (govSigner) => { await broker.connect(govSigner).setGnosis(newGnosis.address) - await broker.connect(govSigner).setTradeImplementation(newTrade.address) + await broker.connect(govSigner).setBatchTradeImplementation(newTrade.address) }) const postGnosis = await broker.gnosis() - const postTrade = await broker.tradeImplementation() + const postTrade = await broker.batchTradeImplementation() if (postGnosis != newGnosis.address) { throw new Error(`setGnosis() failure: received: ${postGnosis} / expected: ${newGnosis.address}`) @@ -55,13 +55,13 @@ export default async ( if (postTrade != newTrade.address) { throw new Error( - `setTradeImplementation() failure: received: ${postTrade} / expected: ${newTrade.address}` + `setBatchTradeImplementation() failure: received: ${postTrade} / expected: ${newTrade.address}` ) } await whileImpersonating(hre, timelock.address, async (govSigner) => { await broker.connect(govSigner).setGnosis(preGnosis) - await broker.connect(govSigner).setTradeImplementation(preTrade) + await broker.connect(govSigner).setBatchTradeImplementation(preTrade) }) // check stRSR updates @@ -121,8 +121,7 @@ export default async ( // buy half of the auction for the absolute minimum price console.log('\n* * * * * Try to break broker...') - const registeredERC20s = await ar.erc20s() - let r = await backingManager.manageTokens(registeredERC20s) + const r = await backingManager.rebalance(TradeKind.BATCH_AUCTION) const resp = await r.wait() for (const event of resp.events!) { let parsedLog: LogDescription | undefined @@ -196,8 +195,7 @@ export default async ( export const proposal_2_1_0: ProposalBuilder = async ( hre: HardhatRuntimeEnvironment, - rTokenAddress: string, - governorAddress: string + rTokenAddress: string ): Promise => { const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) const main = await hre.ethers.getContractAt('IMain', await rToken.main()) @@ -213,7 +211,7 @@ export const proposal_2_1_0: ProposalBuilder = async ( await stRSR.populateTransaction.upgradeTo('0xfDa8C62d86E426D5fB653B6c44a455Bb657b693f'), await basketHandler.populateTransaction.upgradeTo('0x5c13b3b6f40aD4bF7aa4793F844BA24E85482030'), await rToken.populateTransaction.upgradeTo('0x5643D5AC6b79ae8467Cf2F416da6D465d8e7D9C1'), - await broker.populateTransaction.setTradeImplementation( + await broker.populateTransaction.setBatchTradeImplementation( '0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439' ), ] diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index b705c1e8f2..904cddcb6f 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2662,7 +2662,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(near(await rToken.balanceOf(furnace.address), minBuyAmtRToken, 100)).to.equal(true) }) - it('Should not overspend if backingManager.manageTokens() is called with duplicate tokens', async () => { + it('Should not oversend if backingManager.forwardRevenue() is called with duplicate tokens', async () => { expect(await basketHandler.fullyCollateralized()).to.be.true // Change redemption rate for AToken and CToken to double From 6e9e32f60c524af0086b463e90533d31b616d231 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 21:32:52 -0400 Subject: [PATCH 322/462] remove kruft event --- contracts/p1/mixins/RewardableLib.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/p1/mixins/RewardableLib.sol b/contracts/p1/mixins/RewardableLib.sol index 6cdcd42008..9a471ac812 100644 --- a/contracts/p1/mixins/RewardableLib.sol +++ b/contracts/p1/mixins/RewardableLib.sol @@ -16,8 +16,6 @@ library RewardableLibP1 { using Address for address; using SafeERC20 for IERC20; - event Complete(); - // === Used by Traders + RToken === /// Claim all rewards From e1b22548860ba066400e7e507b06169a89bf6b16 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 21:34:05 -0400 Subject: [PATCH 323/462] update RewardableLibP1 comments --- contracts/p1/mixins/RewardableLib.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/p1/mixins/RewardableLib.sol b/contracts/p1/mixins/RewardableLib.sol index 9a471ac812..6490adf0f8 100644 --- a/contracts/p1/mixins/RewardableLib.sol +++ b/contracts/p1/mixins/RewardableLib.sol @@ -21,7 +21,7 @@ library RewardableLibP1 { /// Claim all rewards /// @custom:interaction mostly CEI but see comments // actions: - // do asset.claimRewards() for asset in assets + // try erc20.claimRewards() for erc20 in erc20s function claimRewards(IAssetRegistry reg) internal { Registry memory registry = reg.getRegistry(); for (uint256 i = 0; i < registry.erc20s.length; ++i) { @@ -34,7 +34,7 @@ library RewardableLibP1 { /// Claim rewards for a single ERC20 /// @custom:interaction mostly CEI but see comments // actions: - // do asset.claimRewards() + // try erc20.claimRewards() function claimRewardsSingle(IAsset asset) internal { // empty try/catch because not every erc20 will be wrapped & have a claimRewards func // solhint-disable-next-line From d6459b34c6e3607c78720efe96cbc1276beb873b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 21:41:23 -0400 Subject: [PATCH 324/462] CHANGELOG.md --- CHANGELOG.md | 62 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3091117fda..81eefb5fd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,17 +9,17 @@ - Add `size()` getter for number of registered assets - `BackingManager` [+2 slots] - - Remove `delegatecall` during reward claiming - - Modify `settleTrade(IERC20 sell)` to call `rebalance()` when caller is a trade it deployed. - - Remove `manageTokensSortedOrder(IERC20[] memory erc20s)` - Replace `manageTokens(IERC20[] memory erc20s)` with: - - `rebalance() ` - the argument-agnostic trading part of the logic + - `rebalance(TradeKind)` + `RecollateralizationLibP1` - Modify trading algorithm to not trade RToken, and instead dissolve it when it has a balance above ~1e6. "dissolve" = melt() with a basketsNeeded change, like redemption. - - Add significant caching of asset state to `RecollateralizationLibP1` in addition to removing RToken trading (due to dissolve) - - `forwardRevenue(IERC20[] memory erc20s)` - the revenue distributing part - - Modify backingBuffer logic to keep the backing buffer in collateral tokens only. Fix subtle and inconsequential bug that resulted in not maximizing RToken minting locally. + - Add significant caching to save gas + - `forwardRevenue(IERC20[] memory erc20s)` + - Modify backingBuffer logic to keep the backing buffer in collateral tokens only. Fix subtle and inconsequential bug that resulted in not maximizing RToken minting locally, though overall RToken production would not have been lower. - Use `nonReentrant` over CEI pattern for gas improvement. related to discussion of [this](https://github.com/code-423n4/2023-01-reserve-findings/issues/347) cross-contract reentrancy risk - move `nonReentrant` up outside `tryTrade` internal helper + - Remove `manageTokensSortedOrder(IERC20[] memory erc20s)` + - Modify `settleTrade(IERC20 sell)` to call `rebalance()` when caller is a trade it deployed. + - Remove all `delegatecall` during reward claiming - Functions now revert on unproductive executions, instead of no-op - Do not trade until a warmupPeriod (last time SOUND was newly attained) has passed - Add `cacheComponents()` refresher to be called on upgrade @@ -27,11 +27,11 @@ - `BasketHandler` [+5 slots] - - Add new gov param: `warmupPeriod`. Has `setWarmupPeriod(..)` + - Add new gov param: `warmupPeriod` with setter `setWarmupPeriod(..)` and event `WarmupPeriodSet()` - Add `isReady()` view - Extract basket switching logic out into external library `BasketLibP1` - Enforce `setPrimeBasket()` does not change the net value of a basket in terms of its target units - - Add `quoteCustomRedemption(uint48[] basketNonces, uint192[] memory portions, ..)` to quote a linear combination of current-or-previous baskets. + - Add `quoteCustomRedemption(uint48[] basketNonces, uint192[] memory portions, ..)` to quote a linear combination of current-or-previous baskets for redemption - Add `getHistoricalBasket(uint48 basketNonce)` view - `Broker` [+1 slot] @@ -39,8 +39,13 @@ - Add `TradeKind` enum to track multiple trading types - Add new dutch auction `DutchTrade` - Add minimum auction length of 24s; applies to all auction types - - Add `setDutchAuctionLength(..)` governance setter + - Rename variable `auctionLength` -> `batchAuctionLength` + - Rename setter `setAuctionLength()` -> `setBatchAuctionLength()` + - Rename event `AuctionLengthSet()` -> `BatchAuctionLengthSet()` + - Add `dutchAuctionLength` and `setDutchAuctionLength()` setter and `DutchAuctionLengthSet()` event + - Add `dutchTradeImplementation` and `setDutchTradeImplementation()` setter and `DutchTradeImplementationSet()` event - Modify `openTrade(TradeRequest memory reg)` -> `openTrade(TradeKind kind, TradeRequest memory req)` + - Allow when paused / frozen, since caller must be in-system - `Deployer` [+0 slots] - Modify to handle new gov params: `warmupPeriod`, `dutchAuctionLength`, and `withdrawalLeak` @@ -55,6 +60,7 @@ - `pause()` -> `pauseTrading()` and `pauseIssuance()` - `unpause()` -> `unpauseTrading()` and `unpauseIssuance()` - `pausedOrFrozen()` -> `tradingPausedOrFrozen()` and `issuancePausedOrFrozen()` + - `PausedSet()` event -> `TradingPausedSet()` and `IssuancePausedSet()` - `RevenueTrader` [+3 slots] @@ -63,28 +69,36 @@ - `manageToken(IERC20 sell)` -> `manageToken(IERC20 sell, TradeKind kind)` - Allow `manageToken(..)` to open dust auctions - Revert on 0 balance or collision auction, instead of no-op - - Refresh buy and sell asset + - Refresh buy and sell asset before trade + - `settleTrade(IERC20)` now distributes `tokenToBuy`, instead of requiring separate `manageToken(IERC20)` call - `RToken` [+0 slots] - Remove `exchangeRateIsValidAfter` modifier from all functions except `setBasketsNeeded()` - Modify `issueTo()` to revert before `warmupPeriod` - - Remove `redeem(uint256 amount, uint48 basketNonce)` and `redeemTo(address recipient, uint256 amount, uint48 basketNonce)` - - Add `redeem(uint256 amount)` and `redeemTo(address recipient, uint256 amount)` - always on current basket nonce; reverts on partial redemption - - Add new `redeemCustom(.., uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, ..)` function to allow redemption from a linear combination of current and previous baskets. All non-standard possibly lossy redemptions must go through this function + - Modify `redeem(uint256 amount, uint48 basketNonce)` -> `redeem(uint256 amount)`. Redemptions are on the current basket nonce and revert under partial redemption + - Modify `redeemTo(address recipient, uint256 amount, uint48 basketNonce)` -> `redeemTo(address recipient, uint256 amount)`. Redemptions are on the current basket nonce and revert under partial redemption + - Add new `redeemCustom(.., uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, ..)` function to allow redemption from a linear combination of current and previous baskets. During rebalancing this method of redemption will provide a higher overall redemption value than prorata redemption on the current basket nonce would. - `mint(address recipient, uint256 amtRToken)` -> `mint(uint256 amtRToken)`, since recipient is _always_ BackingManager. Expand scope to include adjustments to `basketsNeeded` - Add `dissolve(uint256 amount)`: burns RToken and reduces `basketsNeeded`, similar to redemption. Only callable by BackingManager - Modify `setBasketsNeeded(..)` to revert when supply is 0 - `StRSR` [+2 slots] - - Remove duplicate `stakeRate()` getter (it's 1 / exchangeRate()) - - Add `withdrawalLeak` gov param, with `setWithdrawalLeak(..)` setter and `leakyRefresh()` helper + - Remove duplicate `stakeRate()` getter (same as `1 / exchangeRate()`) + - Add `withdrawalLeak` gov param, with `setWithdrawalLeak(..)` setter and `WithdrawalLeakSet()` event - Modify `withdraw()` to allow a small % of RSR too exit without paying to refresh all assets - Modify `withdraw()` to check for `warmupPeriod` - Add ability to re-stake during a withdrawal via `cancelUnstake(uint256 endId)` + - Add `UnstakingCancelled()` event - `StRSRVotes` [+0 slots] - Add `stakeAndDelegate(uint256 rsrAmount, address delegate)` function, to encourage people to receive voting weight upon staking #### Facades +- `FacadeWrite` + - Do not automatically grant Guardian PAUSER/SHORT_FREEZER/LONG_FREEZER + - Do not automatically grant Owner PAUSER/SHORT_FREEZER/LONG_FREEZER + - Add ability to initialize with multiple pausers, short freezers, and long freezers + - Modify `setupGovernance(.., address owner, address guardian, address pauser)` -> `setupGovernance(.., GovernanceRoles calldata govRoles)` + - Update `DeploymentParams` and `Implementations` struct to contain new gov params and dutch trade plugin - `FacadeAct` - Remove `getActCalldata(..)` - Modify `runRevenueAuctions(..)` to work with both 3.0.0 and 2.1.0 interfaces @@ -100,7 +114,7 @@ #### DutchTrade -Implements a new, simpler, trading method. Intended to be the new dominant trading method, with GnosisTrade (batch auctions) available as a faster-but-more-gas-expensive backup option. +A cheaper, simpler, trading method. Intended to be the new dominant trading method, with GnosisTrade (batch auctions) available as a faster-but-more-gas-expensive backup option. DutchTrade implements a two-stage, single-lot, falling price dutch auction. In the first 40% of the auction, the price falls from 1000x to the best-case price in a geometric/exponential decay as a price manipulation defense mechanism. Bids are not expected to occur (but note: unlike the GnosisTrade batch auction, this mechanism is not resistant to _arbitrary_ price manipulation). @@ -111,7 +125,8 @@ Duration: 30 min (default) #### Assets and Collateral - Add `version() return (string)` getter to pave way for separation of asset versioning and core protocol versioning -- Remove expectation of `delegatecall` during `claimRewards()` call, though assets can still do it and it won't break anything +- Update `claimRewards()` on all assets to 3.0.0-style, without `delegatecall` +- Add `lastSave()` to `RTokenAsset` ## 2.1.0 @@ -235,8 +250,6 @@ Candidate release for the "all clear" milestone. There wasn't any real usage of - Add `FacadeRead.redeem(IRToken rToken, uint256 amount, uint48 basketNonce)` to return the expected redemption quantities on the basketNonce, or revert - Integrate with OZ 4.7.3 Governance (changes to `quorum()`/t`proposalThreshold()`) -TODO - ## 1.1.0 - Introduce semantic versioning to the Deployer and RToken @@ -267,20 +280,17 @@ event RTokenCreated( - Add `version()` getter on Deployer, Main, and all Components, via mix-in. To be updated with each subsequent release. -Deploy commit [d757d3a5a6097ae42c71fc03a7c787ec001d2efc](https://github.com/reserve-protocol/protocol/commit/d757d3a5a6097ae42c71fc03a7c787ec001d2efc) +[d757d3a5a6097ae42c71fc03a7c787ec001d2efc](https://github.com/reserve-protocol/protocol/commit/d757d3a5a6097ae42c71fc03a7c787ec001d2efc) ## 1.0.0 (This release is the one from the canonical lauch onstage in Bogota. We were missing semantic versioning at the time, but we call this the 1.0.0 release retroactively.) -Deploy commit [eda322894a5ed379bbda2b399c9d1cc65aa8c132](https://github.com/reserve-protocol/protocol/commit/eda322894a5ed379bbda2b399c9d1cc65aa8c132) +[eda322894a5ed379bbda2b399c9d1cc65aa8c132](https://github.com/reserve-protocol/protocol/commit/eda322894a5ed379bbda2b399c9d1cc65aa8c132) # Links - - - -- [[3.0.0]](https://github.com/reserve-protocol/protocol/releases/tag/3.0.0) +- [[Unreleased]](https://github.com/reserve-protocol/protocol/releases/tag/3.0.0-rc1) - https://github.com/reserve-protocol/protocol/compare/2.1.0-rc4...3.0.0 - [[2.1.0]](https://github.com/reserve-protocol/protocol/releases/tag/2.1.0-rc4) - https://github.com/reserve-protocol/protocol/compare/2.0.0-candidate-4...2.1.0-rc4 From 647b6c0680ad5abd6eebea063d4c9924c1b1b2fa Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 22:36:29 -0400 Subject: [PATCH 325/462] add summaries --- CHANGELOG.md | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81eefb5fd1..14655b481e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,15 @@ # Changelog -## 3.0.0 +## 3.0.0 - Unreleased #### Core Protocol Contracts - `AssetRegistry` [+1 slot] + Summary: Other component contracts need to know when refresh() was last called - Add last refresh timestamp tracking and expose via `lastRefresh()` getter - Add `size()` getter for number of registered assets - `BackingManager` [+2 slots] + Summary: manageTokens was broken out into rebalancing and surplus-forwarding functions to allow users to more precisely call the protocol - Replace `manageTokens(IERC20[] memory erc20s)` with: - `rebalance(TradeKind)` + `RecollateralizationLibP1` @@ -26,6 +28,7 @@ - Bugfix: consider `maxTradeVolume()` from both assets on a trade, not just 1 - `BasketHandler` [+5 slots] + Summary: Introduces a notion of basket warmup to defend against short-term oracle manipulation attacks. Prevent RTokens from changing in value due to governance - Add new gov param: `warmupPeriod` with setter `setWarmupPeriod(..)` and event `WarmupPeriodSet()` - Add `isReady()` view @@ -35,6 +38,7 @@ - Add `getHistoricalBasket(uint48 basketNonce)` view - `Broker` [+1 slot] + Summary: Add a new trading plugin that performs single-lot dutch auctions. Batch auctions via Gnosis EasyAuction are expected to be the backup auction (can be faster if more gas costly) going forward. - Add `TradeKind` enum to track multiple trading types - Add new dutch auction `DutchTrade` @@ -48,13 +52,19 @@ - Allow when paused / frozen, since caller must be in-system - `Deployer` [+0 slots] + Summary: Support new governance params + - Modify to handle new gov params: `warmupPeriod`, `dutchAuctionLength`, and `withdrawalLeak` - Do not grant OWNER any of the roles other than ownership + - `Distributor` [+0 slots] - - Remove `notPausedOrFrozen` modifier from `distribute()`; caller only hurts themselves + Summary: Waste of gas to double-check this, since caller is another component + - Remove `notPausedOrFrozen` modifier from `distribute()` - `Furnace` [+0 slots] + Summary: Should be able to melting while redeeming when frozen - Modify `melt()` modifier: `notPausedOrFrozen` -> `notFrozen` - `Main` [+0 slots] + Summary: Breakup pausing into two types of pausing: issuance and trading - Break `paused` into `issuancePaused` and `tradingPaused` - `pause()` -> `pauseTrading()` and `pauseIssuance()` @@ -63,6 +73,7 @@ - `PausedSet()` event -> `TradingPausedSet()` and `IssuancePausedSet()` - `RevenueTrader` [+3 slots] + Summary: QoL improvements. Make compatible with new dutch auction trading method - Remove `delegatecall` during reward claiming - Add `cacheComponents()` refresher to be called on upgrade @@ -73,6 +84,8 @@ - `settleTrade(IERC20)` now distributes `tokenToBuy`, instead of requiring separate `manageToken(IERC20)` call - `RToken` [+0 slots] + Summary: Provide multiple redemption methods for when fullyCollateralized vs not. Should support a higher RToken price during basket changes. + - Remove `exchangeRateIsValidAfter` modifier from all functions except `setBasketsNeeded()` - Modify `issueTo()` to revert before `warmupPeriod` - Modify `redeem(uint256 amount, uint48 basketNonce)` -> `redeem(uint256 amount)`. Redemptions are on the current basket nonce and revert under partial redemption @@ -81,34 +94,47 @@ - `mint(address recipient, uint256 amtRToken)` -> `mint(uint256 amtRToken)`, since recipient is _always_ BackingManager. Expand scope to include adjustments to `basketsNeeded` - Add `dissolve(uint256 amount)`: burns RToken and reduces `basketsNeeded`, similar to redemption. Only callable by BackingManager - Modify `setBasketsNeeded(..)` to revert when supply is 0 + - `StRSR` [+2 slots] + Summary: Add the ability to cancel unstakings and a withdrawal() gas-saver to allow small RSR amounts to be exempt from refreshes + - Remove duplicate `stakeRate()` getter (same as `1 / exchangeRate()`) - Add `withdrawalLeak` gov param, with `setWithdrawalLeak(..)` setter and `WithdrawalLeakSet()` event - Modify `withdraw()` to allow a small % of RSR too exit without paying to refresh all assets - Modify `withdraw()` to check for `warmupPeriod` - Add ability to re-stake during a withdrawal via `cancelUnstake(uint256 endId)` - Add `UnstakingCancelled()` event + - `StRSRVotes` [+0 slots] - Add `stakeAndDelegate(uint256 rsrAmount, address delegate)` function, to encourage people to receive voting weight upon staking #### Facades - `FacadeWrite` + Summary: More expressive and fine-grained control over the set of pausers and freezers + - Do not automatically grant Guardian PAUSER/SHORT_FREEZER/LONG_FREEZER - Do not automatically grant Owner PAUSER/SHORT_FREEZER/LONG_FREEZER - Add ability to initialize with multiple pausers, short freezers, and long freezers - Modify `setupGovernance(.., address owner, address guardian, address pauser)` -> `setupGovernance(.., GovernanceRoles calldata govRoles)` - Update `DeploymentParams` and `Implementations` struct to contain new gov params and dutch trade plugin + - `FacadeAct` + Summary: Remove unused getActCalldata and add way to run revenue auctions + - Remove `getActCalldata(..)` - Modify `runRevenueAuctions(..)` to work with both 3.0.0 and 2.1.0 interfaces + - `FacadeRead` + Summary: Add new data summary views frontends may be interested in + - Remove `basketNonce` from `redeem(.., uint48 basketNonce)` - Remove `traderBalances(..)` - `balancesAcrossAllTraders(IBackingManager) returns (IERC20[] memory erc20s, uint256[] memory balances, uint256[] memory balancesNeededByBackingManager)` - Add `nextRecollateralizationAuction(..) returns (bool canStart, IERC20 sell, IERC20 buy, uint256 sellAmount)` - Add `revenueOverview(IRevenueTrader) returns ( IERC20[] memory erc20s, bool[] memory canStart, uint256[] memory surpluses, uint256[] memory minTradeAmounts)` -- Remove `FacadeMonitor` + +- Remove `FacadeMonitor` - redundant with `nextRecollateralizationAuction()` and `revenueOverview()` ### Plugins From be89c47ea99f792aba6eb22207f15eadfde6d9b6 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 22 May 2023 22:43:41 -0400 Subject: [PATCH 326/462] CHANGELOG.md: 3.0.0 upgrade instructions --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14655b481e..cb6d15e377 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## 3.0.0 - Unreleased +Warning: RTokens upgrading to this major release should proceed carefully. In addition to updating all component addresses: + +- `cacheComponents()` MUST be called to prevent certain functions from reverting, on both the BackingManager and both RevenueTraders +- `setWarmupPeriod()` can be called on the BasketHandler to turn on the warmup period, optionally +- `setWithdrawalLeak()` can be called on StRSR to start saving gas on withdrawals, optionally +- `setDutchAuctionLength()` can be called (with a value such as 1800) or left at duration 0s to keep dutch auctions disabled + +Collateral / Asset plugins from 2.1.0 do not need to be upgraded + #### Core Protocol Contracts - `AssetRegistry` [+1 slot] From 5028f3adf356e014fb4530c01be47afbec427e7e Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 23 May 2023 14:28:42 -0400 Subject: [PATCH 327/462] FacadeRead.overview(): do not revert if an asset lotLow is 0 --- contracts/facade/FacadeRead.sol | 1 + test/FacadeRead.test.ts | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index d4c51e8042..2436676128 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -321,6 +321,7 @@ contract FacadeRead is IFacadeRead { surpluses[i] = reg.erc20s[i].balanceOf(address(revenueTrader)); (uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/tok} + if (lotLow == 0) continue; // {qTok} = {UoA} / {UoA/tok} minTradeAmounts[i] = minTradeVolume.div(lotLow).shiftl_toUint( diff --git a/test/FacadeRead.test.ts b/test/FacadeRead.test.ts index 953cac74dd..8ccfe5efa5 100644 --- a/test/FacadeRead.test.ts +++ b/test/FacadeRead.test.ts @@ -406,6 +406,12 @@ describe('FacadeRead contract', () => { const tokenSurplus = bn('0.5e18') await token.connect(addr1).transfer(trader.address, tokenSurplus) + // Set lotLow to 0 == revenueOverview() should not revert + await setOraclePrice(usdcAsset.address, bn('0')) + await usdcAsset.refresh() + const [lotLow] = await usdcAsset.lotPrice() + expect(lotLow).to.equal(0) + // revenue const [erc20s, canStart, surpluses, minTradeAmounts] = await facade.callStatic.revenueOverview(trader.address) @@ -424,7 +430,7 @@ describe('FacadeRead contract', () => { const asset = await ethers.getContractAt('IAsset', await assetRegistry.toAsset(erc20s[i])) const [low] = await asset.price() expect(minTradeAmounts[i]).to.equal( - minTradeVolume.mul(bn('10').pow(await asset.erc20Decimals())).div(low) + low.gt(0) ? minTradeVolume.mul(bn('10').pow(await asset.erc20Decimals())).div(low) : 0 ) // 1% oracleError } From 9ade0b33b4b05ab725cd5799e21d830d36c6a79d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 23 May 2023 20:32:44 -0400 Subject: [PATCH 328/462] move trading functions around. FacadeRead was over limit --- contracts/facade/FacadeAct.sol | 166 +++++++++++++++++++- contracts/facade/FacadeRead.sol | 148 ----------------- contracts/interfaces/IFacadeAct.sol | 33 ++++ contracts/interfaces/IFacadeRead.sol | 40 +---- test/{FacadeRead.test.ts => Facade.test.ts} | 6 +- 5 files changed, 200 insertions(+), 193 deletions(-) rename test/{FacadeRead.test.ts => Facade.test.ts} (99%) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 049de85de1..a0671dd27f 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.17; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/Multicall.sol"; +import "../plugins/trading/DutchTrade.sol"; import "../interfaces/IBackingManager.sol"; import "../interfaces/IFacadeAct.sol"; import "../interfaces/IFacadeRead.sol"; @@ -13,6 +14,8 @@ import "../interfaces/IFacadeRead.sol"; * For use with ^3.0.0 RTokens. */ contract FacadeAct is IFacadeAct, Multicall { + using FixLib for uint192; + function claimRewards(IRToken rToken) public { IMain main = rToken.main(); main.backingManager().claimRewards(); @@ -21,8 +24,8 @@ contract FacadeAct is IFacadeAct, Multicall { } /// To use this, first call: - /// - FacadeRead.auctionsSettleable(revenueTrader) - /// - FacadeRead.revenueOverview(revenueTrader) + /// - auctionsSettleable(revenueTrader) + /// - revenueOverview(revenueTrader) /// If either arrays returned are non-empty, then can execute this function productively. /// Logic: /// For each ERC20 in `toSettle`: @@ -56,7 +59,7 @@ contract FacadeAct is IFacadeAct, Multicall { ); success = success; // hush warning } else { - revert("unrecognized version"); + revertUnrecognizedVersion(); } } @@ -74,8 +77,163 @@ contract FacadeAct is IFacadeAct, Multicall { ); success = success; // hush warning } else { - revert("unrecognized version"); + revertUnrecognizedVersion(); + } + } + } + + // === Static Calls === + + /// To use this, call via callStatic. + /// @return erc20s The ERC20s that have auctions that can be started + /// @return canStart If the ERC20 auction can be started + /// @return surpluses {qTok} The surplus amount + /// @return minTradeAmounts {qTok} The minimum amount worth trading + /// @custom:static-call + function revenueOverview(IRevenueTrader revenueTrader) + external + returns ( + IERC20[] memory erc20s, + bool[] memory canStart, + uint256[] memory surpluses, + uint256[] memory minTradeAmounts + ) + { + uint192 minTradeVolume = revenueTrader.minTradeVolume(); // {UoA} + Registry memory reg = revenueTrader.main().assetRegistry().getRegistry(); + + // Forward ALL revenue + { + IBackingManager bm = revenueTrader.main().backingManager(); + bytes1 majorVersion = bytes(bm.version())[0]; + + if (majorVersion == MAJOR_VERSION_3) { + // solhint-disable-next-line no-empty-blocks + try bm.forwardRevenue(reg.erc20s) {} catch {} + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = address(bm).call{ value: 0 }( + abi.encodeWithSignature("manageTokens(address[])", reg.erc20s) + ); + success = success; // hush warning + } else { + revertUnrecognizedVersion(); + } + } + + erc20s = new IERC20[](reg.erc20s.length); + canStart = new bool[](reg.erc20s.length); + surpluses = new uint256[](reg.erc20s.length); + minTradeAmounts = new uint256[](reg.erc20s.length); + // Calculate which erc20s can have auctions started + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + // Settle first if possible. Required so we can assess full available balance + ITrade trade = revenueTrader.trades(reg.erc20s[i]); + if (address(trade) != address(0) && trade.canSettle()) { + revenueTrader.settleTrade(reg.erc20s[i]); + } + + uint48 tradesOpen = revenueTrader.tradesOpen(); + erc20s[i] = reg.erc20s[i]; + surpluses[i] = reg.erc20s[i].balanceOf(address(revenueTrader)); + + (uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/tok} + if (lotLow == 0) continue; + + // {qTok} = {UoA} / {UoA/tok} + minTradeAmounts[i] = minTradeVolume.div(lotLow).shiftl_toUint( + int8(reg.assets[i].erc20Decimals()) + ); + + bytes1 majorVersion = bytes(revenueTrader.version())[0]; + if ( + reg.erc20s[i].balanceOf(address(revenueTrader)) > minTradeAmounts[i] && + revenueTrader.trades(reg.erc20s[i]) == ITrade(address(0)) + ) { + if (majorVersion == MAJOR_VERSION_3) { + // solhint-disable-next-line no-empty-blocks + try revenueTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = address(revenueTrader).call{ value: 0 }( + abi.encodeWithSignature("manageToken(address)", erc20s[i]) + ); + success = success; // hush warning + } else { + revertUnrecognizedVersion(); + } + + if (revenueTrader.tradesOpen() - tradesOpen > 0) { + canStart[i] = true; + } + } + } + } + + /// To use this, call via callStatic. + /// If canStart is true, call backingManager.rebalance(). May require settling a + /// trade first; see auctionsSettleable. + /// @return canStart true iff a recollateralization auction can be started + /// @return sell The sell token in the auction + /// @return buy The buy token in the auction + /// @return sellAmount {qSellTok} How much would be sold + /// @custom:static-call + function nextRecollateralizationAuction(IBackingManager bm) + external + returns ( + bool canStart, + IERC20 sell, + IERC20 buy, + uint256 sellAmount + ) + { + IERC20[] memory erc20s = bm.main().assetRegistry().erc20s(); + + // Settle any settle-able open trades + if (bm.tradesOpen() > 0) { + for (uint256 i = 0; i < erc20s.length; ++i) { + ITrade trade = bm.trades(erc20s[i]); + if (address(trade) != address(0) && trade.canSettle()) { + bm.settleTrade(erc20s[i]); + break; // backingManager can only have 1 trade open at a time + } + } + } + + // If no auctions ongoing, try to find a new auction to start + if (bm.tradesOpen() == 0) { + bytes1 majorVersion = bytes(bm.version())[0]; + + if (majorVersion == MAJOR_VERSION_3) { + // solhint-disable-next-line no-empty-blocks + try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch {} + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + IERC20[] memory emptyERC20s = new IERC20[](0); + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = address(bm).call{ value: 0 }( + abi.encodeWithSignature("manageTokens(address[])", emptyERC20s) + ); + success = success; // hush warning + } else { + revertUnrecognizedVersion(); + } + + // Find the started auction + for (uint256 i = 0; i < erc20s.length; ++i) { + DutchTrade trade = DutchTrade(address(bm.trades(erc20s[i]))); + if (address(trade) != address(0)) { + canStart = true; + sell = trade.sell(); + buy = trade.buy(); + sellAmount = trade.sellAmount(); + } } } } + + // === Private === + + function revertUnrecognizedVersion() private pure { + revert("unrecognized version"); + } } diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 2436676128..85bd454b5f 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -206,158 +206,10 @@ contract FacadeRead is IFacadeRead { } } - /// To use this, call via callStatic. - /// If canStart is true, call backingManager.rebalance(). May require settling a - /// trade first; see auctionsSettleable. - /// @return canStart true iff a recollateralization auction can be started - /// @return sell The sell token in the auction - /// @return buy The buy token in the auction - /// @return sellAmount {qSellTok} How much would be sold - /// @custom:static-call - function nextRecollateralizationAuction(IBackingManager bm) - external - returns ( - bool canStart, - IERC20 sell, - IERC20 buy, - uint256 sellAmount - ) - { - IERC20[] memory erc20s = bm.main().assetRegistry().erc20s(); - - // Settle any settle-able open trades - if (bm.tradesOpen() > 0) { - for (uint256 i = 0; i < erc20s.length; ++i) { - ITrade trade = bm.trades(erc20s[i]); - if (address(trade) != address(0) && trade.canSettle()) { - bm.settleTrade(erc20s[i]); - break; // backingManager can only have 1 trade open at a time - } - } - } - - // If no auctions ongoing, try to find a new auction to start - if (bm.tradesOpen() == 0) { - bytes1 majorVersion = bytes(bm.version())[0]; - - if (majorVersion == MAJOR_VERSION_3) { - // solhint-disable-next-line no-empty-blocks - try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { - IERC20[] memory emptyERC20s = new IERC20[](0); - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = address(bm).call{ value: 0 }( - abi.encodeWithSignature("manageTokens(address[])", emptyERC20s) - ); - success = success; // hush warning - } else { - revert("unrecognized version"); - } - - // Find the started auction - for (uint256 i = 0; i < erc20s.length; ++i) { - DutchTrade trade = DutchTrade(address(bm.trades(erc20s[i]))); - if (address(trade) != address(0)) { - canStart = true; - sell = trade.sell(); - buy = trade.buy(); - sellAmount = trade.sellAmount(); - } - } - } - } - - /// To use this, call via callStatic. - /// @return erc20s The ERC20s that have auctions that can be started - /// @return canStart If the ERC20 auction can be started - /// @return surpluses {qTok} The surplus amount - /// @return minTradeAmounts {qTok} The minimum amount worth trading - /// @custom:static-call - function revenueOverview(IRevenueTrader revenueTrader) - external - returns ( - IERC20[] memory erc20s, - bool[] memory canStart, - uint256[] memory surpluses, - uint256[] memory minTradeAmounts - ) - { - uint192 minTradeVolume = revenueTrader.minTradeVolume(); // {UoA} - Registry memory reg = revenueTrader.main().assetRegistry().getRegistry(); - - // Forward ALL revenue - { - IBackingManager bm = revenueTrader.main().backingManager(); - bytes1 majorVersion = bytes(bm.version())[0]; - - if (majorVersion == MAJOR_VERSION_3) { - // solhint-disable-next-line no-empty-blocks - try bm.forwardRevenue(reg.erc20s) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = address(bm).call{ value: 0 }( - abi.encodeWithSignature("manageTokens(address[])", reg.erc20s) - ); - success = success; // hush warning - } else { - revert("unrecognized version"); - } - } - - erc20s = new IERC20[](reg.erc20s.length); - canStart = new bool[](reg.erc20s.length); - surpluses = new uint256[](reg.erc20s.length); - minTradeAmounts = new uint256[](reg.erc20s.length); - // Calculate which erc20s can have auctions started - for (uint256 i = 0; i < reg.erc20s.length; ++i) { - // Settle first if possible. Required so we can assess full available balance - ITrade trade = revenueTrader.trades(reg.erc20s[i]); - if (address(trade) != address(0) && trade.canSettle()) { - revenueTrader.settleTrade(reg.erc20s[i]); - } - - uint48 tradesOpen = revenueTrader.tradesOpen(); - erc20s[i] = reg.erc20s[i]; - surpluses[i] = reg.erc20s[i].balanceOf(address(revenueTrader)); - - (uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/tok} - if (lotLow == 0) continue; - - // {qTok} = {UoA} / {UoA/tok} - minTradeAmounts[i] = minTradeVolume.div(lotLow).shiftl_toUint( - int8(reg.assets[i].erc20Decimals()) - ); - - bytes1 majorVersion = bytes(revenueTrader.version())[0]; - if ( - reg.erc20s[i].balanceOf(address(revenueTrader)) > minTradeAmounts[i] && - revenueTrader.trades(reg.erc20s[i]) == ITrade(address(0)) - ) { - if (majorVersion == MAJOR_VERSION_3) { - // solhint-disable-next-line no-empty-blocks - try revenueTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = address(revenueTrader).call{ value: 0 }( - abi.encodeWithSignature("manageToken(address)", erc20s[i]) - ); - success = success; // hush warning - } else { - revert("unrecognized version"); - } - - if (revenueTrader.tradesOpen() - tradesOpen > 0) { - canStart[i] = true; - } - } - } - } - // === Views === /// @param account The account for the query /// @return unstakings All the pending StRSR unstakings for an account - /// @custom:view function pendingUnstakings(RTokenP1 rToken, address account) external view diff --git a/contracts/interfaces/IFacadeAct.sol b/contracts/interfaces/IFacadeAct.sol index 8b93274299..0702ee3606 100644 --- a/contracts/interfaces/IFacadeAct.sol +++ b/contracts/interfaces/IFacadeAct.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.17; +import "../interfaces/IBackingManager.sol"; import "../interfaces/IRevenueTrader.sol"; import "../interfaces/IRToken.sol"; @@ -28,4 +29,36 @@ interface IFacadeAct { IERC20[] memory toStart, TradeKind kind ) external; + + /// To use this, call via callStatic. + /// If canStart is true, call backingManager.rebalance(). May require settling a + /// trade first; see auctionsSettleable. + /// @return canStart true iff a recollateralization auction can be started + /// @return sell The sell token in the auction + /// @return buy The buy token in the auction + /// @return sellAmount {qSellTok} How much would be sold + /// @custom:static-call + function nextRecollateralizationAuction(IBackingManager bm) + external + returns ( + bool canStart, + IERC20 sell, + IERC20 buy, + uint256 sellAmount + ); + + /// To use this, call via callStatic. + /// @return erc20s The ERC20s that have auctions that can be started + /// @return canStart If the ERC20 auction can be started + /// @return surpluses {qTok} The surplus amount + /// @return minTradeAmounts {qTok} The minimum amount worth trading + /// @custom:static-call + function revenueOverview(IRevenueTrader revenueTrader) + external + returns ( + IERC20[] memory erc20s, + bool[] memory canStart, + uint256[] memory surpluses, + uint256[] memory minTradeAmounts + ); } diff --git a/contracts/interfaces/IFacadeRead.sol b/contracts/interfaces/IFacadeRead.sol index 70f65c257d..0a937b08da 100644 --- a/contracts/interfaces/IFacadeRead.sol +++ b/contracts/interfaces/IFacadeRead.sol @@ -10,11 +10,10 @@ bytes1 constant MAJOR_VERSION_2 = bytes1("2"); bytes1 constant MAJOR_VERSION_3 = bytes1("3"); /** - * @title IFacade - * @notice A UX-friendly layer for non-governance protocol interactions + * @title IFacadeRead + * @notice A UX-friendly layer for read operations, especially those that first require refresh() * * - @custom:static-call - Use ethers callStatic() in order to get result after update - * - @custom:view - Regular view v */ interface IFacadeRead { // === Static Calls === @@ -71,38 +70,6 @@ interface IFacadeRead { uint256[] memory balancesNeededByBackingManager ); - /// To use this, call via callStatic. - /// If canStart is true, call backingManager.rebalance(). May require settling a - /// trade first; see auctionsSettleable. - /// @return canStart true iff a recollateralization auction can be started - /// @return sell The sell token in the auction - /// @return buy The buy token in the auction - /// @return sellAmount {qSellTok} How much would be sold - /// @custom:static-call - function nextRecollateralizationAuction(IBackingManager bm) - external - returns ( - bool canStart, - IERC20 sell, - IERC20 buy, - uint256 sellAmount - ); - - /// To use this, call via callStatic. - /// @return erc20s The ERC20s that have auctions that can be started - /// @return canStart If the ERC20 auction can be started - /// @return surpluses {qTok} The surplus amount - /// @return minTradeAmounts {qTok} The minimum amount worth trading - /// @custom:static-call - function revenueOverview(IRevenueTrader revenueTrader) - external - returns ( - IERC20[] memory erc20s, - bool[] memory canStart, - uint256[] memory surpluses, - uint256[] memory minTradeAmounts - ); - // === Views === struct Pending { @@ -113,7 +80,6 @@ interface IFacadeRead { /// @param account The account for the query /// @return All the pending StRSR unstakings for an account - /// @custom:view function pendingUnstakings(RTokenP1 rToken, address account) external view @@ -143,11 +109,9 @@ interface IFacadeRead { returns (IERC20[] memory erc20s, uint256 max); /// @return tokens The ERC20s backing the RToken - /// @custom:view function basketTokens(IRToken rToken) external view returns (address[] memory tokens); /// @return stTokenAddress The address of the corresponding stToken address - /// @custom:view function stToken(IRToken rToken) external view returns (IStRSR stTokenAddress); /// @return backing The worst-case collaterazation % the protocol will have after done trading diff --git a/test/FacadeRead.test.ts b/test/Facade.test.ts similarity index 99% rename from test/FacadeRead.test.ts rename to test/Facade.test.ts index 8ccfe5efa5..951146b491 100644 --- a/test/FacadeRead.test.ts +++ b/test/Facade.test.ts @@ -37,7 +37,7 @@ import { getLatestBlockTimestamp, setNextBlockTimestamp } from './utils/time' import { CollateralStatus, TradeKind, MAX_UINT256 } from '#/common/constants' import { mintCollaterals } from './utils/tokens' -describe('FacadeRead contract', () => { +describe('FacadeRead + FacadeAct contracts', () => { let owner: SignerWithAddress let addr1: SignerWithAddress let addr2: SignerWithAddress @@ -414,7 +414,7 @@ describe('FacadeRead contract', () => { // revenue const [erc20s, canStart, surpluses, minTradeAmounts] = - await facade.callStatic.revenueOverview(trader.address) + await facadeAct.callStatic.revenueOverview(trader.address) expect(erc20s.length).to.equal(8) // should be full set of registered ERC20s const erc20sToStart = [] @@ -470,7 +470,7 @@ describe('FacadeRead contract', () => { // Confirm nextRecollateralizationAuction is true const [canStart, sell, buy, sellAmount] = - await facade.callStatic.nextRecollateralizationAuction(backingManager.address) + await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) expect(canStart).to.equal(true) expect(sell).to.equal(token.address) expect(buy).to.equal(usdc.address) From 35d015b755d175cbd8c61589a196b7a7d2418ed3 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 23 May 2023 20:37:35 -0400 Subject: [PATCH 329/462] new FacadeRead/FacadeAct addresses --- contracts/interfaces/IFacadeAct.sol | 4 ++++ contracts/interfaces/IFacadeRead.sol | 4 ---- scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/interfaces/IFacadeAct.sol b/contracts/interfaces/IFacadeAct.sol index 0702ee3606..600071daef 100644 --- a/contracts/interfaces/IFacadeAct.sol +++ b/contracts/interfaces/IFacadeAct.sol @@ -5,6 +5,10 @@ import "../interfaces/IBackingManager.sol"; import "../interfaces/IRevenueTrader.sol"; import "../interfaces/IRToken.sol"; +bytes1 constant MAJOR_VERSION_1 = bytes1("1"); +bytes1 constant MAJOR_VERSION_2 = bytes1("2"); +bytes1 constant MAJOR_VERSION_3 = bytes1("3"); + /** * @title IFacadeAct * @notice A Facade to help batch compound actions that cannot be done from an EOA, solely. diff --git a/contracts/interfaces/IFacadeRead.sol b/contracts/interfaces/IFacadeRead.sol index 0a937b08da..3490690d6b 100644 --- a/contracts/interfaces/IFacadeRead.sol +++ b/contracts/interfaces/IFacadeRead.sol @@ -5,10 +5,6 @@ import "../p1/RToken.sol"; import "./IRToken.sol"; import "./IStRSR.sol"; -bytes1 constant MAJOR_VERSION_1 = bytes1("1"); -bytes1 constant MAJOR_VERSION_2 = bytes1("2"); -bytes1 constant MAJOR_VERSION_3 = bytes1("3"); - /** * @title IFacadeRead * @notice A UX-friendly layer for read operations, especially those that first require refresh() diff --git a/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json b/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json index 4f97046bea..2f8fb364f5 100644 --- a/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json +++ b/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json @@ -6,8 +6,8 @@ }, "tradingLib": "", "cvxMiningLib": "", - "facadeRead": "0xad0BFAEE863B1102e9fD4e6330A02B08d885C715", - "facadeAct": "0xBe70970a10C186185b1bc1bE980eA09BD68fD97A", + "facadeRead": "0xcb71dDa3BdB1208B8bf18554Aab8CCF9bDe3e53D", + "facadeAct": "0xeaCaF85eA2df99e56053FD0250330C148D582547", "facadeWriteLib": "", "basketLib": "", "facadeWrite": "", @@ -32,4 +32,4 @@ "stRSR": "" } } -} \ No newline at end of file +} From 0f4115f33e550bd702237824d728ac889b851b33 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 24 May 2023 14:06:24 -0300 Subject: [PATCH 330/462] basket handler fix in historical basket --- contracts/p1/BasketHandler.sol | 18 +++++++--- test/Main.test.ts | 60 ++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index d89f911322..6abe5d998d 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -592,11 +592,19 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { for (uint256 i = 0; i < b.erc20s.length; ++i) { erc20s[i] = b.erc20s[i]; - // {qTok/BU} = {tok/BU} * {qTok/tok} - quantities[i] = quantity(basket.erc20s[i]).shiftl_toUint( - int8(IERC20Metadata(address(basket.erc20s[i])).decimals()), - FLOOR - ); + try assetRegistry.toAsset(IERC20(erc20s[i])) returns (IAsset asset) { + if (!asset.isCollateral()) continue; // skip token if no longer registered + + // {tok} = {BU} * {ref/BU} / {ref/tok} + quantities[i] = safeMulDivFloor( + FIX_ONE, + b.refAmts[erc20s[i]], + ICollateral(address(asset)).refPerTok() + ).shiftl_toUint(int8(asset.erc20Decimals()), FLOOR); + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + } } } diff --git a/test/Main.test.ts b/test/Main.test.ts index f7c7f4b811..3efe71f4e6 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -33,6 +33,7 @@ import { bn, fp } from '../common/numbers' import { Asset, ATokenFiatCollateral, + BasketHandlerP1, CTokenFiatCollateral, DutchTrade, CTokenVaultMock, @@ -77,6 +78,8 @@ import { mintCollaterals } from './utils/tokens' const DEFAULT_THRESHOLD = fp('0.01') // 1% +const itP1 = IMPLEMENTATION == Implementation.P1 ? it : it.skip + const describeGas = IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip @@ -1667,6 +1670,8 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { let eurToken: ERC20Mock beforeEach(async () => { + await upgrades.silenceWarnings() + if (IMPLEMENTATION == Implementation.P0) { const BasketHandlerFactory = await ethers.getContractFactory('BasketHandlerP0') freshBasketHandler = ((await BasketHandlerFactory.deploy()) as unknown) @@ -2244,6 +2249,61 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const balsAfter = await getBalances(addr1.address, expectedTokens) expectDelta(balsBefore, quote.quantities, balsAfter) }) + + itP1('Should return historical basket correctly', async () => { + const bskHandlerP1: BasketHandlerP1 = ( + await ethers.getContractAt('BasketHandlerP1', basketHandler.address) + ) + + // Returns the current prime basket + let [erc20s, quantities] = await bskHandlerP1.getHistoricalBasket(1) + expect(erc20s.length).to.equal(4) + expect(quantities.length).to.equal(4) + const prevERC20s = [token0.address, token1.address, token2.address, token3.address] + const prevQtys = [bn('0.25e18'), bn('0.25e6'), bn('0.25e18'), bn('1.25e9')] + + for (let i = 0; i < 4; i++) { + expect(erc20s[i]).to.equal(prevERC20s[i]) + expect(quantities[i]).to.equal(prevQtys[i]) + } + + // add 2nd token to backup config + await basketHandler + .connect(owner) + .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(2), [ + backupToken1.address, + backupToken2.address, + ]) + // default usdc & refresh basket to use backup collateral + await usdcChainlink.updateAnswer(bn('0.8e8')) // default token1 + await daiChainlink.updateAnswer(bn('0.8e8')) // default token0, token2, token3 + await basketHandler.refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + + // Get basket for current nonce + ;[erc20s, quantities] = await bskHandlerP1.getHistoricalBasket(2) + expect(erc20s.length).to.equal(2) + expect(quantities.length).to.equal(2) + const newERC20s = [backupToken1.address, backupToken2.address] + const newQtys = [bn('0.5e18'), bn('0.5e18')] + + for (let i = 0; i < 2; i++) { + expect(erc20s[i]).to.equal(newERC20s[i]) + expect(quantities[i]).to.equal(newQtys[i]) + } + + // Get basket for prior nonce - will get full quantities + ;[erc20s, quantities] = await bskHandlerP1.getHistoricalBasket(1) + expect(erc20s.length).to.equal(4) + expect(quantities.length).to.equal(4) + + for (let i = 0; i < 4; i++) { + expect(erc20s[i]).to.equal(prevERC20s[i]) + expect(quantities[i]).to.equal(prevQtys[i]) + } + }) }) it('Should return (FIX_ZERO, FIX_MAX) for basketsHeldBy() if the basket is empty', async () => { From cfd1635233ae12687956c271446fe3c81f63473d Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 24 May 2023 14:18:05 -0300 Subject: [PATCH 331/462] add untestable comments --- contracts/facade/FacadeRead.sol | 2 ++ contracts/p0/StRSR.sol | 3 +++ contracts/p1/BackingManager.sol | 2 ++ contracts/p1/BasketHandler.sol | 4 ++++ contracts/p1/StRSR.sol | 2 ++ contracts/p1/mixins/BasketLib.sol | 2 ++ 6 files changed, 15 insertions(+) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 85bd454b5f..3f93878a80 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -73,6 +73,8 @@ contract FacadeRead is IFacadeRead { for (uint256 i = 0; i < tokens.length; ++i) { IAsset asset = reg.toAsset(IERC20(tokens[i])); (uint192 low, uint192 high) = asset.price(); + // untestable: + // if high == FIX_MAX then low has to be zero, so this check will not be reached if (low == 0 || high == FIX_MAX) continue; uint192 mid = (low + high) / 2; diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index 3652e818b4..da9870a0af 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -207,6 +207,9 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { while (start < endId && queue[start].rsrAmount == 0 && queue[start].stakeAmount == 0) start++; + // Return if nothing to process + if (start == endId) return; + // Accumulate and zero executable withdrawals uint256 total = 0; uint256 i = start; diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 386a26fbfc..933d43e1f7 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -88,6 +88,8 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // solhint-disable-next-line no-empty-blocks try this.rebalance(trade.KIND()) {} catch (bytes memory errData) { // prevent MEV searchers from providing less gas on purpose by reverting if OOG + // untested: + // OOG pattern tested in other contracts, cost to test here is high // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string } diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 6abe5d998d..3f54f65c81 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -446,6 +446,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // marginally more penalizing than its sibling calculation that uses _quantity() // because does not intermediately CEIL as part of the division } catch (bytes memory errData) { + // untested: + // OOG pattern tested in other contracts, cost to test here is high // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string } @@ -602,6 +604,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { ICollateral(address(asset)).refPerTok() ).shiftl_toUint(int8(asset.erc20Decimals()), FLOOR); } catch (bytes memory errData) { + // untested: + // OOG pattern tested in other contracts, cost to test here is high // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string } diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 03938d4e88..8301a0bae5 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -306,6 +306,8 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab require(endId <= queue.length, "index out-of-bounds"); require(queue[endId - 1].availableAt <= block.timestamp, "withdrawal unavailable"); + // untestable: + // firstId will never be zero, due to previous checks against endId uint192 oldDrafts = firstId > 0 ? queue[firstId - 1].drafts : 0; uint192 draftAmount = queue[endId - 1].drafts - oldDrafts; diff --git a/contracts/p1/mixins/BasketLib.sol b/contracts/p1/mixins/BasketLib.sol index f4dba9282a..bc52d1c6a4 100644 --- a/contracts/p1/mixins/BasketLib.sol +++ b/contracts/p1/mixins/BasketLib.sol @@ -288,6 +288,8 @@ library BasketLibP1 { IERC20 erc20, IAssetRegistry assetRegistry ) private view returns (bool) { + // untestable: + // ERC20 is not address(0), validated when setting prime/backup baskets if (address(erc20) == address(0)) return false; // P1 gas optimization // We do not need to check that the ERC20 is not a system token From 9d0e65f41a02dddd6241982b68a37b15feb49a6a Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 3 Jul 2023 18:07:36 -0300 Subject: [PATCH 332/462] 3.0.0 rc3 (#837) Co-authored-by: Taylor Brent Co-authored-by: Patrick McKelvy Co-authored-by: Jan Co-authored-by: Akshat Mittal --- .github/workflows/tests.yml | 9 +- .gitignore | 5 +- .../{goerli.json => goerli-old.json} | 3093 +++++++++++++++++ .solcover.js | 16 +- .solhintignore | 3 +- CHANGELOG.md | 2 + README.md | 2 + common/configuration.ts | 39 +- contracts/facade/DeployerRegistry.sol | 2 +- contracts/facade/FacadeAct.sol | 3 +- contracts/facade/FacadeRead.sol | 2 +- contracts/facade/FacadeTest.sol | 2 +- contracts/facade/FacadeWrite.sol | 2 +- contracts/facade/lib/FacadeWriteLib.sol | 2 +- contracts/interfaces/IAsset.sol | 2 +- contracts/interfaces/IAssetRegistry.sol | 2 +- contracts/interfaces/IBackingManager.sol | 2 +- contracts/interfaces/IBasketHandler.sol | 2 +- contracts/interfaces/IBroker.sol | 2 +- contracts/interfaces/IComponent.sol | 2 +- contracts/interfaces/IDeployer.sol | 4 +- contracts/interfaces/IDeployerRegistry.sol | 2 +- contracts/interfaces/IDistributor.sol | 2 +- contracts/interfaces/IFacadeAct.sol | 3 +- contracts/interfaces/IFacadeRead.sol | 2 +- contracts/interfaces/IFacadeTest.sol | 2 +- contracts/interfaces/IFacadeWrite.sol | 2 +- contracts/interfaces/IFurnace.sol | 2 +- contracts/interfaces/IGnosis.sol | 2 +- contracts/interfaces/IMain.sol | 6 +- contracts/interfaces/IRToken.sol | 2 +- contracts/interfaces/IRTokenOracle.sol | 19 + contracts/interfaces/IRevenueTrader.sol | 2 +- contracts/interfaces/IRewardable.sol | 2 +- contracts/interfaces/IStRSR.sol | 2 +- contracts/interfaces/IStRSRVotes.sol | 2 +- contracts/interfaces/ITrade.sol | 2 +- contracts/interfaces/ITrading.sol | 11 +- contracts/interfaces/IVersioned.sol | 2 +- contracts/libraries/Array.sol | 2 +- contracts/libraries/Fixed.sol | 69 +- contracts/libraries/Permit.sol | 2 +- contracts/libraries/String.sol | 2 +- contracts/libraries/Throttle.sol | 2 +- contracts/libraries/test/ArrayCallerMock.sol | 2 +- contracts/libraries/test/FixedCallerMock.sol | 224 +- contracts/libraries/test/StringCallerMock.sol | 2 +- contracts/mixins/Auth.sol | 2 +- contracts/mixins/ComponentRegistry.sol | 2 +- contracts/mixins/NetworkConfigLib.sol | 26 + contracts/mixins/Versioned.sol | 2 +- contracts/p0/AssetRegistry.sol | 2 +- contracts/p0/BackingManager.sol | 11 +- contracts/p0/BasketHandler.sol | 34 +- contracts/p0/Broker.sol | 10 +- contracts/p0/Deployer.sol | 2 +- contracts/p0/Distributor.sol | 2 +- contracts/p0/Furnace.sol | 10 +- contracts/p0/Main.sol | 2 +- contracts/p0/RToken.sol | 2 +- contracts/p0/RevenueTrader.sol | 2 +- contracts/p0/StRSR.sol | 26 +- contracts/p0/mixins/Component.sol | 2 +- contracts/p0/mixins/Rewardable.sol | 2 +- contracts/p0/mixins/Trading.sol | 19 +- contracts/p0/mixins/TradingLib.sol | 35 +- contracts/p1/AssetRegistry.sol | 2 +- contracts/p1/BackingManager.sol | 13 +- contracts/p1/BasketHandler.sol | 59 +- contracts/p1/Broker.sol | 13 +- contracts/p1/Deployer.sol | 2 +- contracts/p1/Distributor.sol | 2 +- contracts/p1/Furnace.sol | 12 +- contracts/p1/Main.sol | 2 +- contracts/p1/RToken.sol | 2 +- contracts/p1/RevenueTrader.sol | 2 +- contracts/p1/StRSR.sol | 31 +- contracts/p1/StRSRVotes.sol | 2 +- contracts/p1/mixins/BasketLib.sol | 2 +- contracts/p1/mixins/Component.sol | 2 +- .../p1/mixins/RecollateralizationLib.sol | 9 +- contracts/p1/mixins/RewardableLib.sol | 2 +- contracts/p1/mixins/TradeLib.sol | 33 +- contracts/p1/mixins/Trading.sol | 22 +- .../assets/AppreciatingFiatCollateral.sol | 2 +- contracts/plugins/assets/Asset.sol | 2 +- .../plugins/assets/EURFiatCollateral.sol | 2 +- contracts/plugins/assets/FiatCollateral.sol | 2 +- .../plugins/assets/NonFiatCollateral.sol | 2 +- contracts/plugins/assets/OracleLib.sol | 2 +- contracts/plugins/assets/RTokenAsset.sol | 51 +- .../assets/SelfReferentialCollateral.sol | 2 +- contracts/plugins/assets/VersionedAsset.sol | 2 +- .../assets/aave/ATokenFiatCollateral.sol | 2 +- .../aave/{vendor => }/IStaticATokenLM.sol | 2 +- .../aave/{vendor => }/StaticATokenErrors.sol | 0 .../aave/{vendor => }/StaticATokenLM.sol | 30 +- .../assets/ankr/AnkrStakedEthCollateral.sol | 2 +- .../plugins/assets/ankr/vendor/IAnkrETH.sol | 2 +- .../plugins/assets/cbeth/CBETHCollateral.sol | 70 + contracts/plugins/assets/cbeth/README.md | 19 + .../compoundv2/CTokenFiatCollateral.sol | 10 +- .../compoundv2/CTokenNonFiatCollateral.sol | 2 +- .../CTokenSelfReferentialCollateral.sol | 12 +- .../plugins/assets/compoundv2/CTokenVault.sol | 33 - .../assets/compoundv2/CTokenWrapper.sol | 42 + .../plugins/assets/compoundv2/ICToken.sol | 2 +- .../assets/compoundv3/CTokenV3Collateral.sol | 4 +- .../assets/compoundv3/CometHelpers.sol | 16 +- .../assets/compoundv3/CusdcV3Wrapper.sol | 4 +- .../assets/compoundv3/ICusdcV3Wrapper.sol | 2 +- .../assets/compoundv3/IWrappedERC20.sol | 2 +- .../assets/compoundv3/WrappedERC20.sol | 6 +- .../assets/compoundv3/vendor/CometCore.sol | 2 +- .../compoundv3/vendor/CometExtInterface.sol | 2 +- .../assets/compoundv3/vendor/CometExtMock.sol | 2 +- .../compoundv3/vendor/CometInterface.sol | 2 +- .../compoundv3/vendor/CometMainInterface.sol | 2 +- .../assets/compoundv3/vendor/CometStorage.sol | 2 +- .../assets/compoundv3/vendor/IComet.sol | 2 +- .../compoundv3/vendor/ICometConfigurator.sol | 2 +- .../compoundv3/vendor/ICometProxyAdmin.sol | 2 +- .../compoundv3/vendor/ICometRewards.sol | 2 +- .../CurveStableCollateral.sol} | 27 +- .../CurveStableMetapoolCollateral.sol} | 18 +- .../CurveStableRTokenMetapoolCollateral.sol} | 18 +- .../CurveVolatileCollateral.sol} | 23 +- .../assets/{convex => curve}/PoolTokens.sol | 18 +- .../assets/curve/crv/CurveGaugeWrapper.sol | 51 + .../assets/{convex => curve/cvx}/README.md | 0 .../cvx}/vendor/ConvexInterfaces.sol | 2 +- .../cvx}/vendor/ConvexStakingWrapper.sol | 0 .../cvx}/vendor/CvxMining.sol | 0 .../cvx}/vendor/IConvexStakingWrapper.sol | 2 +- .../cvx}/vendor/IRewardStaking.sol | 0 .../cvx}/vendor/StableSwap3Pool.vy | 0 contracts/plugins/assets/dsr/README.md | 27 + .../plugins/assets/dsr/SDaiCollateral.sol | 57 + .../RewardableERC20.sol} | 42 +- .../assets/erc20/RewardableERC20Wrapper.sol | 74 + .../assets/erc20/RewardableERC4626Vault.sol | 53 + .../assets/frax-eth/SFraxEthCollateral.sol | 2 +- .../assets/frax-eth/vendor/IfrxEthMinter.sol | 2 +- .../assets/frax-eth/vendor/IsfrxEth.sol | 2 +- .../assets/lido/LidoStakedEthCollateral.sol | 2 +- .../plugins/assets/lido/vendor/ISTETH.sol | 2 +- .../plugins/assets/lido/vendor/IWSTETH.sol | 2 +- .../assets/rocket-eth/RethCollateral.sol | 2 +- .../assets/rocket-eth/vendor/IReth.sol | 2 +- .../vendor/IRocketNetworkBalances.sol | 2 +- .../rocket-eth/vendor/IRocketStorage.sol | 2 +- contracts/plugins/governance/Governance.sol | 2 +- contracts/plugins/mocks/ATokenMock.sol | 2 +- .../plugins/mocks/ATokenNoController.sol | 30 + .../plugins/mocks/AaveLendingPoolMock.sol | 2 +- .../mocks/BackingMgrBackCompatible.sol | 38 + .../plugins/mocks/BadCollateralPlugin.sol | 2 +- contracts/plugins/mocks/BadERC20.sol | 14 +- contracts/plugins/mocks/CTokenMock.sol | 2 +- ...nVaultMock2.sol => CTokenWrapperMock2.sol} | 20 +- contracts/plugins/mocks/CometMock.sol | 2 +- contracts/plugins/mocks/ComptrollerMock.sol | 2 +- contracts/plugins/mocks/CurveMetapoolMock.sol | 2 +- contracts/plugins/mocks/CurvePoolMock.sol | 53 +- .../plugins/mocks/CusdcV3WrapperMock.sol | 2 +- .../plugins/mocks/EACAggregatorProxyMock.sol | 2 +- contracts/plugins/mocks/ERC1271Mock.sol | 2 +- contracts/plugins/mocks/ERC20Mock.sol | 2 +- contracts/plugins/mocks/ERC20MockDecimals.sol | 2 +- .../plugins/mocks/ERC20MockRewarding.sol | 2 +- .../mocks/GasGuzzlingFiatCollateral.sol | 2 +- contracts/plugins/mocks/GnosisMock.sol | 2 +- .../plugins/mocks/GnosisMockReentrant.sol | 2 +- .../mocks/InvalidATokenFiatCollateralMock.sol | 2 +- contracts/plugins/mocks/InvalidBrokerMock.sol | 2 +- .../plugins/mocks/InvalidFiatCollateral.sol | 2 +- .../mocks/InvalidRefPerTokCollateral.sol | 2 +- .../plugins/mocks/InvalidRevTraderP1Mock.sol | 59 + contracts/plugins/mocks/MakerPotMock.sol | 27 + .../plugins/mocks/MockableCollateral.sol | 2 +- .../plugins/mocks/NontrivialPegCollateral.sol | 2 +- contracts/plugins/mocks/RTokenCollateral.sol | 2 +- .../plugins/mocks/RevenueTraderBackComp.sol | 34 + .../mocks/RewardableERC20WrapperTest.sol | 18 + ...est.sol => RewardableERC4626VaultTest.sol} | 8 +- .../mocks/SelfdestructTransferMock.sol | 2 +- contracts/plugins/mocks/SfraxEthMock.sol | 2 +- contracts/plugins/mocks/USDCMock.sol | 2 +- contracts/plugins/mocks/UnpricedPlugins.sol | 2 +- contracts/plugins/mocks/WBTCMock.sol | 2 +- contracts/plugins/mocks/WETH.sol | 6 +- contracts/plugins/mocks/ZeroDecimalMock.sol | 2 +- .../mocks/upgrades/AssetRegistryV2.sol | 2 +- .../mocks/upgrades/BackingManagerV2.sol | 2 +- .../mocks/upgrades/BasketHandlerV2.sol | 3 +- contracts/plugins/mocks/upgrades/BrokerV2.sol | 2 +- .../plugins/mocks/upgrades/DistributorV2.sol | 2 +- .../plugins/mocks/upgrades/FurnaceV2.sol | 2 +- contracts/plugins/mocks/upgrades/MainV2.sol | 2 +- contracts/plugins/mocks/upgrades/RTokenV2.sol | 2 +- .../mocks/upgrades/RevenueTraderV2.sol | 2 +- contracts/plugins/mocks/upgrades/StRSRV2.sol | 2 +- contracts/plugins/trading/DutchTrade.sol | 12 +- contracts/plugins/trading/GnosisTrade.sol | 2 +- contracts/vendor/ERC20PermitUpgradeable.sol | 2 +- docs/dev-env.md | 17 +- docs/solidity-style.md | 2 +- hardhat.config.ts | 15 +- package.json | 7 +- scripts/addresses/5-RTKN-tmp-deployments.json | 35 + .../addresses/5-tmp-assets-collateral.json | 56 + scripts/addresses/5-tmp-deployments.json | 46 +- scripts/deploy.ts | 8 +- scripts/deployment/common.ts | 6 +- .../phase1-common/2_deploy_implementations.ts | 42 +- .../phase2-assets/2_deploy_collateral.ts | 2 +- .../collaterals/deploy_cbeth_collateral.ts | 84 + .../deploy_convex_rToken_metapool_plugin.ts | 68 +- .../deploy_convex_stable_metapool_plugin.ts | 12 +- .../deploy_convex_stable_plugin.ts | 10 +- .../deploy_convex_volatile_plugin.ts | 10 +- .../deploy_curve_rToken_metapool_plugin.ts | 135 + .../deploy_curve_stable_metapool_plugin.ts | 141 + .../collaterals/deploy_curve_stable_plugin.ts | 134 + .../deploy_curve_volatile_plugin.ts | 142 + .../collaterals/deploy_dsr_sdai.ts | 87 + .../deploy_flux_finance_collateral.ts | 2 +- .../deploy_lido_wsteth_collateral.ts | 28 +- .../deploy_rocket_pool_reth_collateral.ts | 19 +- scripts/deployment/utils.ts | 77 +- scripts/exhaustive-tests/run-1.sh | 2 +- .../verification/1_verify_implementations.ts | 6 + scripts/verification/5_verify_facadeWrite.ts | 3 +- scripts/verification/6_verify_collateral.ts | 38 +- .../collateral-plugins/verify_cbeth.ts | 55 + ...able_plugin.ts => verify_convex_stable.ts} | 9 +- ...in.ts => verify_convex_stable_metapool.ts} | 6 +- ...> verify_convex_stable_rtoken_metapool.ts} | 6 +- ...le_plugin.ts => verify_convex_volatile.ts} | 6 +- .../collateral-plugins/verify_curve_stable.ts | 102 + .../verify_curve_stable_metapool.ts | 96 + .../verify_curve_stable_rtoken_metapool.ts | 90 + .../verify_curve_volatile.ts | 98 + ...usdcv3_collateral.ts => verify_cusdcv3.ts} | 0 ...rify_reth_collateral.ts => verify_reth.ts} | 0 .../collateral-plugins/verify_sdai.ts | 55 + ..._wsteth_collateral.ts => verify_wsteth.ts} | 0 scripts/verify_etherscan.ts | 19 +- test/Broker.test.ts | 124 + test/Facade.test.ts | 702 +++- test/FacadeWrite.test.ts | 8 +- test/Furnace.test.ts | 18 +- test/Main.test.ts | 260 +- test/RToken.test.ts | 240 +- test/RTokenExtremes.test.ts | 232 ++ test/Recollateralization.test.ts | 24 +- test/Revenues.test.ts | 256 +- test/ZTradingExtremes.test.ts | 12 +- test/ZZStRSR.test.ts | 96 +- test/fixtures.ts | 12 +- test/integration/AssetPlugins.test.ts | 149 +- test/integration/fixtures.ts | 8 +- .../mainnet-test/StaticATokens.test.ts | 27 +- test/libraries/Fixed.test.ts | 154 +- test/plugins/Asset.test.ts | 21 +- test/plugins/Collateral.test.ts | 16 +- test/plugins/RewardableERC20.test.ts | 617 ++++ test/plugins/RewardableERC20Vault.test.ts | 574 --- .../RewardableERC20Vault.test.ts.snap | 12 +- .../aave/ATokenFiatCollateral.test.ts | 7 +- .../aave/StaticATokenLM.test.ts | 179 +- .../cbeth/CBETHCollateral.test.ts | 233 ++ .../individual-collateral/cbeth/constants.ts | 19 + .../individual-collateral/cbeth/helpers.ts | 17 + .../compoundv2/CTokenFiatCollateral.test.ts | 69 +- .../compoundv3/CusdcV3Wrapper.test.ts | 100 +- .../convex/CvxStableMetapoolSuite.test.ts | 768 ---- .../CvxStableRTokenMetapoolTestSuite.test.ts | 771 ---- .../convex/CvxStableTestSuite.test.ts | 756 ---- .../convex/CvxVolatileTestSuite.test.ts | 799 ----- .../curve/collateralTests.ts | 639 ++++ .../{convex => curve}/constants.ts | 21 +- .../curve/crv/CrvStableMetapoolSuite.test.ts | 229 ++ .../CrvStableRTokenMetapoolTestSuite.test.ts | 219 ++ .../curve/crv/CrvStableTestSuite.test.ts | 238 ++ .../curve/crv/CrvVolatileTestSuite.test.ts | 225 ++ .../curve/crv/helpers.ts | 320 ++ .../curve/cvx/CvxStableMetapoolSuite.test.ts | 237 ++ .../CvxStableRTokenMetapoolTestSuite.test.ts | 221 ++ .../curve/cvx/CvxStableTestSuite.test.ts | 436 +++ .../curve/cvx/CvxVolatileTestSuite.test.ts | 227 ++ .../{convex => curve/cvx}/helpers.ts | 152 +- .../curve/pluginTestTypes.ts | 89 + .../dsr/SDaiCollateralTestSuite.test.ts | 211 ++ .../individual-collateral/dsr/constants.ts | 19 + .../individual-collateral/dsr/helpers.ts | 19 + .../flux-finance/FTokenFiatCollateral.test.ts | 33 +- .../flux-finance/helpers.ts | 6 +- .../individual-collateral/pluginTestTypes.ts | 8 +- .../RethCollateralTestSuite.test.ts | 14 +- test/scenario/ComplexBasket.test.ts | 16 +- test/scenario/MaxBasketSize.test.ts | 14 +- test/scenario/RevenueHiding.test.ts | 6 +- test/scenario/cETH.test.ts | 6 +- test/scenario/cWBTC.test.ts | 10 +- test/utils/tokens.ts | 6 +- utils/subgraph.ts | 1 + yarn.lock | 614 +++- 308 files changed, 13534 insertions(+), 4918 deletions(-) rename .openzeppelin/{goerli.json => goerli-old.json} (50%) create mode 100644 contracts/interfaces/IRTokenOracle.sol create mode 100644 contracts/mixins/NetworkConfigLib.sol rename contracts/plugins/assets/aave/{vendor => }/IStaticATokenLM.sol (99%) rename contracts/plugins/assets/aave/{vendor => }/StaticATokenErrors.sol (100%) rename contracts/plugins/assets/aave/{vendor => }/StaticATokenLM.sol (93%) create mode 100644 contracts/plugins/assets/cbeth/CBETHCollateral.sol create mode 100644 contracts/plugins/assets/cbeth/README.md delete mode 100644 contracts/plugins/assets/compoundv2/CTokenVault.sol create mode 100644 contracts/plugins/assets/compoundv2/CTokenWrapper.sol rename contracts/plugins/assets/{convex/CvxStableCollateral.sol => curve/CurveStableCollateral.sol} (87%) rename contracts/plugins/assets/{convex/CvxStableMetapoolCollateral.sol => curve/CurveStableMetapoolCollateral.sol} (92%) rename contracts/plugins/assets/{convex/CvxStableRTokenMetapoolCollateral.sol => curve/CurveStableRTokenMetapoolCollateral.sol} (74%) rename contracts/plugins/assets/{convex/CvxVolatileCollateral.sol => curve/CurveVolatileCollateral.sol} (75%) rename contracts/plugins/assets/{convex => curve}/PoolTokens.sol (96%) create mode 100644 contracts/plugins/assets/curve/crv/CurveGaugeWrapper.sol rename contracts/plugins/assets/{convex => curve/cvx}/README.md (100%) rename contracts/plugins/assets/{convex => curve/cvx}/vendor/ConvexInterfaces.sol (99%) rename contracts/plugins/assets/{convex => curve/cvx}/vendor/ConvexStakingWrapper.sol (100%) rename contracts/plugins/assets/{convex => curve/cvx}/vendor/CvxMining.sol (100%) rename contracts/plugins/assets/{convex => curve/cvx}/vendor/IConvexStakingWrapper.sol (90%) rename contracts/plugins/assets/{convex => curve/cvx}/vendor/IRewardStaking.sol (100%) rename contracts/plugins/assets/{convex => curve/cvx}/vendor/StableSwap3Pool.vy (100%) create mode 100644 contracts/plugins/assets/dsr/README.md create mode 100644 contracts/plugins/assets/dsr/SDaiCollateral.sol rename contracts/plugins/assets/{vaults/RewardableERC20Vault.sol => erc20/RewardableERC20.sol} (75%) create mode 100644 contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol create mode 100644 contracts/plugins/assets/erc20/RewardableERC4626Vault.sol create mode 100644 contracts/plugins/mocks/ATokenNoController.sol create mode 100644 contracts/plugins/mocks/BackingMgrBackCompatible.sol rename contracts/plugins/mocks/{CTokenVaultMock2.sol => CTokenWrapperMock2.sol} (72%) create mode 100644 contracts/plugins/mocks/InvalidRevTraderP1Mock.sol create mode 100644 contracts/plugins/mocks/MakerPotMock.sol create mode 100644 contracts/plugins/mocks/RevenueTraderBackComp.sol create mode 100644 contracts/plugins/mocks/RewardableERC20WrapperTest.sol rename contracts/plugins/mocks/{RewardableERC20VaultTest.sol => RewardableERC4626VaultTest.sol} (60%) create mode 100644 scripts/addresses/5-RTKN-tmp-deployments.json create mode 100644 scripts/addresses/5-tmp-assets-collateral.json create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_cbeth_collateral.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_metapool_plugin.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_dsr_sdai.ts create mode 100644 scripts/verification/collateral-plugins/verify_cbeth.ts rename scripts/verification/collateral-plugins/{verify_convex_stable_plugin.ts => verify_convex_stable.ts} (92%) rename scripts/verification/collateral-plugins/{verify_convex_stable_metapool_plugin.ts => verify_convex_stable_metapool.ts} (93%) rename scripts/verification/collateral-plugins/{verify_eusd_fraxbp_collateral.ts => verify_convex_stable_rtoken_metapool.ts} (92%) rename scripts/verification/collateral-plugins/{verify_convex_volatile_plugin.ts => verify_convex_volatile.ts} (93%) create mode 100644 scripts/verification/collateral-plugins/verify_curve_stable.ts create mode 100644 scripts/verification/collateral-plugins/verify_curve_stable_metapool.ts create mode 100644 scripts/verification/collateral-plugins/verify_curve_stable_rtoken_metapool.ts create mode 100644 scripts/verification/collateral-plugins/verify_curve_volatile.ts rename scripts/verification/collateral-plugins/{verify_cusdcv3_collateral.ts => verify_cusdcv3.ts} (100%) rename scripts/verification/collateral-plugins/{verify_reth_collateral.ts => verify_reth.ts} (100%) create mode 100644 scripts/verification/collateral-plugins/verify_sdai.ts rename scripts/verification/collateral-plugins/{verify_wsteth_collateral.ts => verify_wsteth.ts} (100%) create mode 100644 test/RTokenExtremes.test.ts create mode 100644 test/plugins/RewardableERC20.test.ts delete mode 100644 test/plugins/RewardableERC20Vault.test.ts create mode 100644 test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts create mode 100644 test/plugins/individual-collateral/cbeth/constants.ts create mode 100644 test/plugins/individual-collateral/cbeth/helpers.ts delete mode 100644 test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts delete mode 100644 test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts delete mode 100644 test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts delete mode 100644 test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts create mode 100644 test/plugins/individual-collateral/curve/collateralTests.ts rename test/plugins/individual-collateral/{convex => curve}/constants.ts (79%) create mode 100644 test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts create mode 100644 test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts create mode 100644 test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts create mode 100644 test/plugins/individual-collateral/curve/crv/CrvVolatileTestSuite.test.ts create mode 100644 test/plugins/individual-collateral/curve/crv/helpers.ts create mode 100644 test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts create mode 100644 test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts create mode 100644 test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts create mode 100644 test/plugins/individual-collateral/curve/cvx/CvxVolatileTestSuite.test.ts rename test/plugins/individual-collateral/{convex => curve/cvx}/helpers.ts (66%) create mode 100644 test/plugins/individual-collateral/curve/pluginTestTypes.ts create mode 100644 test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts create mode 100644 test/plugins/individual-collateral/dsr/constants.ts create mode 100644 test/plugins/individual-collateral/dsr/helpers.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c046a892da..1f3a2cdc5a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,7 +62,7 @@ jobs: hardhat-network-fork- - run: yarn test:plugins:integration env: - NODE_OPTIONS: '--max-old-space-size=4096' + NODE_OPTIONS: '--max-old-space-size=8192' TS_NODE_SKIP_IGNORE: true MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} @@ -79,7 +79,7 @@ jobs: - run: yarn compile - run: yarn test:p0 env: - NODE_OPTIONS: '--max-old-space-size=4096' + NODE_OPTIONS: '--max-old-space-size=8192' p1-tests: name: 'P1 Tests' @@ -94,7 +94,7 @@ jobs: - run: yarn compile - run: yarn test:p1 env: - NODE_OPTIONS: '--max-old-space-size=4096' + NODE_OPTIONS: '--max-old-space-size=8192' scenario-tests: name: 'Scenario Tests' @@ -133,6 +133,7 @@ jobs: hardhat-network-fork- - run: yarn test:extreme:integration env: + NODE_OPTIONS: '--max-old-space-size=8192' TS_NODE_SKIP_IGNORE: true MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} @@ -159,6 +160,6 @@ jobs: - run: yarn compile - run: yarn test:integration env: - NODE_OPTIONS: '--max-old-space-size=4096' + NODE_OPTIONS: '--max-old-space-size=8192' TS_NODE_SKIP_IGNORE: true MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} diff --git a/.gitignore b/.gitignore index 719f036d15..1a1acd0a93 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,7 @@ scripts/addresses/31337* # Scripts for local/test interactions scripts/test.ts -scripts/playground.ts \ No newline at end of file +scripts/playground.ts + +# tenderly deployment/verification artifacts +deployments/ \ No newline at end of file diff --git a/.openzeppelin/goerli.json b/.openzeppelin/goerli-old.json similarity index 50% rename from .openzeppelin/goerli.json rename to .openzeppelin/goerli-old.json index 0269997ae9..ec087c2b12 100644 --- a/.openzeppelin/goerli.json +++ b/.openzeppelin/goerli-old.json @@ -3102,6 +3102,3099 @@ } } } + }, + "b17431f482da5a2bb9bd8511872fc102e0f69c0d53654dfb718e385aaca98522": { + "address": "0x15395aCCbF8c6b28671fe41624D599624709a2D6", + "txHash": "0xd3d08969c43f1ee652aff2df57bc8c34a8abb8d7f3562cdb11b00e618fffb2bb", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC165Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41" + }, + { + "label": "_roles", + "offset": 0, + "slot": "101", + "type": "t_mapping(t_bytes32,t_struct(RoleData)80_storage)", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:61" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:259" + }, + { + "label": "longFreezes", + "offset": 0, + "slot": "151", + "type": "t_mapping(t_address,t_uint256)", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:36" + }, + { + "label": "unfreezeAt", + "offset": 0, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:38" + }, + { + "label": "shortFreeze", + "offset": 6, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:39" + }, + { + "label": "longFreeze", + "offset": 12, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:40" + }, + { + "label": "tradingPaused", + "offset": 18, + "slot": "152", + "type": "t_bool", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:45", + "renamedFrom": "paused" + }, + { + "label": "issuancePaused", + "offset": 19, + "slot": "152", + "type": "t_bool", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:46" + }, + { + "label": "__gap", + "offset": 0, + "slot": "153", + "type": "t_array(t_uint256)48_storage", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:225" + }, + { + "label": "rToken", + "offset": 0, + "slot": "201", + "type": "t_contract(IRToken)22646", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:34" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "202", + "type": "t_contract(IStRSR)22949", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:42" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "203", + "type": "t_contract(IAssetRegistry)20716", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:50" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "204", + "type": "t_contract(IBasketHandler)21025", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:58" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "205", + "type": "t_contract(IBackingManager)20778", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:66" + }, + { + "label": "distributor", + "offset": 0, + "slot": "206", + "type": "t_contract(IDistributor)21478", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:74" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "207", + "type": "t_contract(IRevenueTrader)22735", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:82" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "208", + "type": "t_contract(IRevenueTrader)22735", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:90" + }, + { + "label": "furnace", + "offset": 0, + "slot": "209", + "type": "t_contract(IFurnace)21973", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:98" + }, + { + "label": "broker", + "offset": 0, + "slot": "210", + "type": "t_contract(IBroker)21145", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:106" + }, + { + "label": "__gap", + "offset": 0, + "slot": "211", + "type": "t_array(t_uint256)40_storage", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:119" + }, + { + "label": "__gap", + "offset": 0, + "slot": "251", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "301", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "rsr", + "offset": 0, + "slot": "351", + "type": "t_contract(IERC20)11635", + "contract": "MainP1", + "src": "contracts/p1/Main.sol:19" + }, + { + "label": "__gap", + "offset": 0, + "slot": "352", + "type": "t_array(t_uint256)49_storage", + "contract": "MainP1", + "src": "contracts/p1/Main.sol:71" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)40_storage": { + "label": "uint256[40]", + "numberOfBytes": "1280" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)20716": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)20778": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)21025": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IBroker)21145": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)21478": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)11635": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)21973": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IRToken)22646": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)22735": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)22949": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)80_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(RoleData)80_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "21d20dc99ca71c92408f469f19636b46b188f7821898d70183e299d69ff3f239": { + "address": "0xe981820A4Dd0d5168beb73F57E6dc827420D066C", + "txHash": "0xb8a35b3aedfc4af94e84a45bbafd673474173a5dcac2081fa9d4e4c306b242fb", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22420", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "201", + "type": "t_contract(IBasketHandler)21025", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:18" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "202", + "type": "t_contract(IBackingManager)20778", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:19" + }, + { + "label": "_erc20s", + "offset": 0, + "slot": "203", + "type": "t_struct(AddressSet)17883_storage", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:22" + }, + { + "label": "assets", + "offset": 0, + "slot": "205", + "type": "t_mapping(t_contract(IERC20)11635,t_contract(IAsset)20473)", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:25" + }, + { + "label": "lastRefresh", + "offset": 0, + "slot": "206", + "type": "t_uint48", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "207", + "type": "t_array(t_uint256)46_storage", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:219" + } + ], + "types": { + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAsset)20473": { + "label": "contract IAsset", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)20778": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)21025": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IERC20)11635": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)22420": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)11635,t_contract(IAsset)20473)": { + "label": "mapping(contract IERC20 => contract IAsset)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)17883_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)17582_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Set)17582_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "8fe7d0af60788c44e6cc7060cd99b68b5e670f8ed9514463c83b8507a8c51cc6": { + "address": "0xbe7B053E820c5FBe70a0f075DA0C931aD8816e4F", + "txHash": "0xabcd238cc8bf0eaca91a02a230b8b724c64c91d0683e11e34703011085f93682", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22420", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_status", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:74" + }, + { + "label": "broker", + "offset": 0, + "slot": "251", + "type": "t_contract(IBroker)21145", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:27" + }, + { + "label": "trades", + "offset": 0, + "slot": "252", + "type": "t_mapping(t_contract(IERC20)11635,t_contract(ITrade)23084)", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:30" + }, + { + "label": "tradesOpen", + "offset": 0, + "slot": "253", + "type": "t_uint48", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:31" + }, + { + "label": "maxTradeSlippage", + "offset": 6, + "slot": "253", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:34" + }, + { + "label": "minTradeVolume", + "offset": 0, + "slot": "254", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "255", + "type": "t_array(t_uint256)46_storage", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:161" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "301", + "type": "t_contract(IAssetRegistry)20716", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:25" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "302", + "type": "t_contract(IBasketHandler)21025", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:26" + }, + { + "label": "distributor", + "offset": 0, + "slot": "303", + "type": "t_contract(IDistributor)21478", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:27" + }, + { + "label": "rToken", + "offset": 0, + "slot": "304", + "type": "t_contract(IRToken)22646", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:28" + }, + { + "label": "rsr", + "offset": 0, + "slot": "305", + "type": "t_contract(IERC20)11635", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:29" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "306", + "type": "t_contract(IStRSR)22949", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:30" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "307", + "type": "t_contract(IRevenueTrader)22735", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:31" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "308", + "type": "t_contract(IRevenueTrader)22735", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:32" + }, + { + "label": "tradingDelay", + "offset": 20, + "slot": "308", + "type": "t_uint48", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:36" + }, + { + "label": "backingBuffer", + "offset": 0, + "slot": "309", + "type": "t_uint192", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:37" + }, + { + "label": "furnace", + "offset": 0, + "slot": "310", + "type": "t_contract(IFurnace)21973", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:40" + }, + { + "label": "tradeEnd", + "offset": 0, + "slot": "311", + "type": "t_mapping(t_enum(TradeKind)21047,t_uint48)", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:41" + }, + { + "label": "__gap", + "offset": 0, + "slot": "312", + "type": "t_array(t_uint256)39_storage", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:299" + } + ], + "types": { + "t_array(t_uint256)39_storage": { + "label": "uint256[39]", + "numberOfBytes": "1248" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IAssetRegistry)20716": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)21025": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IBroker)21145": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)21478": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)11635": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)21973": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)22420": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)22646": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)22735": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)22949": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_contract(ITrade)23084": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_enum(TradeKind)21047": { + "label": "enum TradeKind", + "members": [ + "DUTCH_AUCTION", + "BATCH_AUCTION" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_contract(IERC20)11635,t_contract(ITrade)23084)": { + "label": "mapping(contract IERC20 => contract ITrade)", + "numberOfBytes": "32" + }, + "t_mapping(t_enum(TradeKind)21047,t_uint48)": { + "label": "mapping(enum TradeKind => uint48)", + "numberOfBytes": "32" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "06e74acc83f86954e87d2465720810cfada71be55c0cb9f1626d6af4e6192b34": { + "address": "0xA7eCF508CdF5a88ae93b899DE4fcACcB43112Ce8", + "txHash": "0x41b7579559dec433e30e6301072463aa5af90aa2e1d46969397fc1c84a41f064", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22420", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "201", + "type": "t_contract(IAssetRegistry)20716", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:32" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "202", + "type": "t_contract(IBackingManager)20778", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:33" + }, + { + "label": "rsr", + "offset": 0, + "slot": "203", + "type": "t_contract(IERC20)11635", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:34" + }, + { + "label": "rToken", + "offset": 0, + "slot": "204", + "type": "t_contract(IRToken)22646", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:35" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "205", + "type": "t_contract(IStRSR)22949", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:36" + }, + { + "label": "config", + "offset": 0, + "slot": "206", + "type": "t_struct(BasketConfig)49042_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:40" + }, + { + "label": "basket", + "offset": 0, + "slot": "210", + "type": "t_struct(Basket)49052_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:44" + }, + { + "label": "nonce", + "offset": 0, + "slot": "212", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:46" + }, + { + "label": "timestamp", + "offset": 6, + "slot": "212", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:47" + }, + { + "label": "disabled", + "offset": 12, + "slot": "212", + "type": "t_bool", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:51" + }, + { + "label": "_targetNames", + "offset": 0, + "slot": "213", + "type": "t_struct(Bytes32Set)17776_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:57" + }, + { + "label": "_newBasket", + "offset": 0, + "slot": "215", + "type": "t_struct(Basket)49052_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:58" + }, + { + "label": "warmupPeriod", + "offset": 0, + "slot": "217", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:64" + }, + { + "label": "lastStatusTimestamp", + "offset": 6, + "slot": "217", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:68" + }, + { + "label": "lastStatus", + "offset": 12, + "slot": "217", + "type": "t_enum(CollateralStatus)20505", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:69" + }, + { + "label": "basketHistory", + "offset": 0, + "slot": "218", + "type": "t_mapping(t_uint48,t_struct(Basket)49052_storage)", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:75" + }, + { + "label": "_targetAmts", + "offset": 0, + "slot": "219", + "type": "t_struct(Bytes32ToUintMap)17388_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:78" + }, + { + "label": "__gap", + "offset": 0, + "slot": "222", + "type": "t_array(t_uint256)37_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:684" + } + ], + "types": { + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_contract(IERC20)11635)dyn_storage": { + "label": "contract IERC20[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)37_storage": { + "label": "uint256[37]", + "numberOfBytes": "1184" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)20716": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)20778": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IERC20)11635": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)22420": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)22646": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)22949": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_enum(CollateralStatus)20505": { + "label": "enum CollateralStatus", + "members": [ + "SOUND", + "IFFY", + "DISABLED" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_bytes32,t_bytes32)": { + "label": "mapping(bytes32 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(BackupConfig)49022_storage)": { + "label": "mapping(bytes32 => struct BackupConfig)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)11635,t_bytes32)": { + "label": "mapping(contract IERC20 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)11635,t_uint192)": { + "label": "mapping(contract IERC20 => uint192)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint48,t_struct(Basket)49052_storage)": { + "label": "mapping(uint48 => struct Basket)", + "numberOfBytes": "32" + }, + "t_struct(BackupConfig)49022_storage": { + "label": "struct BackupConfig", + "members": [ + { + "label": "max", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)11635)dyn_storage", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Basket)49052_storage": { + "label": "struct Basket", + "members": [ + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)11635)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "refAmts", + "type": "t_mapping(t_contract(IERC20)11635,t_uint192)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(BasketConfig)49042_storage": { + "label": "struct BasketConfig", + "members": [ + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)11635)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "targetAmts", + "type": "t_mapping(t_contract(IERC20)11635,t_uint192)", + "offset": 0, + "slot": "1" + }, + { + "label": "targetNames", + "type": "t_mapping(t_contract(IERC20)11635,t_bytes32)", + "offset": 0, + "slot": "2" + }, + { + "label": "backups", + "type": "t_mapping(t_bytes32,t_struct(BackupConfig)49022_storage)", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_struct(Bytes32Set)17776_storage": { + "label": "struct EnumerableSet.Bytes32Set", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)17582_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Bytes32ToBytes32Map)16465_storage": { + "label": "struct EnumerableMap.Bytes32ToBytes32Map", + "members": [ + { + "label": "_keys", + "type": "t_struct(Bytes32Set)17776_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_values", + "type": "t_mapping(t_bytes32,t_bytes32)", + "offset": 0, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(Bytes32ToUintMap)17388_storage": { + "label": "struct EnumerableMap.Bytes32ToUintMap", + "members": [ + { + "label": "_inner", + "type": "t_struct(Bytes32ToBytes32Map)16465_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "96" + }, + "t_struct(Set)17582_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "564ab7408bfb67e40d480b1e813e3e7111c32f2aba64d9fd45a09a262ce1794c": { + "address": "0xF4aB456F9FBA39b91F46c54185C218E4c32B014e", + "txHash": "0x88e5e4f3057cea882cf1e2be8059f3d84baafc48d50b096211387928f9a4f0a8", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22420", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "201", + "type": "t_contract(IBackingManager)20778", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:29" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "202", + "type": "t_contract(IRevenueTrader)22735", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:30" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "203", + "type": "t_contract(IRevenueTrader)22735", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:31" + }, + { + "label": "batchTradeImplementation", + "offset": 0, + "slot": "204", + "type": "t_contract(ITrade)23084", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:35", + "renamedFrom": "tradeImplementation" + }, + { + "label": "gnosis", + "offset": 0, + "slot": "205", + "type": "t_contract(IGnosis)22073", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:38" + }, + { + "label": "batchAuctionLength", + "offset": 20, + "slot": "205", + "type": "t_uint48", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:42", + "renamedFrom": "auctionLength" + }, + { + "label": "disabled", + "offset": 26, + "slot": "205", + "type": "t_bool", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:46" + }, + { + "label": "trades", + "offset": 0, + "slot": "206", + "type": "t_mapping(t_address,t_bool)", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:49" + }, + { + "label": "dutchTradeImplementation", + "offset": 0, + "slot": "207", + "type": "t_contract(ITrade)23084", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:54" + }, + { + "label": "dutchAuctionLength", + "offset": 20, + "slot": "207", + "type": "t_uint48", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:57" + }, + { + "label": "__gap", + "offset": 0, + "slot": "208", + "type": "t_array(t_uint256)43_storage", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:232" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IBackingManager)20778": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IGnosis)22073": { + "label": "contract IGnosis", + "numberOfBytes": "20" + }, + "t_contract(IMain)22420": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)22735": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(ITrade)23084": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "c442f45e5a30a18c03ad3a19b0773ca6e596eacc7bb582e7317958ad769085c7": { + "address": "0xb120c3429900DDF665b34882d7685e39BB01897B", + "txHash": "0xbaa958e25ef08ac2b3c8141869e0e187b6aee0b98c32eaffae5c5a4e941cd7a1", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22420", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "destinations", + "offset": 0, + "slot": "201", + "type": "t_struct(AddressSet)17883_storage", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:17" + }, + { + "label": "distribution", + "offset": 0, + "slot": "203", + "type": "t_mapping(t_address,t_struct(RevenueShare)21416_storage)", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:18" + }, + { + "label": "rsr", + "offset": 0, + "slot": "204", + "type": "t_contract(IERC20)11635", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:36" + }, + { + "label": "rToken", + "offset": 0, + "slot": "205", + "type": "t_contract(IERC20)11635", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:37" + }, + { + "label": "furnace", + "offset": 0, + "slot": "206", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:38" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "207", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:39" + }, + { + "label": "__gap", + "offset": 0, + "slot": "208", + "type": "t_array(t_uint256)46_storage", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:190" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IERC20)11635": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)22420": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_struct(RevenueShare)21416_storage)": { + "label": "mapping(address => struct RevenueShare)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)17883_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)17582_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(RevenueShare)21416_storage": { + "label": "struct RevenueShare", + "members": [ + { + "label": "rTokenDist", + "type": "t_uint16", + "offset": 0, + "slot": "0" + }, + { + "label": "rsrDist", + "type": "t_uint16", + "offset": 2, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Set)17582_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "e1eeaba6bbd307cda015b0bd4643179b81db28c5abddb6ac31b57095f8ccf1ba": { + "address": "0xa570BF93FC51406809dBf52aB898913541C91C20", + "txHash": "0x609b1c4df3e2925bfe332cf021145f11485f3d23e4ce746d748bc5c9f7f64c44", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22420", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "rToken", + "offset": 0, + "slot": "201", + "type": "t_contract(IRToken)22646", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:18" + }, + { + "label": "ratio", + "offset": 0, + "slot": "202", + "type": "t_uint192", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:21" + }, + { + "label": "lastPayout", + "offset": 24, + "slot": "202", + "type": "t_uint48", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:24" + }, + { + "label": "lastPayoutBal", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:25" + }, + { + "label": "__gap", + "offset": 0, + "slot": "204", + "type": "t_array(t_uint256)47_storage", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:98" + } + ], + "types": { + "t_array(t_uint256)47_storage": { + "label": "uint256[47]", + "numberOfBytes": "1504" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IMain)22420": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)22646": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "1b28a1aa7bfbba80a06938c752e89466601e419fa44682e5bb5fc79b56a7eba6": { + "address": "0x4c5FA27d1785d37B7Ac0942121f95370e61ff6a4", + "txHash": "0xc7fe8213c89b303da2094a8cb56a399bfd8ed436507b77d588145c6d544bbebb", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22420", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_status", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:74" + }, + { + "label": "broker", + "offset": 0, + "slot": "251", + "type": "t_contract(IBroker)21145", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:27" + }, + { + "label": "trades", + "offset": 0, + "slot": "252", + "type": "t_mapping(t_contract(IERC20)11635,t_contract(ITrade)23084)", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:30" + }, + { + "label": "tradesOpen", + "offset": 0, + "slot": "253", + "type": "t_uint48", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:31" + }, + { + "label": "maxTradeSlippage", + "offset": 6, + "slot": "253", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:34" + }, + { + "label": "minTradeVolume", + "offset": 0, + "slot": "254", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "255", + "type": "t_array(t_uint256)46_storage", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:161" + }, + { + "label": "tokenToBuy", + "offset": 0, + "slot": "301", + "type": "t_contract(IERC20)11635", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:19" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "302", + "type": "t_contract(IAssetRegistry)20716", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:20" + }, + { + "label": "distributor", + "offset": 0, + "slot": "303", + "type": "t_contract(IDistributor)21478", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:21" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "304", + "type": "t_contract(IBackingManager)20778", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:22" + }, + { + "label": "furnace", + "offset": 0, + "slot": "305", + "type": "t_contract(IFurnace)21973", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:23" + }, + { + "label": "rToken", + "offset": 0, + "slot": "306", + "type": "t_contract(IRToken)22646", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:24" + }, + { + "label": "__gap", + "offset": 0, + "slot": "307", + "type": "t_array(t_uint256)44_storage", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:155" + } + ], + "types": { + "t_array(t_uint256)44_storage": { + "label": "uint256[44]", + "numberOfBytes": "1408" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IAssetRegistry)20716": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)20778": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBroker)21145": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)21478": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)11635": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)21973": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)22420": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)22646": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(ITrade)23084": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_mapping(t_contract(IERC20)11635,t_contract(ITrade)23084)": { + "label": "mapping(contract IERC20 => contract ITrade)", + "numberOfBytes": "32" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "ec5baa6e23f6979b7cdaac8bf22a9ff7c01800448a786091fa27df763d465f1a": { + "address": "0xd0cb758e918ac6973a2959343ECa4F333d8d25B1", + "txHash": "0xc330d191cbb055d130270257e88736b13fbc220bc95495eca6b8aa6a18bf58c9", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22420", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_balances", + "offset": 0, + "slot": "201", + "type": "t_mapping(t_address,t_uint256)", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:37" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "202", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:39" + }, + { + "label": "_totalSupply", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:41" + }, + { + "label": "_name", + "offset": 0, + "slot": "204", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:43" + }, + { + "label": "_symbol", + "offset": 0, + "slot": "205", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:44" + }, + { + "label": "__gap", + "offset": 0, + "slot": "206", + "type": "t_array(t_uint256)45_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:394" + }, + { + "label": "_HASHED_NAME", + "offset": 0, + "slot": "251", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:32" + }, + { + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "252", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:33" + }, + { + "label": "__gap", + "offset": 0, + "slot": "253", + "type": "t_array(t_uint256)50_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:120" + }, + { + "label": "_nonces", + "offset": 0, + "slot": "303", + "type": "t_mapping(t_address,t_struct(Counter)2739_storage)", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:37" + }, + { + "label": "_PERMIT_TYPEHASH_DEPRECATED_SLOT", + "offset": 0, + "slot": "304", + "type": "t_bytes32", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:51", + "renamedFrom": "_PERMIT_TYPEHASH" + }, + { + "label": "__gap", + "offset": 0, + "slot": "305", + "type": "t_array(t_uint256)48_storage", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:129" + }, + { + "label": "mandate", + "offset": 0, + "slot": "353", + "type": "t_string_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:44" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "354", + "type": "t_contract(IAssetRegistry)20716", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:47" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "355", + "type": "t_contract(IBasketHandler)21025", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:48" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "356", + "type": "t_contract(IBackingManager)20778", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:49" + }, + { + "label": "furnace", + "offset": 0, + "slot": "357", + "type": "t_contract(IFurnace)21973", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:50" + }, + { + "label": "basketsNeeded", + "offset": 0, + "slot": "358", + "type": "t_uint192", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:55" + }, + { + "label": "issuanceThrottle", + "offset": 0, + "slot": "359", + "type": "t_struct(Throttle)25091_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:58" + }, + { + "label": "redemptionThrottle", + "offset": 0, + "slot": "363", + "type": "t_struct(Throttle)25091_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:59" + }, + { + "label": "__gap", + "offset": 0, + "slot": "367", + "type": "t_array(t_uint256)42_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:532" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)42_storage": { + "label": "uint256[42]", + "numberOfBytes": "1344" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)20716": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)20778": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)21025": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)21973": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)22420": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Counter)2739_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Counter)2739_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Params)25083_storage": { + "label": "struct ThrottleLib.Params", + "members": [ + { + "label": "amtRate", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "pctRate", + "type": "t_uint192", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Throttle)25091_storage": { + "label": "struct ThrottleLib.Throttle", + "members": [ + { + "label": "params", + "type": "t_struct(Params)25083_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "lastTimestamp", + "type": "t_uint48", + "offset": 0, + "slot": "2" + }, + { + "label": "lastAvailable", + "type": "t_uint256", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "6fee1f2375271f998ec540323259cd0f6280f0f6fca2d1fba4c264fe6670253d": { + "address": "0xeC12e8412a7AE4598d754f4016D487c269719856", + "txHash": "0xc385c0254f25fa45a0857def570cc839ac915c729d2b1a284cc1e6f4e0528bfd", + "layout": { + "solcVersion": "0.8.17", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22420", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_HASHED_NAME", + "offset": 0, + "slot": "201", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:32" + }, + { + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "202", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:33" + }, + { + "label": "__gap", + "offset": 0, + "slot": "203", + "type": "t_array(t_uint256)50_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:120" + }, + { + "label": "name", + "offset": 0, + "slot": "253", + "type": "t_string_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:43" + }, + { + "label": "symbol", + "offset": 0, + "slot": "254", + "type": "t_string_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:44" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "255", + "type": "t_contract(IAssetRegistry)20716", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:49" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "256", + "type": "t_contract(IBackingManager)20778", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:50" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "257", + "type": "t_contract(IBasketHandler)21025", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:51" + }, + { + "label": "rsr", + "offset": 0, + "slot": "258", + "type": "t_contract(IERC20)11635", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:52" + }, + { + "label": "era", + "offset": 0, + "slot": "259", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:57" + }, + { + "label": "stakes", + "offset": 0, + "slot": "260", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:61" + }, + { + "label": "totalStakes", + "offset": 0, + "slot": "261", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:62" + }, + { + "label": "stakeRSR", + "offset": 0, + "slot": "262", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:63" + }, + { + "label": "stakeRate", + "offset": 0, + "slot": "263", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:64" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "264", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_mapping(t_address,t_uint256)))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:69" + }, + { + "label": "draftEra", + "offset": 0, + "slot": "265", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:74" + }, + { + "label": "draftQueues", + "offset": 0, + "slot": "266", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(CumulativeDraft)46080_storage)dyn_storage))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:82" + }, + { + "label": "firstRemainingDraft", + "offset": 0, + "slot": "267", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:83" + }, + { + "label": "totalDrafts", + "offset": 0, + "slot": "268", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:84" + }, + { + "label": "draftRSR", + "offset": 0, + "slot": "269", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:85" + }, + { + "label": "draftRate", + "offset": 0, + "slot": "270", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:86" + }, + { + "label": "_nonces", + "offset": 0, + "slot": "271", + "type": "t_mapping(t_address,t_struct(Counter)2739_storage)", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:124" + }, + { + "label": "_delegationNonces", + "offset": 0, + "slot": "272", + "type": "t_mapping(t_address,t_struct(Counter)2739_storage)", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:126" + }, + { + "label": "unstakingDelay", + "offset": 0, + "slot": "273", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:136" + }, + { + "label": "rewardRatio", + "offset": 6, + "slot": "273", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:137" + }, + { + "label": "payoutLastPaid", + "offset": 0, + "slot": "274", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:148" + }, + { + "label": "rsrRewardsAtLastPayout", + "offset": 0, + "slot": "275", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:151" + }, + { + "label": "leaked", + "offset": 0, + "slot": "276", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:157" + }, + { + "label": "lastWithdrawRefresh", + "offset": 24, + "slot": "276", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:158" + }, + { + "label": "withdrawalLeak", + "offset": 0, + "slot": "277", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:159" + }, + { + "label": "__gap", + "offset": 0, + "slot": "278", + "type": "t_array(t_uint256)28_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:958" + }, + { + "label": "_delegates", + "offset": 0, + "slot": "306", + "type": "t_mapping(t_address,t_address)", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:31" + }, + { + "label": "_eras", + "offset": 0, + "slot": "307", + "type": "t_array(t_struct(Checkpoint)48275_storage)dyn_storage", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:34" + }, + { + "label": "_checkpoints", + "offset": 0, + "slot": "308", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(Checkpoint)48275_storage)dyn_storage))", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:38" + }, + { + "label": "_totalSupplyCheckpoints", + "offset": 0, + "slot": "309", + "type": "t_mapping(t_uint256,t_array(t_struct(Checkpoint)48275_storage)dyn_storage)", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:40" + }, + { + "label": "__gap", + "offset": 0, + "slot": "310", + "type": "t_array(t_uint256)46_storage", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:243" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_struct(Checkpoint)48275_storage)dyn_storage": { + "label": "struct StRSRP1Votes.Checkpoint[]", + "numberOfBytes": "32" + }, + "t_array(t_struct(CumulativeDraft)46080_storage)dyn_storage": { + "label": "struct StRSRP1.CumulativeDraft[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)28_storage": { + "label": "uint256[28]", + "numberOfBytes": "896" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)20716": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)20778": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)21025": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IERC20)11635": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)22420": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_address)": { + "label": "mapping(address => address)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_array(t_struct(Checkpoint)48275_storage)dyn_storage)": { + "label": "mapping(address => struct StRSRP1Votes.Checkpoint[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_array(t_struct(CumulativeDraft)46080_storage)dyn_storage)": { + "label": "mapping(address => struct StRSRP1.CumulativeDraft[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Counter)2739_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_array(t_struct(Checkpoint)48275_storage)dyn_storage)": { + "label": "mapping(uint256 => struct StRSRP1Votes.Checkpoint[])", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(Checkpoint)48275_storage)dyn_storage))": { + "label": "mapping(uint256 => mapping(address => struct StRSRP1Votes.Checkpoint[]))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(CumulativeDraft)46080_storage)dyn_storage))": { + "label": "mapping(uint256 => mapping(address => struct StRSRP1.CumulativeDraft[]))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_mapping(t_address,t_uint256)))": { + "label": "mapping(uint256 => mapping(address => mapping(address => uint256)))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_uint256))": { + "label": "mapping(uint256 => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Checkpoint)48275_storage": { + "label": "struct StRSRP1Votes.Checkpoint", + "members": [ + { + "label": "fromBlock", + "type": "t_uint48", + "offset": 0, + "slot": "0" + }, + { + "label": "val", + "type": "t_uint224", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Counter)2739_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(CumulativeDraft)46080_storage": { + "label": "struct StRSRP1.CumulativeDraft", + "members": [ + { + "label": "drafts", + "type": "t_uint176", + "offset": 0, + "slot": "0" + }, + { + "label": "availableAt", + "type": "t_uint64", + "offset": 22, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_uint176": { + "label": "uint176", + "numberOfBytes": "22" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint224": { + "label": "uint224", + "numberOfBytes": "28" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } } } } diff --git a/.solcover.js b/.solcover.js index df2f65f1d8..c5f8686d55 100644 --- a/.solcover.js +++ b/.solcover.js @@ -2,17 +2,17 @@ module.exports = { skipFiles: [ 'mocks', 'test', - 'prod/test', + 'vendor', 'libraries/Fixed.sol', 'libraries/test', - 'p0/test', - 'p0/mocks', - 'p1/test', - 'p1/mocks', - 'p2/test', - 'p2/mocks', 'plugins/mocks', - 'plugins/aave', + 'plugins/assets/aave/vendor', + 'plugins/assets/ankr/vendor', + 'plugins/assets/compoundv3/vendor', + 'plugins/assets/curve/cvx/vendor', + 'plugins/assets/frax-eth/vendor', + 'plugins/assets/lido/vendor', + 'plugins/assets/rocket-eth/vendor', 'fuzz', ], configureYulOptimizer: true, diff --git a/.solhintignore b/.solhintignore index 01246d39df..db227ef747 100644 --- a/.solhintignore +++ b/.solhintignore @@ -18,4 +18,5 @@ contracts/libraries/Fixed.sol contracts/vendor contracts/plugins/assets/compound/vendor -contracts/plugins/assets/convex/vendor +contracts/plugins/assets/curve/crv +contracts/plugins/assets/curve/cvx diff --git a/CHANGELOG.md b/CHANGELOG.md index cb6d15e377..c8b49994a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ Collateral / Asset plugins from 2.1.0 do not need to be upgraded #### Core Protocol Contracts +Bump solidity version to 0.8.19 + - `AssetRegistry` [+1 slot] Summary: Other component contracts need to know when refresh() was last called - Add last refresh timestamp tracking and expose via `lastRefresh()` getter diff --git a/README.md b/README.md index 9f10688371..ae48bf253d 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,8 @@ If you would like to contribute, you'll need to configure a secret in your fork Usage: `https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}` +To get setup with tenderly, install the [tenderly cli](https://github.com/Tenderly/tenderly-cli). and login with `tenderly login --authentication-method access-key --access-key {your_access_key} --force`. + ## External Documentation [Video overview](https://youtu.be/341MhkOWsJE) diff --git a/common/configuration.ts b/common/configuration.ts index 1c8cdf1d7b..6f14eff79a 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -13,6 +13,7 @@ export interface ITokens { USDP?: string TUSD?: string BUSD?: string + sUSD?: string FRAX?: string MIM?: string eUSD?: string @@ -48,6 +49,9 @@ export interface ITokens { wstETH?: string rETH?: string cUSDCv3?: string + ONDO?: string + sDAI?: string + cbETH?: string } export interface IFeeds { @@ -55,11 +59,15 @@ export interface IFeeds { stETHUSD?: string } -export interface IPlugins { +export interface IPools { cvx3Pool?: string cvxeUSDFRAXBP?: string cvxTriCrypto?: string cvxMIM3Pool?: string + crv3Pool?: string + crveUSDFRAXBP?: string + crvTriCrypto?: string + crvMIM3Pool?: string } interface INetworkConfig { @@ -69,6 +77,7 @@ interface INetworkConfig { AAVE_LENDING_POOL?: string AAVE_INCENTIVES?: string AAVE_EMISSIONS_MGR?: string + AAVE_RESERVE_TREASURY?: string COMPTROLLER?: string FLUX_FINANCE_COMPTROLLER?: string GNOSIS_EASY_AUCTION?: string @@ -91,6 +100,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { BUSD: '0x4Fabb145d64652a948d72533023f6E7A623C7C53', USDP: '0x8E870D67F660D95d5be530380D0eC0bd388289E1', TUSD: '0x0000000000085d4780B73119b644AE5ecd22b376', + sUSD: '0x57Ab1ec28D129707052df4dF418D58a2D46d5f51', FRAX: '0x853d955aCEf822Db058eb8505911ED77F175b99e', MIM: '0x99d8a9c45b2eca8864373a26d1459e3dff1e17f3', eUSD: '0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F', @@ -126,6 +136,9 @@ export const networkConfig: { [key: string]: INetworkConfig } = { wstETH: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', rETH: '0xae78736Cd615f374D3085123A210448E74Fc6393', cUSDCv3: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', + ONDO: '0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3', + sDAI: '0x83f20f44975d03b1b09e64809b757c47f942beea', + cbETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', }, chainlinkFeeds: { RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', @@ -137,6 +150,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { BUSD: '0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A', USDP: '0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3', TUSD: '0xec746eCF986E2927Abd291a2A1716c940100f8Ba', + sUSD: '0xad35Bd71b9aFE6e4bDc266B345c198eaDEf9Ad94', FRAX: '0xB9E1E3A9feFf48998E45Fa90847ed4D467E8BcfD', MIM: '0x7A364e8770418566e3eb2001A96116E6138Eb32F', ETH: '0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419', @@ -149,10 +163,12 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHETH: '0x86392dc19c0b719886221c78ab11eb8cf5c52812', // stETH/ETH stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH + cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5', AAVE_EMISSIONS_MGR: '0xEE56e2B3D491590B5b31738cC34d5232F378a8D5', + AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', FLUX_FINANCE_COMPTROLLER: '0x95Af143a021DF745bc78e845b54591C53a8B3A51', COMPTROLLER: '0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B', GNOSIS_EASY_AUCTION: '0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101', @@ -176,6 +192,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { BUSD: '0x4Fabb145d64652a948d72533023f6E7A623C7C53', USDP: '0x8E870D67F660D95d5be530380D0eC0bd388289E1', TUSD: '0x0000000000085d4780B73119b644AE5ecd22b376', + sUSD: '0x57Ab1ec28D129707052df4dF418D58a2D46d5f51', FRAX: '0x853d955aCEf822Db058eb8505911ED77F175b99e', MIM: '0x99d8a9c45b2eca8864373a26d1459e3dff1e17f3', eUSD: '0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F', @@ -211,6 +228,9 @@ export const networkConfig: { [key: string]: INetworkConfig } = { wstETH: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', rETH: '0xae78736Cd615f374D3085123A210448E74Fc6393', cUSDCv3: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', + ONDO: '0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3', + sDAI: '0x83f20f44975d03b1b09e64809b757c47f942beea', + cbETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', }, chainlinkFeeds: { RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', @@ -222,6 +242,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { BUSD: '0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A', USDP: '0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3', TUSD: '0xec746eCF986E2927Abd291a2A1716c940100f8Ba', + sUSD: '0xad35Bd71b9aFE6e4bDc266B345c198eaDEf9Ad94', FRAX: '0xB9E1E3A9feFf48998E45Fa90847ed4D467E8BcfD', MIM: '0x7A364e8770418566e3eb2001A96116E6138Eb32F', ETH: '0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419', @@ -234,8 +255,10 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHETH: '0x86392dc19c0b719886221c78ab11eb8cf5c52812', // stETH/ETH stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH + cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', + AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', FLUX_FINANCE_COMPTROLLER: '0x95Af143a021DF745bc78e845b54591C53a8B3A51', COMPTROLLER: '0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B', GNOSIS_EASY_AUCTION: '0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101', @@ -268,7 +291,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { fDAI: '0x7e1e077b289c0153b5ceAD9F264d66215341c9Ab', AAVE: '0xc47324262e1C7be67270Da717e1a0e7b0191c449', stkAAVE: '0x3Db8b170DA19c45B63B959789f20f397F22767D4', - COMP: '0x1b4449895037f25b102B28B45b8bD50c8C44Aca1', + COMP: '0xe16c7165c8fea64069802ae4c4c9c320783f2b6e', // canonical COMP WETH: '0xB5B58F0a853132EA8cB614cb17095dE87AF3E98b', WBTC: '0x528FdEd7CC39209ed67B4edA11937A9ABe1f6249', EURT: '0xD6da5A7ADE2a906d9992612752A339E3485dB508', @@ -278,9 +301,9 @@ export const networkConfig: { [key: string]: INetworkConfig } = { ankrETH: '0xE2b16e14dB6216e33082D5A8Be1Ef01DF7511bBb', frxETH: '0xe0E1d3c6f09DA01399e84699722B11308607BBfC', sfrxETH: '0x291ed25eB61fcc074156eE79c5Da87e5DA94198F', - stETH: '0x97F9d5ed17A0C99B279887caD5254d15fb1B619B', - wstETH: '0xd000a79BD2a07EB6D2e02ECAd73437De40E52d69', - rETH: '0x2304E98cD1E2F0fd3b4E30A1Bc6E9594dE2ea9b7', + stETH: '0x1643E812aE58766192Cf7D2Cf9567dF2C37e9B7F', + wstETH: '0x6320cD32aA674d2898A68ec82e869385Fc5f7E2f', + rETH: '0x178E141a0E3b34152f73Ff610437A7bf9B83267A', cUSDCv3: '0x719fbae9e2Dcd525bCf060a8D5DBC6C9fE104A50', }, chainlinkFeeds: { @@ -300,10 +323,13 @@ export const networkConfig: { [key: string]: INetworkConfig } = { WBTC: '0xe52CE9436F2D4D4B744720aAEEfD9C6dbFC00b34', EURT: '0x68aA66BCde901c741C5EF07314875434E51E5D30', EUR: '0x12336777de46b9a6Edd7176E532810149C787bcD', + rETH: '0xeb1cDb6C2F18173eaC53fEd1DC03fe13286f86ec', + stETHUSD: '0x6dCCE86FFb3c1FC44Ded9a6E200eF12d0D4256a3', + stETHETH: '0x81ff01E93F86f41d3DFf66283Be2aD0a3C284604' }, AAVE_LENDING_POOL: '0x3e9E33B84C1cD9037be16AA45A0B296ae5F185AD', // mock GNOSIS_EASY_AUCTION: '0x1fbab40c338e2e7243da945820ba680c92ef8281', // canonical - COMPTROLLER: '0x3d9819210a31b4961b30ef54be2aed79b9c9cd3b', // canonical + COMPTROLLER: '0x627ea49279fd0de89186a58b8758ad02b6be2867', // canonical }, } @@ -422,6 +448,7 @@ export const MAX_MIN_TRADE_VOLUME = BigNumber.from(10).pow(29) export const MIN_THROTTLE_AMT_RATE = BigNumber.from(10).pow(18) export const MAX_THROTTLE_AMT_RATE = BigNumber.from(10).pow(48) export const MAX_THROTTLE_PCT_RATE = BigNumber.from(10).pow(18) +export const GNOSIS_MAX_TOKENS = BigNumber.from(7).mul(BigNumber.from(10).pow(28)) // Timestamps export const MAX_ORACLE_TIMEOUT = BigNumber.from(2).pow(48).sub(1) diff --git a/contracts/facade/DeployerRegistry.sol b/contracts/facade/DeployerRegistry.sol index 74fd82ad1f..1e3344c898 100644 --- a/contracts/facade/DeployerRegistry.sol +++ b/contracts/facade/DeployerRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/access/Ownable.sol"; import "../interfaces/IDeployerRegistry.sol"; diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index a0671dd27f..283d9cb385 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/Multicall.sol"; @@ -14,6 +14,7 @@ import "../interfaces/IFacadeRead.sol"; * For use with ^3.0.0 RTokens. */ contract FacadeAct is IFacadeAct, Multicall { + using SafeERC20 for IERC20; using FixLib for uint192; function claimRewards(IRToken rToken) public { diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 3f93878a80..29f076e408 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../plugins/trading/DutchTrade.sol"; diff --git a/contracts/facade/FacadeTest.sol b/contracts/facade/FacadeTest.sol index de71f7542f..749a5bf422 100644 --- a/contracts/facade/FacadeTest.sol +++ b/contracts/facade/FacadeTest.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../interfaces/IAsset.sol"; diff --git a/contracts/facade/FacadeWrite.sol b/contracts/facade/FacadeWrite.sol index 134fa7c702..7d4a4c2c12 100644 --- a/contracts/facade/FacadeWrite.sol +++ b/contracts/facade/FacadeWrite.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../interfaces/IFacadeWrite.sol"; import "./lib/FacadeWriteLib.sol"; diff --git a/contracts/facade/lib/FacadeWriteLib.sol b/contracts/facade/lib/FacadeWriteLib.sol index 2671fe1337..8ad05ba40e 100644 --- a/contracts/facade/lib/FacadeWriteLib.sol +++ b/contracts/facade/lib/FacadeWriteLib.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../../plugins/governance/Governance.sol"; diff --git a/contracts/interfaces/IAsset.sol b/contracts/interfaces/IAsset.sol index 824b9c4039..bd796190a7 100644 --- a/contracts/interfaces/IAsset.sol +++ b/contracts/interfaces/IAsset.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/interfaces/IAssetRegistry.sol b/contracts/interfaces/IAssetRegistry.sol index b5bdacf2f6..caeaac2f3e 100644 --- a/contracts/interfaces/IAssetRegistry.sol +++ b/contracts/interfaces/IAssetRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./IAsset.sol"; diff --git a/contracts/interfaces/IBackingManager.sol b/contracts/interfaces/IBackingManager.sol index 982a79aa9e..1aa4b9fb39 100644 --- a/contracts/interfaces/IBackingManager.sol +++ b/contracts/interfaces/IBackingManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./IBroker.sol"; diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 30a9c355d6..f944550840 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../libraries/Fixed.sol"; diff --git a/contracts/interfaces/IBroker.sol b/contracts/interfaces/IBroker.sol index 13a8031098..c02fab9192 100644 --- a/contracts/interfaces/IBroker.sol +++ b/contracts/interfaces/IBroker.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./IAsset.sol"; import "./IComponent.sol"; diff --git a/contracts/interfaces/IComponent.sol b/contracts/interfaces/IComponent.sol index 90f227bd76..88d221e4b6 100644 --- a/contracts/interfaces/IComponent.sol +++ b/contracts/interfaces/IComponent.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./IMain.sol"; import "./IVersioned.sol"; diff --git a/contracts/interfaces/IDeployer.sol b/contracts/interfaces/IDeployer.sol index d338d1160e..6eb344aa02 100644 --- a/contracts/interfaces/IDeployer.sol +++ b/contracts/interfaces/IDeployer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../libraries/Throttle.sol"; @@ -30,7 +30,7 @@ struct DeploymentParams { uint48 longFreeze; // {s} how long each freeze extension lasts // // === Rewards (Furnace + StRSR) === - uint192 rewardRatio; // the fraction of available revenues that are paid out each 12s period + uint192 rewardRatio; // the fraction of available revenues that are paid out each block period // // === StRSR === uint48 unstakingDelay; // {s} the "thawing time" of staked RSR before withdrawal diff --git a/contracts/interfaces/IDeployerRegistry.sol b/contracts/interfaces/IDeployerRegistry.sol index 85c9b1b63e..25e0a33bd5 100644 --- a/contracts/interfaces/IDeployerRegistry.sol +++ b/contracts/interfaces/IDeployerRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./IDeployer.sol"; diff --git a/contracts/interfaces/IDistributor.sol b/contracts/interfaces/IDistributor.sol index 19e69cbdbc..10606c6cba 100644 --- a/contracts/interfaces/IDistributor.sol +++ b/contracts/interfaces/IDistributor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./IComponent.sol"; diff --git a/contracts/interfaces/IFacadeAct.sol b/contracts/interfaces/IFacadeAct.sol index 600071daef..b3c77eea04 100644 --- a/contracts/interfaces/IFacadeAct.sol +++ b/contracts/interfaces/IFacadeAct.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../interfaces/IBackingManager.sol"; +import "../interfaces/IStRSRVotes.sol"; import "../interfaces/IRevenueTrader.sol"; import "../interfaces/IRToken.sol"; diff --git a/contracts/interfaces/IFacadeRead.sol b/contracts/interfaces/IFacadeRead.sol index 3490690d6b..61e2dbd521 100644 --- a/contracts/interfaces/IFacadeRead.sol +++ b/contracts/interfaces/IFacadeRead.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../p1/RToken.sol"; import "./IRToken.sol"; diff --git a/contracts/interfaces/IFacadeTest.sol b/contracts/interfaces/IFacadeTest.sol index cdcb3e1218..6d2de1ece5 100644 --- a/contracts/interfaces/IFacadeTest.sol +++ b/contracts/interfaces/IFacadeTest.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./IRToken.sol"; import "./IStRSR.sol"; diff --git a/contracts/interfaces/IFacadeWrite.sol b/contracts/interfaces/IFacadeWrite.sol index 04d4bd7bec..f56cd32947 100644 --- a/contracts/interfaces/IFacadeWrite.sol +++ b/contracts/interfaces/IFacadeWrite.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./IDeployer.sol"; diff --git a/contracts/interfaces/IFurnace.sol b/contracts/interfaces/IFurnace.sol index 405459d661..25c754aacf 100644 --- a/contracts/interfaces/IFurnace.sol +++ b/contracts/interfaces/IFurnace.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../libraries/Fixed.sol"; import "./IComponent.sol"; diff --git a/contracts/interfaces/IGnosis.sol b/contracts/interfaces/IGnosis.sol index 1c7d0b95c3..4439de8055 100644 --- a/contracts/interfaces/IGnosis.sol +++ b/contracts/interfaces/IGnosis.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/interfaces/IMain.sol b/contracts/interfaces/IMain.sol index fbdb421f18..00bb261de9 100644 --- a/contracts/interfaces/IMain.sol +++ b/contracts/interfaces/IMain.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/access/IAccessControlUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -16,10 +16,6 @@ import "./IStRSR.sol"; import "./ITrading.sol"; import "./IVersioned.sol"; -// Warning, assumption: Chain should have blocktimes of 12s -// See docs/system-design.md for discussion of handling longer or shorter times -uint48 constant ONE_BLOCK = 12; //{s} - // === Auth roles === bytes32 constant OWNER = bytes32(bytes("OWNER")); diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index fe6246923b..31bf6cc9f9 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; // solhint-disable-next-line max-line-length diff --git a/contracts/interfaces/IRTokenOracle.sol b/contracts/interfaces/IRTokenOracle.sol new file mode 100644 index 0000000000..e4f42dde38 --- /dev/null +++ b/contracts/interfaces/IRTokenOracle.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +// RToken Oracle Interface +interface IRTokenOracle { + struct CachedOracleData { + uint192 cachedPrice; // {UoA/tok} + uint256 cachedAtTime; // {s} + uint48 cachedAtNonce; // {basketNonce} + uint48 cachedTradesOpen; + uint256 cachedTradesNonce; // {tradeNonce} + } + + // @returns rTokenPrice {D18} {UoA/rTok} The price of the RToken, in UoA + function latestPrice() external returns (uint192 rTokenPrice, uint256 updatedAt); + + // Force recalculate the price of the RToken + function forceUpdatePrice() external; +} diff --git a/contracts/interfaces/IRevenueTrader.sol b/contracts/interfaces/IRevenueTrader.sol index 7f90f20b89..aaff95bc30 100644 --- a/contracts/interfaces/IRevenueTrader.sol +++ b/contracts/interfaces/IRevenueTrader.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./IBroker.sol"; import "./IComponent.sol"; diff --git a/contracts/interfaces/IRewardable.sol b/contracts/interfaces/IRewardable.sol index 3749ed0f6f..90563bad51 100644 --- a/contracts/interfaces/IRewardable.sol +++ b/contracts/interfaces/IRewardable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./IComponent.sol"; diff --git a/contracts/interfaces/IStRSR.sol b/contracts/interfaces/IStRSR.sol index cf503404f7..45b7db9227 100644 --- a/contracts/interfaces/IStRSR.sol +++ b/contracts/interfaces/IStRSR.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; // solhint-disable-next-line max-line-length diff --git a/contracts/interfaces/IStRSRVotes.sol b/contracts/interfaces/IStRSRVotes.sol index 1f59684708..246ead7cd5 100644 --- a/contracts/interfaces/IStRSRVotes.sol +++ b/contracts/interfaces/IStRSRVotes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/governance/utils/IVotesUpgradeable.sol"; diff --git a/contracts/interfaces/ITrade.sol b/contracts/interfaces/ITrade.sol index 57ae23b501..d05e3028f6 100644 --- a/contracts/interfaces/ITrade.sol +++ b/contracts/interfaces/ITrade.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "./IBroker.sol"; diff --git a/contracts/interfaces/ITrading.sol b/contracts/interfaces/ITrading.sol index fd3801855b..4c40bce0e0 100644 --- a/contracts/interfaces/ITrading.sol +++ b/contracts/interfaces/ITrading.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../libraries/Fixed.sol"; @@ -62,13 +62,8 @@ interface ITrading is IComponent, IRewardableComponent { /// @return The number of ongoing trades open function tradesOpen() external view returns (uint48); - /// Light wrapper around FixLib.mulDiv to support try-catch - function mulDiv( - uint192 x, - uint192 y, - uint192 z, - RoundingMode rounding - ) external pure returns (uint192); + /// @return The number of total trades ever opened + function tradesNonce() external view returns (uint256); } interface TestITrading is ITrading { diff --git a/contracts/interfaces/IVersioned.sol b/contracts/interfaces/IVersioned.sol index 0fc1df4ae4..b7d98407c4 100644 --- a/contracts/interfaces/IVersioned.sol +++ b/contracts/interfaces/IVersioned.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; interface IVersioned { function version() external view returns (string memory); diff --git a/contracts/libraries/Array.sol b/contracts/libraries/Array.sol index 2e8b75d62b..5a46e97ff3 100644 --- a/contracts/libraries/Array.sol +++ b/contracts/libraries/Array.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/libraries/Fixed.sol b/contracts/libraries/Fixed.sol index e22042719f..de4e6c37b6 100644 --- a/contracts/libraries/Fixed.sol +++ b/contracts/libraries/Fixed.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: BlueOak-1.0.0 // solhint-disable func-name-mixedcase func-visibility -pragma solidity ^0.8.17; +pragma solidity ^0.8.19; /// @title FixedPoint, a fixed-point arithmetic library defining the custom type uint192 /// @author Matt Elder and the Reserve Team @@ -532,6 +532,73 @@ library FixLib { return uint192(shiftDelta / FIX_ONE); // {D18} = {D36} / {D18} } } + + /// Divide two fixes, rounding up to FIX_MAX and down to 0 + /// @param a Numerator + /// @param b Denominator + function safeDiv( + uint192 a, + uint192 b, + RoundingMode rounding + ) internal pure returns (uint192) { + if (a == 0) return 0; + if (b == 0) return FIX_MAX; + + uint256 raw = _divrnd(FIX_ONE_256 * a, uint256(b), rounding); + if (raw >= FIX_MAX) return FIX_MAX; + return uint192(raw); // don't need _safeWrap + } + + /// Multiplies two fixes and divide by a third + /// @param a First to multiply + /// @param b Second to multiply + /// @param c Denominator + function safeMulDiv( + uint192 a, + uint192 b, + uint192 c, + RoundingMode rounding + ) internal pure returns (uint192 result) { + if (a == 0 || b == 0) return 0; + if (a == FIX_MAX || b == FIX_MAX || c == 0) return FIX_MAX; + + uint256 result_256; + unchecked { + (uint256 hi, uint256 lo) = fullMul(a, b); + if (hi >= c) return FIX_MAX; + uint256 mm = mulmod(a, b, c); + if (mm > lo) hi -= 1; + lo -= mm; + uint256 pow2 = c & (0 - c); + + uint256 c_256 = uint256(c); + // Warning: Should not access c below this line + + c_256 /= pow2; + lo /= pow2; + lo += hi * ((0 - pow2) / pow2 + 1); + uint256 r = 1; + r *= 2 - c_256 * r; + r *= 2 - c_256 * r; + r *= 2 - c_256 * r; + r *= 2 - c_256 * r; + r *= 2 - c_256 * r; + r *= 2 - c_256 * r; + r *= 2 - c_256 * r; + r *= 2 - c_256 * r; + result_256 = lo * r; + + // Apply rounding + if (rounding == CEIL) { + if (mm > 0) result_256 += 1; + } else if (rounding == ROUND) { + if (mm > ((c_256 - 1) / 2)) result_256 += 1; + } + } + + if (result_256 >= FIX_MAX) return FIX_MAX; + return uint192(result_256); + } } // ================ a couple pure-uint helpers================ diff --git a/contracts/libraries/Permit.sol b/contracts/libraries/Permit.sol index fe04b1f370..ecda68e89d 100644 --- a/contracts/libraries/Permit.sol +++ b/contracts/libraries/Permit.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/cryptography/SignatureCheckerUpgradeable.sol"; diff --git a/contracts/libraries/String.sol b/contracts/libraries/String.sol index a48103f2fd..53a473fe5a 100644 --- a/contracts/libraries/String.sol +++ b/contracts/libraries/String.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; // From https://gist.github.com/ottodevs/c43d0a8b4b891ac2da675f825b1d1dbf library StringLib { diff --git a/contracts/libraries/Throttle.sol b/contracts/libraries/Throttle.sol index d43b89e39f..258796be29 100644 --- a/contracts/libraries/Throttle.sol +++ b/contracts/libraries/Throttle.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./Fixed.sol"; diff --git a/contracts/libraries/test/ArrayCallerMock.sol b/contracts/libraries/test/ArrayCallerMock.sol index d9595c3e23..ab823990b4 100644 --- a/contracts/libraries/test/ArrayCallerMock.sol +++ b/contracts/libraries/test/ArrayCallerMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity ^0.8.17; +pragma solidity ^0.8.19; import "../Array.sol"; diff --git a/contracts/libraries/test/FixedCallerMock.sol b/contracts/libraries/test/FixedCallerMock.sol index f9cdf1decd..98701eb580 100644 --- a/contracts/libraries/test/FixedCallerMock.sol +++ b/contracts/libraries/test/FixedCallerMock.sol @@ -1,156 +1,282 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity ^0.8.17; +pragma solidity ^0.8.19; import "../Fixed.sol"; // Simple mock for Fixed library. -// prettier-ignore contract FixedCallerMock { - function toFix_(uint256 x) public pure returns (uint192 ) { + function toFix_(uint256 x) public pure returns (uint192) { return toFix(x); } - function shiftl_toFix_(uint256 x, int8 d) public pure returns (uint192 ) { + + function shiftl_toFix_(uint256 x, int8 d) public pure returns (uint192) { return shiftl_toFix(x, d); } - function shiftl_toFix_Rnd(uint256 x, int8 d, RoundingMode rnd) public pure returns (uint192 ) { + + function shiftl_toFix_Rnd( + uint256 x, + int8 d, + RoundingMode rnd + ) public pure returns (uint192) { return shiftl_toFix(x, d, rnd); } - function divFix_(uint256 x, uint192 y) public pure returns (uint192 ) { + + function divFix_(uint256 x, uint192 y) public pure returns (uint192) { return divFix(x, y); } + function divuu_(uint256 x, uint256 y) public pure returns (uint256) { return divuu(x, y); } - function fixMin_(uint192 x, uint192 y) public pure returns (uint192 ) { + + function fixMin_(uint192 x, uint192 y) public pure returns (uint192) { return fixMin(x, y); } - function fixMax_(uint192 x, uint192 y) public pure returns (uint192 ) { + + function fixMax_(uint192 x, uint192 y) public pure returns (uint192) { return fixMax(x, y); } + function abs_(int256 x) public pure returns (uint256) { return abs(x); } - function divrnd_(uint256 n, uint256 d, RoundingMode rnd) public pure returns (uint256) { + + function divrnd_( + uint256 n, + uint256 d, + RoundingMode rnd + ) public pure returns (uint256) { return _divrnd(n, d, rnd); } - function toUint(uint192 x) public pure returns (uint256 ) { + + function toUint(uint192 x) public pure returns (uint256) { return FixLib.toUint(x); } - function toUintRnd(uint192 x, RoundingMode rnd) public pure returns (uint256 ) { + + function toUintRnd(uint192 x, RoundingMode rnd) public pure returns (uint256) { return FixLib.toUint(x, rnd); } - function shiftl(uint192 x, int8 decimals) public pure returns (uint192 ) { + + function shiftl(uint192 x, int8 decimals) public pure returns (uint192) { return FixLib.shiftl(x, decimals); } - function shiftlRnd(uint192 x, int8 decimals, RoundingMode rnd) public pure returns (uint192 ) { + + function shiftlRnd( + uint192 x, + int8 decimals, + RoundingMode rnd + ) public pure returns (uint192) { return FixLib.shiftl(x, decimals, rnd); } - function plus(uint192 x, uint192 y) public pure returns (uint192 ) { + + function plus(uint192 x, uint192 y) public pure returns (uint192) { return FixLib.plus(x, y); } - function plusu(uint192 x, uint256 y) public pure returns (uint192 ) { + + function plusu(uint192 x, uint256 y) public pure returns (uint192) { return FixLib.plusu(x, y); } - function minus(uint192 x, uint192 y) public pure returns (uint192 ) { + + function minus(uint192 x, uint192 y) public pure returns (uint192) { return FixLib.minus(x, y); } - function minusu(uint192 x, uint256 y) public pure returns (uint192 ) { + + function minusu(uint192 x, uint256 y) public pure returns (uint192) { return FixLib.minusu(x, y); } - function mul(uint192 x, uint192 y) public pure returns (uint192 ) { + + function mul(uint192 x, uint192 y) public pure returns (uint192) { return FixLib.mul(x, y); } - function mulRnd(uint192 x, uint192 y, RoundingMode rnd) public pure returns (uint192 ) { + + function mulRnd( + uint192 x, + uint192 y, + RoundingMode rnd + ) public pure returns (uint192) { return FixLib.mul(x, y, rnd); } - function mulu(uint192 x, uint256 y) public pure returns (uint192 ) { + + function mulu(uint192 x, uint256 y) public pure returns (uint192) { return FixLib.mulu(x, y); } - function div(uint192 x, uint192 y) public pure returns (uint192 ) { + + function div(uint192 x, uint192 y) public pure returns (uint192) { return FixLib.div(x, y); } - function divRnd(uint192 x, uint192 y, RoundingMode rnd) public pure returns (uint192 ) { + + function divRnd( + uint192 x, + uint192 y, + RoundingMode rnd + ) public pure returns (uint192) { return FixLib.div(x, y, rnd); } - function divu(uint192 x, uint256 y) public pure returns (uint192 ) { + + function divu(uint192 x, uint256 y) public pure returns (uint192) { return FixLib.divu(x, y); } - function divuRnd(uint192 x, uint256 y, RoundingMode rnd) public pure returns (uint192 ) { + + function divuRnd( + uint192 x, + uint256 y, + RoundingMode rnd + ) public pure returns (uint192) { return FixLib.divu(x, y, rnd); } - function powu(uint192 x, uint48 y) public pure returns (uint192 ) { + + function powu(uint192 x, uint48 y) public pure returns (uint192) { return FixLib.powu(x, y); } - function lt(uint192 x, uint192 y) public pure returns (bool) { + + function lt(uint192 x, uint192 y) public pure returns (bool) { return FixLib.lt(x, y); } - function lte(uint192 x, uint192 y) public pure returns (bool) { + + function lte(uint192 x, uint192 y) public pure returns (bool) { return FixLib.lte(x, y); } - function gt(uint192 x, uint192 y) public pure returns (bool) { + + function gt(uint192 x, uint192 y) public pure returns (bool) { return FixLib.gt(x, y); } - function gte(uint192 x, uint192 y) public pure returns (bool) { + + function gte(uint192 x, uint192 y) public pure returns (bool) { return FixLib.gte(x, y); } - function eq(uint192 x, uint192 y) public pure returns (bool) { + + function eq(uint192 x, uint192 y) public pure returns (bool) { return FixLib.eq(x, y); } - function neq(uint192 x, uint192 y) public pure returns (bool) { + + function neq(uint192 x, uint192 y) public pure returns (bool) { return FixLib.neq(x, y); } - function near(uint192 x, uint192 y, uint192 epsilon) public pure returns (bool) { + + function near( + uint192 x, + uint192 y, + uint192 epsilon + ) public pure returns (bool) { return FixLib.near(x, y, epsilon); } // ================ chained operations - function shiftl_toUint(uint192 x, int8 d) public pure returns (uint256) { + function shiftl_toUint(uint192 x, int8 d) public pure returns (uint256) { return FixLib.shiftl_toUint(x, d); } - function shiftl_toUintRnd(uint192 x, int8 d, RoundingMode rnd) public pure returns (uint256) { + + function shiftl_toUintRnd( + uint192 x, + int8 d, + RoundingMode rnd + ) public pure returns (uint256) { return FixLib.shiftl_toUint(x, d, rnd); } - function mulu_toUint(uint192 x, uint256 y) public pure returns (uint256) { + + function mulu_toUint(uint192 x, uint256 y) public pure returns (uint256) { return FixLib.mulu_toUint(x, y); } - function mulu_toUintRnd(uint192 x, uint256 y, RoundingMode rnd) public pure returns (uint256) { + + function mulu_toUintRnd( + uint192 x, + uint256 y, + RoundingMode rnd + ) public pure returns (uint256) { return FixLib.mulu_toUint(x, y, rnd); } - function mul_toUint(uint192 x, uint192 y) public pure returns (uint256) { + + function mul_toUint(uint192 x, uint192 y) public pure returns (uint256) { return FixLib.mul_toUint(x, y); } - function mul_toUintRnd(uint192 x, uint192 y, RoundingMode rnd) public pure returns (uint256) { + + function mul_toUintRnd( + uint192 x, + uint192 y, + RoundingMode rnd + ) public pure returns (uint256) { return FixLib.mul_toUint(x, y, rnd); } - function muluDivu(uint192 x, uint256 y, uint256 z) public pure returns (uint192 ) { + + function muluDivu( + uint192 x, + uint256 y, + uint256 z + ) public pure returns (uint192) { return FixLib.muluDivu(x, y, z); } - function muluDivuRnd(uint192 x, uint256 y, uint256 z, RoundingMode rnd) public pure returns (uint192 ) { + + function muluDivuRnd( + uint192 x, + uint256 y, + uint256 z, + RoundingMode rnd + ) public pure returns (uint192) { return FixLib.muluDivu(x, y, z, rnd); } - function mulDiv(uint192 x, uint192 y, uint192 z) public pure returns (uint192 ) { + + function mulDiv( + uint192 x, + uint192 y, + uint192 z + ) public pure returns (uint192) { return FixLib.mulDiv(x, y, z); } - function mulDivRnd(uint192 x, uint192 y, uint192 z, RoundingMode rnd) public pure returns (uint192 ) { + + function mulDivRnd( + uint192 x, + uint192 y, + uint192 z, + RoundingMode rnd + ) public pure returns (uint192) { return FixLib.mulDiv(x, y, z, rnd); } + // ============== safe* operations - function safeMul_(uint192 a, uint192 b, RoundingMode rnd) public pure returns (uint192) { + function safeMul( + uint192 a, + uint192 b, + RoundingMode rnd + ) public pure returns (uint192) { return FixLib.safeMul(a, b, rnd); } + function safeDiv( + uint192 x, + uint192 y, + RoundingMode rnd + ) public pure returns (uint192) { + return FixLib.safeDiv(x, y, rnd); + } + + function safeMulDiv( + uint192 x, + uint192 y, + uint192 z, + RoundingMode rnd + ) public pure returns (uint192) { + return FixLib.safeMulDiv(x, y, z, rnd); + } + // ================ wide muldiv operations - function mulDiv256_(uint256 x, uint256 y, uint256 z) public pure returns (uint256) { + function mulDiv256_( + uint256 x, + uint256 y, + uint256 z + ) public pure returns (uint256) { return mulDiv256(x, y, z); } - function mulDiv256Rnd_(uint256 x, uint256 y, uint256 z, RoundingMode rnd) - public pure returns (uint256) { + + function mulDiv256Rnd_( + uint256 x, + uint256 y, + uint256 z, + RoundingMode rnd + ) public pure returns (uint256) { return mulDiv256(x, y, z, rnd); } + function fullMul_(uint256 x, uint256 y) public pure returns (uint256 h, uint256 l) { return fullMul(x, y); } - - - } diff --git a/contracts/libraries/test/StringCallerMock.sol b/contracts/libraries/test/StringCallerMock.sol index a96fb14954..7b3e39bc02 100644 --- a/contracts/libraries/test/StringCallerMock.sol +++ b/contracts/libraries/test/StringCallerMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity ^0.8.17; +pragma solidity ^0.8.19; import "../String.sol"; diff --git a/contracts/mixins/Auth.sol b/contracts/mixins/Auth.sol index ea0583b159..24cd81d709 100644 --- a/contracts/mixins/Auth.sol +++ b/contracts/mixins/Auth.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "../interfaces/IMain.sol"; diff --git a/contracts/mixins/ComponentRegistry.sol b/contracts/mixins/ComponentRegistry.sol index 6dfdefa82b..d8136c6270 100644 --- a/contracts/mixins/ComponentRegistry.sol +++ b/contracts/mixins/ComponentRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; diff --git a/contracts/mixins/NetworkConfigLib.sol b/contracts/mixins/NetworkConfigLib.sol new file mode 100644 index 0000000000..b347bec484 --- /dev/null +++ b/contracts/mixins/NetworkConfigLib.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +/** + * @title NetworkConfigLib + * @notice Provides network-specific configuration parameters + */ +library NetworkConfigLib { + error InvalidNetwork(); + + // Returns the blocktime based on the current network (e.g. 12s for Ethereum PoS) + // See docs/system-design.md for discussion of handling longer or shorter times + function blocktime() internal view returns (uint48) { + uint256 chainId = block.chainid; + // untestable: + // most of the branches will be shown as uncovered, because we only run coverage + // on local Ethereum PoS network (31337). Manual testing was performed. + if (chainId == 1 || chainId == 5 || chainId == 31337) { + return 12; // Ethereum PoS, Goerli, HH (tests) + } else if (chainId == 8453 || chainId == 84531) { + return 2; // Base, Base Goerli + } else { + revert InvalidNetwork(); + } + } +} diff --git a/contracts/mixins/Versioned.sol b/contracts/mixins/Versioned.sol index bd57e4bd0d..54c5f75da0 100644 --- a/contracts/mixins/Versioned.sol +++ b/contracts/mixins/Versioned.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../interfaces/IVersioned.sol"; diff --git a/contracts/p0/AssetRegistry.sol b/contracts/p0/AssetRegistry.sol index cd4b0aded1..91b9bb1a2f 100644 --- a/contracts/p0/AssetRegistry.sol +++ b/contracts/p0/AssetRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 04f9104266..cd7c063ca1 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -11,6 +11,7 @@ import "../interfaces/IBroker.sol"; import "../interfaces/IMain.sol"; import "../libraries/Array.sol"; import "../libraries/Fixed.sol"; +import "../mixins/NetworkConfigLib.sol"; /** * @title BackingManager @@ -23,11 +24,18 @@ contract BackingManagerP0 is TradingP0, IBackingManager { uint48 public constant MAX_TRADING_DELAY = 31536000; // {s} 1 year uint192 public constant MAX_BACKING_BUFFER = 1e18; // {%} + // solhint-disable-next-line var-name-mixedcase + uint48 public immutable ONE_BLOCK; // {s} 1 block based on network + uint48 public tradingDelay; // {s} how long to wait until resuming trading after switching uint192 public backingBuffer; // {%} how much extra backing collateral to keep mapping(TradeKind => uint48) private tradeEnd; // {s} last endTime() of an auction per kind + constructor() { + ONE_BLOCK = NetworkConfigLib.blocktime(); + } + function init( IMain main_, uint48 tradingDelay_, @@ -81,7 +89,6 @@ contract BackingManagerP0 is TradingP0, IBackingManager { main.furnace().melt(); // DoS prevention: unless caller is self, require 1 empty block between like-kind auctions - // Assumption: chain has <= 12s blocktimes require( _msgSender() == address(this) || tradeEnd[kind] + ONE_BLOCK < block.timestamp, "already rebalancing" diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index ea6ca7ffaa..3fc647432d 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -402,7 +402,14 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { : reg.toAsset(basket.erc20s[i]).price(); low256 += qty.safeMul(lowP, RoundingMode.FLOOR); - high256 += qty.safeMul(highP, RoundingMode.CEIL); + + if (high256 < FIX_MAX) { + if (highP == FIX_MAX) { + high256 = FIX_MAX; + } else { + high256 += qty.safeMul(highP, RoundingMode.CEIL); + } + } } // safe downcast: FIX_MAX is type(uint192).max @@ -503,7 +510,8 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { if (refPerTok == 0) continue; // {tok} = {BU} * {ref/BU} / {ref/tok} - quantities[i] = safeMulDivFloor(amount, refAmtsAll[i], refPerTok).shiftl_toUint( + + quantities[i] = amount.safeMulDiv(refAmtsAll[i], refPerTok, FLOOR).shiftl_toUint( int8(asset.erc20Decimals()), FLOOR ); @@ -798,24 +806,4 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { return false; } } - - // === Private === - - /// @return The floored result of FixLib.mulDiv - function safeMulDivFloor( - uint192 x, - uint192 y, - uint192 z - ) private view returns (uint192) { - try main.backingManager().mulDiv(x, y, z, FLOOR) returns (uint192 result) { - return result; - } catch Panic(uint256 errorCode) { - // 0x11: overflow - // 0x12: div-by-zero - assert(errorCode == 0x11 || errorCode == 0x12); - } catch (bytes memory reason) { - assert(keccak256(reason) == UIntOutofBoundsHash); - } - return FIX_MAX; - } } diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index fd4e8b9f77..033ef0bf9b 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; @@ -22,8 +22,8 @@ contract BrokerP0 is ComponentP0, IBroker { using SafeERC20 for IERC20Metadata; uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration -1 week - uint48 public constant MIN_AUCTION_LENGTH = ONE_BLOCK * 2; // {s} min auction length - 2 blocks - // warning: blocktime <= 12s assumption + // solhint-disable-next-line var-name-mixedcase + uint48 public immutable MIN_AUCTION_LENGTH; // {s} 2 blocks based on network // Added for interface compatibility with P1 ITrade public batchTradeImplementation; @@ -38,6 +38,10 @@ contract BrokerP0 is ComponentP0, IBroker { bool public disabled; + constructor() { + MIN_AUCTION_LENGTH = NetworkConfigLib.blocktime() * 2; + } + function init( IMain main_, IGnosis gnosis_, diff --git a/contracts/p0/Deployer.sol b/contracts/p0/Deployer.sol index 70689b8f9b..2b089b2242 100644 --- a/contracts/p0/Deployer.sol +++ b/contracts/p0/Deployer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../plugins/assets/Asset.sol"; diff --git a/contracts/p0/Distributor.sol b/contracts/p0/Distributor.sol index bea4059c0a..877485598d 100644 --- a/contracts/p0/Distributor.sol +++ b/contracts/p0/Distributor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/p0/Furnace.sol b/contracts/p0/Furnace.sol index a8e436b663..9301b3b0f9 100644 --- a/contracts/p0/Furnace.sol +++ b/contracts/p0/Furnace.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../libraries/Fixed.sol"; import "../interfaces/IFurnace.sol"; import "./mixins/Component.sol"; +import "../mixins/NetworkConfigLib.sol"; /** * @title FurnaceP0 @@ -13,7 +14,8 @@ contract FurnaceP0 is ComponentP0, IFurnace { using FixLib for uint192; uint192 public constant MAX_RATIO = FIX_ONE; // {1} 100% - uint48 public constant PERIOD = ONE_BLOCK; // {s} 12 seconds; 1 block on PoS Ethereum + // solhint-disable-next-line var-name-mixedcase + uint48 public immutable PERIOD; // {seconds} 1 block based on network uint192 public ratio; // {1} What fraction of balance to melt each PERIOD @@ -21,6 +23,10 @@ contract FurnaceP0 is ComponentP0, IFurnace { uint48 public lastPayout; // {seconds} The last time we did a payout uint256 public lastPayoutBal; // {qRTok} The balance of RToken at the last payout + constructor() { + PERIOD = NetworkConfigLib.blocktime(); + } + function init(IMain main_, uint192 ratio_) public initializer { __Component_init(main_); setRatio(ratio_); diff --git a/contracts/p0/Main.sol b/contracts/p0/Main.sol index 3a3374c0cd..3681f2da52 100644 --- a/contracts/p0/Main.sol +++ b/contracts/p0/Main.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 390d99e617..2dc33334a0 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; // solhint-disable-next-line max-line-length import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index c46cf44826..d9a1ed6e49 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index da9870a0af..f0e2f1e733 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/interfaces/IERC1271Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol"; @@ -16,6 +16,7 @@ import "../interfaces/IMain.sol"; import "../libraries/Fixed.sol"; import "../libraries/Permit.sol"; import "./mixins/Component.sol"; +import "../mixins/NetworkConfigLib.sol"; /* * @title StRSRP0 @@ -31,8 +32,10 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { using EnumerableSet for EnumerableSet.AddressSet; using FixLib for uint192; - uint48 public constant PERIOD = ONE_BLOCK; // {s} 12 seconds; 1 block on PoS Ethereum - uint48 public constant MIN_UNSTAKING_DELAY = PERIOD * 2; // {s} + // solhint-disable-next-line var-name-mixedcase + uint48 public immutable PERIOD; // {s} 1 block based on network + // solhint-disable-next-line var-name-mixedcase + uint48 public immutable MIN_UNSTAKING_DELAY; // {s} based on network uint48 public constant MAX_UNSTAKING_DELAY = 31536000; // {s} 1 year uint192 public constant MAX_REWARD_RATIO = 1e18; uint192 public constant MAX_WITHDRAWAL_LEAK = 3e17; // {1} 30% @@ -104,6 +107,11 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { uint192 public rewardRatio; uint192 public withdrawalLeak; // {1} gov param -- % RSR that can be withdrawn without refresh + constructor() { + PERIOD = NetworkConfigLib.blocktime(); + MIN_UNSTAKING_DELAY = PERIOD * 2; + } + function init( IMain main_, string memory name_, @@ -239,6 +247,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { // require(bh.isReady(), "basket not ready"); Withdrawal[] storage queue = withdrawals[account]; + if (endId == 0) return; require(endId <= queue.length, "index out-of-bounds"); // require(queue[endId - 1].availableAt <= block.timestamp, "withdrawal unavailable"); @@ -248,6 +257,9 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { while (start < endId && queue[start].rsrAmount == 0 && queue[start].stakeAmount == 0) start++; + // Return if nothing to process + if (start == endId) return; + // Accumulate and zero executable withdrawals uint256 total = 0; uint256 i = start; @@ -418,8 +430,8 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { address to, uint256 amount ) private { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); + require(from != address(0), "ERC20: transfer to or from the zero address"); + require(to != address(0), "ERC20: transfer to or from the zero address"); require(to != address(this), "StRSR transfer to self"); uint256 fromBalance = balances[from]; @@ -475,8 +487,8 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { address spender, uint256 amount ) private { - require(owner != address(0), "ERC20: approve from the zero address"); - require(spender != address(0), "ERC20: approve to the zero address"); + require(owner != address(0), "ERC20: approve to or from the zero address"); + require(spender != address(0), "ERC20: approve to or from the zero address"); allowances[owner][spender] = amount; diff --git a/contracts/p0/mixins/Component.sol b/contracts/p0/mixins/Component.sol index cc484b5090..09f5846b68 100644 --- a/contracts/p0/mixins/Component.sol +++ b/contracts/p0/mixins/Component.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; diff --git a/contracts/p0/mixins/Rewardable.sol b/contracts/p0/mixins/Rewardable.sol index 8d22f54d5f..a2d0bcdbe6 100644 --- a/contracts/p0/mixins/Rewardable.sol +++ b/contracts/p0/mixins/Rewardable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/utils/Address.sol"; import "./Component.sol"; diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index 1d2e9f22e0..871092986b 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -26,6 +26,9 @@ abstract contract TradingP0 is RewardableP0, ITrading { uint192 public minTradeVolume; // {UoA} + // === 3.0.0 === + uint256 public tradesNonce; // to keep track of how many trades have been opened in total + // untestable: // `else` branch of `onlyInitializing` (ie. revert) is currently untestable. // This function is only called inside other `init` functions, each of which is wrapped @@ -68,6 +71,8 @@ abstract contract TradingP0 is RewardableP0, ITrading { trade = broker.openTrade(kind, req); trades[req.sell.erc20()] = trade; tradesOpen++; + tradesNonce++; + emit TradeStarted( trade, req.sell.erc20(), @@ -92,16 +97,4 @@ abstract contract TradingP0 is RewardableP0, ITrading { emit MinTradeVolumeSet(minTradeVolume, val); minTradeVolume = val; } - - // === FixLib Helper === - - /// Light wrapper around FixLib.mulDiv to support try-catch - function mulDiv( - uint192 x, - uint192 y, - uint192 z, - RoundingMode rounding - ) external pure returns (uint192) { - return x.mulDiv(y, z, rounding); - } } diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index 3de3f0f0d7..271c6610f1 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../../interfaces/IAsset.sol"; @@ -58,11 +58,10 @@ library TradingLibP0 { // Calculate equivalent buyAmount within [0, FIX_MAX] // {buyTok} = {sellTok} * {1} * {UoA/sellTok} / {UoA/buyTok} - uint192 b = safeMulDivCeil( - ITrading(address(this)), - s.mul(FIX_ONE.minus(maxTradeSlippage)), - trade.sellPrice, // {UoA/sellTok} - trade.buyPrice // {UoA/buyTok} + uint192 b = s.mul(FIX_ONE.minus(maxTradeSlippage)).safeMulDiv( + trade.sellPrice, + trade.buyPrice, + CEIL ); // {*tok} => {q*Tok} @@ -317,11 +316,8 @@ library TradingLibP0 { } else { // surplus: add-in optimistic estimate of baskets purchaseable - // {BU} = {UoA/tok} * {tok} / {UoA/BU} - deltaTop += int256( - uint256(ctx.bm.safeMulDivCeil(high, bal - anchor, buPriceLow)) - ); // needs overflow protection: using high price of asset which can be FIX_MAX + deltaTop += int256(uint256(high.safeMulDiv(bal - anchor, buPriceLow, CEIL))); } } @@ -544,25 +540,6 @@ library TradingLibP0 { amt.shiftl_toUint(int8(asset.erc20Decimals())) > 1; } - /// @return The result of FixLib.mulDiv bounded from above by FIX_MAX in the case of overflow - function safeMulDivCeil( - ITrading trader, - uint192 x, - uint192 y, - uint192 z - ) internal pure returns (uint192) { - try trader.mulDiv(x, y, z, CEIL) returns (uint192 result) { - return result; - } catch Panic(uint256 errorCode) { - // 0x11: overflow - // 0x12: div-by-zero - assert(errorCode == 0x11 || errorCode == 0x12); - } catch (bytes memory reason) { - assert(keccak256(reason) == UIntOutofBoundsHash); - } - return FIX_MAX; - } - // === Private === /// Calculates the minTradeSize for an asset based on the given minTradeVolume and price diff --git a/contracts/p1/AssetRegistry.sol b/contracts/p1/AssetRegistry.sol index 481673eff7..6ae765525c 100644 --- a/contracts/p1/AssetRegistry.sol +++ b/contracts/p1/AssetRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 933d43e1f7..bc0df7db20 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -10,6 +10,7 @@ import "../libraries/Array.sol"; import "../libraries/Fixed.sol"; import "./mixins/Trading.sol"; import "./mixins/RecollateralizationLib.sol"; +import "../mixins/NetworkConfigLib.sol"; /** * @title BackingManager @@ -21,6 +22,10 @@ contract BackingManagerP1 is TradingP1, IBackingManager { using FixLib for uint192; using SafeERC20 for IERC20; + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + // solhint-disable-next-line var-name-mixedcase + uint48 public immutable ONE_BLOCK; // {s} 1 block based on network + // Cache of peer components IAssetRegistry private assetRegistry; IBasketHandler private basketHandler; @@ -43,6 +48,11 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // ==== Invariants ==== // tradingDelay <= MAX_TRADING_DELAY and backingBuffer <= MAX_BACKING_BUFFER + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + ONE_BLOCK = NetworkConfigLib.blocktime(); + } + function init( IMain main_, uint48 tradingDelay_, @@ -107,7 +117,6 @@ contract BackingManagerP1 is TradingP1, IBackingManager { furnace.melt(); // DoS prevention: unless caller is self, require 1 empty block between like-kind auctions - // Assumption: chain has <= 12s blocktimes require( _msgSender() == address(this) || tradeEnd[kind] + ONE_BLOCK < block.timestamp, "already rebalancing" diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 3f54f65c81..568d3d7651 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -17,6 +17,8 @@ import "./mixins/Component.sol"; * @title BasketHandler * @notice Handles the basket configuration, definition, and evolution over time. */ + +/// @custom:oz-upgrades-unsafe-allow external-library-linking contract BasketHandlerP1 is ComponentP1, IBasketHandler { using BasketLibP1 for Basket; using EnumerableMap for EnumerableMap.Bytes32ToUintMap; @@ -337,7 +339,14 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { : assetRegistry.toAsset(basket.erc20s[i]).price(); low256 += qty.safeMul(lowP, RoundingMode.FLOOR); - high256 += qty.safeMul(highP, RoundingMode.CEIL); + + if (high256 < FIX_MAX) { + if (highP == FIX_MAX) { + high256 = FIX_MAX; + } else { + high256 += qty.safeMul(highP, RoundingMode.CEIL); + } + } } // safe downcast: FIX_MAX is type(uint192).max @@ -401,6 +410,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // Add-in refAmts contribution from historical basket for (uint256 j = 0; j < b.erc20s.length; ++j) { IERC20 erc20 = b.erc20s[j]; + // untestable: + // previous baskets erc20s do not contain the zero address if (address(erc20) == address(0)) continue; // Ugly search through erc20sAll @@ -438,11 +449,10 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { if (!asset.isCollateral()) continue; // skip token if no longer registered // {tok} = {BU} * {ref/BU} / {ref/tok} - quantities[i] = safeMulDivFloor( - amount, - refAmtsAll[i], - ICollateral(address(asset)).refPerTok() - ).shiftl_toUint(int8(asset.erc20Decimals()), FLOOR); + quantities[i] = amount + .safeMulDiv(refAmtsAll[i], ICollateral(address(asset)).refPerTok(), FLOOR) + .shiftl_toUint(int8(asset.erc20Decimals()), FLOOR); + // marginally more penalizing than its sibling calculation that uses _quantity() // because does not intermediately CEIL as part of the division } catch (bytes memory errData) { @@ -531,12 +541,6 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { IERC20[] calldata newERC20s, uint192[] calldata newTargetAmts ) private { - // Empty _targetAmts mapping - while (_targetAmts.length() > 0) { - (bytes32 key, ) = _targetAmts.at(0); - _targetAmts.remove(key); - } - // Populate _targetAmts mapping with old basket config uint256 len = config.erc20s.length; for (uint256 i = 0; i < len; ++i) { @@ -598,11 +602,10 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { if (!asset.isCollateral()) continue; // skip token if no longer registered // {tok} = {BU} * {ref/BU} / {ref/tok} - quantities[i] = safeMulDivFloor( - FIX_ONE, - b.refAmts[erc20s[i]], - ICollateral(address(asset)).refPerTok() - ).shiftl_toUint(int8(asset.erc20Decimals()), FLOOR); + quantities[i] = b + .refAmts[erc20s[i]] + .safeDiv(ICollateral(address(asset)).refPerTok(), FLOOR) + .shiftl_toUint(int8(asset.erc20Decimals()), FLOOR); } catch (bytes memory errData) { // untested: // OOG pattern tested in other contracts, cost to test here is high @@ -654,26 +657,6 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { max = backup.max; } - // === Private === - - /// @return The floored result of FixLib.mulDiv - function safeMulDivFloor( - uint192 x, - uint192 y, - uint192 z - ) private view returns (uint192) { - try backingManager.mulDiv(x, y, z, FLOOR) returns (uint192 result) { - return result; - } catch Panic(uint256 errorCode) { - // 0x11: overflow - // 0x12: div-by-zero - assert(errorCode == 0x11 || errorCode == 0x12); - } catch (bytes memory reason) { - assert(keccak256(reason) == UIntOutofBoundsHash); - } - return FIX_MAX; - } - // ==== Storage Gap ==== /** diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 242d28358c..666c604b46 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; @@ -9,6 +9,7 @@ import "../interfaces/IMain.sol"; import "../interfaces/ITrade.sol"; import "../libraries/Fixed.sol"; import "./mixins/Component.sol"; +import "../mixins/NetworkConfigLib.sol"; import "../plugins/trading/DutchTrade.sol"; import "../plugins/trading/GnosisTrade.sol"; @@ -23,8 +24,9 @@ contract BrokerP1 is ComponentP1, IBroker { using Clones for address; uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration - 1 week - uint48 public constant MIN_AUCTION_LENGTH = ONE_BLOCK * 2; // {s} min auction length - 2 blocks - // warning: blocktime <= 12s assumption + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + // solhint-disable-next-line var-name-mixedcase + uint48 public immutable MIN_AUCTION_LENGTH; // {s} 2 blocks based on network IBackingManager private backingManager; IRevenueTrader private rsrTrader; @@ -59,6 +61,11 @@ contract BrokerP1 is ComponentP1, IBroker { // ==== Invariant ==== // (trades[addr] == true) iff this contract has created an ITrade clone at addr + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + MIN_AUCTION_LENGTH = NetworkConfigLib.blocktime() * 2; + } + // effects: initial parameters are set function init( IMain main_, diff --git a/contracts/p1/Deployer.sol b/contracts/p1/Deployer.sol index 2077fc4144..9dd23363a4 100644 --- a/contracts/p1/Deployer.sol +++ b/contracts/p1/Deployer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/proxy/Clones.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index 21b973fcef..734c702d29 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/p1/Furnace.sol b/contracts/p1/Furnace.sol index 1596942cf5..a261f217cc 100644 --- a/contracts/p1/Furnace.sol +++ b/contracts/p1/Furnace.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../libraries/Fixed.sol"; import "../interfaces/IFurnace.sol"; import "./mixins/Component.sol"; +import "../mixins/NetworkConfigLib.sol"; /** * @title FurnaceP1 @@ -13,7 +14,9 @@ contract FurnaceP1 is ComponentP1, IFurnace { using FixLib for uint192; uint192 public constant MAX_RATIO = FIX_ONE; // {1} 100% - uint48 public constant PERIOD = ONE_BLOCK; // {s} 12 seconds; 1 block on PoS Ethereum + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + // solhint-disable-next-line var-name-mixedcase + uint48 public immutable PERIOD; // {seconds} 1 block based on network IRToken private rToken; @@ -24,6 +27,11 @@ contract FurnaceP1 is ComponentP1, IFurnace { uint48 public lastPayout; // {seconds} The last time we did a payout uint256 public lastPayoutBal; // {qRTok} The balance of RToken at the last payout + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() ComponentP1() { + PERIOD = NetworkConfigLib.blocktime(); + } + // ==== Invariants ==== // ratio <= MAX_RATIO = 1e18 // lastPayout was the timestamp of the end of the last period we paid out diff --git a/contracts/p1/Main.sol b/contracts/p1/Main.sol index 7bd551151f..e4922caa9b 100644 --- a/contracts/p1/Main.sol +++ b/contracts/p1/Main.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 3119702d62..75cbcc3b6b 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; // solhint-disable-next-line max-line-length import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 849b06ebd8..d588b7e5f7 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 8301a0bae5..99cbe10d4b 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/interfaces/IERC1271Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; @@ -13,6 +13,7 @@ import "../interfaces/IMain.sol"; import "../libraries/Fixed.sol"; import "../libraries/Permit.sol"; import "./mixins/Component.sol"; +import "../mixins/NetworkConfigLib.sol"; /* * @title StRSRP1 @@ -34,8 +35,12 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab using CountersUpgradeable for CountersUpgradeable.Counter; using SafeERC20Upgradeable for IERC20Upgradeable; - uint48 public constant PERIOD = ONE_BLOCK; // {s} 12 seconds; 1 block on PoS Ethereum - uint48 public constant MIN_UNSTAKING_DELAY = PERIOD * 2; // {s} + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + // solhint-disable-next-line var-name-mixedcase + uint48 public immutable PERIOD; // {s} 1 block based on network + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + // solhint-disable-next-line var-name-mixedcase + uint48 public immutable MIN_UNSTAKING_DELAY; // {s} based on network uint48 public constant MAX_UNSTAKING_DELAY = 31536000; // {s} 1 year uint192 public constant MAX_REWARD_RATIO = FIX_ONE; // {1} 100% @@ -160,6 +165,12 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // ====================== + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() ComponentP1() { + PERIOD = NetworkConfigLib.blocktime(); + MIN_UNSTAKING_DELAY = PERIOD * 2; + } + // init() can only be called once (initializer) // ==== Financial State: // effects: @@ -355,6 +366,8 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // Cancelling unstake does not require checking if the unstaking was available // require(queue[endId - 1].availableAt <= block.timestamp, "withdrawal unavailable"); + // untestable: + // firstId will never be zero, due to previous checks against endId uint192 oldDrafts = firstId > 0 ? queue[firstId - 1].drafts : 0; uint192 draftAmount = queue[endId - 1].drafts - oldDrafts; @@ -787,8 +800,10 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab address to, uint256 amount ) internal { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); + require( + from != address(0) && to != address(0), + "ERC20: transfer to or from the zero address" + ); mapping(address => uint256) storage eraStakes = stakes[era]; uint256 fromBalance = eraStakes[from]; @@ -844,8 +859,10 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab address spender, uint256 amount ) internal { - require(owner != address(0), "ERC20: approve from the zero address"); - require(spender != address(0), "ERC20: approve to the zero address"); + require( + owner != address(0) && spender != address(0), + "ERC20: approve to or from the zero address" + ); _allowances[era][owner][spender] = amount; emit Approval(owner, spender, amount); diff --git a/contracts/p1/StRSRVotes.sol b/contracts/p1/StRSRVotes.sol index 4ba528bd72..2251ac1ff9 100644 --- a/contracts/p1/StRSRVotes.sol +++ b/contracts/p1/StRSRVotes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol"; diff --git a/contracts/p1/mixins/BasketLib.sol b/contracts/p1/mixins/BasketLib.sol index bc52d1c6a4..0ade4b65e8 100644 --- a/contracts/p1/mixins/BasketLib.sol +++ b/contracts/p1/mixins/BasketLib.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; diff --git a/contracts/p1/mixins/Component.sol b/contracts/p1/mixins/Component.sol index ccdf81700b..913379cb85 100644 --- a/contracts/p1/mixins/Component.sol +++ b/contracts/p1/mixins/Component.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index 73281db5c0..a4162df47a 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../../interfaces/IAsset.sol"; @@ -212,11 +212,8 @@ library RecollateralizationLibP1 { } else { // surplus: add-in optimistic estimate of baskets purchaseable - // {BU} = {UoA/tok} * {tok} / {UoA/BU} - deltaTop += int256( - uint256(ctx.bm.safeMulDivCeil(high, bal - anchor, buPriceLow)) - ); - // needs overflow protection: using high price of asset which can be FIX_MAX + // {BU} = {UoA/tok} * {tok} / {UoA/BU} + deltaTop += int256(uint256(high.safeMulDiv(bal - anchor, buPriceLow, CEIL))); } } diff --git a/contracts/p1/mixins/RewardableLib.sol b/contracts/p1/mixins/RewardableLib.sol index 6490adf0f8..58b34987b4 100644 --- a/contracts/p1/mixins/RewardableLib.sol +++ b/contracts/p1/mixins/RewardableLib.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/p1/mixins/TradeLib.sol b/contracts/p1/mixins/TradeLib.sol index 0751f1df45..22f612ef86 100644 --- a/contracts/p1/mixins/TradeLib.sol +++ b/contracts/p1/mixins/TradeLib.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../../interfaces/IAsset.sol"; @@ -61,11 +61,10 @@ library TradeLib { // Calculate equivalent buyAmount within [0, FIX_MAX] // {buyTok} = {sellTok} * {1} * {UoA/sellTok} / {UoA/buyTok} - uint192 b = safeMulDivCeil( - ITrading(address(this)), - s.mul(FIX_ONE.minus(maxTradeSlippage)), - trade.sellPrice, // {UoA/sellTok} - trade.buyPrice // {UoA/buyTok} + uint192 b = s.mul(FIX_ONE.minus(maxTradeSlippage)).safeMulDiv( + trade.sellPrice, + trade.buyPrice, + CEIL ); // {*tok} => {q*Tok} @@ -149,28 +148,6 @@ library TradeLib { amt.shiftl_toUint(int8(asset.erc20Decimals())) > 1; } - /// @return The result of FixLib.mulDiv bounded from above by FIX_MAX in the case of overflow - function safeMulDivCeil( - ITrading trader, - uint192 x, - uint192 y, - uint192 z - ) internal pure returns (uint192) { - try trader.mulDiv(x, y, z, CEIL) returns (uint192 result) { - return result; - } catch Panic(uint256 errorCode) { - // 0x11: overflow - // 0x12: div-by-zero - // untestable: - // Overflow is protected against and checked for in FixLib.mulDiv() - // Div-by-zero is NOT protected against, but no caller will ever use 0 for z - assert(errorCode == 0x11 || errorCode == 0x12); - } catch (bytes memory reason) { - assert(keccak256(reason) == UIntOutofBoundsHash); - } - return FIX_MAX; - } - // === Private === /// Calculates the minTradeSize for an asset based on the given minTradeVolume and price diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 258c7cd342..9a84b1d726 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; @@ -32,9 +32,11 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // === Governance param === uint192 public maxTradeSlippage; // {%} - uint192 public minTradeVolume; // {UoA} + // === 3.0.0 === + uint256 public tradesNonce; // to keep track of how many trades have been opened in total + // ==== Invariants ==== // tradesOpen = len(values(trades)) // trades[sell] != 0 iff trade[sell] has been opened and not yet settled @@ -111,7 +113,6 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // trades' = trades.set(req.sell, tradeID) // tradesOpen' = tradesOpen + 1 function tryTrade(TradeKind kind, TradeRequest memory req) internal returns (ITrade trade) { - /* */ IERC20 sell = req.sell.erc20(); assert(address(trades[sell]) == address(0)); @@ -121,6 +122,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl trade = broker.openTrade(kind, req); trades[sell] = trade; tradesOpen++; + tradesNonce++; emit TradeStarted(trade, sell, req.buy.erc20(), req.sellAmount, req.minBuyAmount); } @@ -141,22 +143,10 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl minTradeVolume = val; } - // === FixLib Helper === - - /// Light wrapper around FixLib.mulDiv to support try-catch - function mulDiv( - uint192 x, - uint192 y, - uint192 z, - RoundingMode round - ) external pure returns (uint192) { - return x.mulDiv(y, z, round); - } - /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[46] private __gap; + uint256[45] private __gap; } diff --git a/contracts/plugins/assets/AppreciatingFiatCollateral.sol b/contracts/plugins/assets/AppreciatingFiatCollateral.sol index a8b620c031..3722b5644d 100644 --- a/contracts/plugins/assets/AppreciatingFiatCollateral.sol +++ b/contracts/plugins/assets/AppreciatingFiatCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../../interfaces/IAsset.sol"; diff --git a/contracts/plugins/assets/Asset.sol b/contracts/plugins/assets/Asset.sol index 64d2073d7d..b35c7941e5 100644 --- a/contracts/plugins/assets/Asset.sol +++ b/contracts/plugins/assets/Asset.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/plugins/assets/EURFiatCollateral.sol b/contracts/plugins/assets/EURFiatCollateral.sol index a37bdf01b7..ae6dcfc3ca 100644 --- a/contracts/plugins/assets/EURFiatCollateral.sol +++ b/contracts/plugins/assets/EURFiatCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../../libraries/Fixed.sol"; diff --git a/contracts/plugins/assets/FiatCollateral.sol b/contracts/plugins/assets/FiatCollateral.sol index 3aa03bdcbd..d4d1412818 100644 --- a/contracts/plugins/assets/FiatCollateral.sol +++ b/contracts/plugins/assets/FiatCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../../interfaces/IAsset.sol"; diff --git a/contracts/plugins/assets/NonFiatCollateral.sol b/contracts/plugins/assets/NonFiatCollateral.sol index 7cf9f7bf4d..2e6b3c531f 100644 --- a/contracts/plugins/assets/NonFiatCollateral.sol +++ b/contracts/plugins/assets/NonFiatCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../../libraries/Fixed.sol"; diff --git a/contracts/plugins/assets/OracleLib.sol b/contracts/plugins/assets/OracleLib.sol index b6bbea745c..e15605f881 100644 --- a/contracts/plugins/assets/OracleLib.sol +++ b/contracts/plugins/assets/OracleLib.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "../../libraries/Fixed.sol"; diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index 87645cc6d0..fd8c78fa24 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -1,19 +1,24 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../../p1/mixins/RecollateralizationLib.sol"; import "../../interfaces/IMain.sol"; import "../../interfaces/IRToken.sol"; +import "../../interfaces/IRTokenOracle.sol"; import "./Asset.sol"; import "./VersionedAsset.sol"; +uint256 constant ORACLE_TIMEOUT = 15 minutes; + /// Once an RToken gets large enough to get a price feed, replacing this asset with /// a simpler one will do wonders for gas usage -contract RTokenAsset is IAsset, VersionedAsset { +// @dev This RTokenAsset is ONLY compatible with Protocol ^3.0.0 +contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { using FixLib for uint192; using OracleLib for AggregatorV3Interface; // Component addresses are not mutable in protocol, so it's safe to cache these + IMain public immutable main; IBasketHandler public immutable basketHandler; IAssetRegistry public immutable assetRegistry; IBackingManager public immutable backingManager; @@ -24,12 +29,15 @@ contract RTokenAsset is IAsset, VersionedAsset { uint192 public immutable maxTradeVolume; // {UoA} + // Oracle State + CachedOracleData public cachedOracleData; + /// @param maxTradeVolume_ {UoA} The max trade volume, in UoA constructor(IRToken erc20_, uint192 maxTradeVolume_) { require(address(erc20_) != address(0), "missing erc20"); require(maxTradeVolume_ > 0, "invalid max trade volume"); - IMain main = erc20_.main(); + main = erc20_.main(); basketHandler = main.basketHandler(); assetRegistry = main.assetRegistry(); backingManager = main.backingManager(); @@ -59,12 +67,15 @@ contract RTokenAsset is IAsset, VersionedAsset { // {UoA/tok} = {BU} * {UoA/BU} / {tok} low = range.bottom.mulDiv(lowBUPrice, supply, FLOOR); high = range.top.mulDiv(highBUPrice, supply, CEIL); + assert(low <= high); // not obviously true } // solhint-disable no-empty-blocks function refresh() public virtual override { // No need to save lastPrice; can piggyback off the backing collateral's lotPrice() + + cachedOracleData.cachedAtTime = 0; // force oracle refresh } // solhint-enable no-empty-blocks @@ -128,8 +139,41 @@ contract RTokenAsset is IAsset, VersionedAsset { // solhint-enable no-empty-blocks + function forceUpdatePrice() external { + _updateCachedPrice(); + } + + function latestPrice() external returns (uint192 rTokenPrice, uint256 updatedAt) { + // Situations that require an update, from most common to least common. + if ( + cachedOracleData.cachedAtTime + ORACLE_TIMEOUT <= block.timestamp || // Cache Timeout + cachedOracleData.cachedAtNonce != basketHandler.nonce() || // Basket nonce was updated + cachedOracleData.cachedTradesNonce != backingManager.tradesNonce() || // New trades + cachedOracleData.cachedTradesOpen != backingManager.tradesOpen() // ..or settled + ) { + _updateCachedPrice(); + } + + return (cachedOracleData.cachedPrice, cachedOracleData.cachedAtTime); + } + // ==== Private ==== + // Update Oracle Data + function _updateCachedPrice() internal { + (uint192 low, uint192 high) = price(); + + require(low != 0 && high != FIX_MAX, "invalid price"); + + cachedOracleData = CachedOracleData( + (low + high) / 2, + block.timestamp, + basketHandler.nonce(), + backingManager.tradesOpen(), + backingManager.tradesNonce() + ); + } + /// Computationally expensive basketRange calculation; used in price() & lotPrice() function basketRange() private view returns (BasketRange memory range) { BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(backingManager)); @@ -145,7 +189,6 @@ contract RTokenAsset is IAsset, VersionedAsset { // the absence of an external price feed. Any RToken that gets reasonably big // should switch over to an asset with a price feed. - IMain main = backingManager.main(); TradingContext memory ctx; ctx.basketsHeld = basketsHeld; diff --git a/contracts/plugins/assets/SelfReferentialCollateral.sol b/contracts/plugins/assets/SelfReferentialCollateral.sol index eaed125e7f..fb3eb2b92b 100644 --- a/contracts/plugins/assets/SelfReferentialCollateral.sol +++ b/contracts/plugins/assets/SelfReferentialCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../../libraries/Fixed.sol"; diff --git a/contracts/plugins/assets/VersionedAsset.sol b/contracts/plugins/assets/VersionedAsset.sol index 1b92bac328..f3fc5e30a3 100644 --- a/contracts/plugins/assets/VersionedAsset.sol +++ b/contracts/plugins/assets/VersionedAsset.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../../interfaces/IVersioned.sol"; diff --git a/contracts/plugins/assets/aave/ATokenFiatCollateral.sol b/contracts/plugins/assets/aave/ATokenFiatCollateral.sol index e9ed7267f9..f6e98c267e 100644 --- a/contracts/plugins/assets/aave/ATokenFiatCollateral.sol +++ b/contracts/plugins/assets/aave/ATokenFiatCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../../../libraries/Fixed.sol"; diff --git a/contracts/plugins/assets/aave/vendor/IStaticATokenLM.sol b/contracts/plugins/assets/aave/IStaticATokenLM.sol similarity index 99% rename from contracts/plugins/assets/aave/vendor/IStaticATokenLM.sol rename to contracts/plugins/assets/aave/IStaticATokenLM.sol index 534771782b..c559bcf54b 100644 --- a/contracts/plugins/assets/aave/vendor/IStaticATokenLM.sol +++ b/contracts/plugins/assets/aave/IStaticATokenLM.sol @@ -4,7 +4,7 @@ pragma experimental ABIEncoderV2; import { IERC20 } from "@aave/protocol-v2/contracts/dependencies/openzeppelin/contracts/IERC20.sol"; import { ILendingPool } from "@aave/protocol-v2/contracts/interfaces/ILendingPool.sol"; -import { IAaveIncentivesController } from "./IAaveIncentivesController.sol"; +import { IAaveIncentivesController } from "./vendor/IAaveIncentivesController.sol"; interface IStaticATokenLM is IERC20 { struct SignatureParams { diff --git a/contracts/plugins/assets/aave/vendor/StaticATokenErrors.sol b/contracts/plugins/assets/aave/StaticATokenErrors.sol similarity index 100% rename from contracts/plugins/assets/aave/vendor/StaticATokenErrors.sol rename to contracts/plugins/assets/aave/StaticATokenErrors.sol diff --git a/contracts/plugins/assets/aave/vendor/StaticATokenLM.sol b/contracts/plugins/assets/aave/StaticATokenLM.sol similarity index 93% rename from contracts/plugins/assets/aave/vendor/StaticATokenLM.sol rename to contracts/plugins/assets/aave/StaticATokenLM.sol index d22344d075..b24a3c14b5 100644 --- a/contracts/plugins/assets/aave/vendor/StaticATokenLM.sol +++ b/contracts/plugins/assets/aave/StaticATokenLM.sol @@ -6,18 +6,18 @@ import { ILendingPool } from "@aave/protocol-v2/contracts/interfaces/ILendingPoo import { IERC20 } from "@aave/protocol-v2/contracts/dependencies/openzeppelin/contracts/IERC20.sol"; import { IERC20Detailed } from "@aave/protocol-v2/contracts/dependencies/openzeppelin/contracts/IERC20Detailed.sol"; -import { IAToken } from "./IAToken.sol"; +import { IAToken } from "./vendor/IAToken.sol"; import { IStaticATokenLM } from "./IStaticATokenLM.sol"; -import { IAaveIncentivesController } from "./IAaveIncentivesController.sol"; +import { IAaveIncentivesController } from "./vendor/IAaveIncentivesController.sol"; import { StaticATokenErrors } from "./StaticATokenErrors.sol"; -import { ERC20 } from "./ERC20.sol"; -import { ReentrancyGuard } from "./ReentrancyGuard.sol"; +import { ERC20 } from "./vendor/ERC20.sol"; +import { ReentrancyGuard } from "./vendor/ReentrancyGuard.sol"; import { SafeERC20 } from "@aave/protocol-v2/contracts/dependencies/openzeppelin/contracts/SafeERC20.sol"; import { WadRayMath } from "@aave/protocol-v2/contracts/protocol/libraries/math/WadRayMath.sol"; -import { RayMathNoRounding } from "./RayMathNoRounding.sol"; +import { RayMathNoRounding } from "./vendor/RayMathNoRounding.sol"; import { SafeMath } from "@aave/protocol-v2/contracts/dependencies/openzeppelin/contracts/SafeMath.sol"; /** @@ -106,6 +106,8 @@ contract StaticATokenLM is } ///@inheritdoc IStaticATokenLM + // untested: + // nonReentrant line is assumed to be working. cost/benefit of direct testing is high function deposit( address recipient, uint256 amount, @@ -116,6 +118,8 @@ contract StaticATokenLM is } ///@inheritdoc IStaticATokenLM + // untested: + // nonReentrant line is assumed to be working. cost/benefit of direct testing is high function withdraw( address recipient, uint256 amount, @@ -125,6 +129,8 @@ contract StaticATokenLM is } ///@inheritdoc IStaticATokenLM + // untested: + // nonReentrant line is assumed to be working. cost/benefit of direct testing is high function withdrawDynamicAmount( address recipient, uint256 amount, @@ -162,6 +168,8 @@ contract StaticATokenLM is } ///@inheritdoc IStaticATokenLM + // untested: + // nonReentrant line is assumed to be working. cost/benefit of direct testing is high function metaDeposit( address depositor, address recipient, @@ -202,6 +210,8 @@ contract StaticATokenLM is } ///@inheritdoc IStaticATokenLM + // untested: + // nonReentrant line is assumed to be working. cost/benefit of direct testing is high function metaWithdraw( address owner, address recipient, @@ -436,6 +446,8 @@ contract StaticATokenLM is } ///@inheritdoc IStaticATokenLM + // untested: + // nonReentrant line is assumed to be working. cost/benefit of direct testing is high function collectAndUpdateRewards() external override nonReentrant { _collectAndUpdateRewards(); } @@ -469,6 +481,8 @@ contract StaticATokenLM is } } + // untested: + // nonReentrant line is assumed to be working. cost/benefit of direct testing is high function claimRewardsOnBehalf( address onBehalfOf, address receiver, @@ -485,6 +499,8 @@ contract StaticATokenLM is _claimRewardsOnBehalf(onBehalfOf, receiver, forceUpdate); } + // untested: + // nonReentrant line is assumed to be working. cost/benefit of direct testing is high function claimRewards(address receiver, bool forceUpdate) external override nonReentrant { if (address(INCENTIVES_CONTROLLER) == address(0)) { return; @@ -492,6 +508,8 @@ contract StaticATokenLM is _claimRewardsOnBehalf(msg.sender, receiver, forceUpdate); } + // untested: + // nonReentrant line is assumed to be working. cost/benefit of direct testing is high function claimRewardsToSelf(bool forceUpdate) external override nonReentrant { if (address(INCENTIVES_CONTROLLER) == address(0)) { return; @@ -499,6 +517,8 @@ contract StaticATokenLM is _claimRewardsOnBehalf(msg.sender, msg.sender, forceUpdate); } + // untested: + // nonReentrant line is assumed to be working. cost/benefit of direct testing is high function claimRewards() external virtual nonReentrant { if (address(INCENTIVES_CONTROLLER) == address(0)) { return; diff --git a/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol b/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol index 01cdb42dd5..251686c301 100644 --- a/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol +++ b/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../../../libraries/Fixed.sol"; diff --git a/contracts/plugins/assets/ankr/vendor/IAnkrETH.sol b/contracts/plugins/assets/ankr/vendor/IAnkrETH.sol index 800ea59a1b..660c8ae584 100644 --- a/contracts/plugins/assets/ankr/vendor/IAnkrETH.sol +++ b/contracts/plugins/assets/ankr/vendor/IAnkrETH.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/plugins/assets/cbeth/CBETHCollateral.sol b/contracts/plugins/assets/cbeth/CBETHCollateral.sol new file mode 100644 index 0000000000..40ee822e38 --- /dev/null +++ b/contracts/plugins/assets/cbeth/CBETHCollateral.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { _safeWrap } from "../../../libraries/Fixed.sol"; +import "../AppreciatingFiatCollateral.sol"; + +interface CBEth is IERC20Metadata { + function mint(address account, uint256 amount) external returns (bool); + + function updateExchangeRate(uint256 exchangeRate) external; + + function configureMinter(address minter, uint256 minterAllowedAmount) external returns (bool); + + function exchangeRate() external view returns (uint256 _exchangeRate); +} + +contract CBEthCollateral is AppreciatingFiatCollateral { + using OracleLib for AggregatorV3Interface; + using FixLib for uint192; + + CBEth public immutable token; + AggregatorV3Interface public immutable refPerTokChainlinkFeed; + uint48 public immutable refPerTokChainlinkTimeout; + + /// @param config.chainlinkFeed {UoA/ref} price of DAI in USD terms + constructor( + CollateralConfig memory config, + uint192 revenueHiding, + AggregatorV3Interface _refPerTokChainlinkFeed, + uint48 _refPerTokChainlinkTimeout + ) AppreciatingFiatCollateral(config, revenueHiding) { + token = CBEth(address(config.erc20)); + + refPerTokChainlinkFeed = _refPerTokChainlinkFeed; + refPerTokChainlinkTimeout = _refPerTokChainlinkTimeout; + } + + /// 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 + /// @return pegPrice {target/ref} The actual price observed in the peg + function tryPrice() + external + view + override + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) + { + // {UoA/tok} = {UoA/ref} * {ref/tok} + uint192 p = chainlinkFeed.price(oracleTimeout).mul( + refPerTokChainlinkFeed.price(refPerTokChainlinkTimeout) + ); + uint192 err = p.mul(oracleError, CEIL); + + high = p + err; + low = p - err; + // assert(low <= high); obviously true just by inspection + + pegPrice = targetPerRef(); // {target/ref} ETH/ETH is always 1 + } + + /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens + function _underlyingRefPerTok() internal view override returns (uint192) { + return _safeWrap(token.exchangeRate()); + } +} diff --git a/contracts/plugins/assets/cbeth/README.md b/contracts/plugins/assets/cbeth/README.md new file mode 100644 index 0000000000..fe74735ca5 --- /dev/null +++ b/contracts/plugins/assets/cbeth/README.md @@ -0,0 +1,19 @@ +# CBETH Collateral Plugin + +## Summary + +This plugin allows `CBETH` holders to use their tokens as collateral in the Reserve Protocol. + +## Implementation + +### Units + +| tok | ref | target | UoA | +| ----- | --- | ------ | --- | +| cbeth | ETH | ETH | ETH | + +### Functions + +#### refPerTok {ref/tok} + +`return _safeWrap(token.exchange_rate());` diff --git a/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol index 2ce5b6a23a..9688f437d2 100644 --- a/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../../../libraries/Fixed.sol"; import "../AppreciatingFiatCollateral.sol"; import "../../../interfaces/IRewardable.sol"; +import "../erc20/RewardableERC20Wrapper.sol"; import "./ICToken.sol"; -import "../../../vendor/oz/IERC4626.sol"; /** * @title CTokenFiatCollateral @@ -22,14 +22,16 @@ contract CTokenFiatCollateral is AppreciatingFiatCollateral { uint8 public immutable referenceERC20Decimals; - ICToken public immutable cToken; + ICToken public immutable cToken; // gas-optimization: access underlying cToken directly + /// @param config.erc20 Should be a CTokenWrapper /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide constructor(CollateralConfig memory config, uint192 revenueHiding) AppreciatingFiatCollateral(config, revenueHiding) { - cToken = ICToken(address(IERC4626(address(config.erc20)).asset())); + cToken = ICToken(address(RewardableERC20Wrapper(address(config.erc20)).underlying())); referenceERC20Decimals = IERC20Metadata(cToken.underlying()).decimals(); + require(referenceERC20Decimals > 0, "referenceERC20Decimals missing"); } /// Refresh exchange rates and update default status. diff --git a/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol index 54b381355f..f0a44584b5 100644 --- a/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../../../libraries/Fixed.sol"; import "../OracleLib.sol"; diff --git a/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol index d3329f4ad2..f4b8adf30b 100644 --- a/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; +import "../erc20/RewardableERC20Wrapper.sol"; import "../AppreciatingFiatCollateral.sol"; import "./ICToken.sol"; @@ -17,6 +18,8 @@ contract CTokenSelfReferentialCollateral is AppreciatingFiatCollateral { uint8 public immutable referenceERC20Decimals; + ICToken public immutable cToken; // gas-optimization: access underlying cToken directly + /// @param config.chainlinkFeed Feed units: {UoA/ref} /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide /// @param referenceERC20Decimals_ The number of decimals in the reference token @@ -27,6 +30,7 @@ contract CTokenSelfReferentialCollateral is AppreciatingFiatCollateral { ) AppreciatingFiatCollateral(config, revenueHiding) { require(config.defaultThreshold == 0, "default threshold not supported"); require(referenceERC20Decimals_ > 0, "referenceERC20Decimals missing"); + cToken = ICToken(address(RewardableERC20Wrapper(address(config.erc20)).underlying())); referenceERC20Decimals = referenceERC20Decimals_; } @@ -59,8 +63,8 @@ contract CTokenSelfReferentialCollateral is AppreciatingFiatCollateral { /// @custom:interaction RCEI function refresh() public virtual override { // == Refresh == - // Update the Compound Protocol - ICToken(address(erc20)).exchangeRateCurrent(); + // Update the Compound Protocol -- access cToken directly + cToken.exchangeRateCurrent(); // Violation of calling super first! Composition broken! Intentional! super.refresh(); // already handles all necessary default checks @@ -68,7 +72,7 @@ contract CTokenSelfReferentialCollateral is AppreciatingFiatCollateral { /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens function _underlyingRefPerTok() internal view override returns (uint192) { - uint256 rate = ICToken(address(erc20)).exchangeRateStored(); + uint256 rate = cToken.exchangeRateStored(); int8 shiftLeft = 8 - int8(referenceERC20Decimals) - 18; return shiftl_toFix(rate, shiftLeft); } diff --git a/contracts/plugins/assets/compoundv2/CTokenVault.sol b/contracts/plugins/assets/compoundv2/CTokenVault.sol deleted file mode 100644 index 8d8c8501aa..0000000000 --- a/contracts/plugins/assets/compoundv2/CTokenVault.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity ^0.8.17; - -import "../vaults/RewardableERC20Vault.sol"; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "./ICToken.sol"; - -contract CTokenVault is RewardableERC20Vault { - using SafeERC20 for ERC20; - - IComptroller public immutable comptroller; - - constructor( - ERC20 _asset, - string memory _name, - string memory _symbol, - IComptroller _comptroller - ) RewardableERC20Vault(_asset, _name, _symbol, ERC20(_comptroller.getCompAddress())) { - comptroller = _comptroller; - } - - function _claimAssetRewards() internal virtual override { - comptroller.claimComp(address(this)); - } - - function exchangeRateCurrent() external returns (uint256) { - return ICToken(asset()).exchangeRateCurrent(); - } - - function exchangeRateStored() external view returns (uint256) { - return ICToken(asset()).exchangeRateStored(); - } -} diff --git a/contracts/plugins/assets/compoundv2/CTokenWrapper.sol b/contracts/plugins/assets/compoundv2/CTokenWrapper.sol new file mode 100644 index 0000000000..534de316a0 --- /dev/null +++ b/contracts/plugins/assets/compoundv2/CTokenWrapper.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "../erc20/RewardableERC20Wrapper.sol"; +import "./ICToken.sol"; + +contract CTokenWrapper is RewardableERC20Wrapper { + using SafeERC20 for ERC20; + + IComptroller public immutable comptroller; + + constructor( + ERC20 _underlying, + string memory _name, + string memory _symbol, + IComptroller _comptroller + ) RewardableERC20Wrapper(_underlying, _name, _symbol, ERC20(_comptroller.getCompAddress())) { + comptroller = _comptroller; + } + + /// === Exchange rate pass-throughs === + + // While these are included in the wrapper, it should probably not be used directly + // by the collateral plugin for gas optimization reasons + + function exchangeRateCurrent() external returns (uint256) { + return ICToken(address(underlying)).exchangeRateCurrent(); + } + + function exchangeRateStored() external view returns (uint256) { + return ICToken(address(underlying)).exchangeRateStored(); + } + + // === Overrides === + + function _claimAssetRewards() internal virtual override { + comptroller.claimComp(address(this)); + } + + // No overrides of _deposit()/_withdraw() necessary: no staking required +} diff --git a/contracts/plugins/assets/compoundv2/ICToken.sol b/contracts/plugins/assets/compoundv2/ICToken.sol index a68a8c6ecb..384ba84980 100644 --- a/contracts/plugins/assets/compoundv2/ICToken.sol +++ b/contracts/plugins/assets/compoundv2/ICToken.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol index ea923e9dce..a4d9e8f625 100644 --- a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol +++ b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -104,6 +104,8 @@ contract CTokenV3Collateral is AppreciatingFiatCollateral { lastSave = uint48(block.timestamp); } else { // must be unpriced + // untested: + // validated in other plugins, cost to test here is high assert(low == 0); } diff --git a/contracts/plugins/assets/compoundv3/CometHelpers.sol b/contracts/plugins/assets/compoundv3/CometHelpers.sol index e8fb6fc044..bc67617cb5 100644 --- a/contracts/plugins/assets/compoundv3/CometHelpers.sol +++ b/contracts/plugins/assets/compoundv3/CometHelpers.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; contract CometHelpers { uint64 internal constant BASE_INDEX_SCALE = 1e15; @@ -12,15 +12,12 @@ contract CometHelpers { error NegativeNumber(); function safe64(uint256 n) internal pure returns (uint64) { + // untested: + // comet code, overflow is hard to cover if (n > type(uint64).max) revert InvalidUInt64(); return uint64(n); } - function signed256(uint256 n) internal pure returns (int256) { - if (n > uint256(type(int256).max)) revert InvalidInt256(); - return int256(n); - } - function presentValueSupply(uint64 baseSupplyIndex_, uint104 principalValue_) internal pure @@ -38,15 +35,12 @@ contract CometHelpers { } function safe104(uint256 n) internal pure returns (uint104) { + // untested: + // comet code, overflow is hard to cover if (n > type(uint104).max) revert InvalidUInt104(); return uint104(n); } - function unsigned256(int256 n) internal pure returns (uint256) { - if (n < 0) revert NegativeNumber(); - return uint256(n); - } - /** * @dev Multiply a number by a factor */ diff --git a/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol b/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol index bb6930c331..9234322186 100644 --- a/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol +++ b/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "./vendor/CometInterface.sol"; @@ -161,6 +161,8 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers { // occasionally comet will withdraw 1-10 wei more than we asked for. // this is ok because 9 times out of 10 we are rounding in favor of the wrapper. // safe because we have already capped the comet withdraw amount to src underlying bal. + // untested: + // difficult to trigger, depends on comet rules regarding rounding if (srcBalPre <= burnAmt) burnAmt = srcBalPre; accrueAccountRewards(src); diff --git a/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol b/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol index 67a676b3be..f1514ec8e8 100644 --- a/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol +++ b/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/plugins/assets/compoundv3/IWrappedERC20.sol b/contracts/plugins/assets/compoundv3/IWrappedERC20.sol index 8b0ea06af9..b9e08fca18 100644 --- a/contracts/plugins/assets/compoundv3/IWrappedERC20.sol +++ b/contracts/plugins/assets/compoundv3/IWrappedERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/plugins/assets/compoundv3/WrappedERC20.sol b/contracts/plugins/assets/compoundv3/WrappedERC20.sol index a2e2b8170b..b3287711d7 100644 --- a/contracts/plugins/assets/compoundv3/WrappedERC20.sol +++ b/contracts/plugins/assets/compoundv3/WrappedERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./IWrappedERC20.sol"; @@ -225,9 +225,13 @@ abstract contract WrappedERC20 is IWrappedERC20 { * - `account` must have at least `amount` tokens. */ function _burn(address account, uint256 amount) internal virtual { + // untestable: + // previously validated, account will not be address(0) if (account == address(0)) revert ZeroAddress(); uint256 accountBalance = _balances[account]; + // untestable: + // ammount previously capped to the account balance if (amount > accountBalance) revert ExceedsBalance(amount); unchecked { _balances[account] = accountBalance - amount; diff --git a/contracts/plugins/assets/compoundv3/vendor/CometCore.sol b/contracts/plugins/assets/compoundv3/vendor/CometCore.sol index ad5d8bff3a..3626c0f3c5 100644 --- a/contracts/plugins/assets/compoundv3/vendor/CometCore.sol +++ b/contracts/plugins/assets/compoundv3/vendor/CometCore.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./CometStorage.sol"; diff --git a/contracts/plugins/assets/compoundv3/vendor/CometExtInterface.sol b/contracts/plugins/assets/compoundv3/vendor/CometExtInterface.sol index d42d48d967..a144d69112 100644 --- a/contracts/plugins/assets/compoundv3/vendor/CometExtInterface.sol +++ b/contracts/plugins/assets/compoundv3/vendor/CometExtInterface.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.17; +pragma solidity 0.8.19; struct TotalsBasic { uint64 baseSupplyIndex; diff --git a/contracts/plugins/assets/compoundv3/vendor/CometExtMock.sol b/contracts/plugins/assets/compoundv3/vendor/CometExtMock.sol index ba4e7fa5db..cc182262b2 100644 --- a/contracts/plugins/assets/compoundv3/vendor/CometExtMock.sol +++ b/contracts/plugins/assets/compoundv3/vendor/CometExtMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./CometCore.sol"; diff --git a/contracts/plugins/assets/compoundv3/vendor/CometInterface.sol b/contracts/plugins/assets/compoundv3/vendor/CometInterface.sol index 3c955966a4..b68bd08137 100644 --- a/contracts/plugins/assets/compoundv3/vendor/CometInterface.sol +++ b/contracts/plugins/assets/compoundv3/vendor/CometInterface.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./CometMainInterface.sol"; import "./CometExtInterface.sol"; diff --git a/contracts/plugins/assets/compoundv3/vendor/CometMainInterface.sol b/contracts/plugins/assets/compoundv3/vendor/CometMainInterface.sol index 6e1f18eb8e..247bc7738a 100644 --- a/contracts/plugins/assets/compoundv3/vendor/CometMainInterface.sol +++ b/contracts/plugins/assets/compoundv3/vendor/CometMainInterface.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.17; +pragma solidity 0.8.19; struct AssetInfo { uint8 offset; diff --git a/contracts/plugins/assets/compoundv3/vendor/CometStorage.sol b/contracts/plugins/assets/compoundv3/vendor/CometStorage.sol index 9564ca89b1..946b8983f4 100644 --- a/contracts/plugins/assets/compoundv3/vendor/CometStorage.sol +++ b/contracts/plugins/assets/compoundv3/vendor/CometStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.17; +pragma solidity 0.8.19; /** * @title Compound's Comet Configuration Interface diff --git a/contracts/plugins/assets/compoundv3/vendor/IComet.sol b/contracts/plugins/assets/compoundv3/vendor/IComet.sol index 5a70d2dde7..44fffae2d7 100644 --- a/contracts/plugins/assets/compoundv3/vendor/IComet.sol +++ b/contracts/plugins/assets/compoundv3/vendor/IComet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; interface IComet { function getReserves() external view returns (int256); diff --git a/contracts/plugins/assets/compoundv3/vendor/ICometConfigurator.sol b/contracts/plugins/assets/compoundv3/vendor/ICometConfigurator.sol index 05361a8547..d92675a384 100644 --- a/contracts/plugins/assets/compoundv3/vendor/ICometConfigurator.sol +++ b/contracts/plugins/assets/compoundv3/vendor/ICometConfigurator.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; interface ICometConfigurator { struct Configuration { diff --git a/contracts/plugins/assets/compoundv3/vendor/ICometProxyAdmin.sol b/contracts/plugins/assets/compoundv3/vendor/ICometProxyAdmin.sol index e96ee181bc..bb778143fe 100644 --- a/contracts/plugins/assets/compoundv3/vendor/ICometProxyAdmin.sol +++ b/contracts/plugins/assets/compoundv3/vendor/ICometProxyAdmin.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/contracts/plugins/assets/compoundv3/vendor/ICometRewards.sol b/contracts/plugins/assets/compoundv3/vendor/ICometRewards.sol index f40b3b4912..5d64950be5 100644 --- a/contracts/plugins/assets/compoundv3/vendor/ICometRewards.sol +++ b/contracts/plugins/assets/compoundv3/vendor/ICometRewards.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; interface ICometRewards { struct RewardConfig { diff --git a/contracts/plugins/assets/convex/CvxStableCollateral.sol b/contracts/plugins/assets/curve/CurveStableCollateral.sol similarity index 87% rename from contracts/plugins/assets/convex/CvxStableCollateral.sol rename to contracts/plugins/assets/curve/CurveStableCollateral.sol index 77123e2745..4a2b0d35ac 100644 --- a/contracts/plugins/assets/convex/CvxStableCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableCollateral.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: ISC -pragma solidity 0.8.17; +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; @@ -7,25 +7,26 @@ import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "contracts/interfaces/IAsset.sol"; import "contracts/libraries/Fixed.sol"; import "contracts/plugins/assets/AppreciatingFiatCollateral.sol"; -import "./vendor/IConvexStakingWrapper.sol"; -import "./PoolTokens.sol"; +import "../curve/PoolTokens.sol"; /** - * @title CvxStableCollateral - * This plugin contract is fully general to any number of tokens in a plain stable pool, - * with between 1 and 2 oracles per each token. Stable means only like-kind pools. + * @title CurveStableCollateral + * This plugin contract is fully general to any number of (fiat) tokens in a Curve stable pool, + * whether this LP token ends up staked in Curve, Convex, Frax, or somewhere else. + * Each token in the pool can have between 1 and 2 oracles per each token. + * Stable means only like-kind pools. * - * tok = ConvexStakingWrapper(cvxStablePlainPool) - * ref = cvxStablePlainPool pool invariant + * tok = ConvexStakingWrapper(stablePlainPool) + * ref = stablePlainPool pool invariant * tar = USD * UoA = USD */ -contract CvxStableCollateral is AppreciatingFiatCollateral, PoolTokens { +contract CurveStableCollateral is AppreciatingFiatCollateral, PoolTokens { using OracleLib for AggregatorV3Interface; using FixLib for uint192; /// @dev config Unused members: chainlinkFeed, oracleError, oracleTimeout - /// @dev config.erc20 should be a IConvexStakingWrapper + /// @dev config.erc20 should be a RewardableERC20 constructor( CollateralConfig memory config, uint192 revenueHiding, @@ -108,6 +109,8 @@ contract CvxStableCollateral is AppreciatingFiatCollateral, PoolTokens { lastSave = uint48(block.timestamp); } else { // must be unpriced + // untested: + // validated in other plugins, cost to test here is high assert(low == 0); } @@ -156,6 +159,8 @@ contract CvxStableCollateral is AppreciatingFiatCollateral, PoolTokens { if (mid < pegBottom || mid > 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; } diff --git a/contracts/plugins/assets/convex/CvxStableMetapoolCollateral.sol b/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol similarity index 92% rename from contracts/plugins/assets/convex/CvxStableMetapoolCollateral.sol rename to contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol index 55cc54c186..450cf1f70b 100644 --- a/contracts/plugins/assets/convex/CvxStableMetapoolCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol @@ -1,7 +1,7 @@ -// SPDX-License-Identifier: ISC -pragma solidity 0.8.17; +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; -import "./CvxStableCollateral.sol"; +import "./CurveStableCollateral.sol"; // solhint-disable no-empty-blocks interface ICurveMetaPool is ICurvePool, IERC20Metadata { @@ -9,8 +9,8 @@ interface ICurveMetaPool is ICurvePool, IERC20Metadata { } /** - * @title CvxStableMetapoolCollateral - * This plugin contract is intended for 2-token stable metapools that + * @title CurveStableMetapoolCollateral + * This plugin contract is intended for 2-fiattoken stable metapools that * DO NOT involve RTokens, such as LUSD-fraxBP or MIM-3CRV. * * tok = ConvexStakingWrapper(PairedUSDToken/USDBasePool) @@ -18,7 +18,7 @@ interface ICurveMetaPool is ICurvePool, IERC20Metadata { * tar = USD * UoA = USD */ -contract CvxStableMetapoolCollateral is CvxStableCollateral { +contract CurveStableMetapoolCollateral is CurveStableCollateral { using OracleLib for AggregatorV3Interface; using FixLib for uint192; @@ -32,14 +32,14 @@ contract CvxStableMetapoolCollateral is CvxStableCollateral { /// @param config.chainlinkFeed Feed units: {UoA/pairedTok} /// @dev config.chainlinkFeed/oracleError/oracleTimeout should be set for paired token - /// @dev config.erc20 should be a IConvexStakingWrapper + /// @dev config.erc20 should be a RewardableERC20 constructor( CollateralConfig memory config, uint192 revenueHiding, PTConfiguration memory ptConfig, ICurveMetaPool metapoolToken_, uint192 pairedTokenDefaultThreshold_ - ) CvxStableCollateral(config, revenueHiding, ptConfig) { + ) CurveStableCollateral(config, revenueHiding, ptConfig) { require(address(metapoolToken_) != address(0), "metapoolToken address is zero"); require( pairedTokenDefaultThreshold_ > 0 && pairedTokenDefaultThreshold_ < FIX_ONE, @@ -129,6 +129,8 @@ contract CvxStableMetapoolCollateral is CvxStableCollateral { if (mid < pairedTokenPegBottom || mid > pairedTokenPegTop) 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; } diff --git a/contracts/plugins/assets/convex/CvxStableRTokenMetapoolCollateral.sol b/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol similarity index 74% rename from contracts/plugins/assets/convex/CvxStableRTokenMetapoolCollateral.sol rename to contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol index 3c34abeb3b..005a7911b8 100644 --- a/contracts/plugins/assets/convex/CvxStableRTokenMetapoolCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol @@ -1,26 +1,26 @@ -// SPDX-License-Identifier: ISC -pragma solidity 0.8.17; +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; -import "./CvxStableMetapoolCollateral.sol"; +import "./CurveStableMetapoolCollateral.sol"; /** - * @title CvxStableRTokenMetapoolCollateral - * This plugin contract is intended for 2-token stable metapools that + * @title CurveStableRTokenMetapoolCollateral + * This plugin contract is intended for 2-fiattoken stable metapools that * involve RTokens, such as eUSD-fraxBP. * - * tok = ConvexStakingWrapper(cvxPairedUSDRToken/USDBasePool) + * tok = ConvexStakingWrapper(pairedUSDRToken/USDBasePool) * ref = PairedUSDRToken/USDBasePool pool invariant * tar = USD * UoA = USD */ -contract CvxStableRTokenMetapoolCollateral is CvxStableMetapoolCollateral { +contract CurveStableRTokenMetapoolCollateral is CurveStableMetapoolCollateral { using FixLib for uint192; IAssetRegistry internal immutable pairedAssetRegistry; // AssetRegistry of pairedToken /// @param config.chainlinkFeed Feed units: {UoA/pairedTok} /// @dev config.chainlinkFeed/oracleError/oracleTimeout are unused; set chainlinkFeed to 0x1 - /// @dev config.erc20 should be a IConvexStakingWrapper + /// @dev config.erc20 should be a RewardableERC20 constructor( CollateralConfig memory config, uint192 revenueHiding, @@ -28,7 +28,7 @@ contract CvxStableRTokenMetapoolCollateral is CvxStableMetapoolCollateral { ICurveMetaPool metapoolToken_, uint192 pairedTokenDefaultThreshold_ ) - CvxStableMetapoolCollateral( + CurveStableMetapoolCollateral( config, revenueHiding, ptConfig, diff --git a/contracts/plugins/assets/convex/CvxVolatileCollateral.sol b/contracts/plugins/assets/curve/CurveVolatileCollateral.sol similarity index 75% rename from contracts/plugins/assets/convex/CvxVolatileCollateral.sol rename to contracts/plugins/assets/curve/CurveVolatileCollateral.sol index 831f5fa3ae..4846f4fa25 100644 --- a/contracts/plugins/assets/convex/CvxVolatileCollateral.sol +++ b/contracts/plugins/assets/curve/CurveVolatileCollateral.sol @@ -1,18 +1,19 @@ -// SPDX-License-Identifier: ISC -pragma solidity 0.8.17; -import "./CvxStableCollateral.sol"; +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "./CurveStableCollateral.sol"; /** - * @title CvxVolatileCollateral - * This plugin contract extends CvxCurveStableCollateral to work for + * @title CurveVolatileCollateral + * This plugin contract extends CrvCurveStableCollateral to work for * volatile pools like TriCrypto. * - * tok = ConvexStakingWrapper(cvxVolatilePlainPool) - * ref = cvxVolatilePlainPool pool invariant - * tar = cvxVolatilePlainPool pool invariant + * tok = ConvexStakingWrapper(volatilePlainPool) + * ref = volatilePlainPool pool invariant + * tar = volatilePlainPool pool invariant * UoA = USD */ -contract CvxVolatileCollateral is CvxStableCollateral { +contract CurveVolatileCollateral is CurveStableCollateral { using FixLib for uint192; // this isn't saved by our parent classes, but we'll need to track it @@ -23,7 +24,7 @@ contract CvxVolatileCollateral is CvxStableCollateral { CollateralConfig memory config, uint192 revenueHiding, PTConfiguration memory ptConfig - ) CvxStableCollateral(config, revenueHiding, ptConfig) { + ) CurveStableCollateral(config, revenueHiding, ptConfig) { _defaultThreshold = config.defaultThreshold; } @@ -44,6 +45,8 @@ contract CvxVolatileCollateral is CvxStableCollateral { valSum += vals[i]; } 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; } diff --git a/contracts/plugins/assets/convex/PoolTokens.sol b/contracts/plugins/assets/curve/PoolTokens.sol similarity index 96% rename from contracts/plugins/assets/convex/PoolTokens.sol rename to contracts/plugins/assets/curve/PoolTokens.sol index 4e196ddfb0..bc5367dcdf 100644 --- a/contracts/plugins/assets/convex/PoolTokens.sol +++ b/contracts/plugins/assets/curve/PoolTokens.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: ISC -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; @@ -32,9 +32,7 @@ interface ICurvePool { ) external; } -// solhint-enable func-name-mixedcase - -/// Supports CvxCurve non-meta pools for up to 4 tokens +/// Supports Curve base pools for up to 4 tokens contract PoolTokens { using OracleLib for AggregatorV3Interface; using FixLib for uint192; @@ -44,7 +42,7 @@ contract PoolTokens { enum CurvePoolType { Plain, - Lending, + Lending, // not supported in this version Metapool // not supported via this class. parent class handles metapool math } @@ -126,10 +124,8 @@ contract PoolTokens { for (uint8 i = 0; i < nTokens; ++i) { if (config.poolType == CurvePoolType.Plain) { tokens[i] = IERC20Metadata(curvePool.coins(i)); - } else if (config.poolType == CurvePoolType.Lending) { - tokens[i] = IERC20Metadata(curvePool.underlying_coins(i)); } else { - revert("Use MetaPoolTokens class"); + revert("invalid poolType"); } } @@ -146,6 +142,8 @@ contract PoolTokens { // token0 bool more = config.feeds[0].length > 0; + // untestable: + // more will always be true based on previous feeds validations _t0feed0 = more ? config.feeds[0][0] : AggregatorV3Interface(address(0)); _t0timeout0 = more && config.oracleTimeouts[0].length > 0 ? config.oracleTimeouts[0][0] : 0; _t0error0 = more && config.oracleErrors[0].length > 0 ? config.oracleErrors[0][0] : 0; @@ -166,6 +164,8 @@ contract PoolTokens { } // token1 + // untestable: + // more will always be true based on previous feeds validations more = config.feeds[1].length > 0; _t1feed0 = more ? config.feeds[1][0] : AggregatorV3Interface(address(0)); _t1timeout0 = more && config.oracleTimeouts[1].length > 0 ? config.oracleTimeouts[1][0] : 0; @@ -307,6 +307,8 @@ contract PoolTokens { // === Private === function getToken(uint8 index) private view returns (IERC20Metadata) { + // untestable: + // getToken is always called with a valid index if (index >= nTokens) revert WrongIndex(nTokens - 1); if (index == 0) return token0; if (index == 1) return token1; diff --git a/contracts/plugins/assets/curve/crv/CurveGaugeWrapper.sol b/contracts/plugins/assets/curve/crv/CurveGaugeWrapper.sol new file mode 100644 index 0000000000..4d712ac720 --- /dev/null +++ b/contracts/plugins/assets/curve/crv/CurveGaugeWrapper.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "../../erc20/RewardableERC20Wrapper.sol"; + +interface IMinter { + /// Mint CRV to msg.sender based on their prorata share of the provided gauge + function mint(address gaugeAddr) external; +} + +interface ILiquidityGauge { + function deposit(uint256 _value) external; + + /// @param _value LP token amount + function withdraw(uint256 _value) external; +} + +contract CurveGaugeWrapper is RewardableERC20Wrapper { + using SafeERC20 for IERC20; + + IMinter public constant MINTER = IMinter(0xd061D61a4d941c39E5453435B6345Dc261C2fcE0); + + ILiquidityGauge public immutable gauge; + + /// @param _lpToken The curve LP token, transferrable + constructor( + ERC20 _lpToken, + string memory _name, + string memory _symbol, + ERC20 _crv, + ILiquidityGauge _gauge + ) RewardableERC20Wrapper(_lpToken, _name, _symbol, _crv) { + gauge = _gauge; + } + + // deposit a curve token + function _afterDeposit(uint256 _amount, address) internal override { + underlying.approve(address(gauge), _amount); + gauge.deposit(_amount); + } + + // withdraw to curve token + function _beforeWithdraw(uint256 _amount, address) internal override { + gauge.withdraw(_amount); + } + + function _claimAssetRewards() internal virtual override { + MINTER.mint(address(gauge)); + } +} diff --git a/contracts/plugins/assets/convex/README.md b/contracts/plugins/assets/curve/cvx/README.md similarity index 100% rename from contracts/plugins/assets/convex/README.md rename to contracts/plugins/assets/curve/cvx/README.md diff --git a/contracts/plugins/assets/convex/vendor/ConvexInterfaces.sol b/contracts/plugins/assets/curve/cvx/vendor/ConvexInterfaces.sol similarity index 99% rename from contracts/plugins/assets/convex/vendor/ConvexInterfaces.sol rename to contracts/plugins/assets/curve/cvx/vendor/ConvexInterfaces.sol index a6404c304b..0a8c00e155 100644 --- a/contracts/plugins/assets/convex/vendor/ConvexInterfaces.sol +++ b/contracts/plugins/assets/curve/cvx/vendor/ConvexInterfaces.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.17; +pragma solidity 0.8.19; interface ICurveGauge { function deposit(uint256) external; diff --git a/contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol b/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol similarity index 100% rename from contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol rename to contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol diff --git a/contracts/plugins/assets/convex/vendor/CvxMining.sol b/contracts/plugins/assets/curve/cvx/vendor/CvxMining.sol similarity index 100% rename from contracts/plugins/assets/convex/vendor/CvxMining.sol rename to contracts/plugins/assets/curve/cvx/vendor/CvxMining.sol diff --git a/contracts/plugins/assets/convex/vendor/IConvexStakingWrapper.sol b/contracts/plugins/assets/curve/cvx/vendor/IConvexStakingWrapper.sol similarity index 90% rename from contracts/plugins/assets/convex/vendor/IConvexStakingWrapper.sol rename to contracts/plugins/assets/curve/cvx/vendor/IConvexStakingWrapper.sol index e177c86b50..970aaf3f72 100644 --- a/contracts/plugins/assets/convex/vendor/IConvexStakingWrapper.sol +++ b/contracts/plugins/assets/curve/cvx/vendor/IConvexStakingWrapper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: ISC -pragma solidity 0.8.17; +pragma solidity 0.8.19; interface IConvexStakingWrapper { function crv() external returns (address); diff --git a/contracts/plugins/assets/convex/vendor/IRewardStaking.sol b/contracts/plugins/assets/curve/cvx/vendor/IRewardStaking.sol similarity index 100% rename from contracts/plugins/assets/convex/vendor/IRewardStaking.sol rename to contracts/plugins/assets/curve/cvx/vendor/IRewardStaking.sol diff --git a/contracts/plugins/assets/convex/vendor/StableSwap3Pool.vy b/contracts/plugins/assets/curve/cvx/vendor/StableSwap3Pool.vy similarity index 100% rename from contracts/plugins/assets/convex/vendor/StableSwap3Pool.vy rename to contracts/plugins/assets/curve/cvx/vendor/StableSwap3Pool.vy diff --git a/contracts/plugins/assets/dsr/README.md b/contracts/plugins/assets/dsr/README.md new file mode 100644 index 0000000000..6adf9b64d1 --- /dev/null +++ b/contracts/plugins/assets/dsr/README.md @@ -0,0 +1,27 @@ +# SDAI DSR Collateral Plugin + +## Summary + +This plugin allows `sDAI` holders to use their tokens as collateral in the Reserve Protocol. + +sDAI is an unowned, immutable, ERC4626-wrapper around the Dai savings rate. + +`sDAI` will accrue the same amount of DAI as it would if it were deposited directly into the DSR. + +Since it is ERC4626, the redeemable DAI amount can be gotten by dividing `sDAI.totalAssets()` by `sDAI.totalSupply()`. However, the same rate can be read out more directly by calling `pot.chi()`, for the MakerDAO pot. There is a mutation required before either of these values can be read. + +`sDAI` contract: + +## Implementation + +### Units + +| tok | ref | target | UoA | +| ---- | --- | ------ | --- | +| sDAI | DAI | USD | USD | + +### Functions + +#### refPerTok {ref/tok} + +`return shiftl_toFix(pot.chi(), -27);` diff --git a/contracts/plugins/assets/dsr/SDaiCollateral.sol b/contracts/plugins/assets/dsr/SDaiCollateral.sol new file mode 100644 index 0000000000..8e7643575f --- /dev/null +++ b/contracts/plugins/assets/dsr/SDaiCollateral.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "../../../libraries/Fixed.sol"; +import "../AppreciatingFiatCollateral.sol"; + +/// MakerDAO Pot +interface IPot { + function rho() external returns (uint256); + + function drip() external returns (uint256); + + /// {ray} + function chi() external view returns (uint256); +} + +/** + * @title SDAI Collateral + * @notice Collateral plugin for the DSR wrapper sDAI + * tok = SDAI (transferrable DSR-locked DAI) + * ref = DAI + * tar = USD + * UoA = USD + */ +contract SDaiCollateral is AppreciatingFiatCollateral { + using OracleLib for AggregatorV3Interface; + using FixLib for uint192; + + IPot public immutable pot; + + /// @param config.chainlinkFeed {UoA/ref} price of DAI in USD terms + constructor( + CollateralConfig memory config, + uint192 revenueHiding, + IPot _pot + ) AppreciatingFiatCollateral(config, revenueHiding) { + pot = _pot; + } + + /// Refresh exchange rates and update default status. + /// @custom:interaction RCEI + function refresh() public virtual override { + // == Refresh == + // Update the DSR contract + + if (block.timestamp > pot.rho()) pot.drip(); + + // Intentional and correct for the super call to be last! + super.refresh(); // already handles all necessary default checks + } + + /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens + function _underlyingRefPerTok() internal view override returns (uint192) { + return shiftl_toFix(pot.chi(), -27); + } +} diff --git a/contracts/plugins/assets/vaults/RewardableERC20Vault.sol b/contracts/plugins/assets/erc20/RewardableERC20.sol similarity index 75% rename from contracts/plugins/assets/vaults/RewardableERC20Vault.sol rename to contracts/plugins/assets/erc20/RewardableERC20.sol index c9140be702..90d1846e6b 100644 --- a/contracts/plugins/assets/vaults/RewardableERC20Vault.sol +++ b/contracts/plugins/assets/erc20/RewardableERC20.sol @@ -1,37 +1,33 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity ^0.8.17; +pragma solidity ^0.8.19; -import "../../../interfaces/IRewardable.sol"; -import "../../../vendor/oz/ERC4626.sol"; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "../../../interfaces/IRewardable.sol"; /** - * @title RewardableERC20Vault - * @notice A transferrable vault token wrapping an inner ERC20 that earns rewards. - * Holding the vault token for a period of time earns the holder the right to - * their prorata share of the global rewards earned during that time. - * @dev To inherit, override _claimAssetRewards() + * @title RewardableERC20 + * @notice An abstract class that can be extended to create rewardable wrapper + * @dev To inherit: + * - override _claimAssetRewards() + * - call ERC20 constructor elsewhere during construction */ -abstract contract RewardableERC20Vault is IRewardable, ERC4626 { - using SafeERC20 for ERC20; +abstract contract RewardableERC20 is IRewardable, ERC20 { + using SafeERC20 for IERC20; uint256 public immutable one; // {qShare/share} - ERC20 public immutable rewardToken; + IERC20 public immutable rewardToken; uint256 public rewardsPerShare; // {qRewards/share} mapping(address => uint256) public lastRewardsPerShare; // {qRewards/share} mapping(address => uint256) public accumulatedRewards; // {qRewards} mapping(address => uint256) public claimedRewards; // {qRewards} - constructor( - ERC20 _asset, - string memory _name, - string memory _symbol, - ERC20 _rewardToken - ) ERC4626(_asset, _name, _symbol) { + /// @dev Extending class must ensure ERC20 constructor is called + constructor(IERC20 _rewardToken, uint8 _decimals) { rewardToken = _rewardToken; - one = 10**decimals(); + one = 10**_decimals; // set via pass-in to prevent inheritance issues } function claimRewards() external { @@ -67,8 +63,6 @@ abstract contract RewardableERC20Vault is IRewardable, ERC4626 { } } - function _claimAssetRewards() internal virtual; - function _claimAccountRewards(address account) internal { uint256 claimableRewards = accumulatedRewards[account] - claimedRewards[account]; emit RewardsClaimed(IERC20(address(rewardToken)), claimableRewards); @@ -87,7 +81,7 @@ abstract contract RewardableERC20Vault is IRewardable, ERC4626 { _syncAccount(to); } - function _decimalsOffset() internal view virtual override returns (uint8) { - return 9; - } + /// === Must override === + + function _claimAssetRewards() internal virtual; } diff --git a/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol b/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol new file mode 100644 index 0000000000..285582a02f --- /dev/null +++ b/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "./RewardableERC20.sol"; + +/** + * @title RewardableERC20Wrapper + * @notice A transferrable ERC20 wrapper token wrapping an inner position that earns rewards. + * @dev To inherit: + * - override _claimAssetRewards() + * - consider overriding _afterDeposit() and _beforeWithdraw() + */ +abstract contract RewardableERC20Wrapper is RewardableERC20 { + using SafeERC20 for IERC20; + + IERC20 public immutable underlying; + + uint8 private immutable underlyingDecimals; + + event Deposited(address indexed _user, address indexed _account, uint256 _amount); + event Withdrawn(address indexed _user, address indexed _account, uint256 _amount); + + /// @dev Extending class must ensure ERC20 constructor is called + constructor( + IERC20Metadata _underlying, + string memory _name, + string memory _symbol, + IERC20 _rewardToken + ) ERC20(_name, _symbol) RewardableERC20(_rewardToken, _underlying.decimals()) { + underlying = _underlying; + underlyingDecimals = _underlying.decimals(); + } + + function decimals() public view virtual override returns (uint8) { + return underlyingDecimals; + } + + /// Deposit the underlying token and optionally take an action such as staking in a gauge + function deposit(uint256 _amount, address _to) external virtual { + if (_amount > 0) { + _mint(_to, _amount); // does balance checkpointing + underlying.safeTransferFrom(msg.sender, address(this), _amount); + _afterDeposit(_amount, _to); + } + emit Deposited(msg.sender, _to, _amount); + } + + /// Withdraw the underlying token and optionally take an action such as staking in a gauge + function withdraw(uint256 _amount, address _to) external virtual { + if (_amount > 0) { + _burn(msg.sender, _amount); // does balance checkpointing + _beforeWithdraw(_amount, _to); + underlying.safeTransfer(_to, _amount); + } + + emit Withdrawn(msg.sender, _to, _amount); + } + + /// === Must override === + + // function _claimAssetRewards() internal virtual; + + /// === May override === + // solhint-disable no-empty-blocks + + /// Any steps that should be taken after deposit, such as staking in a gauge + function _afterDeposit(uint256 _amount, address to) internal virtual {} + + /// Any steps that should be taken before withdraw, such as unstaking from a gauge + function _beforeWithdraw(uint256 _amount, address to) internal virtual {} +} diff --git a/contracts/plugins/assets/erc20/RewardableERC4626Vault.sol b/contracts/plugins/assets/erc20/RewardableERC4626Vault.sol new file mode 100644 index 0000000000..284f717c2e --- /dev/null +++ b/contracts/plugins/assets/erc20/RewardableERC4626Vault.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "../../../interfaces/IRewardable.sol"; +import "../../../vendor/oz/ERC4626.sol"; +import "./RewardableERC20.sol"; + +/** + * @title RewardableERC4626Vault + * @notice A transferrable ERC4626 vault wrapping an inner position that earns rewards. + * Holding the vault token for a period of time earns the holder the right to + * their prorata share of the global rewards earned during that time. + * @dev To inherit: + * - override _claimAssetRewards() + * - consider overriding _afterDeposit() and _beforeWithdraw() + */ +abstract contract RewardableERC4626Vault is ERC4626, RewardableERC20 { + // solhint-disable no-empty-blocks + constructor( + IERC20Metadata _asset, + string memory _name, + string memory _symbol, + ERC20 _rewardToken + ) + ERC4626(_asset, _name, _symbol) + RewardableERC20(_rewardToken, _asset.decimals() + _decimalsOffset()) + {} + + // solhint-enable no-empty-blocks + + function decimals() public view virtual override(ERC4626, ERC20) returns (uint8) { + return ERC4626.decimals(); + } + + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override(RewardableERC20, ERC20) { + RewardableERC20._beforeTokenTransfer(from, to, amount); + } + + function _decimalsOffset() internal view virtual override returns (uint8) { + return 9; + } + + /// === Must override === + + // function _claimAssetRewards() internal virtual; +} diff --git a/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol b/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol index ee50f90eb3..4697ec0da0 100644 --- a/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol +++ b/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../../../libraries/Fixed.sol"; diff --git a/contracts/plugins/assets/frax-eth/vendor/IfrxEthMinter.sol b/contracts/plugins/assets/frax-eth/vendor/IfrxEthMinter.sol index 815e8cb5dd..b8a71ad335 100644 --- a/contracts/plugins/assets/frax-eth/vendor/IfrxEthMinter.sol +++ b/contracts/plugins/assets/frax-eth/vendor/IfrxEthMinter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.17; +pragma solidity ^0.8.19; interface IfrxEthMinter { function submitAndDeposit(address recipient) external payable returns (uint256 shares); diff --git a/contracts/plugins/assets/frax-eth/vendor/IsfrxEth.sol b/contracts/plugins/assets/frax-eth/vendor/IsfrxEth.sol index 70514a7ee6..fe904d0193 100644 --- a/contracts/plugins/assets/frax-eth/vendor/IsfrxEth.sol +++ b/contracts/plugins/assets/frax-eth/vendor/IsfrxEth.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.17; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol b/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol index 575f94d2b6..783896f2c0 100644 --- a/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol +++ b/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../../../libraries/Fixed.sol"; diff --git a/contracts/plugins/assets/lido/vendor/ISTETH.sol b/contracts/plugins/assets/lido/vendor/ISTETH.sol index bb49ff6e8f..9a1b416421 100644 --- a/contracts/plugins/assets/lido/vendor/ISTETH.sol +++ b/contracts/plugins/assets/lido/vendor/ISTETH.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/plugins/assets/lido/vendor/IWSTETH.sol b/contracts/plugins/assets/lido/vendor/IWSTETH.sol index 7d282bac6a..4f5446df5d 100644 --- a/contracts/plugins/assets/lido/vendor/IWSTETH.sol +++ b/contracts/plugins/assets/lido/vendor/IWSTETH.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/plugins/assets/rocket-eth/RethCollateral.sol b/contracts/plugins/assets/rocket-eth/RethCollateral.sol index 6c8708271f..42888b93d7 100644 --- a/contracts/plugins/assets/rocket-eth/RethCollateral.sol +++ b/contracts/plugins/assets/rocket-eth/RethCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../../../libraries/Fixed.sol"; diff --git a/contracts/plugins/assets/rocket-eth/vendor/IReth.sol b/contracts/plugins/assets/rocket-eth/vendor/IReth.sol index ae64bb07dd..8983c3b317 100644 --- a/contracts/plugins/assets/rocket-eth/vendor/IReth.sol +++ b/contracts/plugins/assets/rocket-eth/vendor/IReth.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/plugins/assets/rocket-eth/vendor/IRocketNetworkBalances.sol b/contracts/plugins/assets/rocket-eth/vendor/IRocketNetworkBalances.sol index c131dd9d9a..117fb79799 100644 --- a/contracts/plugins/assets/rocket-eth/vendor/IRocketNetworkBalances.sol +++ b/contracts/plugins/assets/rocket-eth/vendor/IRocketNetworkBalances.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; interface IRocketNetworkBalances { function getTotalETHBalance() external view returns (uint256); diff --git a/contracts/plugins/assets/rocket-eth/vendor/IRocketStorage.sol b/contracts/plugins/assets/rocket-eth/vendor/IRocketStorage.sol index a4b88fa5fb..4ae67598ed 100644 --- a/contracts/plugins/assets/rocket-eth/vendor/IRocketStorage.sol +++ b/contracts/plugins/assets/rocket-eth/vendor/IRocketStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; interface IRocketStorage { function setUint(bytes32 _key, uint256 _value) external; diff --git a/contracts/plugins/governance/Governance.sol b/contracts/plugins/governance/Governance.sol index 2a4af73ae5..c20978fa02 100644 --- a/contracts/plugins/governance/Governance.sol +++ b/contracts/plugins/governance/Governance.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/governance/Governor.sol"; import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol"; diff --git a/contracts/plugins/mocks/ATokenMock.sol b/contracts/plugins/mocks/ATokenMock.sol index c852d1e3a3..5459fbabae 100644 --- a/contracts/plugins/mocks/ATokenMock.sol +++ b/contracts/plugins/mocks/ATokenMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../assets/aave/ATokenFiatCollateral.sol"; import "../../libraries/Fixed.sol"; diff --git a/contracts/plugins/mocks/ATokenNoController.sol b/contracts/plugins/mocks/ATokenNoController.sol new file mode 100644 index 0000000000..b9f94e9b59 --- /dev/null +++ b/contracts/plugins/mocks/ATokenNoController.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.6.12; + +import "@aave/protocol-v2/contracts/protocol/tokenization/AToken.sol"; +import "../assets/aave/vendor/IAaveIncentivesController.sol"; + +contract ATokenNoController is AToken { + constructor( + ILendingPool pool, + address underlyingAssetAddress, + address reserveTreasuryAddress, + string memory tokenName, + string memory tokenSymbol, + address incentivesController + ) + public + AToken( + pool, + underlyingAssetAddress, + reserveTreasuryAddress, + tokenName, + tokenSymbol, + incentivesController + ) + {} + + function getIncentivesController() external pure returns (IAaveIncentivesController) { + return IAaveIncentivesController(address(0)); + } +} diff --git a/contracts/plugins/mocks/AaveLendingPoolMock.sol b/contracts/plugins/mocks/AaveLendingPoolMock.sol index 3ee3218bf0..8c1a969d05 100644 --- a/contracts/plugins/mocks/AaveLendingPoolMock.sol +++ b/contracts/plugins/mocks/AaveLendingPoolMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; // See: https://github.com/aave/protocol-v2/tree/master/contracts/interfaces interface IAaveLendingPool { diff --git a/contracts/plugins/mocks/BackingMgrBackCompatible.sol b/contracts/plugins/mocks/BackingMgrBackCompatible.sol new file mode 100644 index 0000000000..9fe7767cc2 --- /dev/null +++ b/contracts/plugins/mocks/BackingMgrBackCompatible.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../../p1/BackingManager.sol"; + +interface IBackingManagerComp { + function manageTokens(IERC20[] memory erc20s) external; +} + +// BackingManager compatible with version 2 +contract BackingMgrCompatibleV2 is BackingManagerP1, IBackingManagerComp { + function manageTokens(IERC20[] calldata erc20s) external notTradingPausedOrFrozen { + // Mirror V3 logic (only the section relevant to tests) + if (erc20s.length == 0) { + this.rebalance(TradeKind.DUTCH_AUCTION); + } else { + this.forwardRevenue(erc20s); + } + } + + function version() public pure virtual override(Versioned, IVersioned) returns (string memory) { + return "2.1.0"; + } +} + +// BackingManager compatible with version 1 +contract BackingMgrCompatibleV1 is BackingMgrCompatibleV2 { + function version() public pure override(BackingMgrCompatibleV2) returns (string memory) { + return "1.0.0"; + } +} + +// BackingManager with invalid version +contract BackingMgrInvalidVersion is BackingMgrCompatibleV2 { + function version() public pure override(BackingMgrCompatibleV2) returns (string memory) { + return "0.0.0"; + } +} diff --git a/contracts/plugins/mocks/BadCollateralPlugin.sol b/contracts/plugins/mocks/BadCollateralPlugin.sol index a0564ca268..2af22a1291 100644 --- a/contracts/plugins/mocks/BadCollateralPlugin.sol +++ b/contracts/plugins/mocks/BadCollateralPlugin.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../assets/aave/ATokenFiatCollateral.sol"; diff --git a/contracts/plugins/mocks/BadERC20.sol b/contracts/plugins/mocks/BadERC20.sol index 3af7fff789..99c7791e84 100644 --- a/contracts/plugins/mocks/BadERC20.sol +++ b/contracts/plugins/mocks/BadERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/utils/Address.sol"; import "../../libraries/Fixed.sol"; @@ -8,18 +8,24 @@ import "./ERC20Mock.sol"; contract BadERC20 is ERC20Mock { using Address for address; using FixLib for uint192; + uint8 private _decimals; uint192 public transferFee; // {1} - bool public revertDecimals; mapping(address => bool) public censored; - constructor(string memory name, string memory symbol) ERC20Mock(name, symbol) {} + constructor(string memory name, string memory symbol) ERC20Mock(name, symbol) { + _decimals = 18; + } function setTransferFee(uint192 newFee) external { transferFee = newFee; } + function setDecimals(uint8 newVal) external { + _decimals = newVal; + } + function setRevertDecimals(bool newVal) external { revertDecimals = newVal; } @@ -33,7 +39,7 @@ contract BadERC20 is ERC20Mock { // Make an external staticcall to this address, for a function that does not exist if (revertDecimals) address(this).functionStaticCall(data, "No Decimals"); - return 18; + return _decimals; } function transfer(address to, uint256 amount) public virtual override returns (bool) { diff --git a/contracts/plugins/mocks/CTokenMock.sol b/contracts/plugins/mocks/CTokenMock.sol index da13680ec2..d02401dee0 100644 --- a/contracts/plugins/mocks/CTokenMock.sol +++ b/contracts/plugins/mocks/CTokenMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../../libraries/Fixed.sol"; diff --git a/contracts/plugins/mocks/CTokenVaultMock2.sol b/contracts/plugins/mocks/CTokenWrapperMock2.sol similarity index 72% rename from contracts/plugins/mocks/CTokenVaultMock2.sol rename to contracts/plugins/mocks/CTokenWrapperMock2.sol index d303f1440d..7ada69cb2c 100644 --- a/contracts/plugins/mocks/CTokenVaultMock2.sol +++ b/contracts/plugins/mocks/CTokenWrapperMock2.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity ^0.8.17; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "../assets/compoundv2/CTokenVault.sol"; +import "../assets/compoundv2/CTokenWrapper.sol"; import "../assets/compoundv2/ICToken.sol"; import "./CTokenMock.sol"; -contract CTokenVaultMock is ERC20Mock, IRewardable { +contract CTokenWrapperMock is ERC20Mock, IRewardable { ERC20Mock public comp; - CTokenMock public asset; + CTokenMock public underlying; IComptroller public comptroller; bool public revertClaimRewards; @@ -20,25 +20,21 @@ contract CTokenVaultMock is ERC20Mock, IRewardable { ERC20Mock _comp, IComptroller _comptroller ) ERC20Mock(_name, _symbol) { - asset = new CTokenMock("cToken Mock", "cMOCK", _underlyingToken); + underlying = new CTokenMock("cToken Mock", "cMOCK", _underlyingToken); comp = _comp; comptroller = _comptroller; } - // function mint(uint256 amount, address recipient) external { - // _mint(recipient, amount); - // } - function decimals() public pure override returns (uint8) { return 8; } function exchangeRateCurrent() external returns (uint256) { - return asset.exchangeRateCurrent(); + return underlying.exchangeRateCurrent(); } function exchangeRateStored() external view returns (uint256) { - return asset.exchangeRateStored(); + return underlying.exchangeRateStored(); } function claimRewards() external { @@ -51,7 +47,7 @@ contract CTokenVaultMock is ERC20Mock, IRewardable { } function setExchangeRate(uint192 fiatcoinRedemptionRate) external { - asset.setExchangeRate(fiatcoinRedemptionRate); + underlying.setExchangeRate(fiatcoinRedemptionRate); } function setRevertClaimRewards(bool newVal) external { diff --git a/contracts/plugins/mocks/CometMock.sol b/contracts/plugins/mocks/CometMock.sol index 5f8d3ab886..16556c9483 100644 --- a/contracts/plugins/mocks/CometMock.sol +++ b/contracts/plugins/mocks/CometMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.17; +pragma solidity 0.8.19; // prettier-ignore contract CometMock { diff --git a/contracts/plugins/mocks/ComptrollerMock.sol b/contracts/plugins/mocks/ComptrollerMock.sol index 29d5083712..8e0d128508 100644 --- a/contracts/plugins/mocks/ComptrollerMock.sol +++ b/contracts/plugins/mocks/ComptrollerMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../assets/compoundv2/ICToken.sol"; import "./ERC20Mock.sol"; diff --git a/contracts/plugins/mocks/CurveMetapoolMock.sol b/contracts/plugins/mocks/CurveMetapoolMock.sol index 6c8adcb062..70ee17a3f2 100644 --- a/contracts/plugins/mocks/CurveMetapoolMock.sol +++ b/contracts/plugins/mocks/CurveMetapoolMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: ISC -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./CurvePoolMock.sol"; import "./ERC20Mock.sol"; diff --git a/contracts/plugins/mocks/CurvePoolMock.sol b/contracts/plugins/mocks/CurvePoolMock.sol index 4ce7314229..cd33b7ba19 100644 --- a/contracts/plugins/mocks/CurvePoolMock.sol +++ b/contracts/plugins/mocks/CurvePoolMock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: ISC -pragma solidity 0.8.17; +pragma solidity 0.8.19; -import "../../plugins/assets/convex/PoolTokens.sol"; +import "../../plugins/assets/curve/PoolTokens.sol"; contract CurvePoolMock is ICurvePool { uint256[] internal _balances; @@ -38,3 +38,52 @@ contract CurvePoolMock is ICurvePool { uint256 ) external {} } + +interface ICurvePoolVariantInt { + // For Curve Plain Pools and V2 Metapools + function coins(int128) external view returns (address); + + // Only exists in Curve Lending Pools + function underlying_coins(int128) external view returns (address); + + // Uses int128 as index + function balances(int128) external view returns (uint256); + + function get_virtual_price() external view returns (uint256); +} + +// Required for some Curve Pools that use int128 as index +contract CurvePoolMockVariantInt is ICurvePoolVariantInt { + uint256[] internal _balances; + address[] internal _coins; + address[] internal _underlying_coins; + uint256 public get_virtual_price; + + constructor(uint256[] memory initialBalances, address[] memory initialCoins) { + _balances = initialBalances; + _coins = initialCoins; + } + + function setBalances(uint256[] memory newBalances) external { + _balances = newBalances; + } + + function balances(int128 index) external view returns (uint256) { + uint256 newIndex = uint256(abs(index)); + return _balances[newIndex]; + } + + function coins(int128 index) external view returns (address) { + uint256 newIndex = uint256(abs(index)); + return _coins[newIndex]; + } + + function underlying_coins(int128 index) external view returns (address) { + uint256 newIndex = uint256(abs(index)); + return _underlying_coins[newIndex]; + } + + function setVirtualPrice(uint256 newPrice) external { + get_virtual_price = newPrice; + } +} diff --git a/contracts/plugins/mocks/CusdcV3WrapperMock.sol b/contracts/plugins/mocks/CusdcV3WrapperMock.sol index c23f92cea5..844c9ef785 100644 --- a/contracts/plugins/mocks/CusdcV3WrapperMock.sol +++ b/contracts/plugins/mocks/CusdcV3WrapperMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../assets/compoundv3/CusdcV3Wrapper.sol"; import "../assets/compoundv3/ICusdcV3Wrapper.sol"; diff --git a/contracts/plugins/mocks/EACAggregatorProxyMock.sol b/contracts/plugins/mocks/EACAggregatorProxyMock.sol index 2b553b10a6..82b2b52d49 100644 --- a/contracts/plugins/mocks/EACAggregatorProxyMock.sol +++ b/contracts/plugins/mocks/EACAggregatorProxyMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.17; +pragma solidity 0.8.19; /** * @title The Owned contract diff --git a/contracts/plugins/mocks/ERC1271Mock.sol b/contracts/plugins/mocks/ERC1271Mock.sol index 330f248322..ccc982ed19 100644 --- a/contracts/plugins/mocks/ERC1271Mock.sol +++ b/contracts/plugins/mocks/ERC1271Mock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/utils/Address.sol"; import "../../libraries/Fixed.sol"; diff --git a/contracts/plugins/mocks/ERC20Mock.sol b/contracts/plugins/mocks/ERC20Mock.sol index 5d12efbeb5..f374070f52 100644 --- a/contracts/plugins/mocks/ERC20Mock.sol +++ b/contracts/plugins/mocks/ERC20Mock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/contracts/plugins/mocks/ERC20MockDecimals.sol b/contracts/plugins/mocks/ERC20MockDecimals.sol index b036d75276..d0449c1bf9 100644 --- a/contracts/plugins/mocks/ERC20MockDecimals.sol +++ b/contracts/plugins/mocks/ERC20MockDecimals.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./ERC20Mock.sol"; diff --git a/contracts/plugins/mocks/ERC20MockRewarding.sol b/contracts/plugins/mocks/ERC20MockRewarding.sol index 7a2fc54593..69b0aff235 100644 --- a/contracts/plugins/mocks/ERC20MockRewarding.sol +++ b/contracts/plugins/mocks/ERC20MockRewarding.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./ERC20MockDecimals.sol"; diff --git a/contracts/plugins/mocks/GasGuzzlingFiatCollateral.sol b/contracts/plugins/mocks/GasGuzzlingFiatCollateral.sol index 7c91a76b21..ae3b822833 100644 --- a/contracts/plugins/mocks/GasGuzzlingFiatCollateral.sol +++ b/contracts/plugins/mocks/GasGuzzlingFiatCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../assets/FiatCollateral.sol"; diff --git a/contracts/plugins/mocks/GnosisMock.sol b/contracts/plugins/mocks/GnosisMock.sol index 2dc02b8594..3d5490188c 100644 --- a/contracts/plugins/mocks/GnosisMock.sol +++ b/contracts/plugins/mocks/GnosisMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; diff --git a/contracts/plugins/mocks/GnosisMockReentrant.sol b/contracts/plugins/mocks/GnosisMockReentrant.sol index 303dae4aad..52d2382afd 100644 --- a/contracts/plugins/mocks/GnosisMockReentrant.sol +++ b/contracts/plugins/mocks/GnosisMockReentrant.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; diff --git a/contracts/plugins/mocks/InvalidATokenFiatCollateralMock.sol b/contracts/plugins/mocks/InvalidATokenFiatCollateralMock.sol index e349804c35..a1f8bdbb38 100644 --- a/contracts/plugins/mocks/InvalidATokenFiatCollateralMock.sol +++ b/contracts/plugins/mocks/InvalidATokenFiatCollateralMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../assets/aave/ATokenFiatCollateral.sol"; diff --git a/contracts/plugins/mocks/InvalidBrokerMock.sol b/contracts/plugins/mocks/InvalidBrokerMock.sol index 7bbe3b7284..67e4ae98c7 100644 --- a/contracts/plugins/mocks/InvalidBrokerMock.sol +++ b/contracts/plugins/mocks/InvalidBrokerMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/plugins/mocks/InvalidFiatCollateral.sol b/contracts/plugins/mocks/InvalidFiatCollateral.sol index 1233d06d36..343737bd38 100644 --- a/contracts/plugins/mocks/InvalidFiatCollateral.sol +++ b/contracts/plugins/mocks/InvalidFiatCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../assets/FiatCollateral.sol"; diff --git a/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol b/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol index 7506e491d0..3ad165f9ab 100644 --- a/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol +++ b/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../../libraries/Fixed.sol"; diff --git a/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol b/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol new file mode 100644 index 0000000000..b19793ffaf --- /dev/null +++ b/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "../../interfaces/IMain.sol"; +import "../../interfaces/IAssetRegistry.sol"; +import "../../p1/mixins/Trading.sol"; +import "../../p1/mixins/TradeLib.sol"; + +/// Trader Component that reverts on manageToken +contract RevenueTraderP1InvalidReverts is TradingP1, IRevenueTrader { + using FixLib for uint192; + using SafeERC20 for IERC20; + + // Immutable after init() + IERC20 public tokenToBuy; + IAssetRegistry private assetRegistry; + IDistributor private distributor; + IBackingManager private backingManager; + IFurnace private furnace; + IRToken private rToken; + + function init( + IMain main_, + IERC20 tokenToBuy_, + uint192 maxTradeSlippage_, + uint192 minTradeVolume_ + ) external initializer { + require(address(tokenToBuy_) != address(0), "invalid token address"); + __Component_init(main_); + __Trading_init(main_, maxTradeSlippage_, minTradeVolume_); + tokenToBuy = tokenToBuy_; + cacheComponents(); + } + + /// Distribute tokenToBuy to its destinations + function distributeTokenToBuy() public { + uint256 bal = tokenToBuy.balanceOf(address(this)); + tokenToBuy.safeApprove(address(main.distributor()), 0); + tokenToBuy.safeApprove(address(main.distributor()), bal); + main.distributor().distribute(tokenToBuy, bal); + } + + /// Processes a single token; unpermissioned + /// Reverts for testing purposes + function manageToken(IERC20, TradeKind) external notTradingPausedOrFrozen { + rToken = rToken; // silence warning + revert(); + } + + function cacheComponents() public { + assetRegistry = main.assetRegistry(); + distributor = main.distributor(); + backingManager = main.backingManager(); + furnace = main.furnace(); + rToken = main.rToken(); + } +} diff --git a/contracts/plugins/mocks/MakerPotMock.sol b/contracts/plugins/mocks/MakerPotMock.sol new file mode 100644 index 0000000000..1e59ac1582 --- /dev/null +++ b/contracts/plugins/mocks/MakerPotMock.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../assets/dsr/SDaiCollateral.sol"; + +contract PotMock is IPot { + IPot public immutable pot; + + uint256 public chi; // {ray} + + constructor(IPot _pot) { + pot = _pot; + chi = pot.chi(); + } + + function setChi(uint256 newChi) external { + chi = newChi; + } + + function drip() external returns (uint256) { + return pot.drip(); + } + + function rho() external returns (uint256) { + return pot.rho(); + } +} diff --git a/contracts/plugins/mocks/MockableCollateral.sol b/contracts/plugins/mocks/MockableCollateral.sol index f887cef68f..33cc5ab2fd 100644 --- a/contracts/plugins/mocks/MockableCollateral.sol +++ b/contracts/plugins/mocks/MockableCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../assets/aave/ATokenFiatCollateral.sol"; diff --git a/contracts/plugins/mocks/NontrivialPegCollateral.sol b/contracts/plugins/mocks/NontrivialPegCollateral.sol index 66ffd0bd77..f86816d06f 100644 --- a/contracts/plugins/mocks/NontrivialPegCollateral.sol +++ b/contracts/plugins/mocks/NontrivialPegCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../assets/FiatCollateral.sol"; diff --git a/contracts/plugins/mocks/RTokenCollateral.sol b/contracts/plugins/mocks/RTokenCollateral.sol index 7c0c356c5b..0e5cca0eca 100644 --- a/contracts/plugins/mocks/RTokenCollateral.sol +++ b/contracts/plugins/mocks/RTokenCollateral.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../../interfaces/IMain.sol"; diff --git a/contracts/plugins/mocks/RevenueTraderBackComp.sol b/contracts/plugins/mocks/RevenueTraderBackComp.sol new file mode 100644 index 0000000000..f7f8a0855b --- /dev/null +++ b/contracts/plugins/mocks/RevenueTraderBackComp.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../../p1/RevenueTrader.sol"; + +interface IRevenueTraderComp { + function manageToken(IERC20 sell) external; +} + +// RevenueTrader compatible with version 2 +contract RevenueTraderCompatibleV2 is RevenueTraderP1, IRevenueTraderComp { + function manageToken(IERC20 sell) external notTradingPausedOrFrozen { + // Mirror V3 logic (only the section relevant to tests) + this.manageToken(sell, TradeKind.DUTCH_AUCTION); + } + + function version() public pure virtual override(Versioned, IVersioned) returns (string memory) { + return "2.1.0"; + } +} + +// RevenueTrader compatible with version 1 +contract RevenueTraderCompatibleV1 is RevenueTraderCompatibleV2 { + function version() public pure override(RevenueTraderCompatibleV2) returns (string memory) { + return "1.0.0"; + } +} + +// RevenueTrader with invalid version +contract RevenueTraderInvalidVersion is RevenueTraderCompatibleV2 { + function version() public pure override(RevenueTraderCompatibleV2) returns (string memory) { + return "0.0.0"; + } +} diff --git a/contracts/plugins/mocks/RewardableERC20WrapperTest.sol b/contracts/plugins/mocks/RewardableERC20WrapperTest.sol new file mode 100644 index 0000000000..a0629bc11d --- /dev/null +++ b/contracts/plugins/mocks/RewardableERC20WrapperTest.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../assets/erc20/RewardableERC20Wrapper.sol"; +import "./ERC20MockRewarding.sol"; + +contract RewardableERC20WrapperTest is RewardableERC20Wrapper { + constructor( + ERC20 _asset, + string memory _name, + string memory _symbol, + ERC20 _rewardToken + ) RewardableERC20Wrapper(_asset, _name, _symbol, _rewardToken) {} + + function _claimAssetRewards() internal virtual override { + ERC20MockRewarding(address(underlying)).claim(); + } +} diff --git a/contracts/plugins/mocks/RewardableERC20VaultTest.sol b/contracts/plugins/mocks/RewardableERC4626VaultTest.sol similarity index 60% rename from contracts/plugins/mocks/RewardableERC20VaultTest.sol rename to contracts/plugins/mocks/RewardableERC4626VaultTest.sol index 394fe26311..24f7b690af 100644 --- a/contracts/plugins/mocks/RewardableERC20VaultTest.sol +++ b/contracts/plugins/mocks/RewardableERC4626VaultTest.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; -import "../assets/vaults/RewardableERC20Vault.sol"; +import "../assets/erc20/RewardableERC4626Vault.sol"; import "./ERC20MockRewarding.sol"; -contract RewardableERC20VaultTest is RewardableERC20Vault { +contract RewardableERC4626VaultTest is RewardableERC4626Vault { constructor( ERC20 _asset, string memory _name, string memory _symbol, ERC20 _rewardToken - ) RewardableERC20Vault(_asset, _name, _symbol, _rewardToken) {} + ) RewardableERC4626Vault(_asset, _name, _symbol, _rewardToken) {} function _claimAssetRewards() internal virtual override { ERC20MockRewarding(asset()).claim(); diff --git a/contracts/plugins/mocks/SelfdestructTransferMock.sol b/contracts/plugins/mocks/SelfdestructTransferMock.sol index 94953222db..b6820aabc3 100644 --- a/contracts/plugins/mocks/SelfdestructTransferMock.sol +++ b/contracts/plugins/mocks/SelfdestructTransferMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; contract SelfdestructTransfer { function destroyAndTransfer(address payable to) external payable { diff --git a/contracts/plugins/mocks/SfraxEthMock.sol b/contracts/plugins/mocks/SfraxEthMock.sol index b59d9ee0a2..bde9e9e45c 100644 --- a/contracts/plugins/mocks/SfraxEthMock.sol +++ b/contracts/plugins/mocks/SfraxEthMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./ERC20Mock.sol"; diff --git a/contracts/plugins/mocks/USDCMock.sol b/contracts/plugins/mocks/USDCMock.sol index 2ead8acb6c..b1096a2000 100644 --- a/contracts/plugins/mocks/USDCMock.sol +++ b/contracts/plugins/mocks/USDCMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./ERC20Mock.sol"; diff --git a/contracts/plugins/mocks/UnpricedPlugins.sol b/contracts/plugins/mocks/UnpricedPlugins.sol index 55dc369e10..f3d5808761 100644 --- a/contracts/plugins/mocks/UnpricedPlugins.sol +++ b/contracts/plugins/mocks/UnpricedPlugins.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/plugins/mocks/WBTCMock.sol b/contracts/plugins/mocks/WBTCMock.sol index b3781a589e..0d5bf09c1b 100644 --- a/contracts/plugins/mocks/WBTCMock.sol +++ b/contracts/plugins/mocks/WBTCMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./ERC20Mock.sol"; diff --git a/contracts/plugins/mocks/WETH.sol b/contracts/plugins/mocks/WETH.sol index d3d5a0467d..89e04c6ffe 100644 --- a/contracts/plugins/mocks/WETH.sol +++ b/contracts/plugins/mocks/WETH.sol @@ -13,7 +13,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -pragma solidity 0.8.17; +pragma solidity 0.8.19; /// https://github.com/gnosis/canonical-weth at commit 0dd1ea3e295eef916d0c6223ec63141137d22d67 @@ -42,9 +42,9 @@ contract WETH9 { function withdraw(uint256 wad) public { require(balanceOf[msg.sender] >= wad); balanceOf[msg.sender] -= wad; - (bool success, ) = address(msg.sender).call{value: wad}(""); + (bool success, ) = address(msg.sender).call{ value: wad }(""); if (!success) { - revert("transfer failed"); + revert("transfer failed"); } emit Withdrawal(msg.sender, wad); } diff --git a/contracts/plugins/mocks/ZeroDecimalMock.sol b/contracts/plugins/mocks/ZeroDecimalMock.sol index d366547623..70cb2a4c5a 100644 --- a/contracts/plugins/mocks/ZeroDecimalMock.sol +++ b/contracts/plugins/mocks/ZeroDecimalMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "./ERC20Mock.sol"; diff --git a/contracts/plugins/mocks/upgrades/AssetRegistryV2.sol b/contracts/plugins/mocks/upgrades/AssetRegistryV2.sol index 6de8e2900f..652498ae3f 100644 --- a/contracts/plugins/mocks/upgrades/AssetRegistryV2.sol +++ b/contracts/plugins/mocks/upgrades/AssetRegistryV2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../../../p1/AssetRegistry.sol"; diff --git a/contracts/plugins/mocks/upgrades/BackingManagerV2.sol b/contracts/plugins/mocks/upgrades/BackingManagerV2.sol index 7c14bb95a2..9724bf541f 100644 --- a/contracts/plugins/mocks/upgrades/BackingManagerV2.sol +++ b/contracts/plugins/mocks/upgrades/BackingManagerV2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../../../p1/BackingManager.sol"; diff --git a/contracts/plugins/mocks/upgrades/BasketHandlerV2.sol b/contracts/plugins/mocks/upgrades/BasketHandlerV2.sol index 7f63750dda..905eaab0df 100644 --- a/contracts/plugins/mocks/upgrades/BasketHandlerV2.sol +++ b/contracts/plugins/mocks/upgrades/BasketHandlerV2.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../../../p1/BasketHandler.sol"; +/// @custom:oz-upgrades-unsafe-allow external-library-linking contract BasketHandlerP1V2 is BasketHandlerP1 { uint256 public newValue; diff --git a/contracts/plugins/mocks/upgrades/BrokerV2.sol b/contracts/plugins/mocks/upgrades/BrokerV2.sol index 231c8787b4..fa6c043135 100644 --- a/contracts/plugins/mocks/upgrades/BrokerV2.sol +++ b/contracts/plugins/mocks/upgrades/BrokerV2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../../../p1/Broker.sol"; diff --git a/contracts/plugins/mocks/upgrades/DistributorV2.sol b/contracts/plugins/mocks/upgrades/DistributorV2.sol index 9c43361da8..e6bcdd7fb2 100644 --- a/contracts/plugins/mocks/upgrades/DistributorV2.sol +++ b/contracts/plugins/mocks/upgrades/DistributorV2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../../../p1/Distributor.sol"; diff --git a/contracts/plugins/mocks/upgrades/FurnaceV2.sol b/contracts/plugins/mocks/upgrades/FurnaceV2.sol index a596b16f0d..145d0b4a28 100644 --- a/contracts/plugins/mocks/upgrades/FurnaceV2.sol +++ b/contracts/plugins/mocks/upgrades/FurnaceV2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../../../p1/Furnace.sol"; diff --git a/contracts/plugins/mocks/upgrades/MainV2.sol b/contracts/plugins/mocks/upgrades/MainV2.sol index 3ed96ce4fb..afdcd1ff80 100644 --- a/contracts/plugins/mocks/upgrades/MainV2.sol +++ b/contracts/plugins/mocks/upgrades/MainV2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../../../p1/Main.sol"; diff --git a/contracts/plugins/mocks/upgrades/RTokenV2.sol b/contracts/plugins/mocks/upgrades/RTokenV2.sol index 01a6f3e464..0e6bae3e16 100644 --- a/contracts/plugins/mocks/upgrades/RTokenV2.sol +++ b/contracts/plugins/mocks/upgrades/RTokenV2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../../../p1/RToken.sol"; diff --git a/contracts/plugins/mocks/upgrades/RevenueTraderV2.sol b/contracts/plugins/mocks/upgrades/RevenueTraderV2.sol index 40bfd47860..f7c62d8280 100644 --- a/contracts/plugins/mocks/upgrades/RevenueTraderV2.sol +++ b/contracts/plugins/mocks/upgrades/RevenueTraderV2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../../../p1/RevenueTrader.sol"; diff --git a/contracts/plugins/mocks/upgrades/StRSRV2.sol b/contracts/plugins/mocks/upgrades/StRSRV2.sol index c774cf60d5..e8209f68e6 100644 --- a/contracts/plugins/mocks/upgrades/StRSRV2.sol +++ b/contracts/plugins/mocks/upgrades/StRSRV2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "../../../p1/StRSRVotes.sol"; diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 3260d05c38..6c8f90fc72 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../../libraries/Fixed.sol"; import "../../interfaces/IAsset.sol"; import "../../interfaces/ITrade.sol"; +import "../../mixins/NetworkConfigLib.sol"; uint192 constant FORTY_PERCENT = 4e17; // {1} 0.4 uint192 constant SIXTY_PERCENT = 6e17; // {1} 0.6 @@ -44,6 +45,9 @@ contract DutchTrade is ITrade { TradeKind public constant KIND = TradeKind.DUTCH_AUCTION; + // solhint-disable-next-line var-name-mixedcase + uint48 public immutable ONE_BLOCK; // {s} 1 block based on network + TradeStatus public status; // reentrancy protection ITrading public origin; // the address that initialized the contract @@ -54,7 +58,7 @@ contract DutchTrade is ITrade { uint192 public sellAmount; // {sellTok} // The auction runs from [startTime, endTime], inclusive - uint48 public startTime; // {s} when the dutch auction begins (12s after init()) + uint48 public startTime; // {s} when the dutch auction begins (one block after init()) uint48 public endTime; // {s} when the dutch auction ends if no bids are received // highPrice is always 1000x the middlePrice, so we don't need to track it explicitly @@ -90,6 +94,10 @@ contract DutchTrade is ITrade { return sellAmount.mul(price, CEIL).shiftl_toUint(int8(buy.decimals()), CEIL); } + constructor() { + ONE_BLOCK = NetworkConfigLib.blocktime(); + } + // === External === /// @param origin_ The Trader that originated the trade diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index 56bc5c5592..eb93b62a9c 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; diff --git a/contracts/vendor/ERC20PermitUpgradeable.sol b/contracts/vendor/ERC20PermitUpgradeable.sol index c1f471c1e3..b31022a77d 100644 --- a/contracts/vendor/ERC20PermitUpgradeable.sol +++ b/contracts/vendor/ERC20PermitUpgradeable.sol @@ -3,7 +3,7 @@ // The only modification that has been made is in the body of the `permit` function at line 83, /// where we failover to SignatureChecker in order to handle approvals for smart contracts. -pragma solidity 0.8.17; +pragma solidity 0.8.19; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol"; diff --git a/docs/dev-env.md b/docs/dev-env.md index 4fa9d6b910..1744e0e24b 100644 --- a/docs/dev-env.md +++ b/docs/dev-env.md @@ -19,6 +19,7 @@ These instructions assume you already have standard installations of `node`, `np ## Setup +### Basic Dependencies Set up yarn and hardhat, needed for compiling and running tests: ```bash @@ -39,15 +40,25 @@ yarn prepare cp .env.example .env ``` +### Tenderly +If you are going to use a Tenderly network, do the following: +1. Install the [tenderly cli](https://github.com/Tenderly/tenderly-cli) +2. Login +```bash +tenderly login --authentication-method access-key --access-key {your_access_key} --force +``` +3. Configure the `TENDERLY_RPC_URL` in your `.env` file + +### Slither You should also setup `slither`. The [Trail of Bits tools][tob-suite] require solc-select. Check [the installation instructions](https://github.com/crytic/solc-select) to ensure you have all prerequisites. Then: ```bash # Install solc-select and slither pip3 install solc-select slither-analyzer -# Install and use solc version 0.8.17 -solc-select install 0.8.17 -solc-select use 0.8.17 +# Install and use solc version 0.8.19 +solc-select install 0.8.19 +solc-select use 0.8.19 # Double-check that your slither version is at least 0.8.3! hash -r && slither --version diff --git a/docs/solidity-style.md b/docs/solidity-style.md index 4c8be4c18a..37309aeef8 100644 --- a/docs/solidity-style.md +++ b/docs/solidity-style.md @@ -48,7 +48,7 @@ We're using 192 bits instead of the full 256 bits because it makes typical multi Initial versions of this code were written using the custom type `Fix` everywhere, and `Fixed` contained the line `type Fix is int192`. We found later that: - We had essentially no need for negative `Fix` values, so spending a storage bit on sign, and juggling the possibility of negative values, cost extra gas and harmed the clarity of our code. -- While `solc 0.8.17` allows custom types without any issue, practically all of the other tools we want to use on our Solidity source -- `slither`, `prettier`, `solhint` -- would fail when encountering substantial code using a custom type. +- While `solc 0.8.19` allows custom types without any issue, practically all of the other tools we want to use on our Solidity source -- `slither`, `prettier`, `solhint` -- would fail when encountering substantial code using a custom type. Reintroducing this custom type should be mostly mechanicanizable, but now that P1 contains a handful of hotspot optimizations that do raw arithmetic internally to eliminate Fixlib calls, it won't be trivial to do so. Still, if and when those tools achieve adequate support for custom types, we will probably do this conversion ourselves, if only to ensure that conversions between the Fix and integer interpretations of uints are carefully type-checked. diff --git a/hardhat.config.ts b/hardhat.config.ts index 0989a4ffa7..08cf7e3f7e 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -8,6 +8,7 @@ import '@typechain/hardhat' import 'hardhat-contract-sizer' import 'hardhat-gas-reporter' import 'solidity-coverage' +import * as tenderly from '@tenderly/hardhat-tenderly' import { useEnv } from '#/utils/env' import { HardhatUserConfig } from 'hardhat/types' @@ -16,6 +17,8 @@ import forkBlockNumber from '#/test/integration/fork-block-numbers' // eslint-disable-next-line node/no-missing-require require('#/tasks') +tenderly.setup() + const MAINNET_RPC_URL = useEnv(['MAINNET_RPC_URL', 'ALCHEMY_MAINNET_RPC_URL']) const TENDERLY_RPC_URL = useEnv('TENDERLY_RPC_URL') const GOERLI_RPC_URL = useEnv('GOERLI_RPC_URL') @@ -80,13 +83,13 @@ const config: HardhatUserConfig = { solidity: { compilers: [ { - version: '0.8.17', + version: '0.8.19', settings, }, { version: '0.6.12', settings, - } + }, ], overrides: { 'contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol': { @@ -94,7 +97,7 @@ const config: HardhatUserConfig = { settings: { optimizer: { enabled: true, runs: 1 } }, // contract over-size }, 'contracts/facade/FacadeRead.sol': { - version: '0.8.17', + version: '0.8.19', settings: { optimizer: { enabled: true, runs: 1 } }, // contract over-size }, }, @@ -121,6 +124,12 @@ const config: HardhatUserConfig = { etherscan: { apiKey: useEnv('ETHERSCAN_API_KEY'), }, + tenderly: { + // see https://github.com/Tenderly/hardhat-tenderly/tree/master/packages/tenderly-hardhat for details + username: 'Reserveslug', // org name + project: 'testnet', // project name + privateVerification: false, // must be false to verify contracts on a testnet or devnet + }, } if (useEnv('ONLY_FAST')) { diff --git a/package.json b/package.json index 1294fb5a57..731d7178d2 100644 --- a/package.json +++ b/package.json @@ -15,16 +15,16 @@ "deploy:run": "hardhat run scripts/deploy.ts", "deploy:confirm": "hardhat run scripts/confirm.ts", "deploy:verify_etherscan": "hardhat run scripts/verify_etherscan.ts", - "test:extreme": "EXTREME=1 PROTO_IMPL=1 npx hardhat test test/{Furnace,RToken,ZTradingExtremes,ZZStRSR}.test.ts", + "test:extreme": "EXTREME=1 PROTO_IMPL=1 npx hardhat test test/{Furnace,RTokenExtremes,ZTradingExtremes,ZZStRSR}.test.ts", "test:extreme:integration": "FORK=1 EXTREME=1 PROTO_IMPL=1 npx hardhat test test/integration/**/*.test.ts", "test:unit": "yarn test:plugins && yarn test:p0 && yarn test:p1", "test:fast": "bash tools/fast-test.sh", "test:p0": "PROTO_IMPL=0 hardhat test test/*.test.ts", "test:p1": "PROTO_IMPL=1 hardhat test test/*.test.ts", - "test:plugins": "hardhat test test/{libraries,plugins}/*.test.ts --parallel", + "test:plugins": "hardhat test test/{libraries,plugins}/*.test.ts", "test:plugins:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/plugins/individual-collateral/**/*.test.ts", "test:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/integration/**/*.test.ts", - "test:scenario": "PROTO_IMPL=1 hardhat test test/scenario/*.test.ts --parallel", + "test:scenario": "PROTO_IMPL=1 hardhat test test/scenario/*.test.ts", "test:gas": "yarn test:gas:protocol && yarn test:gas:integration", "test:gas:protocol": "REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/{libraries,plugins,scenario}/*.test.ts test/*.test.ts", "test:gas:integration": "FORK=1 REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/integration/**/*.test.ts", @@ -59,6 +59,7 @@ "@openzeppelin/contracts": "~4.7.3", "@openzeppelin/contracts-upgradeable": "~4.7.3", "@openzeppelin/hardhat-upgrades": "^1.23.0", + "@tenderly/hardhat-tenderly": "^1.7.7", "@typechain/ethers-v5": "^7.2.0", "@typechain/hardhat": "^2.3.1", "@types/chai": "^4.3.0", diff --git a/scripts/addresses/5-RTKN-tmp-deployments.json b/scripts/addresses/5-RTKN-tmp-deployments.json new file mode 100644 index 0000000000..1e94d56fff --- /dev/null +++ b/scripts/addresses/5-RTKN-tmp-deployments.json @@ -0,0 +1,35 @@ +{ + "prerequisites": { + "RSR": "0xB58b5530332D2E9e15bfd1f2525E6fD84e830307", + "RSR_FEED": "0x905084691C2c7505b5FC63229621621b616bbbFe", + "GNOSIS_EASY_AUCTION": "0x1fbab40c338e2e7243da945820ba680c92ef8281" + }, + "tradingLib": "0xde1075de2e665d8B37Ae9941fa132574a0E6FcC3", + "cvxMiningLib": "0x073F98792ef4c00bB5f11B1F64f13cB25Cde0d8D", + "facadeRead": "0x62bf08e255706f3855821B2C25007a731D585E59", + "facadeAct": "0x7Bcb39F6d2A902aF8adFe384Ec6D84ABE66D2065", + "facadeWriteLib": "0xF6FB14EDD2c6FA038C3D19bC04238793369e6d6B", + "basketLib": "0x1c21E28F6cd7C4Be734cb60f9c6451484803924d", + "facadeWrite": "0x264Fb85EF99cb2026de73ef0f6f74AFd6335a006", + "deployer": "0x7bdAbdA24406A293f230690Ad5305173d266B7d6", + "rsrAsset": "0x5DeCeA2E6146058EB0FBBd770245284Ce756E068", + "implementations": { + "main": "0x15395aCCbF8c6b28671fe41624D599624709a2D6", + "trading": { + "gnosisTrade": "0xD971Fd59e90E836eCF2b8adE76374102025084A1", + "dutchTrade": "0x0EEa20c426EcE7D3dA5b73946bb1626697aA7c59" + }, + "components": { + "assetRegistry": "0xe981820A4Dd0d5168beb73F57E6dc827420D066C", + "backingManager": "0xbe7B053E820c5FBe70a0f075DA0C931aD8816e4F", + "basketHandler": "0xA7eCF508CdF5a88ae93b899DE4fcACcB43112Ce8", + "broker": "0xF4aB456F9FBA39b91F46c54185C218E4c32B014e", + "distributor": "0xb120c3429900DDF665b34882d7685e39BB01897B", + "furnace": "0xa570BF93FC51406809dBf52aB898913541C91C20", + "rsrTrader": "0x4c5FA27d1785d37B7Ac0942121f95370e61ff6a4", + "rTokenTrader": "0x4c5FA27d1785d37B7Ac0942121f95370e61ff6a4", + "rToken": "0xd0cb758e918ac6973a2959343ECa4F333d8d25B1", + "stRSR": "0xeC12e8412a7AE4598d754f4016D487c269719856" + } + } +} \ No newline at end of file diff --git a/scripts/addresses/5-tmp-assets-collateral.json b/scripts/addresses/5-tmp-assets-collateral.json new file mode 100644 index 0000000000..a60fdaf59f --- /dev/null +++ b/scripts/addresses/5-tmp-assets-collateral.json @@ -0,0 +1,56 @@ +{ + "assets": { + "stkAAVE": "0x7B025f359eB0490bB9bC52B755B8A45AC40676B9", + "COMP": "0x882BBbD5dd09DD77c15f89fE8B50fE48b7765835" + }, + "collateral": { + "DAI": "0x18Be705B263f79884c60Ffb9E8c8C266F755BC7E", + "USDC": "0x70AE8A09E298e146eC6A21248fA295026B705892", + "USDT": "0x8Ce9bE066cD946c32e9fC524D6202F361FDBc5Fd", + "USDP": "0x668137e1d86AECE8dd9F53a8c9F7299445C44911", + "TUSD": "0x1aAF257Cc251557404939F8D67eA1f07849BF02B", + "BUSD": "0x225Af68a32059395DAE3C73DDc4F73fdD4de6769", + "aDAI": "0x9a0915952Abe643f1207A54eb73Ec05dD4854238", + "aUSDC": "0x6C5576bf258FEa59A603D1fdAddEB50B22Eecdb1", + "aUSDT": "0x44b955165E01A6d3a3566CDd522596CC49e64366", + "aBUSD": "0x35341C7768cd8F5081802BFe5aa17f4f54695387", + "aUSDP": "0xA74B16e291965E8b98Aa0929988a679D74c81A7D", + "cDAI": "0xc771dA3be582d6a2CB133d3F4937dEBf190Ede0A", + "cUSDC": "0x7622D205b88e5fB7030E5D717ade7202592B31ab", + "cUSDT": "0xf37adF141BD754e9C9E645de88bB28B5e4a6Db96", + "cUSDP": "0xEB74EC1d4C1DAB412D5d6674F6833FD19d3118Ce", + "cWBTC": "0xa43a653E10fB098579f3E5ccAF32EB5722aB9c11", + "cETH": "0x97f25E9A946ecCD2864C95E26888aA6ADa66CA82", + "WBTC": "0xDB5b8cead52f77De0f6B5255f73F348AAf2CBb8D", + "WETH": "0xe7Cb991c70DEa9E7054aD16cDEdFe39134d95bC9", + "EURT": "0x9B8B71c644f97dcD53C8910133134924d1ece945", + "rETH": "0x4E6f2A55FcC83027c4AF86a93028730DCB2FD739", + "wstETH": "0xD5A780C6cB1c826d9831d7D52b66463df917F245" + }, + "erc20s": { + "stkAAVE": "0x3Db8b170DA19c45B63B959789f20f397F22767D4", + "COMP": "0x1b4449895037f25b102B28B45b8bD50c8C44Aca1", + "DAI": "0x4E35fAA0c4e6BA16534aa28DE0e40f7b702642D3", + "USDC": "0x9276fC221399d81a848E9d543a6FAA5e741E40A7", + "USDT": "0xAE64954A904da3fD9D71945980A849B8A9F755d7", + "USDP": "0x5d3E908ff0649F01d51d1513132736e96477C15d", + "TUSD": "0x56e938BC973fB23aCd7f043Fc11b61b1Ae3DDcC5", + "BUSD": "0x66FE0f43D9f201474A54a3857c77599DEBbD38F4", + "aDAI": "0x4e326FB29FBdDE0a7333A216c452E20999f9440B", + "aUSDC": "0xa29cc69De0F52eE1880616eB9a7Bd566a1bbc071", + "aUSDT": "0x510A90e2195c64d703E5E0959086cd1b7F9109ca", + "aBUSD": "0xD860Bb21773085a664c5C603B72518E1Cb8873Fc", + "aUSDP": "0x0774dF07205a5E9261771b19afa62B6e757f7eF8", + "cDAI": "0x00eD6351d8d3014609c10a68389c1dF474B66262", + "cUSDC": "0x4dd96ebadc4899aA815620eB38EdF2D7e28a1011", + "cUSDT": "0x30B17D09f5E79BB9F21Db2ec63115415AD4C9069", + "cUSDP": "0x75b3E91D55FDBC3A1FBAAE839B4f6B778287f50D", + "cWBTC": "0xCfD19C2791A444ea46016eBdB27143753C864D8A", + "cETH": "0xad6A7850Ac386Aa2139d1e3B6A960cD4952AcFb7", + "WBTC": "0x528FdEd7CC39209ed67B4edA11937A9ABe1f6249", + "WETH": "0xB5B58F0a853132EA8cB614cb17095dE87AF3E98b", + "EURT": "0xD6da5A7ADE2a906d9992612752A339E3485dB508", + "rETH": "0x178E141a0E3b34152f73Ff610437A7bf9B83267A", + "wstETH": "0x6320cD32aA674d2898A68ec82e869385Fc5f7E2f" + } +} \ No newline at end of file diff --git a/scripts/addresses/5-tmp-deployments.json b/scripts/addresses/5-tmp-deployments.json index eeafe862be..9e37b1f977 100644 --- a/scripts/addresses/5-tmp-deployments.json +++ b/scripts/addresses/5-tmp-deployments.json @@ -4,32 +4,32 @@ "RSR_FEED": "0x905084691C2c7505b5FC63229621621b616bbbFe", "GNOSIS_EASY_AUCTION": "0x1fbab40c338e2e7243da945820ba680c92ef8281" }, - "tradingLib": "0x3BECE5EC596331033726E5C6C188c313Ff4E3fE5", - "cvxMiningLib": "0x890FAa00C16EAD6AA76F18A1A7fe9C40838F9122", - "facadeRead": "0xe8461dB45A7430AA7aB40346E68821284980FdFD", - "facadeAct": "0x103BAFaB86f37C407f2C4813ee343020b66b062f", - "facadeWriteLib": "0x1bc463270b8d3D797F59Fe639eDF5ae130f35FF3", - "basketLib": "0x5A4f2FfC4aD066152B344Ceb2fc2275275b1a9C7", - "facadeWrite": "0xdEBe74dc2A415e00bE8B4b9d1e6e0007153D006a", - "deployer": "0x14c443d8BdbE9A65F3a23FA4e199d8741D5B38Fa", - "rsrAsset": "0xC87CDFFD680D57BF50De4C364BF4277B8A90098E", + "tradingLib": "0xde1075de2e665d8B37Ae9941fa132574a0E6FcC3", + "cvxMiningLib": "0x073F98792ef4c00bB5f11B1F64f13cB25Cde0d8D", + "facadeRead": "0x62bf08e255706f3855821B2C25007a731D585E59", + "facadeAct": "0x7Bcb39F6d2A902aF8adFe384Ec6D84ABE66D2065", + "facadeWriteLib": "0xF6FB14EDD2c6FA038C3D19bC04238793369e6d6B", + "basketLib": "0x1c21E28F6cd7C4Be734cb60f9c6451484803924d", + "facadeWrite": "0x264Fb85EF99cb2026de73ef0f6f74AFd6335a006", + "deployer": "0x7bdAbdA24406A293f230690Ad5305173d266B7d6", + "rsrAsset": "0x5DeCeA2E6146058EB0FBBd770245284Ce756E068", "implementations": { - "main": "0x63be601cDE1121C987B885cc319b44e0a9d707a2", + "main": "0x15395aCCbF8c6b28671fe41624D599624709a2D6", "trading": { - "gnosisTrade": "0x9FF9c353136e86EFe02ADD177E7c9769f8a5A77F", - "dutchTrade": "0xc9291eF2f81dBc9B412381aBe83b28954220565E" + "gnosisTrade": "0xD971Fd59e90E836eCF2b8adE76374102025084A1", + "dutchTrade": "0x0EEa20c426EcE7D3dA5b73946bb1626697aA7c59" }, "components": { - "assetRegistry": "0xCBE084C44e7A2223F76362Dcc4EbDacA5Fb1cbA7", - "backingManager": "0xAdfB9BCdA981136c83076a52Ef8fE4D8B2b520e7", - "basketHandler": "0xC9c37FC53682207844B058026024853A9C0b8c7B", - "broker": "0x8Af118a89c5023Bb2B03C70f70c8B396aE71963D", - "distributor": "0xD31eEc6679Dd18D5D42A92F32f01Ed98d4e91941", - "furnace": "0xFdb9F465C56933ab91f341C966DB517f975de5c1", - "rsrTrader": "0x18a26902126154437322fe01fBa04A36b093906f", - "rTokenTrader": "0x18a26902126154437322fe01fBa04A36b093906f", - "rToken": "0x0240E29Be6cBbB178543fF27EA4AaC8F8b870b44", - "stRSR": "0x27F672aAf061cb0b2640a4DFCCBd799cD1a7309A" + "assetRegistry": "0xe981820A4Dd0d5168beb73F57E6dc827420D066C", + "backingManager": "0xbe7B053E820c5FBe70a0f075DA0C931aD8816e4F", + "basketHandler": "0xA7eCF508CdF5a88ae93b899DE4fcACcB43112Ce8", + "broker": "0xF4aB456F9FBA39b91F46c54185C218E4c32B014e", + "distributor": "0xb120c3429900DDF665b34882d7685e39BB01897B", + "furnace": "0xa570BF93FC51406809dBf52aB898913541C91C20", + "rsrTrader": "0x4c5FA27d1785d37B7Ac0942121f95370e61ff6a4", + "rTokenTrader": "0x4c5FA27d1785d37B7Ac0942121f95370e61ff6a4", + "rToken": "0xd0cb758e918ac6973a2959343ECa4F333d8d25B1", + "stRSR": "0xeC12e8412a7AE4598d754f4016D487c269719856" } } -} \ No newline at end of file +} diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 978d6cd9bc..39739b1c5c 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -45,9 +45,15 @@ async function main() { 'phase2-assets/collaterals/deploy_flux_finance_collateral.ts', 'phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts', 'phase2-assets/collaterals/deploy_convex_stable_plugin.ts', - 'phase2-assets/collaterals/deploy_convex_volatile_plugin.ts', + // 'phase2-assets/collaterals/deploy_convex_volatile_plugin.ts', // tricrypto on hold 'phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts', 'phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts', + 'phase2-assets/collaterals/deploy_curve_stable_plugin.ts', + // 'phase2-assets/collaterals/deploy_curve_volatile_plugin.ts', // tricrypto on hold + 'phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts', + 'phase2-assets/collaterals/deploy_curve_stable_metapool_plugin.ts', + 'phase2-assets/collaterals/deploy_dsr_sdai.ts', + 'phase2-assets/collaterals/deploy_cbeth_collateral.ts', // =============================================== // These phase3 scripts will not deploy functional RTokens or Governance. They deploy bricked // versions that are used for verification only. Further deployment is left up to the Register. diff --git a/scripts/deployment/common.ts b/scripts/deployment/common.ts index 6e2a6b4f26..a67b131915 100644 --- a/scripts/deployment/common.ts +++ b/scripts/deployment/common.ts @@ -1,5 +1,5 @@ import fs from 'fs' -import { ITokens, IComponents, IImplementations, IPlugins } from '../../common/configuration' +import { ITokens, IComponents, IImplementations, IPools } from '../../common/configuration' // This file is intended to have minimal imports, so that it can be used from tasks if necessary @@ -25,8 +25,8 @@ export interface IDeployments { export interface IAssetCollDeployments { assets: ITokens - collateral: ITokens & IPlugins - erc20s: ITokens & IPlugins + collateral: ITokens & IPools + erc20s: ITokens & IPools } export interface IRTokenDeployments { diff --git a/scripts/deployment/phase1-common/2_deploy_implementations.ts b/scripts/deployment/phase1-common/2_deploy_implementations.ts index f023e1f786..eff5eac9f6 100644 --- a/scripts/deployment/phase1-common/2_deploy_implementations.ts +++ b/scripts/deployment/phase1-common/2_deploy_implementations.ts @@ -105,7 +105,7 @@ async function main() { if (!upgrade) { mainImplAddr = await upgrades.deployImplementation(MainImplFactory, { kind: 'uups', - }) + }) as string } else { mainImplAddr = await upgrades.prepareUpgrade( prevDeployments.implementations.main, @@ -113,7 +113,7 @@ async function main() { { kind: 'uups', } - ) + ) as string } mainImpl = await ethers.getContractAt('MainP1', mainImplAddr) @@ -158,7 +158,7 @@ async function main() { if (!upgrade) { assetRegImplAddr = await upgrades.deployImplementation(AssetRegImplFactory, { kind: 'uups', - }) + }) as string } else { assetRegImplAddr = await upgrades.prepareUpgrade( prevDeployments.implementations.components.assetRegistry, @@ -166,7 +166,7 @@ async function main() { { kind: 'uups', } - ) + ) as string } assetRegImpl = await ethers.getContractAt('AssetRegistryP1', assetRegImplAddr) @@ -191,7 +191,7 @@ async function main() { backingMgrImplAddr = await upgrades.deployImplementation(BackingMgrImplFactory, { kind: 'uups', unsafeAllow: ['external-library-linking', 'delegatecall'], - }) + }) as string } else { backingMgrImplAddr = await upgrades.prepareUpgrade( prevDeployments.implementations.components.backingManager, @@ -200,7 +200,7 @@ async function main() { kind: 'uups', unsafeAllow: ['external-library-linking', 'delegatecall'], } - ) + ) as string } backingMgrImpl = ( @@ -225,7 +225,7 @@ async function main() { bskHndlrImplAddr = await upgrades.deployImplementation(BskHandlerImplFactory, { kind: 'uups', unsafeAllow: ['external-library-linking'], - }) + }) as string } else { bskHndlrImplAddr = await upgrades.prepareUpgrade( prevDeployments.implementations.components.basketHandler, @@ -234,7 +234,7 @@ async function main() { kind: 'uups', unsafeAllow: ['external-library-linking'], } - ) + ) as string } bskHndlrImpl = await ethers.getContractAt('BasketHandlerP1', bskHndlrImplAddr) @@ -254,7 +254,7 @@ async function main() { if (!upgrade) { brokerImplAddr = await upgrades.deployImplementation(BrokerImplFactory, { kind: 'uups', - }) + }) as string } else { brokerImplAddr = await upgrades.prepareUpgrade( prevDeployments.implementations.components.broker, @@ -262,7 +262,7 @@ async function main() { { kind: 'uups', } - ) + ) as string } brokerImpl = await ethers.getContractAt('BrokerP1', brokerImplAddr) @@ -282,7 +282,7 @@ async function main() { if (!upgrade) { distribImplAddr = await upgrades.deployImplementation(DistribImplFactory, { kind: 'uups', - }) + }) as string } else { distribImplAddr = await upgrades.prepareUpgrade( prevDeployments.implementations.components.distributor, @@ -290,7 +290,7 @@ async function main() { { kind: 'uups', } - ) + ) as string } distribImpl = await ethers.getContractAt('DistributorP1', distribImplAddr) @@ -310,7 +310,7 @@ async function main() { if (!upgrade) { furnaceImplAddr = await upgrades.deployImplementation(FurnaceImplFactory, { kind: 'uups', - }) + }) as string } else { furnaceImplAddr = await upgrades.prepareUpgrade( prevDeployments.implementations.components.furnace, @@ -318,7 +318,7 @@ async function main() { { kind: 'uups', } - ) + ) as string } furnaceImpl = await ethers.getContractAt('FurnaceP1', furnaceImplAddr) @@ -341,7 +341,7 @@ async function main() { rsrTraderImplAddr = await upgrades.deployImplementation(RevTraderImplFactory, { kind: 'uups', unsafeAllow: ['delegatecall'], - }) + }) as string rTokenTraderImplAddr = rsrTraderImplAddr // Both equal in initial deployment } else { // RSR Trader @@ -352,7 +352,7 @@ async function main() { kind: 'uups', unsafeAllow: ['delegatecall'], } - ) + ) as string // If Traders have different implementations, upgrade separately if ( @@ -367,7 +367,7 @@ async function main() { kind: 'uups', unsafeAllow: ['delegatecall'], } - ) + ) as string } else { // Both use the same implementation rTokenTraderImplAddr = rsrTraderImplAddr @@ -402,7 +402,7 @@ async function main() { if (!upgrade) { rTokenImplAddr = await upgrades.deployImplementation(RTokenImplFactory, { kind: 'uups', - }) + }) as string } else { rTokenImplAddr = await upgrades.prepareUpgrade( prevDeployments.implementations.components.rToken, @@ -410,7 +410,7 @@ async function main() { { kind: 'uups', } - ) + ) as string } rTokenImpl = await ethers.getContractAt('RTokenP1', rTokenImplAddr) @@ -431,7 +431,7 @@ async function main() { if (!upgrade) { stRSRImplAddr = await upgrades.deployImplementation(StRSRImplFactory, { kind: 'uups', - }) + }) as string } else { stRSRImplAddr = await upgrades.prepareUpgrade( prevDeployments.implementations.components.stRSR, @@ -439,7 +439,7 @@ async function main() { { kind: 'uups', } - ) + ) as string } stRSRImpl = await ethers.getContractAt('StRSRP1Votes', stRSRImplAddr) diff --git a/scripts/deployment/phase2-assets/2_deploy_collateral.ts b/scripts/deployment/phase2-assets/2_deploy_collateral.ts index c121fbf2a6..475dd1d5c2 100644 --- a/scripts/deployment/phase2-assets/2_deploy_collateral.ts +++ b/scripts/deployment/phase2-assets/2_deploy_collateral.ts @@ -390,7 +390,7 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) /******** Deploy CToken Fiat Collateral - cDAI **************************/ - const CTokenFactory = await ethers.getContractFactory('CTokenVault') + const CTokenFactory = await ethers.getContractFactory('CTokenWrapper') const cDai = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cDAI!) const cDaiVault = await CTokenFactory.deploy( diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_cbeth_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_cbeth_collateral.ts new file mode 100644 index 0000000000..a3a562d7f8 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_cbeth_collateral.ts @@ -0,0 +1,84 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn, fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { priceTimeout, oracleTimeout, combinedError } from '../../utils' +import { CBEthCollateral__factory } from '../../../../typechain' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Coinbase ETH Collateral - CBETH **************************/ + + const CBETHCollateralFactory: CBEthCollateral__factory = (await hre.ethers.getContractFactory( + 'CBEthCollateral' + )) as CBEthCollateral__factory + + const collateral = await CBETHCollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, + oracleError: combinedError(fp('0.005'), fp('0.02')).toString(), // 0.5% & 2%, + erc20: networkConfig[chainId].tokens.cbETH!, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, + targetName: hre.ethers.utils.formatBytes32String('ETH'), + defaultThreshold: fp('0.15').toString(), // 15% + delayUntilDefault: bn('86400').toString(), // 24h + }, + fp('1e-4').toString(), // revenueHiding = 0.01% + networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed + oracleTimeout(chainId, '86400').toString() // refPerTokChainlinkTimeout + ) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log(`Deployed Coinbase cbETH to ${hre.network.name} (${chainId}): ${collateral.address}`) + + assetCollDeployments.collateral.cbETH = collateral.address + assetCollDeployments.erc20s.cbETH = networkConfig[chainId].tokens.cbETH + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts index 36eef8c219..e66b89dcd0 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts @@ -13,7 +13,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { CvxStableRTokenMetapoolCollateral } from '../../../../typechain' +import { CurveStableRTokenMetapoolCollateral } from '../../../../typechain' import { revenueHiding, oracleTimeout } from '../../utils' import { CurvePoolType, @@ -31,7 +31,7 @@ import { USDC_ORACLE_ERROR, USDC_ORACLE_TIMEOUT, USDC_USD_FEED, -} from '../../../../test/plugins/individual-collateral/convex/constants' +} from '../../../../test/plugins/individual-collateral/curve/constants' // This file specifically deploys Convex RToken Metapool Plugin for eUSD/fraxBP @@ -41,7 +41,7 @@ async function main() { const chainId = await getChainId(hre) - console.log(`Deploying CvxStableRTokenMetapoolCollateral to network ${hre.network.name} (${chainId}) + console.log(`Deploying CurveStableRTokenMetapoolCollateral to network ${hre.network.name} (${chainId}) with burner account: ${deployer.address}`) if (!networkConfig[chainId]) { @@ -64,8 +64,8 @@ async function main() { /******** Deploy Convex Stable Metapool for eUSD/fraxBP **************************/ const CvxMining = await ethers.getContractAt('CvxMining', deployments.cvxMiningLib) - const CvxStableCollateralFactory = await hre.ethers.getContractFactory( - 'CvxStableRTokenMetapoolCollateral' + const CurveStableCollateralFactory = await hre.ethers.getContractFactory( + 'CurveStableRTokenMetapoolCollateral' ) const ConvexStakingWrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper', { libraries: { CvxMining: CvxMining.address }, @@ -79,35 +79,35 @@ async function main() { `Deployed wrapper for Convex eUSD/FRAX Metapool on ${hre.network.name} (${chainId}): ${wPool.address} ` ) - const collateral = await CvxStableCollateralFactory.connect( - deployer - ).deploy( - { - erc20: wPool.address, - targetName: ethers.utils.formatBytes32String('USD'), - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero - oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero - maxTradeVolume: MAX_TRADE_VOL, - defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold - delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, - }, - revenueHiding.toString(), - { - nTokens: 2, - curvePool: FRAX_BP, - poolType: CurvePoolType.Plain, - feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, FRAX_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], - ], - oracleErrors: [[FRAX_ORACLE_ERROR], [USDC_ORACLE_ERROR]], - lpToken: FRAX_BP_TOKEN, - }, - eUSD_FRAX_BP, - DEFAULT_THRESHOLD // 2% + const collateral = ( + await CurveStableCollateralFactory.connect(deployer).deploy( + { + erc20: wPool.address, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + oracleTimeout: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold + delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + nTokens: 2, + curvePool: FRAX_BP, + poolType: CurvePoolType.Plain, + feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], + oracleTimeouts: [ + [oracleTimeout(chainId, FRAX_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + ], + oracleErrors: [[FRAX_ORACLE_ERROR], [USDC_ORACLE_ERROR]], + lpToken: FRAX_BP_TOKEN, + }, + eUSD_FRAX_BP, + DEFAULT_THRESHOLD // 2% + ) ) await collateral.deployed() await (await collateral.refresh()).wait() diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts index 7eaf5dc89a..72d0f7debe 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts @@ -12,7 +12,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { CvxStableMetapoolCollateral } from '../../../../typechain' +import { CurveStableMetapoolCollateral } from '../../../../typechain' import { revenueHiding, oracleTimeout } from '../../utils' import { CurvePoolType, @@ -37,7 +37,7 @@ import { USDT_ORACLE_ERROR, USDT_ORACLE_TIMEOUT, USDT_USD_FEED, -} from '../../../../test/plugins/individual-collateral/convex/constants' +} from '../../../../test/plugins/individual-collateral/curve/constants' // This file specifically deploys Convex Metapool Plugin for MIM/3Pool @@ -47,7 +47,7 @@ async function main() { const chainId = await getChainId(hre) - console.log(`Deploying CvxStableMetapoolCollateral to network ${hre.network.name} (${chainId}) + console.log(`Deploying CurveStableMetapoolCollateral to network ${hre.network.name} (${chainId}) with burner account: ${deployer.address}`) if (!networkConfig[chainId]) { @@ -70,8 +70,8 @@ async function main() { /******** Deploy Convex Stable Metapool for MIM/3Pool **************************/ const CvxMining = await ethers.getContractAt('CvxMining', deployments.cvxMiningLib) - const CvxStableCollateralFactory = await hre.ethers.getContractFactory( - 'CvxStableMetapoolCollateral' + const CurveStableCollateralFactory = await hre.ethers.getContractFactory( + 'CurveStableMetapoolCollateral' ) const ConvexStakingWrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper', { libraries: { CvxMining: CvxMining.address }, @@ -85,7 +85,7 @@ async function main() { `Deployed wrapper for Convex Stable MIM/3Pool on ${hre.network.name} (${chainId}): ${wPool.address} ` ) - const collateral = await CvxStableCollateralFactory.connect( + const collateral = await CurveStableCollateralFactory.connect( deployer ).deploy( { diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts index 106003a22e..cc3b31856a 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts @@ -13,7 +13,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { CvxStableCollateral } from '../../../../typechain' +import { CurveStableCollateral } from '../../../../typechain' import { revenueHiding, oracleTimeout } from '../../utils' import { CurvePoolType, @@ -33,7 +33,7 @@ import { USDT_ORACLE_ERROR, USDT_ORACLE_TIMEOUT, USDT_USD_FEED, -} from '../../../../test/plugins/individual-collateral/convex/constants' +} from '../../../../test/plugins/individual-collateral/curve/constants' // This file specifically deploys Convex Stable Plugin for 3pool @@ -66,7 +66,7 @@ async function main() { /******** Deploy Convex Stable Pool for 3pool **************************/ const CvxMining = await ethers.getContractAt('CvxMining', deployments.cvxMiningLib) - const CvxStableCollateralFactory = await hre.ethers.getContractFactory('CvxStableCollateral') + const CurveStableCollateralFactory = await hre.ethers.getContractFactory('CurveStableCollateral') const ConvexStakingWrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper', { libraries: { CvxMining: CvxMining.address }, }) @@ -79,7 +79,9 @@ async function main() { `Deployed wrapper for Convex Stable 3Pool on ${hre.network.name} (${chainId}): ${w3Pool.address} ` ) - const collateral = await CvxStableCollateralFactory.connect(deployer).deploy( + const collateral = await CurveStableCollateralFactory.connect( + deployer + ).deploy( { erc20: w3Pool.address, targetName: ethers.utils.formatBytes32String('USD'), diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts index f0c7b73328..71ff38d29e 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts @@ -13,7 +13,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { CvxVolatileCollateral } from '../../../../typechain' +import { CurveVolatileCollateral } from '../../../../typechain' import { revenueHiding, oracleTimeout } from '../../utils' import { CurvePoolType, @@ -36,7 +36,7 @@ import { USDT_ORACLE_ERROR, USDT_ORACLE_TIMEOUT, USDT_USD_FEED, -} from '../../../../test/plugins/individual-collateral/convex/constants' +} from '../../../../test/plugins/individual-collateral/curve/constants' // This file specifically deploys Convex Volatile Plugin for Tricrypto @@ -69,7 +69,9 @@ async function main() { /******** Deploy Convex Volatile Pool for 3pool **************************/ const CvxMining = await ethers.getContractAt('CvxMining', deployments.cvxMiningLib) - const CvxVolatileCollateralFactory = await hre.ethers.getContractFactory('CvxVolatileCollateral') + const CurveVolatileCollateralFactory = await hre.ethers.getContractFactory( + 'CurveVolatileCollateral' + ) const ConvexStakingWrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper', { libraries: { CvxMining: CvxMining.address }, }) @@ -82,7 +84,7 @@ async function main() { `Deployed wrapper for Convex Volatile TriCrypto on ${hre.network.name} (${chainId}): ${w3Pool.address} ` ) - const collateral = await CvxVolatileCollateralFactory.connect( + const collateral = await CurveVolatileCollateralFactory.connect( deployer ).deploy( { diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts new file mode 100644 index 0000000000..a52f03d7c4 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts @@ -0,0 +1,135 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { CurveStableRTokenMetapoolCollateral } from '../../../../typechain' +import { revenueHiding, oracleTimeout } from '../../utils' +import { + CRV, + CurvePoolType, + DEFAULT_THRESHOLD, + eUSD_FRAX_BP, + eUSD_GAUGE, + FRAX_BP, + FRAX_BP_TOKEN, + FRAX_ORACLE_ERROR, + FRAX_ORACLE_TIMEOUT, + FRAX_USD_FEED, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + RTOKEN_DELAY_UNTIL_DEFAULT, + USDC_ORACLE_ERROR, + USDC_ORACLE_TIMEOUT, + USDC_USD_FEED, +} from '../../../../test/plugins/individual-collateral/curve/constants' + +// Deploy Curve RToken Metapool Plugin for eUSD/fraxBP + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying CurveStableRTokenMetapoolCollateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Curve Stable Metapool for eUSD/fraxBP **************************/ + + const CurveStableCollateralFactory = await hre.ethers.getContractFactory( + 'CurveStableRTokenMetapoolCollateral' + ) + const CurveStakingWrapperFactory = await ethers.getContractFactory('CurveGaugeWrapper') + + const wPool = await CurveStakingWrapperFactory.deploy( + eUSD_FRAX_BP, + 'Wrapped Curve eUSD+FRAX/USDC', + 'weUSDFRAXBP', + CRV, + eUSD_GAUGE + ) + await wPool.deployed() + + console.log( + `Deployed wrapper for Curve eUSD/FRAX Metapool on ${hre.network.name} (${chainId}): ${wPool.address} ` + ) + + const collateral = ( + await CurveStableCollateralFactory.connect(deployer).deploy( + { + erc20: wPool.address, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + oracleTimeout: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold + delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + nTokens: 2, + curvePool: FRAX_BP, + poolType: CurvePoolType.Plain, + feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], + oracleTimeouts: [ + [oracleTimeout(chainId, FRAX_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + ], + oracleErrors: [[FRAX_ORACLE_ERROR], [USDC_ORACLE_ERROR]], + lpToken: FRAX_BP_TOKEN, + }, + eUSD_FRAX_BP, + DEFAULT_THRESHOLD // 2% + ) + ) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed Curve RToken Metapool Collateral to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.crveUSDFRAXBP = collateral.address + assetCollDeployments.erc20s.crveUSDFRAXBP = wPool.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_metapool_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_metapool_plugin.ts new file mode 100644 index 0000000000..f97882d214 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_metapool_plugin.ts @@ -0,0 +1,141 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { CurveStableMetapoolCollateral } from '../../../../typechain' +import { revenueHiding, oracleTimeout } from '../../utils' +import { + CRV, + CurvePoolType, + DELAY_UNTIL_DEFAULT, + MIM_DEFAULT_THRESHOLD, + MIM_THREE_POOL, + MIM_THREE_POOL_GAUGE, + MIM_USD_FEED, + MIM_ORACLE_ERROR, + MIM_ORACLE_TIMEOUT, + MAX_TRADE_VOL, + THREE_POOL_DEFAULT_THRESHOLD, + THREE_POOL, + THREE_POOL_TOKEN, + PRICE_TIMEOUT, + USDC_ORACLE_ERROR, + USDC_ORACLE_TIMEOUT, + USDC_USD_FEED, + DAI_ORACLE_ERROR, + DAI_ORACLE_TIMEOUT, + DAI_USD_FEED, + USDT_ORACLE_ERROR, + USDT_ORACLE_TIMEOUT, + USDT_USD_FEED, +} from '../../../../test/plugins/individual-collateral/curve/constants' + +// Deploy Curve Metapool Plugin for MIM/3Pool + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying CurveStableMetapoolCollateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Curve Stable Metapool for MIM/3Pool **************************/ + + const CurveStableCollateralFactory = await hre.ethers.getContractFactory( + 'CurveStableMetapoolCollateral' + ) + const CurveStakingWrapperFactory = await ethers.getContractFactory('CurveGaugeWrapper') + const wPool = await CurveStakingWrapperFactory.deploy( + MIM_THREE_POOL, + 'Wrapped Curve MIM+3Pool', + 'wMIM3CRV', + CRV, + MIM_THREE_POOL_GAUGE + ) + await wPool.deployed() + + console.log( + `Deployed wrapper for Curve Stable MIM/3Pool on ${hre.network.name} (${chainId}): ${wPool.address} ` + ) + + const collateral = await CurveStableCollateralFactory.connect( + deployer + ).deploy( + { + erc20: wPool.address, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: MIM_USD_FEED, + oracleError: MIM_ORACLE_ERROR, + oracleTimeout: MIM_ORACLE_TIMEOUT, + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: THREE_POOL_DEFAULT_THRESHOLD, // 1.25% + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + nTokens: 3, + curvePool: THREE_POOL, + poolType: CurvePoolType.Plain, + feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], + oracleTimeouts: [ + [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + ], + oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], + lpToken: THREE_POOL_TOKEN, + }, + MIM_THREE_POOL, + MIM_DEFAULT_THRESHOLD + ) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed Curve Metapool Collateral to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.crvMIM3Pool = collateral.address + assetCollDeployments.erc20s.crvMIM3Pool = wPool.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts new file mode 100644 index 0000000000..8aba2f5aaf --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts @@ -0,0 +1,134 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { CurveStableCollateral } from '../../../../typechain' +import { revenueHiding, oracleTimeout } from '../../utils' +import { + CRV, + CurvePoolType, + DAI_ORACLE_ERROR, + DAI_ORACLE_TIMEOUT, + DAI_USD_FEED, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + THREE_POOL, + THREE_POOL_TOKEN, + THREE_POOL_GAUGE, + USDC_ORACLE_ERROR, + USDC_ORACLE_TIMEOUT, + USDC_USD_FEED, + USDT_ORACLE_ERROR, + USDT_ORACLE_TIMEOUT, + USDT_USD_FEED, +} from '../../../../test/plugins/individual-collateral/curve/constants' + +// Deploy Curve Stable Plugin for 3pool + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Curve Stable Pool for 3pool **************************/ + + const CurveStableCollateralFactory = await hre.ethers.getContractFactory('CurveStableCollateral') + const CurveGaugeWrapperFactory = await ethers.getContractFactory('CurveGaugeWrapper') + + const w3Pool = await CurveGaugeWrapperFactory.deploy( + THREE_POOL_TOKEN, + 'Wrapped Staked Curve.fi DAI/USDC/USDT', + 'ws3Crv', + CRV, + THREE_POOL_GAUGE + ) + await w3Pool.deployed() + + console.log( + `Deployed wrapper for Curve Stable 3Pool on ${hre.network.name} (${chainId}): ${w3Pool.address} ` + ) + + const collateral = await CurveStableCollateralFactory.connect( + deployer + ).deploy( + { + erc20: w3Pool.address, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + oracleTimeout: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + nTokens: 3, + curvePool: THREE_POOL, + poolType: CurvePoolType.Plain, + feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], + oracleTimeouts: [ + [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + ], + oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], + lpToken: THREE_POOL_TOKEN, + } + ) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed Curve Stable Collateral to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.cvx3Pool = collateral.address + assetCollDeployments.erc20s.crv3Pool = w3Pool.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts new file mode 100644 index 0000000000..938c9e1683 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts @@ -0,0 +1,142 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { CurveVolatileCollateral } from '../../../../typechain' +import { revenueHiding, oracleTimeout } from '../../utils' +import { + CRV, + CurvePoolType, + BTC_USD_ORACLE_ERROR, + BTC_ORACLE_TIMEOUT, + BTC_USD_FEED, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + TRI_CRYPTO, + TRI_CRYPTO_TOKEN, + TRI_CRYPTO_GAUGE, + WBTC_BTC_ORACLE_ERROR, + WETH_ORACLE_TIMEOUT, + WBTC_BTC_FEED, + WBTC_ORACLE_TIMEOUT, + WETH_USD_FEED, + WETH_ORACLE_ERROR, + USDT_ORACLE_ERROR, + USDT_ORACLE_TIMEOUT, + USDT_USD_FEED, +} from '../../../../test/plugins/individual-collateral/curve/constants' + +// Deploy Curve Volatile Plugin for Tricrypto + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Curve Volatile Pool for 3pool **************************/ + + const CurveVolatileCollateralFactory = await hre.ethers.getContractFactory( + 'CurveVolatileCollateral' + ) + const CurveStakingWrapperFactory = await ethers.getContractFactory('CurveGaugeWrapper') + const w3Pool = await CurveStakingWrapperFactory.deploy( + TRI_CRYPTO_TOKEN, + 'Wrapped Curve.fi USD-BTC-ETH', + 'wcrv3crypto', + CRV, + TRI_CRYPTO_GAUGE + ) + await w3Pool.deployed() + + console.log( + `Deployed wrapper for Curve Volatile TriCrypto on ${hre.network.name} (${chainId}): ${w3Pool.address} ` + ) + + const collateral = await CurveVolatileCollateralFactory.connect( + deployer + ).deploy( + { + erc20: w3Pool.address, + targetName: ethers.utils.formatBytes32String('TRICRYPTO'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + oracleTimeout: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + nTokens: 3, + curvePool: TRI_CRYPTO, + poolType: CurvePoolType.Plain, + feeds: [[USDT_USD_FEED], [WBTC_BTC_FEED, BTC_USD_FEED], [WETH_USD_FEED]], + oracleTimeouts: [ + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, WBTC_ORACLE_TIMEOUT), oracleTimeout(chainId, BTC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, WETH_ORACLE_TIMEOUT)], + ], + oracleErrors: [ + [USDT_ORACLE_ERROR], + [WBTC_BTC_ORACLE_ERROR, BTC_USD_ORACLE_ERROR], + [WETH_ORACLE_ERROR], + ], + lpToken: TRI_CRYPTO_TOKEN, + } + ) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed Curve Volatile Collateral to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.crvTriCrypto = collateral.address + assetCollDeployments.erc20s.crvTriCrypto = w3Pool.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_dsr_sdai.ts b/scripts/deployment/phase2-assets/collaterals/deploy_dsr_sdai.ts new file mode 100644 index 0000000000..c3fa6bbe40 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_dsr_sdai.ts @@ -0,0 +1,87 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn, fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { POT } from '../../../../test/plugins/individual-collateral/dsr/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { priceTimeout, oracleTimeout } from '../../utils' +import { SDaiCollateral } from '../../../../typechain' +import { ContractFactory } from 'ethers' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy SDAI Collateral - sDAI **************************/ + + const SDaiCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( + 'SDaiCollateral' + ) + + const collateral = await SDaiCollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI, + oracleError: fp('0.0025').toString(), // 0.25% + erc20: networkConfig[chainId].tokens.sDAI, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.0125').toString(), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + }, + fp('1e-6').toString(), // revenueHiding = 0.0001% + POT + ) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed DSR-wrapping sDAI to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.sDAI = collateral.address + assetCollDeployments.erc20s.sDAI = networkConfig[chainId].tokens.sDAI + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts index f186889189..0aeb08cefa 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts @@ -41,7 +41,7 @@ async function main() { const deployedCollateral: string[] = [] /******** Deploy FToken Fiat Collateral - fUSDC **************************/ - const FTokenFactory = await ethers.getContractFactory('CTokenVault') + const FTokenFactory = await ethers.getContractFactory('CTokenWrapper') const fUsdc = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.fUSDC!) const fUsdcVault = await FTokenFactory.deploy( diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts index 4f85427eb4..7381cf92e8 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts @@ -40,6 +40,30 @@ async function main() { const deployedCollateral: string[] = [] + const deployedOracle: string[] = [] + + /******** Deploy Mock Oracle (if needed) **************************/ + let stethUsdOracleAddress: string = networkConfig[chainId].chainlinkFeeds.stETHUSD! + let stethEthOracleAddress: string = networkConfig[chainId].chainlinkFeeds.stETHETH! + if (chainId == 5) { + const MockOracleFactory = await hre.ethers.getContractFactory('MockV3Aggregator') + const mockStethUsdOracle = await MockOracleFactory.connect(deployer).deploy(8, bn(2000e8)) + await mockStethUsdOracle.deployed() + console.log( + `Deployed MockV3Aggregator on ${hre.network.name} (${chainId}): ${mockStethUsdOracle.address} ` + ) + deployedOracle.push(mockStethUsdOracle.address) + stethUsdOracleAddress = mockStethUsdOracle.address + + const mockStethEthOracle = await MockOracleFactory.connect(deployer).deploy(8, bn(1e8)) + await mockStethEthOracle.deployed() + console.log( + `Deployed MockV3Aggregator on ${hre.network.name} (${chainId}): ${mockStethEthOracle.address} ` + ) + deployedOracle.push(mockStethEthOracle.address) + stethEthOracleAddress = mockStethEthOracle.address + } + /******** Deploy Lido Staked ETH Collateral - wstETH **************************/ const LidoStakedEthCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( @@ -51,7 +75,7 @@ async function main() { ).deploy( { priceTimeout: priceTimeout.toString(), - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.stETHUSD, + chainlinkFeed: stethUsdOracleAddress, oracleError: fp('0.01').toString(), // 1%: only for stETHUSD feed erc20: networkConfig[chainId].tokens.wstETH, maxTradeVolume: fp('1e6').toString(), // $1m, @@ -61,7 +85,7 @@ async function main() { delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4').toString(), // revenueHiding = 0.01% - networkConfig[chainId].chainlinkFeeds.stETHETH, // targetPerRefChainlinkFeed + stethEthOracleAddress, // targetPerRefChainlinkFeed oracleTimeout(chainId, '86400').toString() // targetPerRefChainlinkTimeout ) await collateral.deployed() diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts index cc8a2e4cd7..386985881f 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts @@ -13,7 +13,7 @@ import { fileExists, } from '../../common' import { priceTimeout, oracleTimeout, combinedError } from '../../utils' -import { RethCollateral } from '../../../../typechain' +import { MockV3Aggregator, RethCollateral } from '../../../../typechain' import { ContractFactory } from 'ethers' async function main() { @@ -40,6 +40,21 @@ async function main() { const deployedCollateral: string[] = [] + const deployedOracle: string[] = [] + + /******** Deploy Mock Oracle (if needed) **************************/ + let rethOracleAddress: string = networkConfig[chainId].chainlinkFeeds.rETH! + if (chainId == 5) { + const MockOracleFactory = await hre.ethers.getContractFactory('MockV3Aggregator') + const mockOracle = await MockOracleFactory.connect(deployer).deploy(8, fp(2000)) + await mockOracle.deployed() + console.log( + `Deployed MockV3Aggregator on ${hre.network.name} (${chainId}): ${mockOracle.address} ` + ) + deployedOracle.push(mockOracle.address) + rethOracleAddress = mockOracle.address + } + /******** Deploy Rocket Pool ETH Collateral - rETH **************************/ const RethCollateralFactory: ContractFactory = await hre.ethers.getContractFactory( @@ -59,7 +74,7 @@ async function main() { delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4').toString(), // revenueHiding = 0.01% - networkConfig[chainId].chainlinkFeeds.rETH, // refPerTokChainlinkFeed + rethOracleAddress, // refPerTokChainlinkFeed oracleTimeout(chainId, '86400').toString() // refPerTokChainlinkTimeout ) await collateral.deployed() diff --git a/scripts/deployment/utils.ts b/scripts/deployment/utils.ts index e5450ae9b4..8b39f6a2e2 100644 --- a/scripts/deployment/utils.ts +++ b/scripts/deployment/utils.ts @@ -1,4 +1,4 @@ -import hre from 'hardhat' +import hre, { tenderly } from 'hardhat' import * as readline from 'readline' import axios from 'axios' import { exec } from 'child_process' @@ -106,42 +106,50 @@ export async function verifyContract( console.time(`Verifying ${contract}`) console.log(`Verifying ${contract}`) - // Sleep 0.5s to not overwhelm API - await new Promise((r) => setTimeout(r, 500)) - - const ETHERSCAN_API_KEY = useEnv('ETHERSCAN_API_KEY') - - // Check to see if already verified - const url = `${getEtherscanBaseURL( - chainId, - true - )}/api/?module=contract&action=getsourcecode&address=${address}&apikey=${ETHERSCAN_API_KEY}` - const { data, status } = await axios.get(url, { headers: { Accept: 'application/json' } }) - if (status != 200 || data['status'] != '1') { - throw new Error("Can't communicate with Etherscan API") - } - - // Only run verification script if not verified - if (data['result'][0]['SourceCode']?.length > 0) { - console.log('Already verified. Continuing') + if (hre.network.name == 'tenderly') { + await tenderly.verify({ + name: contract, + address: address!, + libraries + }); } else { - console.log('Running new verification') - try { - await hre.run('verify:verify', { - address, - constructorArguments, - contract, - libraries, - }) - } catch (e) { - console.log( - `IMPORTANT: failed to verify ${contract}. - ${getEtherscanBaseURL(chainId)}/address/${address}#code`, - e - ) + // Sleep 0.5s to not overwhelm API + await new Promise((r) => setTimeout(r, 500)) + + const ETHERSCAN_API_KEY = useEnv('ETHERSCAN_API_KEY') + + // Check to see if already verified + const url = `${getEtherscanBaseURL( + chainId, + true + )}/api/?module=contract&action=getsourcecode&address=${address}&apikey=${ETHERSCAN_API_KEY}` + const { data, status } = await axios.get(url, { headers: { Accept: 'application/json' } }) + if (status != 200 || data['status'] != '1') { + throw new Error("Can't communicate with Etherscan API") + } + + // Only run verification script if not verified + if (data['result'][0]['SourceCode']?.length > 0) { + console.log('Already verified. Continuing') + } else { + console.log('Running new verification') + try { + await hre.run('verify:verify', { + address, + constructorArguments, + contract, + libraries, + }) + } catch (e) { + console.log( + `IMPORTANT: failed to verify ${contract}. + ${getEtherscanBaseURL(chainId)}/address/${address}#code`, + e + ) + } } + console.timeEnd(`Verifying ${contract}`) } - console.timeEnd(`Verifying ${contract}`) } export const getEtherscanBaseURL = (chainId: number, api = false) => { @@ -197,6 +205,7 @@ export const prompt = async (query: string): Promise => { rl.question(query, (ans) => { rl.close() resolve(ans) + return ans }) ) } else { diff --git a/scripts/exhaustive-tests/run-1.sh b/scripts/exhaustive-tests/run-1.sh index a8e0fa737d..fbad597e14 100644 --- a/scripts/exhaustive-tests/run-1.sh +++ b/scripts/exhaustive-tests/run-1.sh @@ -1,3 +1,3 @@ echo "Running RToken & Furnace exhaustive tests for commit hash: " git rev-parse HEAD; -NODE_OPTIONS=--max-old-space-size=30000 EXTREME=1 SLOW=1 PROTO_IMPL=1 npx hardhat test test/RToken.test.ts test/Furnace.test.ts; +NODE_OPTIONS=--max-old-space-size=30000 EXTREME=1 SLOW=1 PROTO_IMPL=1 npx hardhat test test/RTokenExtremes.test.ts test/Furnace.test.ts; diff --git a/scripts/verification/1_verify_implementations.ts b/scripts/verification/1_verify_implementations.ts index 1e5094c1b1..8d2f853f91 100644 --- a/scripts/verification/1_verify_implementations.ts +++ b/scripts/verification/1_verify_implementations.ts @@ -64,11 +64,17 @@ async function main() { name: 'backingManager', desc: 'BackingManager', contract: 'contracts/p1/BackingManager.sol:BackingManagerP1', + libraries: { + 'RecollateralizationLibP1': deployments.tradingLib, + } }, { name: 'basketHandler', desc: 'BasketHandler', contract: 'contracts/p1/BasketHandler.sol:BasketHandlerP1', + libraries: { + 'BasketLibP1': deployments.basketLib, + } }, { name: 'broker', diff --git a/scripts/verification/5_verify_facadeWrite.ts b/scripts/verification/5_verify_facadeWrite.ts index b27bcb903c..e6d569ac44 100644 --- a/scripts/verification/5_verify_facadeWrite.ts +++ b/scripts/verification/5_verify_facadeWrite.ts @@ -33,7 +33,8 @@ async function main() { chainId, deployments.facadeWrite, [deployments.deployer], - 'contracts/facade/FacadeWrite.sol:FacadeWrite' + 'contracts/facade/FacadeWrite.sol:FacadeWrite', + { FacadeWriteLib: deployments.facadeWriteLib} ) } diff --git a/scripts/verification/6_verify_collateral.ts b/scripts/verification/6_verify_collateral.ts index 31be198561..347c5fd2a3 100644 --- a/scripts/verification/6_verify_collateral.ts +++ b/scripts/verification/6_verify_collateral.ts @@ -15,7 +15,7 @@ import { revenueHiding, verifyContract, } from '../deployment/utils' -import { ATokenMock, ATokenFiatCollateral } from '../../typechain' +import { ATokenMock, ATokenFiatCollateral, ICToken, CTokenFiatCollateral } from '../../typechain' let deployments: IAssetCollDeployments @@ -70,7 +70,7 @@ async function main() { 'Static ' + (await aToken.name()), 's' + (await aToken.symbol()), ], - 'contracts/plugins/assets/aave/StaticATokenLM.sol:StaticATokenLM' + 'contracts/plugins/assets/aave/vendor/StaticATokenLM.sol:StaticATokenLM' ) /******** Verify ATokenFiatCollateral - aDAI **************************/ await verifyContract( @@ -92,6 +92,25 @@ async function main() { ], 'contracts/plugins/assets/aave/ATokenFiatCollateral.sol:ATokenFiatCollateral' ) + /******** Verify CTokenWrapper - cDAI **************************/ + const cToken: ICToken = ( + await ethers.getContractAt('ICToken', networkConfig[chainId].tokens.cDAI as string) + ) + const cTokenCollateral: CTokenFiatCollateral = ( + await ethers.getContractAt('CTokenFiatCollateral', deployments.collateral.cDAI as string) + ) + + await verifyContract( + chainId, + await cTokenCollateral.erc20(), + [ + cToken.address, + `${await cToken.name()} Vault`, + `${await cToken.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER!, + ], + 'contracts/plugins/assets/compoundv2/CTokenWrapper.sol:CTokenWrapper' + ) /********************** Verify CTokenFiatCollateral - cDAI ****************************************/ await verifyContract( chainId, @@ -101,15 +120,14 @@ async function main() { priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI, oracleError: fp('0.0025').toString(), // 0.25% - erc20: networkConfig[chainId].tokens.cDAI, + erc20: deployments.erc20s.cDAI, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '3600').toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h }, - revenueHiding.toString(), - networkConfig[chainId].COMPTROLLER, + revenueHiding.toString() ], 'contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol:CTokenFiatCollateral' ) @@ -127,7 +145,7 @@ async function main() { priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.WBTC, oracleError: combinedBTCWBTCError.toString(), - erc20: networkConfig[chainId].tokens.cWBTC, + erc20: deployments.erc20s.cWBTC, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('BTC'), @@ -136,8 +154,7 @@ async function main() { }, networkConfig[chainId].chainlinkFeeds.BTC, oracleTimeout(chainId, '3600').toString(), - revenueHiding.toString(), - networkConfig[chainId].COMPTROLLER, + revenueHiding.toString() ], 'contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol:CTokenNonFiatCollateral' ) @@ -150,7 +167,7 @@ async function main() { priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH, oracleError: fp('0.005').toString(), // 0.5% - erc20: networkConfig[chainId].tokens.cETH, + erc20: deployments.erc20s.cETH, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '3600').toString(), targetName: hre.ethers.utils.formatBytes32String('ETH'), @@ -158,8 +175,7 @@ async function main() { delayUntilDefault: '0', }, revenueHiding.toString(), - '18', - networkConfig[chainId].COMPTROLLER, + '18' ], 'contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol:CTokenSelfReferentialCollateral' ) diff --git a/scripts/verification/collateral-plugins/verify_cbeth.ts b/scripts/verification/collateral-plugins/verify_cbeth.ts new file mode 100644 index 0000000000..abe6322e57 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_cbeth.ts @@ -0,0 +1,55 @@ +import hre from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { fp, bn } from '../../../common/numbers' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { priceTimeout, oracleTimeout, verifyContract, combinedError } from '../../deployment/utils' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + /******** Verify Coinbase staked ETH - CBETH **************************/ + await verifyContract( + chainId, + deployments.collateral.cbETH, + [ + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH, + oracleError: combinedError(fp('0.005'), fp('0.02')).toString(), // 0.5% & 2%, + erc20: networkConfig[chainId].tokens.cbETH!, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, + targetName: hre.ethers.utils.formatBytes32String('ETH'), + defaultThreshold: fp('0.15').toString(), // 15% + delayUntilDefault: bn('86400').toString(), // 24h + }, + fp('1e-4'), // revenueHiding = 0.01% + networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed + oracleTimeout(chainId, '86400').toString(), // refPerTokChainlinkTimeout + ], + 'contracts/plugins/assets/cbeth/CBEthCollateral.sol:CBEthCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_convex_stable_plugin.ts b/scripts/verification/collateral-plugins/verify_convex_stable.ts similarity index 92% rename from scripts/verification/collateral-plugins/verify_convex_stable_plugin.ts rename to scripts/verification/collateral-plugins/verify_convex_stable.ts index fbbac7cca3..4a1a4b7bba 100644 --- a/scripts/verification/collateral-plugins/verify_convex_stable_plugin.ts +++ b/scripts/verification/collateral-plugins/verify_convex_stable.ts @@ -29,7 +29,7 @@ import { USDT_ORACLE_ERROR, USDT_ORACLE_TIMEOUT, USDT_USD_FEED, -} from '../../../test/plugins/individual-collateral/convex/constants' +} from '../../../test/plugins/individual-collateral/curve/constants' let deployments: IAssetCollDeployments @@ -51,7 +51,7 @@ async function main() { deployments = getDeploymentFile(assetCollDeploymentFilename) const w3PoolCollateral = await ethers.getContractAt( - 'CvxStableCollateral', + 'CurveStableCollateral', deployments.collateral.cvx3Pool as string ) @@ -61,7 +61,8 @@ async function main() { chainId, await w3PoolCollateral.erc20(), [], - 'contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol:ConvexStakingWrapper' + 'contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol:ConvexStakingWrapper', + {CvxMining: coreDeployments.cvxMiningLib} ) /******** Verify CvxMining Lib **************************/ @@ -104,7 +105,7 @@ async function main() { lpToken: THREE_POOL_TOKEN, }, ], - 'contracts/plugins/assets/convex/CvxStableCollateral.sol:CvxStableCollateral' + 'contracts/plugins/assets/convex/CurveStableCollateral.sol:CurveStableCollateral' ) } diff --git a/scripts/verification/collateral-plugins/verify_convex_stable_metapool_plugin.ts b/scripts/verification/collateral-plugins/verify_convex_stable_metapool.ts similarity index 93% rename from scripts/verification/collateral-plugins/verify_convex_stable_metapool_plugin.ts rename to scripts/verification/collateral-plugins/verify_convex_stable_metapool.ts index d0e3b09117..417ff521c6 100644 --- a/scripts/verification/collateral-plugins/verify_convex_stable_metapool_plugin.ts +++ b/scripts/verification/collateral-plugins/verify_convex_stable_metapool.ts @@ -30,7 +30,7 @@ import { USDT_ORACLE_ERROR, USDT_ORACLE_TIMEOUT, USDT_USD_FEED, -} from '../../../test/plugins/individual-collateral/convex/constants' +} from '../../../test/plugins/individual-collateral/curve/constants' let deployments: IAssetCollDeployments @@ -49,7 +49,7 @@ async function main() { deployments = getDeploymentFile(assetCollDeploymentFilename) const wPoolCollateral = await ethers.getContractAt( - 'CvxStableMetapoolCollateral', + 'CurveStableMetapoolCollateral', deployments.collateral.cvxMIM3Pool as string ) @@ -86,7 +86,7 @@ async function main() { MIM_THREE_POOL, MIM_DEFAULT_THRESHOLD, ], - 'contracts/plugins/assets/convex/CvxStableMetapoolCollateral.sol:CvxStableMetapoolCollateral' + 'contracts/plugins/assets/convex/CurveStableMetapoolCollateral.sol:CurveStableMetapoolCollateral' ) } diff --git a/scripts/verification/collateral-plugins/verify_eusd_fraxbp_collateral.ts b/scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts similarity index 92% rename from scripts/verification/collateral-plugins/verify_eusd_fraxbp_collateral.ts rename to scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts index 0f4b68a0d4..00d7ae5b37 100644 --- a/scripts/verification/collateral-plugins/verify_eusd_fraxbp_collateral.ts +++ b/scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts @@ -25,7 +25,7 @@ import { USDC_ORACLE_ERROR, USDC_ORACLE_TIMEOUT, USDC_USD_FEED, -} from '../../../test/plugins/individual-collateral/convex/constants' +} from '../../../test/plugins/individual-collateral/curve/constants' let deployments: IAssetCollDeployments @@ -44,7 +44,7 @@ async function main() { deployments = getDeploymentFile(assetCollDeploymentFilename) const eUSDPlugin = await ethers.getContractAt( - 'CvxStableRTokenMetapoolCollateral', + 'CurveStableRTokenMetapoolCollateral', deployments.collateral.cvxeUSDFRAXBP as string ) @@ -80,7 +80,7 @@ async function main() { eUSD_FRAX_BP, DEFAULT_THRESHOLD, // 2% ], - 'contracts/plugins/assets/convex/CvxStableRTokenMetapoolCollateral.sol:CvxStableRTokenMetapoolCollateral' + 'contracts/plugins/assets/convex/CurveStableRTokenMetapoolCollateral.sol:CurveStableRTokenMetapoolCollateral' ) } diff --git a/scripts/verification/collateral-plugins/verify_convex_volatile_plugin.ts b/scripts/verification/collateral-plugins/verify_convex_volatile.ts similarity index 93% rename from scripts/verification/collateral-plugins/verify_convex_volatile_plugin.ts rename to scripts/verification/collateral-plugins/verify_convex_volatile.ts index d1255deed3..8c48da0e56 100644 --- a/scripts/verification/collateral-plugins/verify_convex_volatile_plugin.ts +++ b/scripts/verification/collateral-plugins/verify_convex_volatile.ts @@ -30,7 +30,7 @@ import { USDT_ORACLE_ERROR, USDT_ORACLE_TIMEOUT, USDT_USD_FEED, -} from '../../../test/plugins/individual-collateral/convex/constants' +} from '../../../test/plugins/individual-collateral/curve/constants' let deployments: IAssetCollDeployments @@ -49,7 +49,7 @@ async function main() { deployments = getDeploymentFile(assetCollDeploymentFilename) const wTriCrypto = await ethers.getContractAt( - 'CvxVolatileCollateral', + 'CurveVolatileCollateral', deployments.collateral.cvxTriCrypto as string ) @@ -88,7 +88,7 @@ async function main() { lpToken: TRI_CRYPTO_TOKEN, }, ], - 'contracts/plugins/assets/convex/CvxVolatileCollateral.sol:CvxVolatileCollateral' + 'contracts/plugins/assets/convex/CurveVolatileCollateral.sol:CurveVolatileCollateral' ) } diff --git a/scripts/verification/collateral-plugins/verify_curve_stable.ts b/scripts/verification/collateral-plugins/verify_curve_stable.ts new file mode 100644 index 0000000000..e43317350d --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_curve_stable.ts @@ -0,0 +1,102 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { bn } from '../../../common/numbers' +import { ONE_ADDRESS } from '../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { revenueHiding, oracleTimeout } from '../../deployment/utils' +import { + CRV, + CurvePoolType, + DAI_ORACLE_ERROR, + DAI_ORACLE_TIMEOUT, + DAI_USD_FEED, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + THREE_POOL, + THREE_POOL_TOKEN, + THREE_POOL_GAUGE, + USDC_ORACLE_ERROR, + USDC_ORACLE_TIMEOUT, + USDC_USD_FEED, + USDT_ORACLE_ERROR, + USDT_ORACLE_TIMEOUT, + USDT_USD_FEED, +} from '../../../test/plugins/individual-collateral/curve/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + const w3PoolCollateral = await ethers.getContractAt( + 'CurveStableCollateral', + deployments.collateral.crv3Pool as string + ) + + /******** Verify CurveGaugeWrapper **************************/ + + await verifyContract( + chainId, + await w3PoolCollateral.erc20(), + [THREE_POOL_TOKEN, 'Wrapped Staked Curve.fi DAI/USDC/USDT', 'ws3Crv', CRV, THREE_POOL_GAUGE], + 'contracts/plugins/assets/curve/crv/CurveGaugeWrapper.sol:CurveGaugeWrapper' + ) + + /******** Verify 3Pool plugin **************************/ + await verifyContract( + chainId, + deployments.collateral.crv3Pool, + [ + { + erc20: await w3PoolCollateral.erc20(), + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + oracleTimeout: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + nTokens: 3, + curvePool: THREE_POOL, + poolType: CurvePoolType.Plain, + feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], + oracleTimeouts: [ + [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + ], + oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], + lpToken: THREE_POOL_TOKEN, + }, + ], + 'contracts/plugins/assets/curve/CurveStableCollateral.sol:CurveStableCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_curve_stable_metapool.ts b/scripts/verification/collateral-plugins/verify_curve_stable_metapool.ts new file mode 100644 index 0000000000..60be29f1e0 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_curve_stable_metapool.ts @@ -0,0 +1,96 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { revenueHiding, oracleTimeout } from '../../deployment/utils' +import { + CurvePoolType, + DAI_ORACLE_ERROR, + DAI_ORACLE_TIMEOUT, + DAI_USD_FEED, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + MIM_DEFAULT_THRESHOLD, + MIM_USD_FEED, + MIM_ORACLE_ERROR, + MIM_ORACLE_TIMEOUT, + MIM_THREE_POOL, + PRICE_TIMEOUT, + THREE_POOL_DEFAULT_THRESHOLD, + THREE_POOL, + THREE_POOL_TOKEN, + USDC_ORACLE_ERROR, + USDC_ORACLE_TIMEOUT, + USDC_USD_FEED, + USDT_ORACLE_ERROR, + USDT_ORACLE_TIMEOUT, + USDT_USD_FEED, +} from '../../../test/plugins/individual-collateral/curve/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + const wPoolCollateral = await ethers.getContractAt( + 'CurveStableMetapoolCollateral', + deployments.collateral.crvMIM3Pool as string + ) + + /******** Verify Curve MIM/3Pool plugin **************************/ + await verifyContract( + chainId, + deployments.collateral.crvMIM3Pool, + [ + { + erc20: await wPoolCollateral.erc20(), + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: MIM_USD_FEED, + oracleError: MIM_ORACLE_ERROR, + oracleTimeout: MIM_ORACLE_TIMEOUT, + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: THREE_POOL_DEFAULT_THRESHOLD, // 1.25% + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + nTokens: 3, + curvePool: THREE_POOL, + poolType: CurvePoolType.Plain, + feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], + oracleTimeouts: [ + [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + ], + oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], + lpToken: THREE_POOL_TOKEN, + }, + MIM_THREE_POOL, + MIM_DEFAULT_THRESHOLD, + ], + 'contracts/plugins/assets/convex/CurveStableMetapoolCollateral.sol:CurveStableMetapoolCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_curve_stable_rtoken_metapool.ts b/scripts/verification/collateral-plugins/verify_curve_stable_rtoken_metapool.ts new file mode 100644 index 0000000000..45101d93fd --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_curve_stable_rtoken_metapool.ts @@ -0,0 +1,90 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { bn } from '../../../common/numbers' +import { ONE_ADDRESS } from '../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { revenueHiding, oracleTimeout } from '../../deployment/utils' +import { + CurvePoolType, + DEFAULT_THRESHOLD, + eUSD_FRAX_BP, + FRAX_BP, + FRAX_BP_TOKEN, + FRAX_ORACLE_ERROR, + FRAX_ORACLE_TIMEOUT, + FRAX_USD_FEED, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + RTOKEN_DELAY_UNTIL_DEFAULT, + USDC_ORACLE_ERROR, + USDC_ORACLE_TIMEOUT, + USDC_USD_FEED, +} from '../../../test/plugins/individual-collateral/curve/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + const eUSDPlugin = await ethers.getContractAt( + 'CurveStableRTokenMetapoolCollateral', + deployments.collateral.crveUSDFRAXBP as string + ) + + /******** Verify eUSD/fraxBP plugin **************************/ + await verifyContract( + chainId, + deployments.collateral.crveUSDFRAXBP, + [ + { + erc20: await eUSDPlugin.erc20(), + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + oracleTimeout: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold + delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + nTokens: 2, + curvePool: FRAX_BP, + poolType: CurvePoolType.Plain, + feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], + oracleTimeouts: [ + [oracleTimeout(chainId, FRAX_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], + ], + oracleErrors: [[FRAX_ORACLE_ERROR], [USDC_ORACLE_ERROR]], + lpToken: FRAX_BP_TOKEN, + }, + eUSD_FRAX_BP, + DEFAULT_THRESHOLD, // 2% + ], + 'contracts/plugins/assets/convex/CurveStableRTokenMetapoolCollateral.sol:CurveStableRTokenMetapoolCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_curve_volatile.ts b/scripts/verification/collateral-plugins/verify_curve_volatile.ts new file mode 100644 index 0000000000..2f5c53b2c1 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_curve_volatile.ts @@ -0,0 +1,98 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { bn } from '../../../common/numbers' +import { ONE_ADDRESS } from '../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { verifyContract } from '../../deployment/utils' +import { revenueHiding, oracleTimeout } from '../../deployment/utils' +import { + CurvePoolType, + BTC_USD_ORACLE_ERROR, + BTC_ORACLE_TIMEOUT, + BTC_USD_FEED, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + PRICE_TIMEOUT, + TRI_CRYPTO, + TRI_CRYPTO_TOKEN, + WBTC_BTC_ORACLE_ERROR, + WETH_ORACLE_TIMEOUT, + WBTC_BTC_FEED, + WBTC_ORACLE_TIMEOUT, + WETH_USD_FEED, + WETH_ORACLE_ERROR, + USDT_ORACLE_ERROR, + USDT_ORACLE_TIMEOUT, + USDT_USD_FEED, +} from '../../../test/plugins/individual-collateral/curve/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + const wTriCrypto = await ethers.getContractAt( + 'CurveVolatileCollateral', + deployments.collateral.crvTriCrypto as string + ) + + /******** Verify TriCrypto plugin **************************/ + await verifyContract( + chainId, + deployments.collateral.crvTriCrypto, + [ + { + erc20: await wTriCrypto.erc20(), + targetName: ethers.utils.formatBytes32String('TRICRYPTO'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + oracleTimeout: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + revenueHiding.toString(), + { + nTokens: 3, + curvePool: TRI_CRYPTO, + poolType: CurvePoolType.Plain, + feeds: [[USDT_USD_FEED], [WBTC_BTC_FEED, BTC_USD_FEED], [WETH_USD_FEED]], + oracleTimeouts: [ + [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, WBTC_ORACLE_TIMEOUT), oracleTimeout(chainId, BTC_ORACLE_TIMEOUT)], + [oracleTimeout(chainId, WETH_ORACLE_TIMEOUT)], + ], + oracleErrors: [ + [USDT_ORACLE_ERROR], + [WBTC_BTC_ORACLE_ERROR, BTC_USD_ORACLE_ERROR], + [WETH_ORACLE_ERROR], + ], + lpToken: TRI_CRYPTO_TOKEN, + }, + ], + 'contracts/plugins/assets/convex/CurveVolatileCollateral.sol:CurveVolatileCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_cusdcv3_collateral.ts b/scripts/verification/collateral-plugins/verify_cusdcv3.ts similarity index 100% rename from scripts/verification/collateral-plugins/verify_cusdcv3_collateral.ts rename to scripts/verification/collateral-plugins/verify_cusdcv3.ts diff --git a/scripts/verification/collateral-plugins/verify_reth_collateral.ts b/scripts/verification/collateral-plugins/verify_reth.ts similarity index 100% rename from scripts/verification/collateral-plugins/verify_reth_collateral.ts rename to scripts/verification/collateral-plugins/verify_reth.ts diff --git a/scripts/verification/collateral-plugins/verify_sdai.ts b/scripts/verification/collateral-plugins/verify_sdai.ts new file mode 100644 index 0000000000..e5d9290c39 --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_sdai.ts @@ -0,0 +1,55 @@ +import hre from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { fp, bn } from '../../../common/numbers' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { priceTimeout, oracleTimeout, verifyContract } from '../../deployment/utils' +import { POT } from '../../../test/plugins/individual-collateral/dsr/constants' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + /******** Verify sDAI **************************/ + await verifyContract( + chainId, + deployments.collateral.sDAI, + [ + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI, + oracleError: fp('0.0025').toString(), // 0.25% + erc20: networkConfig[chainId].tokens.sDAI, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.0125').toString(), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + }, + fp('1e-6').toString(), // revenueHiding = 0.0001% + POT, + ], + 'contracts/plugins/assets/dsr/SDaiCollateral.sol:SDaiCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_wsteth_collateral.ts b/scripts/verification/collateral-plugins/verify_wsteth.ts similarity index 100% rename from scripts/verification/collateral-plugins/verify_wsteth_collateral.ts rename to scripts/verification/collateral-plugins/verify_wsteth.ts diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index 3d10e2a98b..857d72645a 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -45,13 +45,18 @@ async function main() { '6_verify_collateral.ts', '7_verify_rToken.ts', '8_verify_governance.ts', - 'collateral-plugins/verify_convex_stable_plugin.ts', - 'collateral-plugins/verify_convex_stable_metapool_plugin.ts', - 'collateral-plugins/verify_convex_volatile_plugin.ts', - 'collateral-plugins/verify_eusd_fraxbp_collateral.ts', - 'collateral-plugins/verify_cusdcv3_collateral.ts', - 'collateral-plugins/verify_reth_collateral.ts', - 'collateral-plugins/verify_wsteth_collateral.ts', + 'collateral-plugins/verify_convex_stable.ts', + 'collateral-plugins/verify_convex_stable_metapool.ts', + 'collateral-plugins/verify_convex_volatile.ts', + 'collateral-plugins/verify_convex_stable_rtoken_metapool.ts', + 'collateral-plugins/verify_curve_stable.ts', + 'collateral-plugins/verify_curve_stable_metapool.ts', + 'collateral-plugins/verify_curve_volatile.ts', + 'collateral-plugins/verify_curve_stable_rtoken_metapool.ts', + 'collateral-plugins/verify_cusdcv3.ts', + 'collateral-plugins/verify_reth.ts', + 'collateral-plugins/verify_wsteth.ts', + 'collateral-plugins/verify_cbeth.ts', ] for (const script of scripts) { diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 1f788f3fb1..a0eccff666 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -10,9 +10,11 @@ import { bn, fp, divCeil, toBNDecimals } from '../common/numbers' import { DutchTrade, ERC20Mock, + FiatCollateral, GnosisMock, GnosisMockReentrant, GnosisTrade, + IAssetRegistry, TestIBackingManager, TestIBroker, TestIMain, @@ -27,12 +29,19 @@ import { defaultFixture, Implementation, IMPLEMENTATION, + ORACLE_ERROR, + ORACLE_TIMEOUT, + PRICE_TIMEOUT, } from './fixtures' import snapshotGasCost from './utils/snapshotGasCost' +import { setOraclePrice } from './utils/oracles' import { advanceTime, advanceToTimestamp, getLatestBlockTimestamp } from './utils/time' import { ITradeRequest } from './utils/trades' import { useEnv } from '#/utils/env' +const DEFAULT_THRESHOLD = fp('0.01') // 1% +const DELAY_UNTIL_DEFAULT = bn('86400') // 24h + const describeGas = IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip @@ -59,6 +68,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // Main contracts let main: TestIMain + let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager let rsrTrader: TestIRevenueTrader let rTokenTrader: TestIRevenueTrader @@ -73,6 +83,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { basket, config, main, + assetRegistry, backingManager, broker, gnosis, @@ -972,6 +983,43 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ).to.be.revertedWith('Invalid trade state') }) + it('Should not initialize DutchTrade with bad prices', async () => { + // Fund trade + await token0.connect(owner).mint(trade.address, amount) + + // Set bad price for sell token + await setOraclePrice(collateral0.address, bn(0)) + await collateral0.refresh() + + // Attempt to initialize with bad sell price + await expect( + trade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ).to.be.revertedWith('bad sell pricing') + + // Fix sell price, set bad buy price + await setOraclePrice(collateral0.address, bn(1e8)) + await collateral0.refresh() + + await setOraclePrice(collateral1.address, bn(0)) + await collateral1.refresh() + + await expect( + trade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ).to.be.revertedWith('bad buy pricing') + }) + it('Should apply full maxTradeSlippage to lowPrice at minTradeVolume', async () => { amount = config.minTradeVolume @@ -998,6 +1046,48 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await trade.lowPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) }) + it('Should apply full maxTradeSlippage with low maxTradeVolume', async () => { + // Set low maxTradeVolume for collateral + const FiatCollateralFactory = await ethers.getContractFactory('FiatCollateral') + const newCollateral0: FiatCollateral = await FiatCollateralFactory.deploy({ + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await collateral0.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: token0.address, + maxTradeVolume: bn(500), + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }) + + // Refresh and swap collateral + await newCollateral0.refresh() + await assetRegistry.connect(owner).swapRegistered(newCollateral0.address) + + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + backingManager.address, + newCollateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ).to.not.be.reverted + + // Check trade values + const [sellLow, sellHigh] = await newCollateral0.price() + const [buyLow, buyHigh] = await collateral1.price() + expect(await trade.middlePrice()).to.equal(divCeil(sellHigh.mul(fp('1')), buyLow)) + const withoutSlippage = sellLow.mul(fp('1')).div(buyHigh) + const withSlippage = withoutSlippage.sub( + withoutSlippage.mul(config.maxTradeSlippage).div(fp('1')) + ) + expect(await trade.lowPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) + }) + it('Should not allow to initialize an unfunded trade', async () => { // Attempt to initialize without funding await expect( @@ -1011,6 +1101,40 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ).to.be.revertedWith('unfunded trade') }) + it('Should not allow to settle until auction is over', async () => { + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + backingManager.address, + collateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength + ) + ).to.not.be.reverted + + // Should not be able to settle + expect(await trade.canSettle()).to.equal(false) + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.be.revertedWith('auction not over') + }) + + // Advance time till trade can be settled + await advanceTime(config.dutchAuctionLength.add(100).toString()) + + // Settle trade + expect(await trade.canSettle()).to.equal(true) + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.not.be.reverted + }) + + // Cannot settle again with trade closed + await whileImpersonating(backingManager.address, async (bmSigner) => { + await expect(trade.connect(bmSigner).settle()).to.be.revertedWith('Invalid trade state') + }) + }) + it('Should allow anyone to transfer to origin after a DutchTrade is complete', async () => { // Fund trade and initialize await token0.connect(owner).mint(trade.address, amount) diff --git a/test/Facade.test.ts b/test/Facade.test.ts index 951146b491..ffc8bea931 100644 --- a/test/Facade.test.ts +++ b/test/Facade.test.ts @@ -1,23 +1,37 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' -import { BigNumber } from 'ethers' +import { BigNumber, ContractFactory } from 'ethers' import { ethers } from 'hardhat' +import { expectEvents } from '../common/events' +import { IConfig } from '#/common/configuration' import { bn, fp } from '../common/numbers' import { setOraclePrice } from './utils/oracles' import { Asset, - CTokenVaultMock, + BackingManagerP1, + BackingMgrCompatibleV1, + BackingMgrCompatibleV2, + BackingMgrInvalidVersion, + ComptrollerMock, + CTokenWrapperMock, ERC20Mock, FacadeAct, FacadeRead, FacadeTest, MockV3Aggregator, + RecollateralizationLibP1, + RevenueTraderCompatibleV1, + RevenueTraderCompatibleV2, + RevenueTraderP1InvalidReverts, + RevenueTraderInvalidVersion, + RevenueTraderP1, StaticATokenMock, StRSRP1, IAssetRegistry, - IBackingManager, IBasketHandler, + TestIBackingManager, TestIBroker, TestIRevenueTrader, TestIMain, @@ -34,9 +48,14 @@ import { ORACLE_ERROR, } from './fixtures' import { getLatestBlockTimestamp, setNextBlockTimestamp } from './utils/time' -import { CollateralStatus, TradeKind, MAX_UINT256 } from '#/common/constants' +import { CollateralStatus, TradeKind, MAX_UINT256, ZERO_ADDRESS } from '#/common/constants' +import { expectTrade } from './utils/trades' import { mintCollaterals } from './utils/tokens' +const describeP1 = IMPLEMENTATION == Implementation.P1 ? describe : describe.skip + +const itP1 = IMPLEMENTATION == Implementation.P1 ? it : it.skip + describe('FacadeRead + FacadeAct contracts', () => { let owner: SignerWithAddress let addr1: SignerWithAddress @@ -48,7 +67,10 @@ describe('FacadeRead + FacadeAct contracts', () => { let token: ERC20Mock let usdc: USDCMock let aToken: StaticATokenMock - let cTokenVault: CTokenVaultMock + let cTokenVault: CTokenWrapperMock + let aaveToken: ERC20Mock + let compToken: ERC20Mock + let compoundMock: ComptrollerMock let rsr: ERC20Mock let basket: Collateral[] @@ -70,22 +92,38 @@ describe('FacadeRead + FacadeAct contracts', () => { let basketHandler: IBasketHandler let rTokenTrader: TestIRevenueTrader let rsrTrader: TestIRevenueTrader - let backingManager: IBackingManager + let backingManager: TestIBackingManager let broker: TestIBroker let assetRegistry: IAssetRegistry // RSR let rsrAsset: Asset + // Config values + let config: IConfig + + // Factories + let RevenueTraderV2ImplFactory: ContractFactory + let RevenueTraderV1ImplFactory: ContractFactory + let RevenueTraderInvalidVerImplFactory: ContractFactory + let RevenueTraderRevertsImplFactory: ContractFactory + let BackingMgrV2ImplFactory: ContractFactory + let BackingMgrV1ImplFactory: ContractFactory + let BackingMgrInvalidVerImplFactory: ContractFactory + beforeEach(async () => { ;[owner, addr1, addr2, other] = await ethers.getSigners() // Deploy fixture ;({ stRSR, + aaveToken, + compToken, + compoundMock, rsr, rsrAsset, basket, + config, facade, facadeAct, facadeTest, @@ -107,12 +145,47 @@ describe('FacadeRead + FacadeAct contracts', () => { aToken = ( await ethers.getContractAt('StaticATokenMock', await aTokenAsset.erc20()) ) - cTokenVault = ( - await ethers.getContractAt('CTokenVaultMock', await cTokenAsset.erc20()) + cTokenVault = ( + await ethers.getContractAt('CTokenWrapperMock', await cTokenAsset.erc20()) + ) + + // Factories used in tests + RevenueTraderV2ImplFactory = await ethers.getContractFactory('RevenueTraderCompatibleV2') + + RevenueTraderV1ImplFactory = await ethers.getContractFactory('RevenueTraderCompatibleV1') + + RevenueTraderInvalidVerImplFactory = await ethers.getContractFactory( + 'RevenueTraderInvalidVersion' + ) + + RevenueTraderRevertsImplFactory = await ethers.getContractFactory( + 'RevenueTraderP1InvalidReverts' + ) + + const tradingLib: RecollateralizationLibP1 = ( + await (await ethers.getContractFactory('RecollateralizationLibP1')).deploy() ) + + BackingMgrV2ImplFactory = await ethers.getContractFactory('BackingMgrCompatibleV2', { + libraries: { + RecollateralizationLibP1: tradingLib.address, + }, + }) + + BackingMgrV1ImplFactory = await ethers.getContractFactory('BackingMgrCompatibleV1', { + libraries: { + RecollateralizationLibP1: tradingLib.address, + }, + }) + + BackingMgrInvalidVerImplFactory = await ethers.getContractFactory('BackingMgrInvalidVersion', { + libraries: { + RecollateralizationLibP1: tradingLib.address, + }, + }) }) - describe('Views', () => { + describe('FacadeRead + interactions with FacadeAct', () => { let issueAmount: BigNumber const expectValidBasketBreakdown = async (rToken: TestIRToken) => { @@ -194,6 +267,28 @@ describe('FacadeRead + FacadeAct contracts', () => { expect(uoas[3]).to.equal(issueAmount.div(4)) }) + it('Should handle UNPRICED when returning issuable quantities', async () => { + // Set unpriced assets, should return UoA = 0 + await setOraclePrice(tokenAsset.address, MAX_UINT256.div(2).sub(1)) + const [toks, quantities, uoas] = await facade.callStatic.issue(rToken.address, issueAmount) + expect(toks.length).to.equal(4) + expect(toks[0]).to.equal(token.address) + expect(toks[1]).to.equal(usdc.address) + expect(toks[2]).to.equal(aToken.address) + expect(toks[3]).to.equal(cTokenVault.address) + expect(quantities.length).to.equal(4) + expect(quantities[0]).to.equal(issueAmount.div(4)) + expect(quantities[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) + expect(quantities[2]).to.equal(issueAmount.div(4)) + expect(quantities[3]).to.equal(issueAmount.div(4).mul(50).div(bn('1e10'))) + expect(uoas.length).to.equal(4) + // Three assets are unpriced + expect(uoas[0]).to.equal(0) + expect(uoas[1]).to.equal(issueAmount.div(4)) + expect(uoas[2]).to.equal(0) + expect(uoas[3]).to.equal(0) + }) + it('Should return redeemable quantities correctly', async () => { const [toks, quantities, isProrata] = await facade.callStatic.redeem( rToken.address, @@ -296,18 +391,6 @@ describe('FacadeRead + FacadeAct contracts', () => { expect(overCollateralization).to.equal(0) }) - it('Should return backingOverview backing correctly when RSR is UNPRICED', async () => { - await setOraclePrice(tokenAsset.address, MAX_UINT256.div(2).sub(1)) - await basketHandler.refreshBasket() - const [backing, overCollateralization] = await facade.callStatic.backingOverview( - rToken.address - ) - - // Check values - Fully collateralized and no over-collateralization - expect(backing).to.equal(fp('1')) - expect(overCollateralization).to.equal(0) - }) - it('Should return backingOverview over-collateralization correctly when RSR price is 0', async () => { // Mint some RSR const stakeAmount = bn('50e18') // Half in value compared to issued RTokens @@ -413,7 +496,7 @@ describe('FacadeRead + FacadeAct contracts', () => { expect(lotLow).to.equal(0) // revenue - const [erc20s, canStart, surpluses, minTradeAmounts] = + let [erc20s, canStart, surpluses, minTradeAmounts] = await facadeAct.callStatic.revenueOverview(trader.address) expect(erc20s.length).to.equal(8) // should be full set of registered ERC20s @@ -443,6 +526,131 @@ describe('FacadeRead + FacadeAct contracts', () => { const data = funcSig.substring(0, 10) + args.slice(2) await expect(facadeAct.multicall([data])).to.emit(trader, 'TradeStarted') + // Another call to revenueOverview should not propose any auction + ;[erc20s, canStart, surpluses, minTradeAmounts] = + await facadeAct.callStatic.revenueOverview(trader.address) + expect(canStart).to.eql(Array(8).fill(false)) + + // Nothing should be settleable + expect((await facade.auctionsSettleable(trader.address)).length).to.equal(0) + + // Advance time till auction ended + await advanceTime(auctionLength + 13) + + // Now should be settleable + const settleable = await facade.auctionsSettleable(trader.address) + expect(settleable.length).to.equal(1) + expect(settleable[0]).to.equal(token.address) + + // Another call to revenueOverview should settle and propose new auction + ;[erc20s, canStart, surpluses, minTradeAmounts] = + await facadeAct.callStatic.revenueOverview(trader.address) + + // Should repeat the same auctions + for (let i = 0; i < 8; i++) { + if (erc20s[i] == token.address) { + expect(canStart[i]).to.equal(true) + expect(surpluses[i]).to.equal(tokenSurplus) + } else { + expect(canStart[i]).to.equal(false) + expect(surpluses[i]).to.equal(0) + } + } + + // Settle and start new auction + await facadeAct.runRevenueAuctions( + trader.address, + erc20sToStart, + erc20sToStart, + TradeKind.DUTCH_AUCTION + ) + + // Send additional revenues + await token.connect(addr1).transfer(trader.address, tokenSurplus) + + // Call revenueOverview, cannot open new auctions + ;[erc20s, canStart, surpluses, minTradeAmounts] = + await facadeAct.callStatic.revenueOverview(trader.address) + expect(canStart).to.eql(Array(8).fill(false)) + } + }) + + itP1('Should handle other versions when running revenueOverview revenue', async () => { + // Use P1 specific versions + backingManager = ( + await ethers.getContractAt('BackingManagerP1', backingManager.address) + ) + rTokenTrader = ( + await ethers.getContractAt('RevenueTraderP1', rTokenTrader.address) + ) + rsrTrader = await ethers.getContractAt('RevenueTraderP1', rsrTrader.address) + + const revTraderV2: RevenueTraderCompatibleV2 = ( + await RevenueTraderV2ImplFactory.deploy() + ) + + const revTraderV1: RevenueTraderCompatibleV1 = ( + await RevenueTraderV1ImplFactory.deploy() + ) + + const backingManagerV2: BackingMgrCompatibleV2 = ( + await BackingMgrV2ImplFactory.deploy() + ) + + const backingManagerV1: BackingMgrCompatibleV1 = ( + await BackingMgrV1ImplFactory.deploy() + ) + + // Upgrade RevenueTraders and BackingManager to V2 + await rsrTrader.connect(owner).upgradeTo(revTraderV2.address) + await rTokenTrader.connect(owner).upgradeTo(revTraderV2.address) + await backingManager.connect(owner).upgradeTo(backingManagerV2.address) + + const traders = [rTokenTrader, rsrTrader] + for (let traderIndex = 0; traderIndex < traders.length; traderIndex++) { + const trader = traders[traderIndex] + + const minTradeVolume = await trader.minTradeVolume() + const auctionLength = await broker.dutchAuctionLength() + const tokenSurplus = bn('0.5e18') + await token.connect(addr1).transfer(trader.address, tokenSurplus) + + // revenue + let [erc20s, canStart, surpluses, minTradeAmounts] = + await facadeAct.callStatic.revenueOverview(trader.address) + expect(erc20s.length).to.equal(8) // should be full set of registered ERC20s + + const erc20sToStart = [] + for (let i = 0; i < 8; i++) { + if (erc20s[i] == token.address) { + erc20sToStart.push(erc20s[i]) + expect(canStart[i]).to.equal(true) + expect(surpluses[i]).to.equal(tokenSurplus) + } else { + expect(canStart[i]).to.equal(false) + expect(surpluses[i]).to.equal(0) + } + const asset = await ethers.getContractAt('IAsset', await assetRegistry.toAsset(erc20s[i])) + const [low] = await asset.price() + expect(minTradeAmounts[i]).to.equal( + minTradeVolume.mul(bn('10').pow(await asset.erc20Decimals())).div(low) + ) // 1% oracleError + } + + // Run revenue auctions via multicall + const funcSig = ethers.utils.id('runRevenueAuctions(address,address[],address[],uint8)') + const args = ethers.utils.defaultAbiCoder.encode( + ['address', 'address[]', 'address[]', 'uint8'], + [trader.address, [], erc20sToStart, TradeKind.DUTCH_AUCTION] + ) + const data = funcSig.substring(0, 10) + args.slice(2) + await expect(facadeAct.multicall([data])).to.emit(trader, 'TradeStarted') + + // Another call to revenueOverview should not propose any auction + ;[erc20s, canStart, surpluses, minTradeAmounts] = + await facadeAct.callStatic.revenueOverview(trader.address) + expect(canStart).to.eql(Array(8).fill(false)) + // Nothing should be settleable expect((await facade.auctionsSettleable(trader.address)).length).to.equal(0) @@ -453,9 +661,96 @@ describe('FacadeRead + FacadeAct contracts', () => { const settleable = await facade.auctionsSettleable(trader.address) expect(settleable.length).to.equal(1) expect(settleable[0]).to.equal(token.address) + + // Upgrade to V1 + await trader.connect(owner).upgradeTo(revTraderV1.address) + await backingManager.connect(owner).upgradeTo(backingManagerV1.address) + + // Another call to revenueOverview should settle and propose new auction + ;[erc20s, canStart, surpluses, minTradeAmounts] = + await facadeAct.callStatic.revenueOverview(trader.address) + + // Should repeat the same auctions + for (let i = 0; i < 8; i++) { + if (erc20s[i] == token.address) { + expect(canStart[i]).to.equal(true) + expect(surpluses[i]).to.equal(tokenSurplus) + } else { + expect(canStart[i]).to.equal(false) + expect(surpluses[i]).to.equal(0) + } + } + + // Settle and start new auction + await facadeAct.runRevenueAuctions( + trader.address, + erc20sToStart, + erc20sToStart, + TradeKind.DUTCH_AUCTION + ) + + // Send additional revenues + await token.connect(addr1).transfer(trader.address, tokenSurplus) + + // Call revenueOverview, cannot open new auctions + ;[erc20s, canStart, surpluses, minTradeAmounts] = + await facadeAct.callStatic.revenueOverview(trader.address) + expect(canStart).to.eql(Array(8).fill(false)) } }) + itP1('Should handle invalid versions when running revenueOverview', async () => { + // Use P1 specific versions + rsrTrader = await ethers.getContractAt('RevenueTraderP1', rsrTrader.address) + backingManager = ( + await ethers.getContractAt('BackingManagerP1', backingManager.address) + ) + + const revTraderInvalidVer: RevenueTraderInvalidVersion = ( + await RevenueTraderInvalidVerImplFactory.deploy() + ) + + const bckMgrInvalidVer: BackingMgrInvalidVersion = ( + await BackingMgrInvalidVerImplFactory.deploy() + ) + + const revTraderV2: RevenueTraderCompatibleV2 = ( + await RevenueTraderV2ImplFactory.deploy() + ) + + // Upgrade RevenueTrader to V0 - Use RSR as an example + await rsrTrader.connect(owner).upgradeTo(revTraderInvalidVer.address) + + const tokenSurplus = bn('0.5e18') + await token.connect(addr1).transfer(rsrTrader.address, tokenSurplus) + + await expect(facadeAct.callStatic.revenueOverview(rsrTrader.address)).to.be.revertedWith( + 'unrecognized version' + ) + + // Upgrade to a version where manageToken reverts in Traders + const revTraderReverts: RevenueTraderP1InvalidReverts = ( + await RevenueTraderRevertsImplFactory.deploy() + ) + await rsrTrader.connect(owner).upgradeTo(revTraderReverts.address) + + // revenue + const [erc20s, canStart, ,] = await facadeAct.callStatic.revenueOverview(rsrTrader.address) + expect(erc20s.length).to.equal(8) // should be full set of registered ERC20s + + // No auction can be started + expect(canStart).to.eql(Array(8).fill(false)) + + // Set revenue trader to a valid version but have an invalid Backing Manager + await rsrTrader.connect(owner).upgradeTo(revTraderV2.address) + await backingManager.connect(owner).upgradeTo(bckMgrInvalidVer.address) + + // Reverts due to invalid version when forwarding revenue + await expect(facadeAct.callStatic.revenueOverview(rsrTrader.address)).to.be.revertedWith( + 'unrecognized version' + ) + }) + it('Should return nextRecollateralizationAuction', async () => { // Setup prime basket await basketHandler.connect(owner).setPrimeBasket([usdc.address], [fp('1')]) @@ -469,12 +764,131 @@ describe('FacadeRead + FacadeAct contracts', () => { const sellAmt: BigNumber = await token.balanceOf(backingManager.address) // Confirm nextRecollateralizationAuction is true - const [canStart, sell, buy, sellAmount] = + let [canStart, sell, buy, sellAmount] = + await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + expect(canStart).to.equal(true) + expect(sell).to.equal(token.address) + expect(buy).to.equal(usdc.address) + expect(sellAmount).to.equal(sellAmt) + + // Trigger auction + await backingManager.rebalance(TradeKind.BATCH_AUCTION) + + const auctionTimestamp: number = await getLatestBlockTimestamp() + + // Check auction registered + // token -> usdc Auction + await expectTrade(backingManager, { + sell: token.address, + buy: usdc.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) + + // nextRecollateralizationAuction should return false (trade open) + ;[canStart, sell, buy, sellAmount] = + await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + expect(canStart).to.equal(false) + expect(sell).to.equal(ZERO_ADDRESS) + expect(buy).to.equal(ZERO_ADDRESS) + expect(sellAmount).to.equal(0) + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // nextRecollateralizationAuction should return the next trade + // In this case it will retry the same auction + ;[canStart, sell, buy, sellAmount] = + await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + expect(canStart).to.equal(true) + expect(sell).to.equal(token.address) + expect(buy).to.equal(usdc.address) + expect(sellAmount).to.equal(sellAmt) + }) + + itP1('Should handle other versions for nextRecollateralizationAuction', async () => { + // Use P1 specific versions + backingManager = ( + await ethers.getContractAt('BackingManagerP1', backingManager.address) + ) + + const backingManagerV2: BackingMgrCompatibleV2 = ( + await BackingMgrV2ImplFactory.deploy() + ) + + const backingManagerV1: BackingMgrCompatibleV1 = ( + await BackingMgrV1ImplFactory.deploy() + ) + + const backingManagerInvalidVer: BackingMgrInvalidVersion = ( + await BackingMgrInvalidVerImplFactory.deploy() + ) + + // Upgrade BackingManager to V2 + await backingManager.connect(owner).upgradeTo(backingManagerV2.address) + + // Setup prime basket + await basketHandler.connect(owner).setPrimeBasket([usdc.address], [fp('1')]) + + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(2, [usdc.address], [fp('1')], false) + + // Trigger recollateralization + const sellAmt: BigNumber = await token.balanceOf(backingManager.address) + + // Confirm nextRecollateralizationAuction is true + let [canStart, sell, buy, sellAmount] = + await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + expect(canStart).to.equal(true) + expect(sell).to.equal(token.address) + expect(buy).to.equal(usdc.address) + expect(sellAmount).to.equal(sellAmt) + + // Trigger auction + await backingManager.rebalance(TradeKind.BATCH_AUCTION) + + const auctionTimestamp: number = await getLatestBlockTimestamp() + + // Check auction registered + // token -> usdc Auction + await expectTrade(backingManager, { + sell: token.address, + buy: usdc.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) + + // Upgrade BackingManager to V1 + await backingManager.connect(owner).upgradeTo(backingManagerV1.address) + + // nextRecollateralizationAuction should return false (trade open) + ;[canStart, sell, buy, sellAmount] = + await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + expect(canStart).to.equal(false) + expect(sell).to.equal(ZERO_ADDRESS) + expect(buy).to.equal(ZERO_ADDRESS) + expect(sellAmount).to.equal(0) + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // nextRecollateralizationAuction should return the next trade + // In this case it will retry the same auction + ;[canStart, sell, buy, sellAmount] = await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) expect(canStart).to.equal(true) expect(sell).to.equal(token.address) expect(buy).to.equal(usdc.address) expect(sellAmount).to.equal(sellAmt) + + // Invalid versions are also handled + await backingManager.connect(owner).upgradeTo(backingManagerInvalidVer.address) + + await expect( + facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + ).to.be.revertedWith('unrecognized version') }) it('Should return basketBreakdown correctly for paused token', async () => { @@ -631,4 +1045,246 @@ describe('FacadeRead + FacadeAct contracts', () => { }) } }) + + // P1 only + describeP1('FacadeAct', () => { + let issueAmount: BigNumber + + beforeEach(async () => { + // Mint Tokens + initialBal = bn('10000000000e18') + await token.connect(owner).mint(addr1.address, initialBal) + await usdc.connect(owner).mint(addr1.address, initialBal) + await aToken.connect(owner).mint(addr1.address, initialBal) + await cTokenVault.connect(owner).mint(addr1.address, initialBal) + + await token.connect(owner).mint(addr2.address, initialBal) + await usdc.connect(owner).mint(addr2.address, initialBal) + await aToken.connect(owner).mint(addr2.address, initialBal) + await cTokenVault.connect(owner).mint(addr2.address, initialBal) + + // Mint RSR + await rsr.connect(owner).mint(addr1.address, initialBal) + + // Issue some RTokens + issueAmount = bn('100e18') + + // Provide approvals + await token.connect(addr1).approve(rToken.address, initialBal) + await usdc.connect(addr1).approve(rToken.address, initialBal) + await aToken.connect(addr1).approve(rToken.address, initialBal) + await cTokenVault.connect(addr1).approve(rToken.address, initialBal) + + // Issue rTokens + await rToken.connect(addr1).issue(issueAmount) + + // Use P1 specific versions + backingManager = ( + await ethers.getContractAt('BackingManagerP1', backingManager.address) + ) + rTokenTrader = ( + await ethers.getContractAt('RevenueTraderP1', rTokenTrader.address) + ) + rsrTrader = await ethers.getContractAt('RevenueTraderP1', rsrTrader.address) + }) + + it('Should claim rewards', async () => { + const rewardAmountAAVE = bn('0.5e18') + const rewardAmountCOMP = bn('0.8e18') + + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(0) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) + expect(await compToken.balanceOf(rsrTrader.address)).to.equal(0) + + // AAVE Rewards + await aToken.setRewards(backingManager.address, rewardAmountAAVE) + await aToken.setRewards(rTokenTrader.address, rewardAmountAAVE) + + // COMP Rewards + await compoundMock.setRewards(rsrTrader.address, rewardAmountCOMP) + + // Via Facade, claim rewards from backingManager + await expectEvents(facadeAct.claimRewards(rToken.address), [ + { + contract: aToken, + name: 'RewardsClaimed', + args: [aaveToken.address, rewardAmountAAVE], + emitted: true, + }, + { + contract: aToken, + name: 'RewardsClaimed', + args: [aaveToken.address, rewardAmountAAVE], + emitted: true, + }, + { + contract: cTokenVault, + name: 'RewardsClaimed', + args: [compToken.address, rewardAmountCOMP], + emitted: true, + }, + ]) + + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmountAAVE) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(rewardAmountAAVE) + expect(await compToken.balanceOf(rsrTrader.address)).to.equal(rewardAmountCOMP) + }) + + it('Should run revenue auctions correctly', async () => { + const auctionLength = await broker.dutchAuctionLength() + const tokenSurplus = bn('0.5e18') + await token.connect(addr1).transfer(rsrTrader.address, tokenSurplus) + + // Run revenue auctions + await expect( + facadeAct.runRevenueAuctions( + rsrTrader.address, + [], + [token.address], + TradeKind.DUTCH_AUCTION + ) + ) + .to.emit(rsrTrader, 'TradeStarted') + .withArgs(anyValue, token.address, rsr.address, anyValue, anyValue) + + // Nothing should be settleable + expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(0) + + // Advance time till auction ended + await advanceTime(auctionLength + 13) + + // Settle and start new auction - Will retry + await expectEvents( + facadeAct.runRevenueAuctions( + rsrTrader.address, + [token.address], + [token.address], + TradeKind.DUTCH_AUCTION + ), + [ + { + contract: rsrTrader, + name: 'TradeSettled', + args: [anyValue, token.address, rsr.address, anyValue, anyValue], + emitted: true, + }, + { + contract: rsrTrader, + name: 'TradeStarted', + args: [anyValue, token.address, rsr.address, anyValue, anyValue], + emitted: true, + }, + ] + ) + }) + + it('Should handle other versions when running revenue auctions', async () => { + const revTraderV2: RevenueTraderCompatibleV2 = ( + await RevenueTraderV2ImplFactory.deploy() + ) + + const revTraderV1: RevenueTraderCompatibleV1 = ( + await RevenueTraderV1ImplFactory.deploy() + ) + + const backingManagerV2: BackingMgrCompatibleV2 = ( + await BackingMgrV2ImplFactory.deploy() + ) + + const backingManagerV1: BackingMgrCompatibleV1 = ( + await BackingMgrV1ImplFactory.deploy() + ) + + // Upgrade components to V2 + await backingManager.connect(owner).upgradeTo(backingManagerV2.address) + await rTokenTrader.connect(owner).upgradeTo(revTraderV2.address) + + const auctionLength = await broker.dutchAuctionLength() + const tokenSurplus = bn('0.5e18') + await token.connect(addr1).transfer(rTokenTrader.address, tokenSurplus) + + // Run revenue auctions + await expect( + facadeAct.runRevenueAuctions( + rTokenTrader.address, + [], + [token.address], + TradeKind.DUTCH_AUCTION + ) + ) + .to.emit(rTokenTrader, 'TradeStarted') + .withArgs(anyValue, token.address, rToken.address, anyValue, anyValue) + + // Nothing should be settleable + expect((await facade.auctionsSettleable(rTokenTrader.address)).length).to.equal(0) + + // Advance time till auction ended + await advanceTime(auctionLength + 13) + + // Upgrade to V1 + await backingManager.connect(owner).upgradeTo(backingManagerV1.address) + await rTokenTrader.connect(owner).upgradeTo(revTraderV1.address) + + // Settle and start new auction - Will retry + await expectEvents( + facadeAct.runRevenueAuctions( + rTokenTrader.address, + [token.address], + [token.address], + TradeKind.DUTCH_AUCTION + ), + [ + { + contract: rTokenTrader, + name: 'TradeSettled', + args: [anyValue, token.address, rToken.address, anyValue, anyValue], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + args: [anyValue, token.address, rToken.address, anyValue, anyValue], + emitted: true, + }, + ] + ) + }) + + it('Should handle invalid versions when running revenue auctions', async () => { + const revTraderInvalidVer: RevenueTraderInvalidVersion = ( + await RevenueTraderInvalidVerImplFactory.deploy() + ) + + const backingManagerInvalidVer: BackingMgrInvalidVersion = ( + await BackingMgrInvalidVerImplFactory.deploy() + ) + + // Upgrade RevenueTrader to invalid version - Use RSR as an example + await rsrTrader.connect(owner).upgradeTo(revTraderInvalidVer.address) + + const tokenSurplus = bn('0.5e18') + await token.connect(addr1).transfer(rsrTrader.address, tokenSurplus) + + await expect( + facadeAct.runRevenueAuctions( + rsrTrader.address, + [], + [token.address], + TradeKind.DUTCH_AUCTION + ) + ).to.be.revertedWith('unrecognized version') + + // Also set BackingManager to invalid version + await backingManager.connect(owner).upgradeTo(backingManagerInvalidVer.address) + + await expect( + facadeAct.runRevenueAuctions( + rsrTrader.address, + [], + [token.address], + TradeKind.DUTCH_AUCTION + ) + ).to.be.revertedWith('unrecognized version') + }) + }) }) diff --git a/test/FacadeWrite.test.ts b/test/FacadeWrite.test.ts index ab843462f9..2618e777da 100644 --- a/test/FacadeWrite.test.ts +++ b/test/FacadeWrite.test.ts @@ -49,7 +49,7 @@ import { TestIRToken, TimelockController, USDCMock, - CTokenVaultMock, + CTokenWrapperMock, } from '../typechain' import { Collateral, @@ -79,7 +79,7 @@ describe('FacadeWrite contract', () => { // Tokens let token: ERC20Mock let usdc: USDCMock - let cTokenVault: CTokenVaultMock + let cTokenVault: CTokenWrapperMock let basket: Collateral[] // Aave / Comp @@ -144,8 +144,8 @@ describe('FacadeWrite contract', () => { token = await ethers.getContractAt('ERC20Mock', await tokenAsset.erc20()) usdc = await ethers.getContractAt('USDCMock', await usdcAsset.erc20()) - cTokenVault = ( - await ethers.getContractAt('CTokenVaultMock', await cTokenAsset.erc20()) + cTokenVault = ( + await ethers.getContractAt('CTokenWrapperMock', await cTokenAsset.erc20()) ) // Deploy DFacadeWriteLib lib diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index 12d8da2244..f74e01ba29 100644 --- a/test/Furnace.test.ts +++ b/test/Furnace.test.ts @@ -6,7 +6,7 @@ import hre, { ethers, upgrades } from 'hardhat' import { IConfig, MAX_RATIO } from '../common/configuration' import { bn, fp } from '../common/numbers' import { - CTokenVaultMock, + CTokenWrapperMock, ERC20Mock, StaticATokenMock, TestIFurnace, @@ -52,7 +52,7 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { let token0: ERC20Mock let token1: ERC20Mock let token2: StaticATokenMock - let token3: CTokenVaultMock + let token3: CTokenWrapperMock let collateral0: Collateral let collateral1: Collateral @@ -85,8 +85,8 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { token2 = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - token3 = ( - await ethers.getContractAt('CTokenVaultMock', await collateral3.erc20()) + token3 = ( + await ethers.getContractAt('CTokenWrapperMock', await collateral3.erc20()) ) // Mint Tokens @@ -103,18 +103,18 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { // Applies to all components - used here as an example it('Deployment does not accept invalid main address', async () => { let FurnaceFactory: ContractFactory + let newFurnace: TestIFurnace if (IMPLEMENTATION == Implementation.P0) { FurnaceFactory = await ethers.getContractFactory('FurnaceP0') - return await FurnaceFactory.deploy() + newFurnace = await FurnaceFactory.deploy() } else if (IMPLEMENTATION == Implementation.P1) { FurnaceFactory = await ethers.getContractFactory('FurnaceP1') - return await upgrades.deployProxy(FurnaceFactory, [], { + newFurnace = await upgrades.deployProxy(FurnaceFactory, [], { kind: 'uups', }) } else { throw new Error('PROTO_IMPL must be set to either `0` or `1`') } - const newFurnace = await FurnaceFactory.deploy() await expect(newFurnace.init(ZERO_ADDRESS, config.rewardRatio)).to.be.revertedWith( 'main is zero address' ) @@ -237,7 +237,7 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { expect(await rToken.balanceOf(furnace.address)).to.equal(hndAmt) // Advance one period - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) + await advanceTime(Number(ONE_PERIOD)) // Melt await expect(furnace.connect(addr1).melt()).to.not.emit(rToken, 'Melted') @@ -256,7 +256,7 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { await rToken.connect(addr1).transfer(furnace.address, hndAmt) // Get past first noop melt - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) + await advanceTime(Number(ONE_PERIOD)) await expect(furnace.connect(addr1).melt()).to.not.emit(rToken, 'Melted') diff --git a/test/Main.test.ts b/test/Main.test.ts index 3efe71f4e6..841d2bf791 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -33,10 +33,11 @@ import { bn, fp } from '../common/numbers' import { Asset, ATokenFiatCollateral, + BackingManagerP1, BasketHandlerP1, CTokenFiatCollateral, DutchTrade, - CTokenVaultMock, + CTokenWrapperMock, ERC20Mock, FacadeRead, FacadeTest, @@ -47,6 +48,7 @@ import { InvalidRefPerTokCollateralMock, MockV3Aggregator, MockableCollateral, + RevenueTraderP1, RTokenAsset, StaticATokenMock, TestIBackingManager, @@ -80,6 +82,8 @@ const DEFAULT_THRESHOLD = fp('0.01') // 1% const itP1 = IMPLEMENTATION == Implementation.P1 ? it : it.skip +const describeP1 = IMPLEMENTATION == Implementation.P1 ? describe : describe.skip + const describeGas = IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip @@ -114,7 +118,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { let token0: ERC20Mock let token1: USDCMock let token2: StaticATokenMock - let token3: CTokenVaultMock + let token3: CTokenWrapperMock let backupToken1: ERC20Mock let backupToken2: ERC20Mock let collateral0: FiatCollateral @@ -179,7 +183,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { token0 = erc20s[collateral.indexOf(basket[0])] token1 = erc20s[collateral.indexOf(basket[1])] token2 = erc20s[collateral.indexOf(basket[2])] - token3 = erc20s[collateral.indexOf(basket[3])] + token3 = erc20s[collateral.indexOf(basket[3])] backupToken1 = erc20s[2] // USDT backupCollateral1 = collateral[2] @@ -1818,6 +1822,28 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { ) }) + it('Should be able to set prime basket multiple times', async () => { + await expect( + basketHandler + .connect(owner) + .setPrimeBasket([token0.address, token3.address], [fp('0.5'), fp('0.5')]) + ) + .to.emit(basketHandler, 'PrimeBasketSet') + .withArgs( + [token0.address, token3.address], + [fp('0.5'), fp('0.5')], + [ethers.utils.formatBytes32String('USD')] + ) + + await expect(basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')])) + .to.emit(basketHandler, 'PrimeBasketSet') + .withArgs([token1.address], [fp('1')], [ethers.utils.formatBytes32String('USD')]) + + await expect(basketHandler.connect(owner).setPrimeBasket([token2.address], [fp('1')])) + .to.emit(basketHandler, 'PrimeBasketSet') + .withArgs([token2.address], [fp('1')], [ethers.utils.formatBytes32String('USD')]) + }) + it('Should not allow to set prime Basket as superset of old basket', async () => { await assetRegistry.connect(owner).register(backupCollateral1.address) await expect( @@ -1967,6 +1993,15 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { } } + it('Should perform validations on quoteCustomRedemption', async () => { + const basketNonces = [1, 2] + const portions = [fp('1')] + const amount = fp('10000') + await expect( + basketHandler.quoteCustomRedemption(basketNonces, portions, amount) + ).to.be.revertedWith('portions does not mirror basketNonces') + }) + it('Should correctly quote the current basket, same as quote()', async () => { /* Test Quote @@ -2250,6 +2285,157 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expectDelta(balsBefore, quote.quantities, balsAfter) }) + it('Should correctly quote a historical redemption with unregistered asset', async () => { + // default usdc & refresh basket to use backup collateral + await usdcChainlink.updateAnswer(bn('0.8e8')) // default token1 + await daiChainlink.updateAnswer(bn('0.8e8')) // default token0, token2, token3 + await basketHandler.refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + + // Unregister asset in previous basket + await assetRegistry.connect(owner).unregister(collateral1.address) + /* + Test Quote + */ + const basketNonces = [1] + const portions = [fp('1')] + const amount = fp('10000') + const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, amount) + + expect(quote.erc20s.length).equal(4) + expect(quote.quantities.length).equal(4) + + const expectedTokens = [token0, token1, token2, token3] + const expectedAddresses = expectedTokens.map((t) => t.address) + const expectedQuantities = [ + fp('0.25') + .mul(issueAmount) + .div(await collateral0.refPerTok()) + .div(bn(`1e${18 - (await token0.decimals())}`)), + bn('0') + .mul(issueAmount) + .div(await collateral1.refPerTok()) + .div(bn(`1e${18 - (await token1.decimals())}`)), + fp('0.25') + .mul(issueAmount) + .div(await collateral2.refPerTok()) + .div(bn(`1e${18 - (await token2.decimals())}`)), + fp('0.25') + .mul(issueAmount) + .div(await collateral3.refPerTok()) + .div(bn(`1e${18 - (await token3.decimals())}`)), + ] + expectEqualArrays(quote.erc20s, expectedAddresses) + expectEqualArrays(quote.quantities, expectedQuantities) + + /* + Test Custom Redemption + */ + const balsBefore = await getBalances(addr1.address, expectedTokens) + await backupToken1.mint(backingManager.address, issueAmount) + + // rToken redemption + await expect( + rToken + .connect(addr1) + .redeemCustom( + addr1.address, + amount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + ).to.not.be.reverted + + const balsAfter = await getBalances(addr1.address, expectedTokens) + expectDelta(balsBefore, quote.quantities, balsAfter) + }) + + it('Should correctly quote a historical redemption with an non-collateral asset', async () => { + // default usdc & refresh basket to use backup collateral + await usdcChainlink.updateAnswer(bn('0.8e8')) // default token1 + await daiChainlink.updateAnswer(bn('0.8e8')) // default token0, token2, token3 + await basketHandler.refreshBasket() + await advanceTime(Number(config.warmupPeriod) + 1) + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + + // Swap collateral for asset in previous basket + const AssetFactory: ContractFactory = await ethers.getContractFactory('Asset') + const newAsset1: Asset = ( + await AssetFactory.deploy( + PRICE_TIMEOUT, + await collateral1.chainlinkFeed(), + ORACLE_ERROR, + token1.address, + config.rTokenMaxTradeVolume, + 1 + ) + ) + + await assetRegistry.connect(owner).swapRegistered(newAsset1.address) + + /* + Test Quote + */ + const basketNonces = [1] + const portions = [fp('1')] + const amount = fp('10000') + const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, amount) + + expect(quote.erc20s.length).equal(4) + expect(quote.quantities.length).equal(4) + + const expectedTokens = [token0, token1, token2, token3] + const expectedAddresses = expectedTokens.map((t) => t.address) + const expectedQuantities = [ + fp('0.25') + .mul(issueAmount) + .div(await collateral0.refPerTok()) + .div(bn(`1e${18 - (await token0.decimals())}`)), + bn('0') + .mul(issueAmount) + .div(await collateral1.refPerTok()) + .div(bn(`1e${18 - (await token1.decimals())}`)), + fp('0.25') + .mul(issueAmount) + .div(await collateral2.refPerTok()) + .div(bn(`1e${18 - (await token2.decimals())}`)), + fp('0.25') + .mul(issueAmount) + .div(await collateral3.refPerTok()) + .div(bn(`1e${18 - (await token3.decimals())}`)), + ] + expectEqualArrays(quote.erc20s, expectedAddresses) + expectEqualArrays(quote.quantities, expectedQuantities) + + /* + Test Custom Redemption - Should behave as if token is not registered + */ + const balsBefore = await getBalances(addr1.address, expectedTokens) + await backupToken1.mint(backingManager.address, issueAmount) + + // rToken redemption + await expect( + rToken + .connect(addr1) + .redeemCustom( + addr1.address, + amount, + basketNonces, + portions, + quote.erc20s, + quote.quantities + ) + ).to.not.be.reverted + + const balsAfter = await getBalances(addr1.address, expectedTokens) + expectDelta(balsBefore, quote.quantities, balsAfter) + }) + itP1('Should return historical basket correctly', async () => { const bskHandlerP1: BasketHandlerP1 = ( await ethers.getContractAt('BasketHandlerP1', basketHandler.address) @@ -2303,6 +2489,37 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(erc20s[i]).to.equal(prevERC20s[i]) expect(quantities[i]).to.equal(prevQtys[i]) } + + // Swap collateral for asset in previous basket + const AssetFactory: ContractFactory = await ethers.getContractFactory('Asset') + const newAsset1: Asset = ( + await AssetFactory.deploy( + PRICE_TIMEOUT, + await collateral1.chainlinkFeed(), + ORACLE_ERROR, + token1.address, + config.rTokenMaxTradeVolume, + 1 + ) + ) + + await assetRegistry.connect(owner).swapRegistered(newAsset1.address) + + // Get basket for prior nonce - returns 0 qty for the non-collateral + ;[erc20s, quantities] = await bskHandlerP1.getHistoricalBasket(1) + expect(erc20s.length).to.equal(4) + expect(quantities.length).to.equal(4) + + expect(erc20s).to.eql(prevERC20s) + expect(quantities).to.eql([prevQtys[0], bn(0), prevQtys[2], prevQtys[3]]) + + // Unregister that same asset + await assetRegistry.connect(owner).unregister(newAsset1.address) + + // Returns same result as before + ;[erc20s, quantities] = await bskHandlerP1.getHistoricalBasket(1) + expect(erc20s).to.eql(prevERC20s) + expect(quantities).to.eql([prevQtys[0], bn(0), prevQtys[2], prevQtys[3]]) }) }) @@ -2393,6 +2610,23 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { ) }) + it('Should allow anyone to refresh basket if disabled and not paused/frozen', async () => { + // Set backup configuration + await basketHandler + .connect(owner) + .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [token0.address]) + + // Unregister one basket collateral + await expect(assetRegistry.connect(owner).unregister(collateral1.address)).to.emit( + assetRegistry, + 'AssetUnregistered' + ) + + expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) + + await expect(basketHandler.connect(other).refreshBasket()).to.emit(basketHandler, 'BasketSet') + }) + it('Should allow to poke when trading paused', async () => { await main.connect(owner).pauseTrading() await main.connect(other).poke() @@ -3000,6 +3234,26 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { }) }) + describeP1('BackingManagerP1', () => { + it('Should allow to cache components', async () => { + const bckMgrP1: BackingManagerP1 = await ethers.getContractAt( + 'BackingManagerP1', + backingManager.address + ) + await expect(bckMgrP1.cacheComponents()).to.not.be.reverted + }) + }) + + describeP1('RevenueTraderP1', () => { + it('Should allow to cache components', async () => { + const rsrTrader: RevenueTraderP1 = await ethers.getContractAt( + 'RevenueTraderP1', + await main.rsrTrader() + ) + await expect(rsrTrader.cacheComponents()).to.not.be.reverted + }) + }) + describeGas('Gas Reporting', () => { it('Asset Registry - Refresh', async () => { // Basket handler can run refresh diff --git a/test/RToken.test.ts b/test/RToken.test.ts index 1e018b4024..d4436ec7a1 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -2,28 +2,20 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { signERC2612Permit } from 'eth-permit' -import { BigNumber, ContractFactory } from 'ethers' +import { BigNumber } from 'ethers' import hre, { ethers } from 'hardhat' import { getChainId } from '../common/blockchain-utils' import { IConfig, ThrottleParams, MAX_THROTTLE_AMT_RATE } from '../common/configuration' -import { - BN_SCALE_FACTOR, - CollateralStatus, - MAX_UINT256, - ONE_PERIOD, - ZERO_ADDRESS, -} from '../common/constants' +import { CollateralStatus, MAX_UINT256, ONE_PERIOD, ZERO_ADDRESS } from '../common/constants' import { expectRTokenPrice, setOraclePrice } from './utils/oracles' -import { bn, fp, shortString, toBNDecimals } from '../common/numbers' +import { bn, fp, toBNDecimals } from '../common/numbers' import { ATokenFiatCollateral, CTokenFiatCollateral, ERC20Mock, ERC1271Mock, FacadeTest, - FiatCollateral, IAssetRegistry, - MockV3Aggregator, RTokenAsset, StaticATokenMock, TestIBackingManager, @@ -31,7 +23,7 @@ import { TestIMain, TestIRToken, USDCMock, - CTokenVaultMock, + CTokenWrapperMock, } from '../typechain' import { whileImpersonating } from './utils/impersonation' import snapshotGasCost from './utils/snapshotGasCost' @@ -47,24 +39,19 @@ import { Implementation, IMPLEMENTATION, ORACLE_ERROR, - SLOW, ORACLE_TIMEOUT, - PRICE_TIMEOUT, VERSION, } from './fixtures' import { expectEqualArrays } from './utils/matchers' -import { cartesianProduct } from './utils/cases' import { useEnv } from '#/utils/env' import { mintCollaterals } from './utils/tokens' +import { expectEvents } from '#/common/events' const BLOCKS_PER_HOUR = bn(300) const describeGas = IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip -const describeExtreme = - IMPLEMENTATION == Implementation.P1 && useEnv('EXTREME') ? describe.only : describe.skip - describe(`RTokenP${IMPLEMENTATION} contract`, () => { let owner: SignerWithAddress let addr1: SignerWithAddress @@ -76,7 +63,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { let token0: ERC20Mock let token1: USDCMock let token2: StaticATokenMock - let token3: CTokenVaultMock + let token3: CTokenWrapperMock let tokens: ERC20Mock[] let collateral0: Collateral @@ -123,8 +110,8 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { token2 = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - token3 = ( - await ethers.getContractAt('CTokenVaultMock', await collateral3.erc20()) + token3 = ( + await ethers.getContractAt('CTokenWrapperMock', await collateral3.erc20()) ) tokens = [token0, token1, token2, token3] @@ -1032,6 +1019,40 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.totalSupply()).to.equal(0) }) + it('Should handle an extremely small redeem #fast', async function () { + expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) + + const reedemAmt = bn(10) + + // Skips transfers for tokens with less than 18 decimals + await expectEvents(rToken.connect(addr1).redeem(reedemAmt), [ + { + contract: token0, + name: 'Transfer', + emitted: true, + }, + { + contract: token1, + name: 'Transfer', + emitted: false, + }, + { + contract: token2, + name: 'Transfer', + emitted: true, + }, + { + contract: token3, + name: 'Transfer', + emitted: false, + }, + ]) + + // Checkbalances + expect(await rToken.totalSupply()).to.equal(issueAmount.sub(reedemAmt)) + expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount.sub(reedemAmt)) + }) + it('Should redeem if paused #fast', async function () { await main.connect(owner).pauseTrading() await main.connect(owner).pauseIssuance() @@ -2706,183 +2727,6 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) }) - describeExtreme(`Extreme Values ${SLOW ? 'slow mode' : 'fast mode'}`, () => { - // makeColl: Deploy and register a new constant-price collateral - async function makeColl(index: number | string): Promise { - const ERC20: ContractFactory = await ethers.getContractFactory('ERC20Mock') - const erc20: ERC20Mock = await ERC20.deploy('Token ' + index, 'T' + index) - const OracleFactory: ContractFactory = await ethers.getContractFactory('MockV3Aggregator') - const oracle: MockV3Aggregator = await OracleFactory.deploy(8, bn('1e8')) - await oracle.deployed() // fix extreme value tests failing - const CollateralFactory: ContractFactory = await ethers.getContractFactory('FiatCollateral') - const coll: FiatCollateral = await CollateralFactory.deploy({ - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: oracle.address, - oracleError: ORACLE_ERROR, - erc20: erc20.address, - maxTradeVolume: fp('1e36'), - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.01'), - delayUntilDefault: bn(86400), - }) - await assetRegistry.register(coll.address) - expect(await assetRegistry.isRegistered(erc20.address)).to.be.true - await backingManager.grantRTokenAllowance(erc20.address) - return erc20 - } - - async function forceUpdateGetStatus(): Promise { - await whileImpersonating(basketHandler.address, async (bhSigner) => { - await assetRegistry.connect(bhSigner).refresh() - }) - return basketHandler.status() - } - - async function runScenario([ - toIssue, - toRedeem, - totalSupply, // in this scenario, rtoken supply _after_ issuance. - numBasketAssets, - weightFirst, // target amount per asset (weight of first asset) - weightRest, // another target amount per asset (weight of second+ assets) - issuancePctAmt, // range under test: [.000_001 to 1.0] - redemptionPctAmt, // range under test: [.000_001 to 1.0] - ]: BigNumber[]) { - // skip nonsense cases - if ( - (numBasketAssets.eq(1) && !weightRest.eq(1)) || - toRedeem.gt(totalSupply) || - toIssue.gt(totalSupply) - ) { - return - } - - // ==== Deploy and register basket collateral - - const N = numBasketAssets.toNumber() - const erc20s: ERC20Mock[] = [] - const weights: BigNumber[] = [] - let totalWeight: BigNumber = fp(0) - for (let i = 0; i < N; i++) { - const erc20 = await makeColl(i) - erc20s.push(erc20) - const currWeight = i == 0 ? weightFirst : weightRest - weights.push(currWeight) - totalWeight = totalWeight.add(currWeight) - } - expect(await forceUpdateGetStatus()).to.equal(CollateralStatus.SOUND) - - // ==== Switch Basket - - const basketAddresses: string[] = erc20s.map((erc20) => erc20.address) - await basketHandler.connect(owner).setPrimeBasket(basketAddresses, weights) - await basketHandler.connect(owner).refreshBasket() - expect(await forceUpdateGetStatus()).to.equal(CollateralStatus.SOUND) - - for (let i = 0; i < basketAddresses.length; i++) { - expect(await basketHandler.quantity(basketAddresses[i])).to.equal(weights[i]) - } - - // ==== Mint basket tokens to owner and addr1 - - const toIssue0 = totalSupply.sub(toIssue) - const e18 = BN_SCALE_FACTOR - for (let i = 0; i < N; i++) { - const erc20: ERC20Mock = erc20s[i] - // user owner starts with enough basket assets to issue (totalSupply - toIssue) - const toMint0: BigNumber = toIssue0.mul(weights[i]).add(e18.sub(1)).div(e18) - await erc20.mint(owner.address, toMint0) - await erc20.connect(owner).increaseAllowance(rToken.address, toMint0) - - // user addr1 starts with enough basket assets to issue (toIssue) - const toMint: BigNumber = toIssue.mul(weights[i]).add(e18.sub(1)).div(e18) - await erc20.mint(addr1.address, toMint) - await erc20.connect(addr1).increaseAllowance(rToken.address, toMint) - } - - // Set up throttles - const issuanceThrottleParams = { amtRate: bn('1e48'), pctRate: issuancePctAmt } - const redemptionThrottleParams = { amtRate: bn('1e48'), pctRate: redemptionPctAmt } - - await rToken.connect(owner).setIssuanceThrottleParams(issuanceThrottleParams) - await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) - - // ==== Issue the "initial" rtoken supply to owner - - expect(await rToken.balanceOf(owner.address)).to.equal(bn(0)) - if (toIssue0.gt(0)) { - await rToken.connect(owner).issue(toIssue0) - expect(await rToken.balanceOf(owner.address)).to.equal(toIssue0) - } - - // ==== Issue the toIssue supply to addr1 - - expect(await rToken.balanceOf(addr1.address)).to.equal(0) - await rToken.connect(addr1).issue(toIssue) - expect(await rToken.balanceOf(addr1.address)).to.equal(toIssue) - - // ==== Send enough rTokens to addr2 that it can redeem the amount `toRedeem` - - // owner has toIssue0 rToken, addr1 has toIssue rToken. - if (toRedeem.lte(toIssue0)) { - await rToken.connect(owner).transfer(addr2.address, toRedeem) - } else { - await rToken.connect(owner).transfer(addr2.address, toIssue0) - await rToken.connect(addr1).transfer(addr2.address, toRedeem.sub(toIssue0)) - } - expect(await rToken.balanceOf(addr2.address)).to.equal(toRedeem) - - // ==== Redeem tokens - - await rToken.connect(addr2).redeem(toRedeem) - expect(await rToken.balanceOf(addr2.address)).to.equal(0) - } - - // ==== Generate the tests - const MAX_RTOKENS = bn('1e48') - const MAX_WEIGHT = fp(1000) - const MIN_WEIGHT = fp('1e-6') - const MIN_ISSUANCE_PCT = fp('1e-6') - const MIN_REDEMPTION_PCT = fp('1e-6') - const MIN_RTOKENS = fp('1e-6') - - let paramList - - if (SLOW) { - const bounds: BigNumber[][] = [ - [MIN_RTOKENS, MAX_RTOKENS, bn('1.205e24')], // toIssue - [MIN_RTOKENS, MAX_RTOKENS, bn('4.4231e24')], // toRedeem - [MAX_RTOKENS, bn('7.907e24')], // totalSupply - [bn(1), bn(3), bn(100)], // numAssets - [MIN_WEIGHT, MAX_WEIGHT, fp('0.1')], // weightFirst - [MIN_WEIGHT, MAX_WEIGHT, fp('0.2')], // weightRest - [MIN_ISSUANCE_PCT, fp('1e-2'), fp(1)], // issuanceThrottle.pctRate - [MIN_REDEMPTION_PCT, fp('1e-2'), fp(1)], // redemptionThrottle.pctRate - ] - - paramList = cartesianProduct(...bounds) - } else { - const bounds: BigNumber[][] = [ - [MIN_RTOKENS, MAX_RTOKENS], // toIssue - [MIN_RTOKENS, MAX_RTOKENS], // toRedeem - [MAX_RTOKENS], // totalSupply - [bn(1)], // numAssets - [MIN_WEIGHT, MAX_WEIGHT], // weightFirst - [MIN_WEIGHT], // weightRest - [MIN_ISSUANCE_PCT, fp(1)], // issuanceThrottle.pctRate - [MIN_REDEMPTION_PCT, fp(1)], // redemptionThrottle.pctRate - ] - paramList = cartesianProduct(...bounds) - } - const numCases = paramList.length.toString() - paramList.forEach((params, index) => { - it(`case ${index + 1} of ${numCases}: ${params.map(shortString).join(' ')}`, async () => { - await runScenario(params) - }) - }) - }) - describeGas('Gas Reporting', () => { let issueAmount: BigNumber diff --git a/test/RTokenExtremes.test.ts b/test/RTokenExtremes.test.ts new file mode 100644 index 0000000000..f55c9f619e --- /dev/null +++ b/test/RTokenExtremes.test.ts @@ -0,0 +1,232 @@ +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { expect } from 'chai' +import { BigNumber, ContractFactory } from 'ethers' +import { ethers } from 'hardhat' +import { BN_SCALE_FACTOR, CollateralStatus } from '../common/constants' +import { bn, fp, shortString } from '../common/numbers' +import { + ERC20Mock, + FiatCollateral, + IAssetRegistry, + MockV3Aggregator, + TestIBackingManager, + TestIBasketHandler, + TestIRToken, +} from '../typechain' +import { whileImpersonating } from './utils/impersonation' +import { advanceTime } from './utils/time' +import { + Implementation, + IMPLEMENTATION, + ORACLE_ERROR, + SLOW, + ORACLE_TIMEOUT, + PRICE_TIMEOUT, + defaultFixtureNoBasket, +} from './fixtures' +import { cartesianProduct } from './utils/cases' +import { useEnv } from '#/utils/env' + +const describeExtreme = + IMPLEMENTATION == Implementation.P1 && useEnv('EXTREME') ? describe.only : describe.skip + +describe(`RTokenP${IMPLEMENTATION} contract`, () => { + let owner: SignerWithAddress + let addr1: SignerWithAddress + let addr2: SignerWithAddress + + // Main + let rToken: TestIRToken + let assetRegistry: IAssetRegistry + let backingManager: TestIBackingManager + let basketHandler: TestIBasketHandler + + beforeEach(async () => { + ;[owner, addr1, addr2] = await ethers.getSigners() + + // Deploy fixture + ;({ assetRegistry, backingManager, basketHandler, rToken } = await loadFixture( + defaultFixtureNoBasket + )) + }) + + describeExtreme(`Extreme Values ${SLOW ? 'slow mode' : 'fast mode'}`, () => { + // makeColl: Deploy and register a new constant-price collateral + async function makeColl(index: number | string): Promise { + const ERC20: ContractFactory = await ethers.getContractFactory('ERC20Mock') + const erc20: ERC20Mock = await ERC20.deploy('Token ' + index, 'T' + index) + const OracleFactory: ContractFactory = await ethers.getContractFactory('MockV3Aggregator') + const oracle: MockV3Aggregator = await OracleFactory.deploy(8, bn('1e8')) + await oracle.deployed() // fix extreme value tests failing + const CollateralFactory: ContractFactory = await ethers.getContractFactory('FiatCollateral') + const coll: FiatCollateral = await CollateralFactory.deploy({ + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: oracle.address, + oracleError: ORACLE_ERROR, + erc20: erc20.address, + maxTradeVolume: fp('1e36'), + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01'), + delayUntilDefault: bn(86400), + }) + await assetRegistry.register(coll.address) + expect(await assetRegistry.isRegistered(erc20.address)).to.be.true + await backingManager.grantRTokenAllowance(erc20.address) + return erc20 + } + + async function forceUpdateGetStatus(): Promise { + await whileImpersonating(basketHandler.address, async (bhSigner) => { + await assetRegistry.connect(bhSigner).refresh() + }) + return basketHandler.status() + } + + async function runScenario([ + toIssue, + toRedeem, + totalSupply, // in this scenario, rtoken supply _after_ issuance. + numBasketAssets, + weightFirst, // target amount per asset (weight of first asset) + weightRest, // another target amount per asset (weight of second+ assets) + issuancePctAmt, // range under test: [.000_001 to 1.0] + redemptionPctAmt, // range under test: [.000_001 to 1.0] + ]: BigNumber[]) { + // skip nonsense cases + if ( + (numBasketAssets.eq(1) && !weightRest.eq(1)) || + toRedeem.gt(totalSupply) || + toIssue.gt(totalSupply) + ) { + return + } + + // ==== Deploy and register basket collateral + + const N = numBasketAssets.toNumber() + const erc20s: ERC20Mock[] = [] + const weights: BigNumber[] = [] + let totalWeight: BigNumber = fp(0) + for (let i = 0; i < N; i++) { + const erc20 = await makeColl(i) + erc20s.push(erc20) + const currWeight = i == 0 ? weightFirst : weightRest + weights.push(currWeight) + totalWeight = totalWeight.add(currWeight) + } + expect(await forceUpdateGetStatus()).to.equal(CollateralStatus.DISABLED) + + // ==== Switch Basket + + const basketAddresses: string[] = erc20s.map((erc20) => erc20.address) + await basketHandler.connect(owner).setPrimeBasket(basketAddresses, weights) + await basketHandler.connect(owner).refreshBasket() + expect(await forceUpdateGetStatus()).to.equal(CollateralStatus.SOUND) + + for (let i = 0; i < basketAddresses.length; i++) { + expect(await basketHandler.quantity(basketAddresses[i])).to.equal(weights[i]) + } + + // ==== Mint basket tokens to owner and addr1 + + const toIssue0 = totalSupply.sub(toIssue) + const e18 = BN_SCALE_FACTOR + for (let i = 0; i < N; i++) { + const erc20: ERC20Mock = erc20s[i] + // user owner starts with enough basket assets to issue (totalSupply - toIssue) + const toMint0: BigNumber = toIssue0.mul(weights[i]).add(e18.sub(1)).div(e18) + await erc20.mint(owner.address, toMint0) + await erc20.connect(owner).increaseAllowance(rToken.address, toMint0) + + // user addr1 starts with enough basket assets to issue (toIssue) + const toMint: BigNumber = toIssue.mul(weights[i]).add(e18.sub(1)).div(e18) + await erc20.mint(addr1.address, toMint) + await erc20.connect(addr1).increaseAllowance(rToken.address, toMint) + } + + // Set up throttles + const issuanceThrottleParams = { amtRate: bn('1e48'), pctRate: issuancePctAmt } + const redemptionThrottleParams = { amtRate: bn('1e48'), pctRate: redemptionPctAmt } + + await rToken.connect(owner).setIssuanceThrottleParams(issuanceThrottleParams) + await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + + await advanceTime(await basketHandler.warmupPeriod()) + + // ==== Issue the "initial" rtoken supply to owner + expect(await rToken.balanceOf(owner.address)).to.equal(bn(0)) + if (toIssue0.gt(0)) { + await rToken.connect(owner).issue(toIssue0) + expect(await rToken.balanceOf(owner.address)).to.equal(toIssue0) + } + + // ==== Issue the toIssue supply to addr1 + + expect(await rToken.balanceOf(addr1.address)).to.equal(0) + await rToken.connect(addr1).issue(toIssue) + expect(await rToken.balanceOf(addr1.address)).to.equal(toIssue) + + // ==== Send enough rTokens to addr2 that it can redeem the amount `toRedeem` + + // owner has toIssue0 rToken, addr1 has toIssue rToken. + if (toRedeem.lte(toIssue0)) { + await rToken.connect(owner).transfer(addr2.address, toRedeem) + } else { + await rToken.connect(owner).transfer(addr2.address, toIssue0) + await rToken.connect(addr1).transfer(addr2.address, toRedeem.sub(toIssue0)) + } + expect(await rToken.balanceOf(addr2.address)).to.equal(toRedeem) + + // ==== Redeem tokens + + await rToken.connect(addr2).redeem(toRedeem) + expect(await rToken.balanceOf(addr2.address)).to.equal(0) + } + + // ==== Generate the tests + const MAX_RTOKENS = bn('1e48') + const MAX_WEIGHT = fp(1000) + const MIN_WEIGHT = fp('1e-6') + const MIN_ISSUANCE_PCT = fp('1e-6') + const MIN_REDEMPTION_PCT = fp('1e-6') + const MIN_RTOKENS = fp('1e-6') + + let paramList + + if (SLOW) { + const bounds: BigNumber[][] = [ + [MIN_RTOKENS, MAX_RTOKENS, bn('1.205e24')], // toIssue + [MIN_RTOKENS, MAX_RTOKENS, bn('4.4231e24')], // toRedeem + [MAX_RTOKENS, bn('7.907e24')], // totalSupply + [bn(1), bn(3), bn(100)], // numAssets + [MIN_WEIGHT, MAX_WEIGHT, fp('0.1')], // weightFirst + [MIN_WEIGHT, MAX_WEIGHT, fp('0.2')], // weightRest + [MIN_ISSUANCE_PCT, fp('1e-2'), fp(1)], // issuanceThrottle.pctRate + [MIN_REDEMPTION_PCT, fp('1e-2'), fp(1)], // redemptionThrottle.pctRate + ] + + paramList = cartesianProduct(...bounds) + } else { + const bounds: BigNumber[][] = [ + [MIN_RTOKENS, MAX_RTOKENS], // toIssue + [MIN_RTOKENS, MAX_RTOKENS], // toRedeem + [MAX_RTOKENS], // totalSupply + [bn(1)], // numAssets + [MIN_WEIGHT, MAX_WEIGHT], // weightFirst + [MIN_WEIGHT], // weightRest + [MIN_ISSUANCE_PCT, fp(1)], // issuanceThrottle.pctRate + [MIN_REDEMPTION_PCT, fp(1)], // redemptionThrottle.pctRate + ] + paramList = cartesianProduct(...bounds) + } + const numCases = paramList.length.toString() + + paramList.forEach((params, index) => { + it(`case ${index + 1} of ${numCases}: ${params.map(shortString).join(' ')}`, async () => { + await runScenario(params) + }) + }) + }) +}) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 86cd3e88b2..21225ecbcb 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -19,7 +19,7 @@ import { ATokenFiatCollateral, CTokenMock, DutchTrade, - CTokenVaultMock, + CTokenWrapperMock, ERC20Mock, FacadeRead, FacadeTest, @@ -81,7 +81,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { let token0: ERC20Mock let token1: USDCMock let token2: StaticATokenMock - let token3Vault: CTokenVaultMock + let token3Vault: CTokenWrapperMock let token3: CTokenMock let backupToken1: ERC20Mock let backupToken2: ERC20Mock @@ -173,10 +173,10 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { token0 = erc20s[collateral.indexOf(basket[0])] token1 = erc20s[collateral.indexOf(basket[1])] token2 = erc20s[collateral.indexOf(basket[2])] - token3Vault = ( - await ethers.getContractAt('CTokenVaultMock', await basket[3].erc20()) + token3Vault = ( + await ethers.getContractAt('CTokenWrapperMock', await basket[3].erc20()) ) - token3 = await ethers.getContractAt('CTokenMock', await token3Vault.asset()) + token3 = await ethers.getContractAt('CTokenMock', await token3Vault.underlying()) // Set Aave revenue token await token2.setAaveToken(aaveToken.address) @@ -1009,6 +1009,20 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) }) + it('Should not settle trades if trading paused', async () => { + await main.connect(owner).pauseTrading() + await expect(backingManager.settleTrade(token0.address)).to.be.revertedWith( + 'frozen or trading paused' + ) + }) + + it('Should not settle trades if frozen', async () => { + await main.connect(owner).freezeShort() + await expect(backingManager.settleTrade(token0.address)).to.be.revertedWith( + 'frozen or trading paused' + ) + }) + context('Should successfully recollateralize after governance basket switch', () => { afterEach(async () => { // Should be fully capitalized again diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 904cddcb6f..4e4314224b 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -4,7 +4,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { BigNumber, ContractFactory, Wallet } from 'ethers' import { ethers, upgrades } from 'hardhat' -import { IConfig } from '../common/configuration' +import { IConfig, GNOSIS_MAX_TOKENS } from '../common/configuration' import { BN_SCALE_FACTOR, FURNACE_DEST, @@ -12,6 +12,7 @@ import { ZERO_ADDRESS, CollateralStatus, TradeKind, + MAX_UINT192, } from '../common/constants' import { expectEvents } from '../common/events' import { bn, divCeil, fp, near } from '../common/numbers' @@ -20,7 +21,7 @@ import { ATokenFiatCollateral, ComptrollerMock, CTokenFiatCollateral, - CTokenVaultMock, + CTokenWrapperMock, ERC20Mock, FacadeTest, GnosisMock, @@ -88,7 +89,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { let token0: ERC20Mock let token1: USDCMock let token2: StaticATokenMock - let token3: CTokenVaultMock + let token3: CTokenWrapperMock let collateral0: FiatCollateral let collateral1: FiatCollateral let collateral2: ATokenFiatCollateral @@ -182,8 +183,8 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { token2 = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - token3 = ( - await ethers.getContractAt('CTokenVaultMock', await collateral3.erc20()) + token3 = ( + await ethers.getContractAt('CTokenWrapperMock', await collateral3.erc20()) ) // Mint initial balances @@ -449,6 +450,119 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { .withArgs(anyValue, token0.address, rToken.address, issueAmount, withinQuad(minBuyAmt)) }) + it('Should forward revenue to traders', async () => { + const rewardAmt = bn('100e18') + await token2.setRewards(backingManager.address, rewardAmt) + await backingManager.claimRewardsSingle(token2.address) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmt) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(0) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) + + await expect(backingManager.forwardRevenue([aaveToken.address])).to.emit( + aaveToken, + 'Transfer' + ) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(0) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(rewardAmt.mul(60).div(100)) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(rewardAmt.mul(40).div(100)) + }) + + it('Should not forward revenue if basket not ready', async () => { + const rewardAmt = bn('100e18') + await token2.setRewards(backingManager.address, rewardAmt) + await backingManager.claimRewardsSingle(token2.address) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmt) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(0) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) + + // Default a token and update basket status + await setOraclePrice(collateral0.address, bn('0.5e8')) + await collateral0.refresh() + expect(await collateral0.status()).to.equal(CollateralStatus.IFFY) + await basketHandler.trackStatus() + + // Cannot forward if not SOUND + await expect(backingManager.forwardRevenue([aaveToken.address])).to.be.revertedWith( + 'basket not ready' + ) + + // Regain SOUND + await setOraclePrice(collateral0.address, bn('1e8')) + await collateral0.refresh() + expect(await collateral0.status()).to.equal(CollateralStatus.SOUND) + await basketHandler.trackStatus() + + // Still cannot forward revenue, in warmup period + await expect(backingManager.forwardRevenue([aaveToken.address])).to.be.revertedWith( + 'basket not ready' + ) + + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmt) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(0) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) + + // Advance time post warmup period + await advanceTime(Number(config.warmupPeriod) + 1) + + // Now we can forward revenue successfully + await expect(backingManager.forwardRevenue([aaveToken.address])).to.emit( + aaveToken, + 'Transfer' + ) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(0) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(rewardAmt.mul(60).div(100)) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(rewardAmt.mul(40).div(100)) + }) + + it('Should not forward revenue if paused', async () => { + const rewardAmt = bn('100e18') + await token2.setRewards(backingManager.address, rewardAmt) + await backingManager.claimRewardsSingle(token2.address) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmt) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(0) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) + + // Pause + await main.connect(owner).pauseTrading() + + // Cannot forward revenue + await expect(backingManager.forwardRevenue([aaveToken.address])).to.be.revertedWith( + 'frozen or trading paused' + ) + }) + + it('Should not forward revenue if frozen', async () => { + const rewardAmt = bn('100e18') + await token2.setRewards(backingManager.address, rewardAmt) + await backingManager.claimRewardsSingle(token2.address) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmt) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(0) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) + + // Pause + await main.connect(owner).freezeShort() + + // Cannot forward revenue + await expect(backingManager.forwardRevenue([aaveToken.address])).to.be.revertedWith( + 'frozen or trading paused' + ) + }) + + it('Should forward RSR revenue directly to StRSR', async () => { + const amount = bn('2000e18') + await rsr.connect(owner).mint(backingManager.address, amount) + expect(await rsr.balanceOf(backingManager.address)).to.equal(amount) + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rsr.balanceOf(rsrTrader.address)).to.equal(0) + expect(await rsr.balanceOf(rTokenTrader.address)).to.equal(0) + + await expect(backingManager.forwardRevenue([rsr.address])).to.emit(rsr, 'Transfer') + expect(await rsr.balanceOf(backingManager.address)).to.equal(0) + expect(await rsr.balanceOf(stRSR.address)).to.equal(amount) + expect(await rsr.balanceOf(rsrTrader.address)).to.equal(0) + expect(await rsr.balanceOf(rTokenTrader.address)).to.equal(0) + }) + it('Should not launch revenue auction if UNPRICED', async () => { await advanceTime(ORACLE_TIMEOUT.toString()) await rsr.connect(addr1).transfer(rTokenTrader.address, issueAmount) @@ -802,6 +916,53 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(furnace.address)).to.equal(0) }) + it('Should handle GNOSIS_MAX_TOKENS cap in BATCH_AUCTION', async () => { + // Set Max trade volume very high for both assets in trade + const chainlinkFeed = ( + await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) + ) + const newSellAsset: Asset = ( + await AssetFactory.deploy( + PRICE_TIMEOUT, + chainlinkFeed.address, + ORACLE_ERROR, + aaveToken.address, + MAX_UINT192, + ORACLE_TIMEOUT + ) + ) + + const newRSRAsset: Asset = ( + await AssetFactory.deploy( + PRICE_TIMEOUT, + await rsrAsset.chainlinkFeed(), + ORACLE_ERROR, + rsr.address, + MAX_UINT192, + ORACLE_TIMEOUT + ) + ) + + // Perform asset swap + await assetRegistry.connect(owner).swapRegistered(newSellAsset.address) + await assetRegistry.connect(owner).swapRegistered(newRSRAsset.address) + await basketHandler.refreshBasket() + + // Set rewards manually + const amount = MAX_UINT192 + await aaveToken.connect(owner).mint(rsrTrader.address, amount) + + const p1RevenueTrader = await ethers.getContractAt('RevenueTraderP1', rsrTrader.address) + + await expect( + p1RevenueTrader.manageToken(aaveToken.address, TradeKind.BATCH_AUCTION) + ).to.emit(rsrTrader, 'TradeStarted') + + // Check trade is using the GNOSIS limits + const trade = await getTrade(rsrTrader, aaveToken.address) + expect(await trade.initBal()).to.equal(GNOSIS_MAX_TOKENS) + }) + it('Should claim AAVE and handle revenue auction correctly - small amount processed in single auction', async () => { rewardAmountAAVE = bn('0.5e18') @@ -2236,6 +2397,50 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await rTokenTrader.manageToken(token1.address, TradeKind.DUTCH_AUCTION) }) + it('Should not return bid amount before auction starts', async () => { + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + + const trade = await ethers.getContractAt( + 'DutchTrade', + await rTokenTrader.trades(token0.address) + ) + + // Cannot get bid amount yet + await expect( + trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + ).to.be.revertedWith('auction not started') + + // Advance to start time + const start = await trade.startTime() + await advanceToTimestamp(start) + + // Now we can get bid amount + const actual = await trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + expect(actual).to.be.gt(bn(0)) + }) + + it('Should allow one bidder', async () => { + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount.div(2)) + await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + + const trade = await ethers.getContractAt( + 'DutchTrade', + await rTokenTrader.trades(token0.address) + ) + + // Advance to auction on-going + await advanceToTimestamp((await trade.endTime()) - 1000) + + // Bid + await rToken.connect(addr1).approve(trade.address, initialBal) + await trade.connect(addr1).bid() + expect(await trade.bidder()).to.equal(addr1.address) + + // Cannot bid once is settled + await expect(trade.connect(addr1).bid()).to.be.revertedWith('bid already received') + }) + it('Should quote piecewise-falling price correctly throughout entirety of auction', async () => { issueAmount = issueAmount.div(10000) await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) @@ -2277,6 +2482,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await rTokenTrader.trades(token0.address) ) await rToken.connect(addr1).approve(trade.address, initialBal) + await advanceToTimestamp((await trade.endTime()) + 1) await expect( trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) @@ -3114,6 +3320,46 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rToken.totalSupply()).to.equal(mintAmt.mul(2)) expect(await rToken.basketsNeeded()).to.equal(mintAmt.mul(2)) }) + + it('Should not forward revenue before trading delay', async () => { + // Set trading delay + const newDelay = 3600 + await backingManager.connect(owner).setTradingDelay(newDelay) // 1 hour + + const rewardAmt = bn('100e18') + await token2.setRewards(backingManager.address, rewardAmt) + await backingManager.claimRewardsSingle(token2.address) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmt) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(0) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) + + // Switch basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(3, [token1.address], [fp('1')], false) + + // Cannot forward revenue yet + await expect(backingManager.forwardRevenue([aaveToken.address])).to.be.revertedWith( + 'trading delayed' + ) + + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(rewardAmt) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(0) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) + + // Advance time post trading delay + await advanceTime(newDelay + 1) + + // Now we can forward revenue successfully + await expect(backingManager.forwardRevenue([aaveToken.address])).to.emit( + aaveToken, + 'Transfer' + ) + expect(await aaveToken.balanceOf(backingManager.address)).to.equal(0) + expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(rewardAmt.mul(60).div(100)) + expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(rewardAmt.mul(40).div(100)) + }) }) }) diff --git a/test/ZTradingExtremes.test.ts b/test/ZTradingExtremes.test.ts index 103afb886d..537ba81761 100644 --- a/test/ZTradingExtremes.test.ts +++ b/test/ZTradingExtremes.test.ts @@ -11,7 +11,7 @@ import { ATokenFiatCollateral, ComptrollerMock, CTokenFiatCollateral, - CTokenVaultMock, + CTokenWrapperMock, ERC20Mock, FacadeTest, FiatCollateral, @@ -76,7 +76,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, let ERC20Mock: ContractFactory let ATokenMockFactory: ContractFactory - let CTokenVaultMockFactory: ContractFactory + let CTokenWrapperMockFactory: ContractFactory let ATokenCollateralFactory: ContractFactory let CTokenCollateralFactory: ContractFactory @@ -110,7 +110,7 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, ERC20Mock = await ethers.getContractFactory('ERC20Mock') ATokenMockFactory = await ethers.getContractFactory('StaticATokenMock') - CTokenVaultMockFactory = await ethers.getContractFactory('CTokenVaultMock') + CTokenWrapperMockFactory = await ethers.getContractFactory('CTokenWrapperMock') ATokenCollateralFactory = await ethers.getContractFactory('ATokenFiatCollateral') CTokenCollateralFactory = await ethers.getContractFactory('CTokenFiatCollateral') @@ -157,12 +157,12 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, return erc20 } - const prepCToken = async (index: number): Promise => { + const prepCToken = async (index: number): Promise => { const underlying: ERC20Mock = ( await ERC20Mock.deploy(`ERC20_NAME:${index}`, `ERC20_SYM:${index}`) ) - const erc20: CTokenVaultMock = ( - await CTokenVaultMockFactory.deploy( + const erc20: CTokenWrapperMock = ( + await CTokenWrapperMockFactory.deploy( `CToken_NAME:${index}`, `CToken_SYM:${index}`, underlying.address, diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index ecc31f6822..9f7503df5e 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -21,7 +21,7 @@ import { TestIMain, TestIRToken, TestIStRSR, - CTokenVaultMock, + CTokenWrapperMock, } from '../typechain' import { IConfig, MAX_RATIO, MAX_UNSTAKING_DELAY } from '../common/configuration' import { CollateralStatus, MAX_UINT256, ONE_PERIOD, ZERO_ADDRESS } from '../common/constants' @@ -79,7 +79,7 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { let token0: ERC20Mock let token1: ERC20Mock let token2: StaticATokenMock - let token3: CTokenVaultMock + let token3: CTokenWrapperMock let collateral0: Collateral let collateral1: Collateral let collateral2: Collateral @@ -178,8 +178,8 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { token2 = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - token3 = ( - await ethers.getContractAt('CTokenVaultMock', await collateral3.erc20()) + token3 = ( + await ethers.getContractAt('CTokenWrapperMock', await collateral3.erc20()) ) }) @@ -668,6 +668,12 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { // Exchange rate remains steady expect(await stRSR.exchangeRate()).to.equal(fp('1')) + // Cancelling the unstake with invalid index does nothing + await expect(stRSR.connect(addr1).cancelUnstake(0)).to.not.emit(stRSR, 'UnstakingCancelled') + await expect(stRSR.connect(addr1).cancelUnstake(2)).to.be.revertedWith('index out-of-bounds') + expect(await stRSR.balanceOf(addr1.address)).to.equal(0) + expect(await stRSR.totalSupply()).to.equal(0) + // Let's cancel the unstake await expect(stRSR.connect(addr1).cancelUnstake(1)).to.emit(stRSR, 'UnstakingCancelled') @@ -1165,6 +1171,12 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { expect(await stRSR.endIdForWithdraw(addr1.address)).to.equal(1) expect(await stRSR.endIdForWithdraw(addr2.address)).to.equal(3) + // Cancelling the unstake with invalid index does nothing + await expect(stRSR.connect(addr2).cancelUnstake(1)).to.not.emit(stRSR, 'UnstakingCancelled') + await expect(stRSR.connect(addr2).cancelUnstake(4)).to.be.revertedWith( + 'index out-of-bounds' + ) + // Withdraw await stRSR .connect(addr1) @@ -2000,6 +2012,74 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { expect(await stRSR.totalSupply()).to.equal(amount.sub(one)) }) + it('Should handle cancel unstake after a significant RSR seizure', async () => { + stkWithdrawalDelay = bn(await stRSR.unstakingDelay()).toNumber() + + const unstakeAmount: BigNumber = fp('1e-9') + const amount: BigNumber = bn('1e18').add(unstakeAmount).add(1) + + // Stake enough for 2 unstakings + await rsr.connect(addr1).approve(stRSR.address, amount.add(1)) + await stRSR.connect(addr1).stake(amount.add(1)) + + // Check balances and stakes + expect(await rsr.balanceOf(stRSR.address)).to.equal(amount.add(1)) + expect(await rsr.balanceOf(stRSR.address)).to.equal(await stRSR.totalSupply()) + expect(await rsr.balanceOf(addr1.address)).to.equal(initialBal.sub(amount.add(1))) + expect(await stRSR.balanceOf(addr1.address)).to.equal(amount.add(1)) + + // Unstake twice + const availableAt = (await getLatestBlockTimestamp()) + config.unstakingDelay.toNumber() + 1 + // Set next block timestamp - for deterministic result + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 1) + + await expect(stRSR.connect(addr1).unstake(1)) + .emit(stRSR, 'UnstakingStarted') + .withArgs(0, 1, addr1.address, 1, 1, availableAt) + await expect(stRSR.connect(addr1).unstake(unstakeAmount)) + .emit(stRSR, 'UnstakingStarted') + .withArgs(1, 1, addr1.address, unstakeAmount, unstakeAmount, availableAt + 1) + + // Check balances and stakes + expect(await rsr.balanceOf(stRSR.address)).to.equal(amount.add(1)) + expect(await rsr.balanceOf(addr1.address)).to.equal(initialBal.sub(amount.add(1))) + + // All staked funds withdrawn upfront + expect(await stRSR.balanceOf(addr1.address)).to.equal(amount.sub(unstakeAmount)) + expect(await stRSR.totalSupply()).to.equal(amount.sub(unstakeAmount)) + + // Rate does not change + expect(await stRSR.exchangeRate()).to.equal(fp('1')) + + // Seize most of the RSR + const seizeAmt = fp('0.99999999').mul(amount).div(fp('1')).add(1) + const exchangeRate = fp('1e-8') + await whileImpersonating(backingManager.address, async (signer) => { + await expect(stRSR.connect(signer).seizeRSR(seizeAmt)).to.emit(stRSR, 'ExchangeRateSet') + }) + + // Check new rate + expect(await stRSR.exchangeRate()).to.be.closeTo(exchangeRate, bn(10)) + + // Check balances and stakes + expect(await rsr.balanceOf(stRSR.address)).to.equal(exchangeRate.add(10)) + expect(await stRSR.totalSupply()).to.equal(amount.sub(unstakeAmount)) + expect(await rsr.balanceOf(addr1.address)).to.equal(initialBal.sub(amount.add(1))) + expect(await stRSR.balanceOf(addr1.address)).to.equal(amount.sub(unstakeAmount)) + + // Move forward past stakingWithdrawalDelay + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay) + + // Cancel the larger unstake -- should round down to 0 + await stRSR.connect(addr1).cancelUnstake(1) + + // Check balances and stakes - No changes + expect(await rsr.balanceOf(stRSR.address)).to.equal(exchangeRate.add(10)) + expect(await stRSR.totalSupply()).to.equal(amount.sub(unstakeAmount)) + expect(await rsr.balanceOf(addr1.address)).to.equal(initialBal.sub(amount.add(1))) + expect(await stRSR.balanceOf(addr1.address)).to.equal(amount.sub(unstakeAmount)) + }) + it('Should not allow stakeRate manipulation', async () => { // send RSR to stRSR (attempt to manipulate stake rate) await rsr.connect(addr1).transfer(stRSR.address, fp('200')) @@ -2092,13 +2172,13 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { // Attempt to send to zero address await expect(stRSR.connect(addr1).transfer(ZERO_ADDRESS, amount)).to.be.revertedWith( - 'ERC20: transfer to the zero address' + 'ERC20: transfer to or from the zero address' ) // Attempt to send from zero address - Impersonation is the only way to get to this validation await whileImpersonating(ZERO_ADDRESS, async (signer) => { await expect(stRSR.connect(signer).transfer(addr2.address, amount)).to.be.revertedWith( - 'ERC20: transfer from the zero address' + 'ERC20: transfer to or from the zero address' ) }) @@ -2305,13 +2385,13 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { // Attempt to set allowance to zero address await expect(stRSR.connect(addr1).approve(ZERO_ADDRESS, amount)).to.be.revertedWith( - 'ERC20: approve to the zero address' + 'ERC20: approve to or from the zero address' ) // Attempt set allowance from zero address - Impersonation is the only way to get to this validation await whileImpersonating(ZERO_ADDRESS, async (signer) => { await expect(stRSR.connect(signer).approve(addr2.address, amount)).to.be.revertedWith( - 'ERC20: approve from the zero address' + 'ERC20: approve to or from the zero address' ) }) diff --git a/test/fixtures.ts b/test/fixtures.ts index ac8db13cce..2f20d2a669 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -17,7 +17,7 @@ import { ComptrollerMock, CTokenFiatCollateral, CTokenSelfReferentialCollateral, - CTokenVaultMock, + CTokenWrapperMock, ERC20Mock, DeployerP0, DeployerP1, @@ -165,7 +165,9 @@ async function collateralFixture( const ERC20: ContractFactory = await ethers.getContractFactory('ERC20Mock') const USDC: ContractFactory = await ethers.getContractFactory('USDCMock') const ATokenMockFactory: ContractFactory = await ethers.getContractFactory('StaticATokenMock') - const CTokenVaultMockFactory: ContractFactory = await ethers.getContractFactory('CTokenVaultMock') + const CTokenWrapperMockFactory: ContractFactory = await ethers.getContractFactory( + 'CTokenWrapperMock' + ) const FiatCollateralFactory: ContractFactory = await ethers.getContractFactory('FiatCollateral') const ATokenCollateralFactory = await ethers.getContractFactory('ATokenFiatCollateral') const CTokenCollateralFactory = await ethers.getContractFactory('CTokenFiatCollateral') @@ -240,9 +242,9 @@ async function collateralFixture( referenceERC20: ERC20Mock, chainlinkAddr: string, compToken: ERC20Mock - ): Promise<[CTokenVaultMock, CTokenFiatCollateral]> => { - const erc20: CTokenVaultMock = ( - await CTokenVaultMockFactory.deploy( + ): Promise<[CTokenWrapperMock, CTokenFiatCollateral]> => { + const erc20: CTokenWrapperMock = ( + await CTokenWrapperMockFactory.deploy( symbol + ' Token', symbol, referenceERC20.address, diff --git a/test/integration/AssetPlugins.test.ts b/test/integration/AssetPlugins.test.ts index 9638125005..672f5566de 100644 --- a/test/integration/AssetPlugins.test.ts +++ b/test/integration/AssetPlugins.test.ts @@ -46,7 +46,7 @@ import { TestIRToken, USDCMock, WETH9, - CTokenVault, + CTokenWrapper, } from '../../typechain' import { useEnv } from '#/utils/env' @@ -110,20 +110,20 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, let stataUsdp: StaticATokenLM let cDai: CTokenMock - let cDaiVault: CTokenVault + let cDaiVault: CTokenWrapper let cUsdc: CTokenMock - let cUsdcVault: CTokenVault + let cUsdcVault: CTokenWrapper let cUsdt: CTokenMock - let cUsdtVault: CTokenVault + let cUsdtVault: CTokenWrapper let cUsdp: CTokenMock - let cUsdpVault: CTokenVault + let cUsdpVault: CTokenWrapper let wbtc: ERC20Mock let cWBTC: CTokenMock - let cWBTCVault: CTokenVault + let cWBTCVault: CTokenWrapper let weth: ERC20Mock let cETH: CTokenMock - let cETHVault: CTokenVault + let cETHVault: CTokenWrapper let eurt: ERC20Mock let daiCollateral: FiatCollateral @@ -228,44 +228,59 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, busd = erc20s[3] // BUSD usdp = erc20s[4] // USDP tusd = erc20s[5] // TUSD - cDaiVault = erc20s[6] // cDAI - cDai = await ethers.getContractAt('CTokenMock', await cDaiVault.asset()) // cDAI - cUsdcVault = erc20s[7] // cUSDC - cUsdc = await ethers.getContractAt('CTokenMock', await cUsdcVault.asset()) // cUSDC - cUsdtVault = erc20s[8] // cUSDT - cUsdt = await ethers.getContractAt('CTokenMock', await cUsdtVault.asset()) // cUSDT - cUsdpVault = erc20s[9] // cUSDT - cUsdp = await ethers.getContractAt('CTokenMock', await cUsdpVault.asset()) // cUSDT + cDaiVault = erc20s[6] // cDAI + cDai = await ethers.getContractAt('CTokenMock', await cDaiVault.underlying()) // cDAI + cUsdcVault = erc20s[7] // cUSDC + cUsdc = await ethers.getContractAt('CTokenMock', await cUsdcVault.underlying()) // cUSDC + cUsdtVault = erc20s[8] // cUSDT + cUsdt = await ethers.getContractAt('CTokenMock', await cUsdtVault.underlying()) // cUSDT + cUsdpVault = erc20s[9] // cUSDT + cUsdp = await ethers.getContractAt('CTokenMock', await cUsdpVault.underlying()) // cUSDT stataDai = erc20s[10] // static aDAI stataUsdc = erc20s[11] // static aUSDC stataUsdt = erc20s[12] // static aUSDT stataBusd = erc20s[13] // static aBUSD stataUsdp = erc20s[14] // static aUSDP wbtc = erc20s[15] // wBTC - cWBTCVault = erc20s[16] // cWBTC - cWBTC = await ethers.getContractAt('CTokenMock', await cWBTCVault.asset()) // cWBTC + cWBTCVault = erc20s[16] // cWBTC + cWBTC = await ethers.getContractAt('CTokenMock', await cWBTCVault.underlying()) // cWBTC weth = erc20s[17] // wETH - cETHVault = erc20s[18] // cETH - cETH = await ethers.getContractAt('CTokenMock', await cETHVault.asset()) // cETH + cETHVault = erc20s[18] // cETH + cETH = await ethers.getContractAt('CTokenMock', await cETHVault.underlying()) // cETH eurt = erc20s[19] // eurt // Get plain aTokens aDai = ( - await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aDAI || '') + await ethers.getContractAt( + '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', + networkConfig[chainId].tokens.aDAI || '' + ) ) aUsdc = ( - await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aUSDC || '') + await ethers.getContractAt( + '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', + networkConfig[chainId].tokens.aUSDC || '' + ) ) aUsdt = ( - await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aUSDT || '') + await ethers.getContractAt( + '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', + networkConfig[chainId].tokens.aUSDT || '' + ) ) aBusd = ( - await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aBUSD || '') + await ethers.getContractAt( + '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', + networkConfig[chainId].tokens.aBUSD || '' + ) ) aUsdp = ( - await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aUSDP || '') + await ethers.getContractAt( + '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', + networkConfig[chainId].tokens.aUSDP || '' + ) ) // Get collaterals daiCollateral = collateral[0] // DAI @@ -301,7 +316,10 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Get plain aToken aDai = ( - await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aDAI || '') + await ethers.getContractAt( + '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', + networkConfig[chainId].tokens.aDAI || '' + ) ) // Setup balances for addr1 - Transfer from Mainnet holders DAI, cDAI and aDAI (for default basket) @@ -321,7 +339,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await whileImpersonating(holderCDAI, async (cdaiSigner) => { await cDai.connect(cdaiSigner).transfer(addr1.address, toBNDecimals(initialBal, 8).mul(100)) await cDai.connect(addr1).approve(cDaiVault.address, toBNDecimals(initialBal, 8).mul(100)) - await cDaiVault.connect(addr1).mint(toBNDecimals(initialBal, 17).mul(100), addr1.address) + await cDaiVault.connect(addr1).deposit(toBNDecimals(initialBal, 8).mul(100), addr1.address) }) // Setup balances for USDT @@ -346,7 +364,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, .approve(cWBTCVault.address, toBNDecimals(initialBalBtcEth, 8).mul(1000)) await cWBTCVault .connect(addr1) - .mint(toBNDecimals(initialBalBtcEth, 17).mul(1000), addr1.address) + .deposit(toBNDecimals(initialBalBtcEth, 8).mul(1000), addr1.address) }) // WETH @@ -364,7 +382,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, .approve(cETHVault.address, toBNDecimals(initialBalBtcEth, 8).mul(1000)) await cETHVault .connect(addr1) - .mint(toBNDecimals(initialBalBtcEth, 17).mul(1000), addr1.address) + .deposit(toBNDecimals(initialBalBtcEth, 8).mul(1000), addr1.address) }) //EURT @@ -513,7 +531,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: ERC20Mock tokenAddress: string cToken: CTokenMock - cTokenVault: CTokenVault + cTokenVault: CTokenWrapper cTokenAddress: string cTokenCollateral: CTokenFiatCollateral pegPrice: BigNumber @@ -572,9 +590,9 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await ctkInf.token.decimals() ) expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) - expect(await ctkInf.cTokenVault.asset()).to.equal(ctkInf.cTokenAddress) + expect(await ctkInf.cTokenVault.underlying()).to.equal(ctkInf.cTokenAddress) expect(await ctkInf.cToken.decimals()).to.equal(8) - expect(await ctkInf.cTokenVault.decimals()).to.equal(17) + expect(await ctkInf.cTokenVault.decimals()).to.equal(8) expect(await ctkInf.cTokenCollateral.targetName()).to.equal( ethers.utils.formatBytes32String('USD') ) @@ -802,7 +820,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: ERC20Mock tokenAddress: string cToken: CTokenMock - cTokenVault: CTokenVault + cTokenVault: CTokenWrapper cTokenAddress: string cTokenCollateral: CTokenNonFiatCollateral targetPrice: BigNumber @@ -835,9 +853,9 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await ctkInf.token.decimals() ) expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) - expect(await ctkInf.cTokenVault.asset()).to.equal(ctkInf.cTokenAddress) + expect(await ctkInf.cTokenVault.underlying()).to.equal(ctkInf.cTokenAddress) expect(await ctkInf.cToken.decimals()).to.equal(8) - expect(await ctkInf.cTokenVault.decimals()).to.equal(17) + expect(await ctkInf.cTokenVault.decimals()).to.equal(8) expect(await ctkInf.cTokenCollateral.targetName()).to.equal( ethers.utils.formatBytes32String(ctkInf.targetName) ) @@ -933,7 +951,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, token: ERC20Mock tokenAddress: string cToken: CTokenMock - cTokenVault: CTokenVault + cTokenVault: CTokenWrapper cTokenAddress: string cTokenCollateral: CTokenSelfReferentialCollateral price: BigNumber @@ -964,9 +982,9 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await ctkInf.token.decimals() ) expect(await ctkInf.cTokenCollateral.erc20()).to.equal(ctkInf.cTokenVault.address) - expect(await ctkInf.cTokenVault.asset()).to.equal(ctkInf.cTokenAddress) + expect(await ctkInf.cTokenVault.underlying()).to.equal(ctkInf.cTokenAddress) expect(await ctkInf.cToken.decimals()).to.equal(8) - expect(await ctkInf.cTokenVault.decimals()).to.equal(17) + expect(await ctkInf.cTokenVault.decimals()).to.equal(8) expect(await ctkInf.cTokenCollateral.targetName()).to.equal( ethers.utils.formatBytes32String(ctkInf.targetName) ) @@ -1750,7 +1768,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await stataDai.connect(addr1).approve(rToken.address, issueAmount) await cDaiVault .connect(addr1) - .approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) + .approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') await expectRTokenPrice( @@ -1762,7 +1780,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, ) }) - it('Should issue/reedem correctly with simple basket ', async function () { + it('Should issue/reedem correctly with simple basket', async function () { const MIN_ISSUANCE_PER_BLOCK = bn('10000e18') const issueAmount: BigNumber = MIN_ISSUANCE_PER_BLOCK @@ -1776,7 +1794,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, const initialBalAToken = initialBal.mul(9321).div(10000) expect(await stataDai.balanceOf(addr1.address)).to.be.closeTo(initialBalAToken, fp('1.5')) expect(await cDaiVault.balanceOf(addr1.address)).to.equal( - toBNDecimals(initialBal, 17).mul(100) + toBNDecimals(initialBal, 8).mul(100) ) // Provide approvals @@ -1784,7 +1802,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await stataDai.connect(addr1).approve(rToken.address, issueAmount) await cDaiVault .connect(addr1) - .approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) + .approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) // Check rToken balance expect(await rToken.balanceOf(addr1.address)).to.equal(0) @@ -1801,10 +1819,10 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, issueAmtAToken, fp('1') ) - const requiredCTokens: BigNumber = bn('227116e17') // approx 227K needed (~5K, 50% of basket) - Price: ~0.022 + const requiredCTokens: BigNumber = bn('227116e8') // approx 227K needed (~5K, 50% of basket) - Price: ~0.022 expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo( requiredCTokens, - bn('1e17') + bn('1e8') ) // Balances for user @@ -1814,8 +1832,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, fp('1.5') ) expect(await cDaiVault.balanceOf(addr1.address)).to.be.closeTo( - toBNDecimals(initialBal, 17).mul(100).sub(requiredCTokens), - bn('1e17') + toBNDecimals(initialBal, 8).mul(100).sub(requiredCTokens), + bn('1e8') ) // Check RTokens issued to user expect(await rToken.balanceOf(addr1.address)).to.equal(issueAmount) @@ -1844,7 +1862,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await dai.balanceOf(addr1.address)).to.equal(initialBal) expect(await stataDai.balanceOf(addr1.address)).to.be.closeTo(initialBalAToken, fp('1.5')) expect(await cDaiVault.balanceOf(addr1.address)).to.be.closeTo( - toBNDecimals(initialBal, 17).mul(100), + toBNDecimals(initialBal, 8).mul(100), bn('1e16') ) @@ -1864,7 +1882,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await stataDai.connect(addr1).approve(rToken.address, issueAmount) await cDaiVault .connect(addr1) - .approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) + .approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) // Issue rTokens await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') @@ -2012,7 +2030,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check received tokens represent ~10K in value at current prices expect(newBalanceAddr1Dai.sub(balanceAddr1Dai)).to.equal(issueAmount.div(4)) // = 2.5K (25% of basket) expect(newBalanceAddr1aDai.sub(balanceAddr1aDai)).to.be.closeTo(fp('2110.5'), fp('0.5')) // ~1.1873 * 2110.5 ~= 2.5K (25% of basket) - expect(newBalanceAddr1cDai.sub(balanceAddr1cDai)).to.be.closeTo(bn('151785e17'), bn('5e16')) // ~0.03294 * 151785.3 ~= 5K (50% of basket) + expect(newBalanceAddr1cDai.sub(balanceAddr1cDai)).to.be.closeTo(bn('151785e8'), bn('5e16')) // ~0.03294 * 151785.3 ~= 5K (50% of basket) // Check remainders in Backing Manager expect(await dai.balanceOf(backingManager.address)).to.equal(0) @@ -2021,7 +2039,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, fp('0.01') ) expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo( - bn('75331e17'), + bn('75331e8'), bn('5e16') ) // ~= 2481 usd in value @@ -2060,7 +2078,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await stataDai.connect(addr1).approve(rToken.address, issueAmount) await cDaiVault .connect(addr1) - .approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) + .approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) // Check rToken balance expect(await rToken.balanceOf(addr1.address)).to.equal(0) @@ -2190,11 +2208,11 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await wbtc.balanceOf(addr1.address)).to.equal(toBNDecimals(initialBalBtcEth, 8)) expect(await cWBTCVault.balanceOf(addr1.address)).to.equal( - toBNDecimals(initialBalBtcEth, 17).mul(1000) + toBNDecimals(initialBalBtcEth, 8).mul(1000) ) expect(await weth.balanceOf(addr1.address)).to.equal(initialBalBtcEth) expect(await cETHVault.balanceOf(addr1.address)).to.equal( - toBNDecimals(initialBalBtcEth, 17).mul(1000) + toBNDecimals(initialBalBtcEth, 8).mul(1000) ) expect(await eurt.balanceOf(addr1.address)).to.equal( toBNDecimals(initialBalBtcEth, 6).mul(1000) @@ -2206,13 +2224,13 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check Balances after expect(await wbtc.balanceOf(backingManager.address)).to.equal(toBNDecimals(issueAmount, 8)) //1 full units - const requiredCWBTC: BigNumber = toBNDecimals(fp('49.85'), 17) // approx 49.5 cWBTC needed (~1 wbtc / 0.02006) + const requiredCWBTC: BigNumber = toBNDecimals(fp('49.85'), 8) // approx 49.5 cWBTC needed (~1 wbtc / 0.02006) expect(await cWBTCVault.balanceOf(backingManager.address)).to.be.closeTo( requiredCWBTC, point1Pct(requiredCWBTC) ) expect(await weth.balanceOf(backingManager.address)).to.equal(issueAmount) //1 full units - const requiredCETH: BigNumber = toBNDecimals(fp('49.8'), 17) // approx 49.8 cETH needed (~1 weth / 0.02020) + const requiredCETH: BigNumber = toBNDecimals(fp('49.8'), 8) // approx 49.8 cETH needed (~1 weth / 0.02020) expect(await cETHVault.balanceOf(backingManager.address)).to.be.closeTo( requiredCETH, point1Pct(requiredCETH) @@ -2223,13 +2241,13 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await wbtc.balanceOf(addr1.address)).to.equal( toBNDecimals(initialBalBtcEth.sub(issueAmount), 8) ) - const expectedcWBTCBalance = toBNDecimals(initialBalBtcEth, 17).mul(1000).sub(requiredCWBTC) + const expectedcWBTCBalance = toBNDecimals(initialBalBtcEth, 8).mul(1000).sub(requiredCWBTC) expect(await cWBTCVault.balanceOf(addr1.address)).to.be.closeTo( expectedcWBTCBalance, point1Pct(expectedcWBTCBalance) ) expect(await weth.balanceOf(addr1.address)).to.equal(initialBalBtcEth.sub(issueAmount)) - const expectedcETHBalance = toBNDecimals(initialBalBtcEth, 17).mul(1000).sub(requiredCETH) + const expectedcETHBalance = toBNDecimals(initialBalBtcEth, 8).mul(1000).sub(requiredCETH) expect(await cWBTCVault.balanceOf(addr1.address)).to.be.closeTo( expectedcETHBalance, point1Pct(expectedcETHBalance) @@ -2266,12 +2284,12 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Check funds returned to user expect(await wbtc.balanceOf(addr1.address)).to.equal(toBNDecimals(initialBalBtcEth, 8)) expect(await cWBTCVault.balanceOf(addr1.address)).to.be.closeTo( - toBNDecimals(initialBalBtcEth, 17).mul(1000), + toBNDecimals(initialBalBtcEth, 8).mul(1000), bn('10e9') ) expect(await weth.balanceOf(addr1.address)).to.equal(initialBalBtcEth) expect(await cETHVault.balanceOf(addr1.address)).to.be.closeTo( - toBNDecimals(initialBalBtcEth, 17).mul(1000), + toBNDecimals(initialBalBtcEth, 8).mul(1000), bn('10e9') ) expect(await eurt.balanceOf(addr1.address)).to.equal( @@ -2493,14 +2511,17 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, stataDai = ( await ethers.getContractAt('StaticATokenLM', await aDaiCollateral.erc20()) ) - cDaiVault = ( - await ethers.getContractAt('CTokenVault', await cDaiCollateral.erc20()) + cDaiVault = ( + await ethers.getContractAt('CTokenWrapper', await cDaiCollateral.erc20()) ) - cDai = await ethers.getContractAt('CTokenMock', await cDaiVault.asset()) + cDai = await ethers.getContractAt('CTokenMock', await cDaiVault.underlying()) // Get plain aToken aDai = ( - await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aDAI || '') + await ethers.getContractAt( + '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', + networkConfig[chainId].tokens.aDAI || '' + ) ) // Setup balances for addr1 - Transfer from Mainnet holders DAI, cDAI and aDAI (for default basket) @@ -2520,7 +2541,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await whileImpersonating(holderCDAI, async (cdaiSigner) => { await cDai.connect(cdaiSigner).transfer(addr1.address, toBNDecimals(initialBal, 8).mul(100)) await cDai.connect(addr1).approve(cDaiVault.address, toBNDecimals(initialBal, 8).mul(100)) - await cDaiVault.connect(addr1).mint(toBNDecimals(initialBal, 17).mul(100), addr1.address) + await cDaiVault.connect(addr1).deposit(toBNDecimals(initialBal, 8).mul(100), addr1.address) }) }) @@ -2554,7 +2575,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Provide approvals for issuances await dai.connect(addr1).approve(rToken.address, issueAmount) await stataDai.connect(addr1).approve(rToken.address, issueAmount) - await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) + await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) // Issue rTokens await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index c11414f9a6..fa2a573586 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -156,7 +156,7 @@ async function collateralFixture( throw new Error(`Missing network configuration for ${hre.network.name}`) } - const CTokenVaultFactory = await ethers.getContractFactory('CTokenVault') + const CTokenWrapperFactory = await ethers.getContractFactory('CTokenWrapper') const StaticATokenFactory: ContractFactory = await ethers.getContractFactory('StaticATokenLM') const FiatCollateralFactory: ContractFactory = await ethers.getContractFactory('FiatCollateral') @@ -206,7 +206,7 @@ async function collateralFixture( const erc20: IERC20Metadata = ( await ethers.getContractAt('CTokenMock', tokenAddress) ) - const vault = await CTokenVaultFactory.deploy( + const vault = await CTokenWrapperFactory.deploy( erc20.address, `${await erc20.name()} Vault`, `${await erc20.symbol()}-VAULT`, @@ -301,7 +301,7 @@ async function collateralFixture( const erc20: IERC20Metadata = ( await ethers.getContractAt('CTokenMock', tokenAddress) ) - const vault = await CTokenVaultFactory.deploy( + const vault = await CTokenWrapperFactory.deploy( erc20.address, `${await erc20.name()} Vault`, `${await erc20.symbol()}-VAULT`, @@ -357,7 +357,7 @@ async function collateralFixture( const erc20: IERC20Metadata = ( await ethers.getContractAt('CTokenMock', tokenAddress) ) - const vault = await CTokenVaultFactory.deploy( + const vault = await CTokenWrapperFactory.deploy( erc20.address, `${await erc20.name()} Vault`, `${await erc20.symbol()}-VAULT`, diff --git a/test/integration/mainnet-test/StaticATokens.test.ts b/test/integration/mainnet-test/StaticATokens.test.ts index efc3bdadad..3a363fea7b 100644 --- a/test/integration/mainnet-test/StaticATokens.test.ts +++ b/test/integration/mainnet-test/StaticATokens.test.ts @@ -103,7 +103,10 @@ describeFork(`Static ATokens - Mainnet Check - Mainnet Forking P${IMPLEMENTATION // Get plain aTokens aDai = ( - await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aDAI || '') + await ethers.getContractAt( + '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', + networkConfig[chainId].tokens.aDAI || '' + ) ) // Get collaterals @@ -159,18 +162,30 @@ describeFork(`Static ATokens - Mainnet Check - Mainnet Forking P${IMPLEMENTATION // Get plain aTokens aDai = ( - await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aDAI || '') + await ethers.getContractAt( + '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', + networkConfig[chainId].tokens.aDAI || '' + ) ) aUsdc = ( - await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aUSDC || '') + await ethers.getContractAt( + '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', + networkConfig[chainId].tokens.aUSDC || '' + ) ) aUsdt = ( - await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aUSDT || '') + await ethers.getContractAt( + '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', + networkConfig[chainId].tokens.aUSDT || '' + ) ) aBusd = ( - await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aBUSD || '') + await ethers.getContractAt( + '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', + networkConfig[chainId].tokens.aBUSD || '' + ) ) // Set balance amount @@ -260,7 +275,7 @@ describeFork(`Static ATokens - Mainnet Check - Mainnet Forking P${IMPLEMENTATION }) // Wrap aUSDC - Underlying = false - expect(await aUsdc.balanceOf(addr1.address)).to.equal(initialBalUSDC) + expect(await aUsdc.balanceOf(addr1.address)).to.be.closeTo(initialBalUSDC, 1) expect(await stataUsdc.balanceOf(addr1.address)).to.equal(0) // Wrap aUSDC into a staticaUSDC diff --git a/test/libraries/Fixed.test.ts b/test/libraries/Fixed.test.ts index 91c5d55801..2b8ca66ebd 100644 --- a/test/libraries/Fixed.test.ts +++ b/test/libraries/Fixed.test.ts @@ -504,7 +504,7 @@ describe('In FixLib,', () => { const table = commutes.flatMap(([a, b, c]) => [[a, b, c]]) for (const [a, b, c] of table) { expect(await caller.mul(fp(a), fp(b)), `mul(fp(${a}), fp(${b}))`).to.equal(fp(c)) - expect(await caller.safeMul_(fp(a), fp(b), ROUND), `safeMul(fp(${a}), fp(${b}))`).to.equal( + expect(await caller.safeMul(fp(a), fp(b), ROUND), `safeMul(fp(${a}), fp(${b}))`).to.equal( fp(c) ) } @@ -513,7 +513,7 @@ describe('In FixLib,', () => { function mulTest(x: string, y: string, result: string) { it(`mul(${x}, ${y}) == ${result}`, async () => { expect(await caller.mul(fp(x), fp(y))).to.equal(fp(result)) - expect(await caller.safeMul_(fp(x), fp(y), ROUND)).to.equal(fp(result)) + expect(await caller.safeMul(fp(x), fp(y), ROUND)).to.equal(fp(result)) }) } @@ -531,8 +531,8 @@ describe('In FixLib,', () => { for (const [a, b, c] of table) { expect(await caller.mul(a, b), `mul(${a}, ${b})`).to.equal(c) expect(await caller.mul(b, a), `mul(${b}, ${a})`).to.equal(c) - expect(await caller.safeMul_(a, b, ROUND), `safeMul(${b}, ${a})`).to.equal(c) - expect(await caller.safeMul_(b, a, ROUND), `safeMul(${b}, ${a})`).to.equal(c) + expect(await caller.safeMul(a, b, ROUND), `safeMul(${b}, ${a})`).to.equal(c) + expect(await caller.safeMul(b, a, ROUND), `safeMul(${b}, ${a})`).to.equal(c) } }) @@ -547,9 +547,9 @@ describe('In FixLib,', () => { expect(await caller.mulRnd(a, b, FLOOR), `mulRnd((${a}, ${b}, FLOOR)`).to.equal(floor) expect(await caller.mulRnd(a, b, ROUND), `mulRnd((${a}, ${b}, ROUND)`).to.equal(round) expect(await caller.mulRnd(a, b, CEIL), `mulRnd((${a}, ${b}, CEIL)`).to.equal(ceil) - expect(await caller.safeMul_(a, b, FLOOR), `safeMul((${a}, ${b}, FLOOR)`).to.equal(floor) - expect(await caller.safeMul_(a, b, ROUND), `safeMul((${a}, ${b}, ROUND)`).to.equal(round) - expect(await caller.safeMul_(a, b, CEIL), `safeMul((${a}, ${b}, CEIL)`).to.equal(ceil) + expect(await caller.safeMul(a, b, FLOOR), `safeMul((${a}, ${b}, FLOOR)`).to.equal(floor) + expect(await caller.safeMul(a, b, ROUND), `safeMul((${a}, ${b}, ROUND)`).to.equal(round) + expect(await caller.safeMul(a, b, CEIL), `safeMul((${a}, ${b}, CEIL)`).to.equal(ceil) } }) it('fails outside its range', async () => { @@ -558,12 +558,10 @@ describe('In FixLib,', () => { .reverted // SafeMul should not fail + await expect(caller.safeMul(MAX_UINT192.div(2).add(1), fp(2), ROUND), 'safeMul(MAX/2 + 2, 2)') + .to.not.be.reverted await expect( - caller.safeMul_(MAX_UINT192.div(2).add(1), fp(2), ROUND), - 'safeMul(MAX/2 + 2, 2)' - ).to.not.be.reverted - await expect( - caller.safeMul_(fp(bn(2).pow(81)), fp(bn(2).pow(81)), ROUND), + caller.safeMul(fp(bn(2).pow(81)), fp(bn(2).pow(81)), ROUND), 'safeMul(2^81, 2^81)' ).to.not.be.reverted }) @@ -988,9 +986,135 @@ describe('In FixLib,', () => { describe('safeMul', () => { it('rounds up to FIX_MAX', async () => { - expect(await caller.safeMul_(MAX_UINT192.div(2).add(1), fp(2), FLOOR)).to.equal(MAX_UINT192) - expect(await caller.safeMul_(MAX_UINT192.div(2).add(1), fp(2), ROUND)).to.equal(MAX_UINT192) - expect(await caller.safeMul_(MAX_UINT192.div(2).add(1), fp(2), CEIL)).to.equal(MAX_UINT192) + expect(await caller.safeMul(MAX_UINT192.div(2).add(1), fp(2), FLOOR)).to.equal(MAX_UINT192) + expect(await caller.safeMul(MAX_UINT192.div(2).add(1), fp(2), ROUND)).to.equal(MAX_UINT192) + expect(await caller.safeMul(MAX_UINT192.div(2).add(1), fp(2), CEIL)).to.equal(MAX_UINT192) + + expect(await caller.safeMul(MAX_UINT192.sub(1), MAX_UINT192.sub(1), CEIL)).to.equal( + MAX_UINT192 + ) + }) + }) + + describe('safeDiv', () => { + it('rounds up to FIX_MAX', async () => { + expect(await caller.safeDiv(MAX_UINT192, fp(1).sub(1), FLOOR)).to.equal(MAX_UINT192) + expect(await caller.safeDiv(MAX_UINT192, fp(1).sub(1), ROUND)).to.equal(MAX_UINT192) + expect(await caller.safeDiv(MAX_UINT192, fp(1).sub(1), CEIL)).to.equal(MAX_UINT192) + + expect(await caller.safeDiv(MAX_UINT192, 0, FLOOR)).to.equal(MAX_UINT192) + expect(await caller.safeDiv(MAX_UINT192, 0, ROUND)).to.equal(MAX_UINT192) + expect(await caller.safeDiv(MAX_UINT192, 0, CEIL)).to.equal(MAX_UINT192) + }) + + it('rounds down to 0', async () => { + expect(await caller.safeDiv(0, 0, FLOOR)).to.equal(0) + expect(await caller.safeDiv(0, 0, ROUND)).to.equal(0) + expect(await caller.safeDiv(0, 0, CEIL)).to.equal(0) + + expect(await caller.safeDiv(MAX_UINT192.div(fp(1)).sub(1), MAX_UINT192, FLOOR)).to.equal(0) + expect(await caller.safeDiv(MAX_UINT192.div(fp(1)).sub(1), MAX_UINT192, ROUND)).to.equal(1) + expect(await caller.safeDiv(MAX_UINT192.div(fp(2)).sub(1), MAX_UINT192, ROUND)).to.equal(0) + expect(await caller.safeDiv(MAX_UINT192.div(fp(1)).sub(1), MAX_UINT192, CEIL)).to.equal(1) + expect(await caller.safeDiv(MAX_UINT192.div(fp(2)).sub(1), MAX_UINT192, CEIL)).to.equal(1) + }) + }) + + describe('safeMulDiv', () => { + it('resolves to FIX_MAX', async () => { + expect(await caller.safeMulDiv(MAX_UINT192, fp(2), fp(1), FLOOR)).to.equal(MAX_UINT192) + expect(await caller.safeMulDiv(MAX_UINT192, fp(2), fp(1), ROUND)).to.equal(MAX_UINT192) + expect(await caller.safeMulDiv(MAX_UINT192, fp(2), fp(1), CEIL)).to.equal(MAX_UINT192) + + expect(await caller.safeMulDiv(MAX_UINT192, fp(1), fp(1).div(2), FLOOR)).to.equal(MAX_UINT192) + expect(await caller.safeMulDiv(MAX_UINT192, fp(1), fp(1).div(2), ROUND)).to.equal(MAX_UINT192) + expect(await caller.safeMulDiv(MAX_UINT192, fp(1), fp(1).div(2), CEIL)).to.equal(MAX_UINT192) + + expect(await caller.safeMulDiv(MAX_UINT192, MAX_UINT192, fp(1), FLOOR)).to.equal(MAX_UINT192) + expect(await caller.safeMulDiv(MAX_UINT192, MAX_UINT192, fp(1), ROUND)).to.equal(MAX_UINT192) + expect(await caller.safeMulDiv(MAX_UINT192, MAX_UINT192, fp(1), CEIL)).to.equal(MAX_UINT192) + + expect(await caller.safeMulDiv(MAX_UINT192, MAX_UINT192, fp(1).div(2), FLOOR)).to.equal( + MAX_UINT192 + ) + expect(await caller.safeMulDiv(MAX_UINT192, MAX_UINT192, fp(1).div(2), ROUND)).to.equal( + MAX_UINT192 + ) + expect(await caller.safeMulDiv(MAX_UINT192, MAX_UINT192, fp(1).div(2), CEIL)).to.equal( + MAX_UINT192 + ) + + expect( + await caller.safeMulDiv(MAX_UINT192.sub(1), MAX_UINT192.sub(10), fp(1), FLOOR) + ).to.equal(MAX_UINT192) + expect( + await caller.safeMulDiv(MAX_UINT192.sub(1), MAX_UINT192.sub(10), fp(1), ROUND) + ).to.equal(MAX_UINT192) + expect( + await caller.safeMulDiv(MAX_UINT192.sub(1), MAX_UINT192.sub(10), fp(1), CEIL) + ).to.equal(MAX_UINT192) + + expect(await caller.safeMulDiv(MAX_UINT192, MAX_UINT192, MAX_UINT192, FLOOR)).to.equal( + MAX_UINT192 + ) + expect(await caller.safeMulDiv(MAX_UINT192, MAX_UINT192, MAX_UINT192, ROUND)).to.equal( + MAX_UINT192 + ) + expect(await caller.safeMulDiv(MAX_UINT192, MAX_UINT192, MAX_UINT192, CEIL)).to.equal( + MAX_UINT192 + ) + + expect( + await caller.safeMulDiv(MAX_UINT192.sub(1), MAX_UINT192.sub(10), MAX_UINT192.sub(1), FLOOR) + ).to.equal(MAX_UINT192.sub(10)) + expect( + await caller.safeMulDiv(MAX_UINT192.sub(1), MAX_UINT192.sub(10), MAX_UINT192.sub(1), ROUND) + ).to.equal(MAX_UINT192.sub(10)) + expect( + await caller.safeMulDiv(MAX_UINT192.sub(1), MAX_UINT192.sub(10), MAX_UINT192.sub(1), CEIL) + ).to.equal(MAX_UINT192.sub(10)) + + expect(await caller.safeMulDiv(fp(1).sub(1), fp(1).add(1), fp(1).sub(1), FLOOR)).to.equal( + fp(1).add(1) + ) + expect(await caller.safeMulDiv(fp(1).sub(1), fp(1).add(1), fp(1).sub(1), ROUND)).to.equal( + fp(1).add(1) + ) + expect(await caller.safeMulDiv(fp(1).sub(1), fp(1).add(1), fp(1).sub(1), CEIL)).to.equal( + fp(1).add(1) + ) + }) + + it('rounds up to FIX_MAX', async () => { + expect(await caller.safeMulDiv(MAX_UINT192, fp(1), fp(1).sub(1), FLOOR)).to.equal(MAX_UINT192) + expect(await caller.safeMulDiv(MAX_UINT192, fp(1), fp(1).sub(1), ROUND)).to.equal(MAX_UINT192) + expect(await caller.safeMulDiv(MAX_UINT192, fp(1), fp(1).sub(1), CEIL)).to.equal(MAX_UINT192) + + expect(await caller.safeMulDiv(MAX_UINT192, fp(1), 0, FLOOR)).to.equal(MAX_UINT192) + expect(await caller.safeMulDiv(MAX_UINT192, fp(1), 0, ROUND)).to.equal(MAX_UINT192) + expect(await caller.safeMulDiv(MAX_UINT192, fp(1), 0, CEIL)).to.equal(MAX_UINT192) + }) + + it('rounds down to 0', async () => { + expect(await caller.safeMulDiv(0, fp(1), 0, FLOOR)).to.equal(0) + expect(await caller.safeMulDiv(0, fp(1), 0, ROUND)).to.equal(0) + expect(await caller.safeMulDiv(0, fp(1), 0, CEIL)).to.equal(0) + + expect( + await caller.safeMulDiv(MAX_UINT192.div(fp(1)).sub(1), fp(1), MAX_UINT192, FLOOR) + ).to.equal(0) + expect( + await caller.safeMulDiv(MAX_UINT192.div(fp(1)).sub(1), fp(1), MAX_UINT192, ROUND) + ).to.equal(1) + expect( + await caller.safeMulDiv(MAX_UINT192.div(fp(2)).sub(1), fp(1), MAX_UINT192, ROUND) + ).to.equal(0) + expect( + await caller.safeMulDiv(MAX_UINT192.div(fp(1)).sub(1), fp(1), MAX_UINT192, CEIL) + ).to.equal(1) + expect( + await caller.safeMulDiv(MAX_UINT192.div(fp(2)).sub(1), fp(1), MAX_UINT192, CEIL) + ).to.equal(1) }) }) diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index e1ab308702..3706a33177 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -18,7 +18,7 @@ import { Asset, ATokenFiatCollateral, CTokenFiatCollateral, - CTokenVaultMock, + CTokenWrapperMock, ERC20Mock, FiatCollateral, IAssetRegistry, @@ -37,6 +37,7 @@ import { ORACLE_TIMEOUT, ORACLE_ERROR, PRICE_TIMEOUT, + VERSION, } from '../fixtures' const DEFAULT_THRESHOLD = fp('0.01') // 1% @@ -51,7 +52,7 @@ describe('Assets contracts #fast', () => { let token: ERC20Mock let usdc: USDCMock let aToken: StaticATokenMock - let cToken: CTokenVaultMock + let cToken: CTokenWrapperMock // Assets let collateral0: FiatCollateral @@ -111,8 +112,8 @@ describe('Assets contracts #fast', () => { aToken = ( await ethers.getContractAt('StaticATokenMock', await collateral2.erc20()) ) - cToken = ( - await ethers.getContractAt('CTokenVaultMock', await collateral3.erc20()) + cToken = ( + await ethers.getContractAt('CTokenWrapperMock', await collateral3.erc20()) ) await rsr.connect(wallet).mint(wallet.address, amt) @@ -137,6 +138,7 @@ describe('Assets contracts #fast', () => { expect(await rsrAsset.isCollateral()).to.equal(false) expect(await rsrAsset.erc20()).to.equal(rsr.address) expect(await rsr.decimals()).to.equal(18) + expect(await rsrAsset.version()).to.equal(VERSION) expect(await rsrAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) expect(await rsrAsset.bal(wallet.address)).to.equal(amt) await expectPrice(rsrAsset.address, fp('1'), ORACLE_ERROR, true) @@ -146,6 +148,7 @@ describe('Assets contracts #fast', () => { expect(await compAsset.isCollateral()).to.equal(false) expect(await compAsset.erc20()).to.equal(compToken.address) expect(await compToken.decimals()).to.equal(18) + expect(await compAsset.version()).to.equal(VERSION) expect(await compAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) expect(await compAsset.bal(wallet.address)).to.equal(amt) await expectPrice(compAsset.address, fp('1'), ORACLE_ERROR, true) @@ -155,6 +158,7 @@ describe('Assets contracts #fast', () => { expect(await aaveAsset.isCollateral()).to.equal(false) expect(await aaveAsset.erc20()).to.equal(aaveToken.address) expect(await aaveToken.decimals()).to.equal(18) + expect(await aaveAsset.version()).to.equal(VERSION) expect(await aaveAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) expect(await aaveAsset.bal(wallet.address)).to.equal(amt) await expectPrice(aaveAsset.address, fp('1'), ORACLE_ERROR, true) @@ -164,6 +168,7 @@ describe('Assets contracts #fast', () => { expect(await rTokenAsset.isCollateral()).to.equal(false) expect(await rTokenAsset.erc20()).to.equal(rToken.address) expect(await rToken.decimals()).to.equal(18) + expect(await rTokenAsset.version()).to.equal(VERSION) expect(await rTokenAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) expect(await rTokenAsset.bal(wallet.address)).to.equal(amt) await expectRTokenPrice( @@ -301,6 +306,12 @@ describe('Assets contracts #fast', () => { config.minTradeVolume.mul((await assetRegistry.erc20s()).length) ) expect(await rTokenAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) + + // Should have lot price, equal to price when feed works OK + const [lowPrice, highPrice] = await rTokenAsset.price() + const [lotLow, lotHigh] = await rTokenAsset.lotPrice() + expect(lotLow).to.equal(lowPrice) + expect(lotHigh).to.equal(highPrice) }) it('Should calculate trade min correctly', async () => { @@ -389,7 +400,7 @@ describe('Assets contracts #fast', () => { await invalidFiatCollateral.setSimplyRevert(false) await expect(invalidFiatCollateral.price()).to.be.reverted - // Check RToken price reverrts + // Check RToken price reverts await expect(rTokenAsset.price()).to.be.reverted }) diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index dae6d7d079..d2fdbc0177 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -11,7 +11,7 @@ import { ComptrollerMock, CTokenFiatCollateral, CTokenNonFiatCollateral, - CTokenVaultMock, + CTokenWrapperMock, CTokenSelfReferentialCollateral, ERC20Mock, EURFiatCollateral, @@ -64,7 +64,7 @@ describe('Collateral contracts', () => { let token: ERC20Mock let usdc: USDCMock let aToken: StaticATokenMock - let cToken: CTokenVaultMock + let cToken: CTokenWrapperMock let aaveToken: ERC20Mock let compToken: ERC20Mock @@ -125,8 +125,8 @@ describe('Collateral contracts', () => { aToken = ( await ethers.getContractAt('StaticATokenMock', await aTokenCollateral.erc20()) ) - cToken = ( - await ethers.getContractAt('CTokenVaultMock', await cTokenCollateral.erc20()) + cToken = ( + await ethers.getContractAt('CTokenWrapperMock', await cTokenCollateral.erc20()) ) await token.connect(owner).mint(owner.address, amt) @@ -1282,7 +1282,7 @@ describe('Collateral contracts', () => { let CTokenNonFiatFactory: ContractFactory let cTokenNonFiatCollateral: CTokenNonFiatCollateral let nonFiatToken: ERC20Mock - let cNonFiatTokenVault: CTokenVaultMock + let cNonFiatTokenVault: CTokenWrapperMock let targetUnitOracle: MockV3Aggregator let referenceUnitOracle: MockV3Aggregator @@ -1299,7 +1299,7 @@ describe('Collateral contracts', () => { ) // cToken cNonFiatTokenVault = await ( - await ethers.getContractFactory('CTokenVaultMock') + await ethers.getContractFactory('CTokenWrapperMock') ).deploy( 'cWBTC Token', 'cWBTC', @@ -1716,7 +1716,7 @@ describe('Collateral contracts', () => { let CTokenSelfReferentialFactory: ContractFactory let cTokenSelfReferentialCollateral: CTokenSelfReferentialCollateral let selfRefToken: WETH9 - let cSelfRefToken: CTokenVaultMock + let cSelfRefToken: CTokenWrapperMock let chainlinkFeed: MockV3Aggregator beforeEach(async () => { @@ -1727,7 +1727,7 @@ describe('Collateral contracts', () => { // cToken Self Ref cSelfRefToken = await ( - await ethers.getContractFactory('CTokenVaultMock') + await ethers.getContractFactory('CTokenWrapperMock') ).deploy('cETH Token', 'cETH', selfRefToken.address, compToken.address, compoundMock.address) CTokenSelfReferentialFactory = await ethers.getContractFactory( diff --git a/test/plugins/RewardableERC20.test.ts b/test/plugins/RewardableERC20.test.ts new file mode 100644 index 0000000000..5e16d63c85 --- /dev/null +++ b/test/plugins/RewardableERC20.test.ts @@ -0,0 +1,617 @@ +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { expect } from 'chai' +import { Wallet, ContractFactory, ContractTransaction, BigNumber } from 'ethers' +import { ethers } from 'hardhat' +import { bn, fp } from '../../common/numbers' +import { + ERC20MockDecimals, + ERC20MockRewarding, + RewardableERC20Wrapper, + RewardableERC4626Vault, +} from '../../typechain' +import { cartesianProduct } from '../utils/cases' +import { useEnv } from '#/utils/env' +import { Implementation } from '../fixtures' +import snapshotGasCost from '../utils/snapshotGasCost' + +type Fixture = () => Promise + +interface RewardableERC20Fixture { + rewardableVault: RewardableERC4626Vault | RewardableERC20Wrapper + rewardableAsset: ERC20MockRewarding + rewardToken: ERC20MockDecimals +} + +// 18 cases: test two wrappers with 2 combinations of decimals [6, 8, 18] + +enum Wrapper { + ERC20 = 'RewardableERC20WrapperTest', + ERC4626 = 'RewardableERC4626VaultTest', +} +const wrapperNames: Wrapper[] = [Wrapper.ERC20, Wrapper.ERC4626] + +for (const wrapperName of wrapperNames) { + // this style preferred due to handling gas section correctly + + const getFixture = ( + assetDecimals: number, + rewardDecimals: number + ): Fixture => { + const fixture: Fixture = + async function (): Promise { + const rewardTokenFactory: ContractFactory = await ethers.getContractFactory( + 'ERC20MockDecimals' + ) + const rewardToken = ( + await rewardTokenFactory.deploy('Reward Token', 'REWARD', rewardDecimals) + ) + + const rewardableAssetFactory: ContractFactory = await ethers.getContractFactory( + 'ERC20MockRewarding' + ) + const rewardableAsset = ( + await rewardableAssetFactory.deploy( + 'Rewarding Test Asset', + 'rewardTEST', + assetDecimals, + rewardToken.address + ) + ) + + const rewardableVaultFactory: ContractFactory = await ethers.getContractFactory(wrapperName) + const rewardableVault = ( + await rewardableVaultFactory.deploy( + rewardableAsset.address, + 'Rewarding Test Asset Vault', + 'vrewardTEST', + rewardToken.address + ) + ) + + return { + rewardableVault, + rewardableAsset, + rewardToken, + } + } + return fixture + } + + const toShares = (assets: BigNumber, assetDecimals: number, shareDecimals: number): BigNumber => { + return assets.mul(bn(10).pow(shareDecimals - assetDecimals)) + } + + // helper to handle different withdraw() signatures for each wrapper type + const withdraw = ( + wrapper: RewardableERC4626Vault | RewardableERC20Wrapper, + amount: BigNumber, + to: string + ): Promise => { + if (wrapperName == Wrapper.ERC20) { + const wrapperERC20 = wrapper as RewardableERC20Wrapper + return wrapperERC20.withdraw(amount, to) + } else { + const wrapperERC4626 = wrapper as RewardableERC4626Vault + return wrapperERC4626.withdraw(amount, to, to) + } + } + + const runTests = (assetDecimals: number, rewardDecimals: number) => { + describe(wrapperName, () => { + // Decimals + let shareDecimals: number + + // Assets + let rewardableVault: RewardableERC20Wrapper | RewardableERC4626Vault + let rewardableAsset: ERC20MockRewarding + let rewardToken: ERC20MockDecimals + + // Main + let alice: Wallet + let bob: Wallet + + const initBalance = fp('10000').div(bn(10).pow(18 - assetDecimals)) + const rewardAmount = fp('200').div(bn(10).pow(18 - rewardDecimals)) + let oneShare: BigNumber + let initShares: BigNumber + + const fixture = getFixture(assetDecimals, rewardDecimals) + + before('load wallets', async () => { + ;[alice, bob] = (await ethers.getSigners()) as unknown as Wallet[] + }) + + beforeEach(async () => { + // Deploy fixture + ;({ rewardableVault, rewardableAsset, rewardToken } = await loadFixture(fixture)) + + await rewardableAsset.mint(alice.address, initBalance) + await rewardableAsset.connect(alice).approve(rewardableVault.address, initBalance) + await rewardableAsset.mint(bob.address, initBalance) + await rewardableAsset.connect(bob).approve(rewardableVault.address, initBalance) + + shareDecimals = await rewardableVault.decimals() + initShares = toShares(initBalance, assetDecimals, shareDecimals) + oneShare = bn('1').mul(bn(10).pow(shareDecimals)) + }) + + describe('Deployment', () => { + it('sets the rewardableVault rewardableAsset', async () => { + const seenAsset = await (wrapperName == Wrapper.ERC4626 + ? (rewardableVault as RewardableERC4626Vault).asset() + : (rewardableVault as RewardableERC20Wrapper).underlying()) + + expect(seenAsset).equal(rewardableAsset.address) + }) + + it('sets the rewardableVault reward token', async () => { + const seenRewardToken = await rewardableVault.rewardToken() + expect(seenRewardToken).equal(rewardToken.address) + }) + + it('no rewards yet', async () => { + await rewardableVault.connect(alice).claimRewards() + expect(await rewardableVault.rewardsPerShare()).to.equal(bn(0)) + expect(await rewardableVault.lastRewardsPerShare(alice.address)).to.equal(bn(0)) + }) + }) + + describe('alice deposit, accrue, alice deposit, bob deposit', () => { + let rewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.connect(alice).deposit(initBalance.div(8), alice.address) + + // bob deposit + await rewardableVault.connect(bob).deposit(initBalance.div(8), bob.address) + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct balance', async () => { + expect(initShares.mul(3).div(8)).equal(await rewardableVault.balanceOf(alice.address)) + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('bob shows correct balance', async () => { + expect(initShares.div(8)).equal(await rewardableVault.balanceOf(bob.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + + it('rewardsPerShare is correct', async () => { + // rewards / alice's deposit + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + }) + }) + + describe('alice deposit, accrue, alice deposit, accrue, bob deposit', () => { + let rewardsPerShare: BigNumber + let initRewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + + initRewardsPerShare = await rewardableVault.rewardsPerShare() + + // accrue + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + + // bob deposit + await rewardableVault.connect(alice).deposit(initBalance.div(8), bob.address) + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct lastRewardsPerShare', async () => { + // rewards / alice's deposit + expect(initRewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + expect(initRewardsPerShare).equal( + await rewardableVault.lastRewardsPerShare(alice.address) + ) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + const expectedRewardsPerShare = rewardAmount + .mul(oneShare) + .div(initShares.div(4)) + .add(rewardAmount.mul(oneShare).div(initShares.div(2))) + expect(rewardsPerShare).equal(expectedRewardsPerShare) + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + }) + + describe('alice deposit, accrue, alice withdraw', () => { + let rewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await withdraw(rewardableVault.connect(alice), initBalance.div(8), alice.address) + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('rewardsPerShare is correct', async () => { + // rewards / alice's deposit + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + }) + }) + + describe('alice deposit and withdraw with 0 amount', () => { + beforeEach(async () => { + // alice deposit, accrue, and claim - 0 amount + await rewardableVault.connect(alice).deposit(bn(0), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await withdraw(rewardableVault.connect(alice), bn(0), alice.address) + }) + + it('no rewards', async () => { + expect(await rewardableVault.lastRewardsPerShare(alice.address)).to.equal(bn(0)) + expect(await rewardableVault.rewardsPerShare()).to.equal(bn(0)) + }) + }) + + describe('alice deposit, accrue, bob deposit, alice withdraw', () => { + let rewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) + await withdraw(rewardableVault.connect(alice), initBalance.div(8), alice.address) + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + + it('rewardsPerShare is correct', async () => { + // rewards / alice's deposit + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + }) + }) + + describe('alice deposit, accrue, bob deposit, alice fully withdraw', () => { + let rewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) + await withdraw(rewardableVault.connect(alice), initBalance.div(4), alice.address) + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct balance', async () => { + expect(0).equal(await rewardableVault.balanceOf(alice.address)) + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('bob shows correct balance', async () => { + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + + it('rewardsPerShare is correct', async () => { + // rewards / alice's deposit + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + }) + }) + + describe('alice deposit, accrue, alice claim, bob deposit', () => { + let rewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.connect(alice).claimRewards() + + // bob deposit + await rewardableVault.connect(bob).deposit(initBalance.div(8), bob.address) + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct balance', async () => { + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) + }) + + it('alice has claimed rewards', async () => { + expect(rewardAmount).equal(await rewardToken.balanceOf(alice.address)) + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('bob shows correct balance', async () => { + expect(initShares.div(8)).equal(await rewardableVault.balanceOf(bob.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + + it('rewardsPerShare is correct', async () => { + // rewards / alice's deposit + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + }) + }) + + describe('alice deposit, accrue, bob deposit, accrue, bob claim, alice claim', () => { + let rewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + + // bob deposit + await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) + + // accrue + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + + // claims + await rewardableVault.connect(bob).claimRewards() + await rewardableVault.connect(alice).claimRewards() + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct balance', async () => { + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) + }) + + it('alice has claimed rewards', async () => { + expect(rewardAmount.add(rewardAmount.div(2))).equal( + await rewardToken.balanceOf(alice.address) + ) + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('bob shows correct balance', async () => { + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + + it('bob has claimed rewards', async () => { + expect(rewardAmount.div(2)).equal(await rewardToken.balanceOf(bob.address)) + }) + + it('rewardsPerShare is correct', async () => { + // (rewards / alice's deposit) + (rewards / (alice's deposit + bob's deposit)) + const expectedRewardsPerShare = rewardAmount + .mul(oneShare) + .div(initShares.div(4)) + .add(rewardAmount.mul(oneShare).div(initShares.div(2))) + expect(rewardsPerShare).equal(expectedRewardsPerShare) + }) + }) + + describe('does not accure rewards for an account while it has no deposits', () => { + // alice deposit, accrue, bob deposit, alice fully withdraw, accrue, alice deposit, alice claim, bob claim + let rewardsPerShare: BigNumber + + beforeEach(async () => { + // alice deposit, accrue, and claim + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + // accrue + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + // bob deposit + await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) + // alice withdraw all + await withdraw(rewardableVault.connect(alice), initBalance.div(4), alice.address) + // accrue + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + // alice re-deposit + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + // both claim + await rewardableVault.connect(bob).claimRewards() + await rewardableVault.connect(alice).claimRewards() + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct balance', async () => { + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('alice has claimed rewards', async () => { + expect(rewardAmount).equal(await rewardToken.balanceOf(alice.address)) + }) + + it('bob shows correct balance', async () => { + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + + it('bob has claimed rewards', async () => { + expect(rewardAmount).equal(await rewardToken.balanceOf(bob.address)) + }) + + it('rewardsPerShare is correct', async () => { + // (rewards / alice's deposit) + (rewards / bob's deposit) + expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4)).mul(2)) + }) + }) + + describe('correctly updates rewards on transfer', () => { + let rewardsPerShare: BigNumber + + beforeEach(async () => { + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) + await rewardableVault.connect(alice).transfer(bob.address, initShares.div(4)) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + await rewardableVault.connect(bob).claimRewards() + await rewardableVault.connect(alice).claimRewards() + + rewardsPerShare = await rewardableVault.rewardsPerShare() + }) + + it('alice shows correct balance', async () => { + expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) + }) + + it('alice shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) + }) + + it('alice has claimed rewards', async () => { + expect(rewardAmount).equal(await rewardToken.balanceOf(alice.address)) + }) + + it('bob shows correct balance', async () => { + expect(initShares.div(2)).equal(await rewardableVault.balanceOf(bob.address)) + }) + + it('bob shows correct lastRewardsPerShare', async () => { + expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) + }) + + it('bob has claimed rewards', async () => { + expect(rewardAmount).equal(await rewardToken.balanceOf(bob.address)) + }) + + it('rewardsPerShare is correct', async () => { + // (rewards / alice's deposit) + (rewards / (alice's deposit + bob's deposit)) + expect(rewardsPerShare).equal( + rewardAmount + .mul(oneShare) + .div(initShares.div(4)) + .add(rewardAmount.mul(oneShare).div(initShares.div(2))) + ) + }) + }) + }) + } + + const decimalSeeds = [6, 8, 18] + const cases = cartesianProduct(decimalSeeds, decimalSeeds) + cases.forEach((params) => { + const wrapperStr = wrapperName.replace('Test', '') + describe(`${wrapperStr} - asset decimals: ${params[0]} / reward decimals: ${params[1]}`, () => { + runTests(params[0], params[1]) + }) + }) + + const IMPLEMENTATION: Implementation = + useEnv('PROTO_IMPL') == Implementation.P1.toString() ? Implementation.P1 : Implementation.P0 + + const describeGas = + IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip + + // This only needs to run once per Wrapper. Should not run for multiple decimal combinations + describeGas('Gas Reporting', () => { + // Assets + let rewardableVault: RewardableERC4626Vault | RewardableERC20Wrapper + let rewardableAsset: ERC20MockRewarding + + // Main + let alice: Wallet + + const initBalance = fp('10000') + const rewardAmount = fp('200') + + const fixture = getFixture(18, 18) + + before('load wallets', async () => { + ;[alice] = (await ethers.getSigners()) as unknown as Wallet[] + }) + + beforeEach(async () => { + // Deploy fixture + ;({ rewardableVault, rewardableAsset } = await loadFixture(fixture)) + + await rewardableAsset.mint(alice.address, initBalance) + await rewardableAsset.connect(alice).approve(rewardableVault.address, initBalance) + }) + + describe(wrapperName, () => { + it('deposit', async function () { + // Deposit + await snapshotGasCost( + rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + ) + + // Deposit again + await snapshotGasCost( + rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) + ) + }) + + it('withdraw', async function () { + await rewardableVault.connect(alice).deposit(initBalance, alice.address) + + await snapshotGasCost( + withdraw(rewardableVault.connect(alice), initBalance.div(2), alice.address) + ) + + await snapshotGasCost( + withdraw(rewardableVault.connect(alice), initBalance.div(2), alice.address) + ) + }) + + it('claimRewards', async function () { + await rewardableVault.connect(alice).deposit(initBalance, alice.address) + + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + + await snapshotGasCost(rewardableVault.connect(alice).claimRewards()) + + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + + await snapshotGasCost(rewardableVault.connect(alice).claimRewards()) + }) + }) + }) +} diff --git a/test/plugins/RewardableERC20Vault.test.ts b/test/plugins/RewardableERC20Vault.test.ts deleted file mode 100644 index 47f24678bd..0000000000 --- a/test/plugins/RewardableERC20Vault.test.ts +++ /dev/null @@ -1,574 +0,0 @@ -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' -import { expect } from 'chai' -import { Wallet, ContractFactory, BigNumber } from 'ethers' -import { ethers } from 'hardhat' -import { bn, fp } from '../../common/numbers' -import { - ERC20MockDecimals, - ERC20MockRewarding, - RewardableERC20Vault, - RewardableERC20VaultTest, -} from '../../typechain' -import { cartesianProduct } from '../utils/cases' -import { useEnv } from '#/utils/env' -import { Implementation } from '../fixtures' -import snapshotGasCost from '../utils/snapshotGasCost' - -type Fixture = () => Promise - -interface RewardableERC20VaultFixture { - rewardableVault: RewardableERC20Vault - rewardableAsset: ERC20MockRewarding - rewardToken: ERC20MockDecimals -} - -const getFixture = ( - assetDecimals: number, - rewardDecimals: number -): Fixture => { - const fixture: Fixture = - async function (): Promise { - const rewardTokenFactory: ContractFactory = await ethers.getContractFactory( - 'ERC20MockDecimals' - ) - const rewardToken = ( - await rewardTokenFactory.deploy('Reward Token', 'REWARD', rewardDecimals) - ) - - const rewardableAssetFactory: ContractFactory = await ethers.getContractFactory( - 'ERC20MockRewarding' - ) - const rewardableAsset = ( - await rewardableAssetFactory.deploy( - 'Rewarding Test Asset', - 'rewardTEST', - assetDecimals, - rewardToken.address - ) - ) - - const rewardableVaultFactory: ContractFactory = await ethers.getContractFactory( - 'RewardableERC20VaultTest' - ) - const rewardableVault = ( - await rewardableVaultFactory.deploy( - rewardableAsset.address, - 'Rewarding Test Asset Vault', - 'vrewardTEST', - rewardToken.address - ) - ) - - return { - rewardableVault, - rewardableAsset, - rewardToken, - } - } - return fixture -} - -const toShares = (assets: BigNumber, assetDecimals: number, shareDecimals: number): BigNumber => { - return assets.mul(bn(10).pow(shareDecimals - assetDecimals)) -} - -const runTests = (assetDecimals: number, rewardDecimals: number) => { - describe('RewardableERC20Vault', () => { - // Decimals - let shareDecimals: number - - // Assets - let rewardableVault: RewardableERC20Vault - let rewardableAsset: ERC20MockRewarding - let rewardToken: ERC20MockDecimals - - // Main - let alice: Wallet - let bob: Wallet - - const initBalance = fp('10000').div(bn(10).pow(18 - assetDecimals)) - const rewardAmount = fp('200').div(bn(10).pow(18 - rewardDecimals)) - let oneShare: BigNumber - let initShares: BigNumber - - const fixture = getFixture(assetDecimals, rewardDecimals) - - before('load wallets', async () => { - ;[alice, bob] = (await ethers.getSigners()) as unknown as Wallet[] - }) - - beforeEach(async () => { - // Deploy fixture - ;({ rewardableVault, rewardableAsset, rewardToken } = await loadFixture(fixture)) - - await rewardableAsset.mint(alice.address, initBalance) - await rewardableAsset.connect(alice).approve(rewardableVault.address, initBalance) - await rewardableAsset.mint(bob.address, initBalance) - await rewardableAsset.connect(bob).approve(rewardableVault.address, initBalance) - - shareDecimals = await rewardableVault.decimals() - initShares = toShares(initBalance, assetDecimals, shareDecimals) - oneShare = bn('1').mul(bn(10).pow(shareDecimals)) - }) - - describe('Deployment', () => { - it('sets the rewardableVault rewardableAsset', async () => { - const seenAsset = await rewardableVault.asset() - expect(seenAsset).equal(rewardableAsset.address) - }) - - it('sets the rewardableVault reward token', async () => { - const seenRewardToken = await rewardableVault.rewardToken() - expect(seenRewardToken).equal(rewardToken.address) - }) - }) - - describe('alice deposit, accrue, alice deposit, bob deposit', () => { - let rewardsPerShare: BigNumber - - beforeEach(async () => { - // alice deposit, accrue, and claim - await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - await rewardableVault.connect(alice).deposit(initBalance.div(8), alice.address) - - // bob deposit - await rewardableVault.connect(bob).deposit(initBalance.div(8), bob.address) - - rewardsPerShare = await rewardableVault.rewardsPerShare() - }) - - it('alice shows correct balance', async () => { - expect(initShares.mul(3).div(8)).equal(await rewardableVault.balanceOf(alice.address)) - }) - - it('alice shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) - }) - - it('bob shows correct balance', async () => { - expect(initShares.div(8)).equal(await rewardableVault.balanceOf(bob.address)) - }) - - it('bob shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) - }) - - it('rewardsPerShare is correct', async () => { - // rewards / alice's deposit - expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) - }) - }) - - describe('alice deposit, accrue, alice deposit, accrue, bob deposit', () => { - let rewardsPerShare: BigNumber - let initRewardsPerShare: BigNumber - - beforeEach(async () => { - // alice deposit, accrue, and claim - await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) - - initRewardsPerShare = await rewardableVault.rewardsPerShare() - - // accrue - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - - // bob deposit - await rewardableVault.connect(alice).deposit(initBalance.div(8), bob.address) - - rewardsPerShare = await rewardableVault.rewardsPerShare() - }) - - it('alice shows correct lastRewardsPerShare', async () => { - // rewards / alice's deposit - expect(initRewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) - expect(initRewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) - }) - - it('bob shows correct lastRewardsPerShare', async () => { - const expectedRewardsPerShare = rewardAmount - .mul(oneShare) - .div(initShares.div(4)) - .add(rewardAmount.mul(oneShare).div(initShares.div(2))) - expect(rewardsPerShare).equal(expectedRewardsPerShare) - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) - }) - }) - - describe('alice deposit, accrue, alice withdraw', () => { - let rewardsPerShare: BigNumber - - beforeEach(async () => { - // alice deposit, accrue, and claim - await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - await rewardableVault - .connect(alice) - .withdraw(initBalance.div(8), alice.address, alice.address) - - rewardsPerShare = await rewardableVault.rewardsPerShare() - }) - - it('alice shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) - }) - - it('rewardsPerShare is correct', async () => { - // rewards / alice's deposit - expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) - }) - }) - - describe('alice deposit, accrue, bob deposit, alice withdraw', () => { - let rewardsPerShare: BigNumber - - beforeEach(async () => { - // alice deposit, accrue, and claim - await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) - await rewardableVault - .connect(alice) - .withdraw(initBalance.div(8), alice.address, alice.address) - - rewardsPerShare = await rewardableVault.rewardsPerShare() - }) - - it('alice shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) - }) - - it('bob shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) - }) - - it('rewardsPerShare is correct', async () => { - // rewards / alice's deposit - expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) - }) - }) - - describe('alice deposit, accrue, bob deposit, alice fully withdraw', () => { - let rewardsPerShare: BigNumber - - beforeEach(async () => { - // alice deposit, accrue, and claim - await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) - await rewardableVault - .connect(alice) - .withdraw(initBalance.div(4), alice.address, alice.address) - - rewardsPerShare = await rewardableVault.rewardsPerShare() - }) - - it('alice shows correct balance', async () => { - expect(0).equal(await rewardableVault.balanceOf(alice.address)) - }) - - it('alice shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) - }) - - it('bob shows correct balance', async () => { - expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) - }) - - it('bob shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) - }) - - it('rewardsPerShare is correct', async () => { - // rewards / alice's deposit - expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) - }) - }) - - describe('alice deposit, accrue, alice claim, bob deposit', () => { - let rewardsPerShare: BigNumber - - beforeEach(async () => { - // alice deposit, accrue, and claim - await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - await rewardableVault.connect(alice).claimRewards() - - // bob deposit - await rewardableVault.connect(bob).deposit(initBalance.div(8), bob.address) - - rewardsPerShare = await rewardableVault.rewardsPerShare() - }) - - it('alice shows correct balance', async () => { - expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) - }) - - it('alice has claimed rewards', async () => { - expect(rewardAmount).equal(await rewardToken.balanceOf(alice.address)) - }) - - it('alice shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) - }) - - it('bob shows correct balance', async () => { - expect(initShares.div(8)).equal(await rewardableVault.balanceOf(bob.address)) - }) - - it('bob shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) - }) - - it('rewardsPerShare is correct', async () => { - // rewards / alice's deposit - expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) - }) - }) - - describe('alice deposit, accrue, bob deposit, accrue, bob claim, alice claim', () => { - let rewardsPerShare: BigNumber - - beforeEach(async () => { - // alice deposit, accrue, and claim - await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - - // bob deposit - await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) - - // accrue - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - - // claims - await rewardableVault.connect(bob).claimRewards() - await rewardableVault.connect(alice).claimRewards() - - rewardsPerShare = await rewardableVault.rewardsPerShare() - }) - - it('alice shows correct balance', async () => { - expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) - }) - - it('alice has claimed rewards', async () => { - expect(rewardAmount.add(rewardAmount.div(2))).equal( - await rewardToken.balanceOf(alice.address) - ) - }) - - it('alice shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) - }) - - it('bob shows correct balance', async () => { - expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) - }) - - it('bob shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) - }) - - it('bob has claimed rewards', async () => { - expect(rewardAmount.div(2)).equal(await rewardToken.balanceOf(bob.address)) - }) - - it('rewardsPerShare is correct', async () => { - // (rewards / alice's deposit) + (rewards / (alice's deposit + bob's deposit)) - const expectedRewardsPerShare = rewardAmount - .mul(oneShare) - .div(initShares.div(4)) - .add(rewardAmount.mul(oneShare).div(initShares.div(2))) - expect(rewardsPerShare).equal(expectedRewardsPerShare) - }) - }) - - describe('does not accure rewards for an account while it has no deposits', () => { - // alice deposit, accrue, bob deposit, alice fully withdraw, accrue, alice deposit, alice claim, bob claim - let rewardsPerShare: BigNumber - - beforeEach(async () => { - // alice deposit, accrue, and claim - await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) - // accrue - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - // bob deposit - await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) - // alice withdraw all - await rewardableVault - .connect(alice) - .withdraw(initBalance.div(4), alice.address, alice.address) - // accrue - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - // alice re-deposit - await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) - // both claim - await rewardableVault.connect(bob).claimRewards() - await rewardableVault.connect(alice).claimRewards() - - rewardsPerShare = await rewardableVault.rewardsPerShare() - }) - - it('alice shows correct balance', async () => { - expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) - }) - - it('alice shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) - }) - - it('alice has claimed rewards', async () => { - expect(rewardAmount).equal(await rewardToken.balanceOf(alice.address)) - }) - - it('bob shows correct balance', async () => { - expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) - }) - - it('bob shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) - }) - - it('bob has claimed rewards', async () => { - expect(rewardAmount).equal(await rewardToken.balanceOf(bob.address)) - }) - - it('rewardsPerShare is correct', async () => { - // (rewards / alice's deposit) + (rewards / bob's deposit) - expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4)).mul(2)) - }) - }) - - describe('correctly updates rewards on transfer', () => { - let rewardsPerShare: BigNumber - - beforeEach(async () => { - await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) - await rewardableVault.connect(alice).transfer(bob.address, initShares.div(4)) - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) - await rewardableVault.connect(bob).claimRewards() - await rewardableVault.connect(alice).claimRewards() - - rewardsPerShare = await rewardableVault.rewardsPerShare() - }) - - it('alice shows correct balance', async () => { - expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) - }) - - it('alice shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(alice.address)) - }) - - it('alice has claimed rewards', async () => { - expect(rewardAmount).equal(await rewardToken.balanceOf(alice.address)) - }) - - it('bob shows correct balance', async () => { - expect(initShares.div(2)).equal(await rewardableVault.balanceOf(bob.address)) - }) - - it('bob shows correct lastRewardsPerShare', async () => { - expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) - }) - - it('bob has claimed rewards', async () => { - expect(rewardAmount).equal(await rewardToken.balanceOf(bob.address)) - }) - - it('rewardsPerShare is correct', async () => { - // (rewards / alice's deposit) + (rewards / (alice's deposit + bob's deposit)) - expect(rewardsPerShare).equal( - rewardAmount - .mul(oneShare) - .div(initShares.div(4)) - .add(rewardAmount.mul(oneShare).div(initShares.div(2))) - ) - }) - }) - }) -} - -const decimalSeeds = [6, 8, 18] -const cases = cartesianProduct(decimalSeeds, decimalSeeds) -// const cases = [[6, 6]] -cases.forEach((params) => { - describe(`rewardableAsset assetDecimals: ${params[0]} / reward assetDecimals: ${params[1]}`, () => { - runTests(params[0], params[1]) - }) -}) - -export const IMPLEMENTATION: Implementation = - useEnv('PROTO_IMPL') == Implementation.P1.toString() ? Implementation.P1 : Implementation.P0 - -const describeGas = - IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip - -describeGas('Gas Reporting', () => { - // Assets - let rewardableVault: RewardableERC20Vault - let rewardableAsset: ERC20MockRewarding - - // Main - let alice: Wallet - - const initBalance = fp('10000') - const rewardAmount = fp('200') - - const fixture = getFixture(18, 18) - - before('load wallets', async () => { - ;[alice] = (await ethers.getSigners()) as unknown as Wallet[] - }) - - beforeEach(async () => { - // Deploy fixture - ;({ rewardableVault, rewardableAsset } = await loadFixture(fixture)) - - await rewardableAsset.mint(alice.address, initBalance) - await rewardableAsset.connect(alice).approve(rewardableVault.address, initBalance) - }) - - describe('RewardableERC20Vault', () => { - it('deposit', async function () { - // Deposit - await snapshotGasCost( - rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) - ) - - // Deposit again - await snapshotGasCost( - rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) - ) - }) - - it('withdraw', async function () { - await rewardableVault.connect(alice).deposit(initBalance, alice.address) - - await snapshotGasCost( - rewardableVault.connect(alice).withdraw(initBalance.div(2), alice.address, alice.address) - ) - - await snapshotGasCost( - rewardableVault.connect(alice).withdraw(initBalance.div(2), alice.address, alice.address) - ) - }) - - it('claimRewards', async function () { - await rewardableVault.connect(alice).deposit(initBalance, alice.address) - - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - - await snapshotGasCost(rewardableVault.connect(alice).claimRewards()) - - await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) - - await snapshotGasCost(rewardableVault.connect(alice).claimRewards()) - }) - }) -}) diff --git a/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap b/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap index dd642750d6..5d104c5e65 100644 --- a/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap +++ b/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Gas Reporting RewardableERC20Vault claimRewards 1`] = `159832`; +exports[`Gas Reporting RewardableERC4626Vault claimRewards 1`] = `159832`; -exports[`Gas Reporting RewardableERC20Vault claimRewards 2`] = `83066`; +exports[`Gas Reporting RewardableERC4626Vault claimRewards 2`] = `83066`; -exports[`Gas Reporting RewardableERC20Vault deposit 1`] = `119033`; +exports[`Gas Reporting RewardableERC4626Vault deposit 1`] = `119033`; -exports[`Gas Reporting RewardableERC20Vault deposit 2`] = `85387`; +exports[`Gas Reporting RewardableERC4626Vault deposit 2`] = `85387`; -exports[`Gas Reporting RewardableERC20Vault withdraw 1`] = `98068`; +exports[`Gas Reporting RewardableERC4626Vault withdraw 1`] = `98068`; -exports[`Gas Reporting RewardableERC20Vault withdraw 2`] = `66568`; +exports[`Gas Reporting RewardableERC4626Vault withdraw 2`] = `66568`; diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index 5b3eeea84e..38853a79c9 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -167,7 +167,12 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await ethers.getContractAt('ERC20Mock', networkConfig[chainId].tokens.DAI || '') ) // aDAI token - aDai = await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aDAI || '') + aDai = ( + await ethers.getContractAt( + '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', + networkConfig[chainId].tokens.aDAI || '' + ) + ) // stkAAVE stkAave = ( diff --git a/test/plugins/individual-collateral/aave/StaticATokenLM.test.ts b/test/plugins/individual-collateral/aave/StaticATokenLM.test.ts index eee4409004..bb78081cd7 100644 --- a/test/plugins/individual-collateral/aave/StaticATokenLM.test.ts +++ b/test/plugins/individual-collateral/aave/StaticATokenLM.test.ts @@ -20,6 +20,7 @@ import { getChainId } from '../../../../common/blockchain-utils' import { networkConfig } from '../../../../common/configuration' import { MAX_UINT256, ZERO_ADDRESS } from '../../../../common/constants' import { + ATokenNoController, ERC20Mock, IAaveIncentivesController, IAToken, @@ -207,7 +208,7 @@ describeFork('StaticATokenLM: aToken wrapper with static balances and liquidity ) incentives = ( await ethers.getContractAt( - 'IAaveIncentivesController', + 'contracts/plugins/assets/aave/vendor/IAaveIncentivesController.sol:IAaveIncentivesController', networkConfig[chainId].AAVE_INCENTIVES || '', userSigner ) @@ -217,7 +218,11 @@ describeFork('StaticATokenLM: aToken wrapper with static balances and liquidity await ethers.getContractAt('IWETH', networkConfig[chainId].tokens.WETH || '', userSigner) ) aweth = ( - await ethers.getContractAt('IAToken', networkConfig[chainId].tokens.aWETH || '', userSigner) + await ethers.getContractAt( + 'contracts/plugins/assets/aave/vendor/IAToken.sol:IAToken', + networkConfig[chainId].tokens.aWETH || '', + userSigner + ) ) stkAave = ( await ethers.getContractAt( @@ -242,6 +247,8 @@ describeFork('StaticATokenLM: aToken wrapper with static balances and liquidity networkConfig[chainId].AAVE_INCENTIVES ) + expect(await staticAToken.UNDERLYING_ASSET_ADDRESS()).to.be.eq(weth.address) + ctxtParams = { staticAToken: staticAToken, underlying: (weth), @@ -848,6 +855,50 @@ describeFork('StaticATokenLM: aToken wrapper with static balances and liquidity expect(ctxtAfterWithdrawal.userStkAaveBalance).to.be.eq(0) }) + it('Withdraw using withdrawDynamicAmount() - exceeding balance', async () => { + const amountToDeposit = utils.parseEther('5') + // Exceed available balance + const amountToWithdraw = utils.parseEther('10') + + // Preparation + await waitForTx(await weth.deposit({ value: amountToDeposit })) + await waitForTx(await weth.approve(staticAToken.address, amountToDeposit, defaultTxParams)) + + // Deposit + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ) + + const ctxtBeforeWithdrawal = await getContext(ctxtParams) + + // Withdraw dynamic amount + await waitForTx( + await staticAToken.withdrawDynamicAmount( + userSigner._address, + amountToWithdraw, + false, + defaultTxParams + ) + ) + + const ctxtAfterWithdrawal = await getContext(ctxtParams) + + expect(ctxtBeforeWithdrawal.userATokenBalance).to.be.eq(0) + expect(ctxtBeforeWithdrawal.staticATokenATokenBalance).to.be.closeTo(amountToDeposit, 2) + // Withdraws all balance + expect(ctxtAfterWithdrawal.userATokenBalance).to.be.closeTo( + BigNumber.from( + rayMul( + new bnjs(ctxtBeforeWithdrawal.userStaticATokenBalance.toString()), + new bnjs(ctxtAfterWithdrawal.currentRate.toString()) + ).toString() + ), + 2 + ) + expect(ctxtAfterWithdrawal.userDynamicStaticATokenBalance).to.equal(0) + expect(ctxtAfterWithdrawal.userStkAaveBalance).to.equal(0) + }) + it('Withdraw using metaWithdraw()', async () => { const amountToDeposit = utils.parseEther('5') const chainId = hre.network.config.chainId ? hre.network.config.chainId : 1 @@ -2283,4 +2334,128 @@ describeFork('StaticATokenLM: aToken wrapper with static balances and liquidity expect(await staticAToken.getClaimableRewards(user.address)).to.be.eq(0) expect(await stkAave.balanceOf(user.address)).to.be.gt(0) }) + + it('Handles AToken with no incentives controller', async () => { + const StaticATokenFactory: ContractFactory = await ethers.getContractFactory('StaticATokenLM') + + const aWETHNoController: ATokenNoController = ( + await ( + await ethers.getContractFactory('ATokenNoController') + ).deploy( + networkConfig[chainId].AAVE_LENDING_POOL, + weth.address, + networkConfig[chainId].AAVE_RESERVE_TREASURY, + 'aWETH-NC', + 'aWETH-NC', + ZERO_ADDRESS + ) + ) + + const staticATokenNoController: StaticATokenLM = ( + await StaticATokenFactory.connect(userSigner).deploy( + networkConfig[chainId].AAVE_LENDING_POOL, + aWETHNoController.address, + 'Static Aave Interest Bearing WETH - No controller', + 'stataWETH-NC' + ) + ) + + expect(await staticATokenNoController.getIncentivesController()).to.be.eq(ZERO_ADDRESS) + + expect(await staticATokenNoController.UNDERLYING_ASSET_ADDRESS()).to.be.eq(weth.address) + + // Deposit + const amountToDeposit = utils.parseEther('5') + const amountToWithdraw = MAX_UINT256 + + // Just preparation + await waitForTx(await weth.deposit({ value: amountToDeposit.mul(2) })) + await waitForTx( + await weth.approve(staticATokenNoController.address, amountToDeposit.mul(2), defaultTxParams) + ) + + // Depositing + await waitForTx( + await staticATokenNoController.deposit( + userSigner._address, + amountToDeposit, + 0, + true, + defaultTxParams + ) + ) + + const pendingRewards1 = await staticATokenNoController.getClaimableRewards(userSigner._address) + + expect(pendingRewards1).to.equal(0) + + // Depositing + await waitForTx( + await staticATokenNoController.deposit( + userSigner._address, + amountToDeposit, + 0, + true, + defaultTxParams + ) + ) + + const pendingRewards2 = await staticATokenNoController.getClaimableRewards(userSigner._address) + + await waitForTx(await staticATokenNoController.collectAndUpdateRewards()) + await waitForTx(await staticATokenNoController.connect(userSigner)['claimRewards()']()) + + const pendingRewards3 = await staticATokenNoController.getClaimableRewards(userSigner._address) + + expect(pendingRewards2).to.equal(0) + expect(pendingRewards3).to.equal(0) + + // Withdrawing all. + await waitForTx( + await staticATokenNoController.withdraw( + userSigner._address, + amountToWithdraw, + true, + defaultTxParams + ) + ) + + const pendingRewards4 = await staticATokenNoController.getClaimableRewards(userSigner._address) + const totPendingRewards4 = await staticATokenNoController.getTotalClaimableRewards() + const claimedRewards4 = await stkAave.balanceOf(userSigner._address) + const stkAaveStatic4 = await stkAave.balanceOf(staticATokenNoController.address) + + await waitForTx(await staticATokenNoController.connect(userSigner).claimRewardsToSelf(false)) + await waitForTx( + await staticATokenNoController + .connect(userSigner) + ['claimRewards(address,bool)'](userSigner._address, true) + ) + await waitForTx( + await staticATokenNoController + .connect(userSigner) + .claimRewardsOnBehalf(userSigner._address, userSigner._address, true) + ) + + const pendingRewards5 = await staticATokenNoController.getClaimableRewards(userSigner._address) + const totPendingRewards5 = await staticATokenNoController.getTotalClaimableRewards() + const claimedRewards5 = await stkAave.balanceOf(userSigner._address) + const stkAaveStatic5 = await stkAave.balanceOf(staticATokenNoController.address) + + await waitForTx(await staticATokenNoController.collectAndUpdateRewards()) + const pendingRewards6 = await staticATokenNoController.getClaimableRewards(userSigner._address) + + // Checks + expect(pendingRewards2).to.equal(0) + expect(pendingRewards3).to.equal(0) + expect(pendingRewards4).to.equal(0) + expect(totPendingRewards4).to.eq(0) + expect(pendingRewards5).to.be.eq(0) + expect(pendingRewards6).to.be.eq(0) + expect(claimedRewards4).to.be.eq(0) + expect(claimedRewards5).to.be.eq(0) + expect(totPendingRewards5).to.be.eq(0) + expect(stkAaveStatic4).to.equal(0) + expect(stkAaveStatic5).to.equal(0) + }) }) diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts new file mode 100644 index 0000000000..60473286fc --- /dev/null +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts @@ -0,0 +1,233 @@ +import collateralTests from '../collateralTests' +import { CollateralFixtureContext, CollateralOpts, MintCollateralFunc } from '../pluginTestTypes' +import { + CBETH_ETH_PRICE_FEED, + CB_ETH, + CB_ETH_ORACLE, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + ETH_USD_PRICE_FEED, + MAX_TRADE_VOL, + ORACLE_ERROR, + ORACLE_TIMEOUT, + PRICE_TIMEOUT, +} from './constants' +import { BigNumber, BigNumberish, ContractFactory } from 'ethers' +import { bn, fp } from '#/common/numbers' +import { TestICollateral } from '@typechain/TestICollateral' +import { ethers } from 'hardhat' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { MockV3Aggregator } from '@typechain/MockV3Aggregator' +import { CBEth, ERC20Mock, MockV3Aggregator__factory } from '@typechain/index' +import { mintCBETH, resetFork } from './helpers' +import { whileImpersonating } from '#/utils/impersonation' +import hre from 'hardhat' + +interface CbEthCollateralFixtureContext extends CollateralFixtureContext { + cbETH: CBEth + refPerTokChainlinkFeed: MockV3Aggregator +} + +interface CbEthCollateralOpts extends CollateralOpts { + refPerTokChainlinkFeed?: string + refPerTokChainlinkTimeout?: BigNumberish +} + +export const deployCollateral = async ( + opts: CbEthCollateralOpts = {} +): Promise => { + opts = { ...defaultCBEthCollateralOpts, ...opts } + + const CBETHCollateralFactory: ContractFactory = await ethers.getContractFactory('CBEthCollateral') + + const collateral = await CBETHCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + opts.refPerTokChainlinkFeed ?? CBETH_ETH_PRICE_FEED, + opts.refPerTokChainlinkTimeout ?? ORACLE_TIMEOUT, + { gasLimit: 2000000000 } + ) + await collateral.deployed() + + await expect(collateral.refresh()) + + return collateral +} + +const chainlinkDefaultAnswer = bn('1600e8') +const refPerTokChainlinkDefaultAnswer = fp('1') + +type Fixture = () => Promise + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CbEthCollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultCBEthCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, chainlinkDefaultAnswer) + ) + collateralOpts.chainlinkFeed = chainlinkFeed.address + const refPerTokChainlinkFeed = ( + await MockV3AggregatorFactory.deploy(18, refPerTokChainlinkDefaultAnswer) + ) + + collateralOpts.refPerTokChainlinkFeed = refPerTokChainlinkFeed.address + collateralOpts.refPerTokChainlinkTimeout = PRICE_TIMEOUT + + const cbETH = (await ethers.getContractAt('CBEth', CB_ETH)) as unknown as CBEth + const collateral = await deployCollateral(collateralOpts) + + return { + alice, + collateral, + chainlinkFeed, + refPerTokChainlinkFeed, + cbETH, + tok: cbETH as unknown as ERC20Mock, + } + } + + return makeCollateralFixtureContext +} +/* + Define helper functions +*/ + +const mintCollateralTo: MintCollateralFunc = async ( + ctx: CbEthCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintCBETH(amount, recipient) +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const reduceTargetPerRef = async () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const increaseTargetPerRef = async () => {} + +const changeRefPerTok = async (ctx: CbEthCollateralFixtureContext, percentChange: BigNumber) => { + await whileImpersonating(hre, CB_ETH_ORACLE, async (oracleSigner) => { + const rate = await ctx.cbETH.exchangeRate() + await ctx.cbETH + .connect(oracleSigner) + .updateExchangeRate(rate.add(rate.mul(percentChange).div(bn('100')))) + { + const lastRound = await ctx.refPerTokChainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) + await ctx.refPerTokChainlinkFeed.updateAnswer(nextAnswer) + } + + { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } + }) +} + +// prettier-ignore +const reduceRefPerTok = async ( + ctx: CbEthCollateralFixtureContext, + pctDecrease: BigNumberish +) => { + await changeRefPerTok( + ctx, + bn(pctDecrease).mul(-1) + ) +} +// prettier-ignore +const increaseRefPerTok = async ( + ctx: CbEthCollateralFixtureContext, + pctIncrease: BigNumberish +) => { + await changeRefPerTok( + ctx, + bn(pctIncrease) + ) +} +const getExpectedPrice = async (ctx: CbEthCollateralFixtureContext): Promise => { + const clData = await ctx.chainlinkFeed.latestRoundData() + const clDecimals = await ctx.chainlinkFeed.decimals() + + const clRptData = await ctx.refPerTokChainlinkFeed.latestRoundData() + const clRptDecimals = await ctx.refPerTokChainlinkFeed.decimals() + + return clData.answer + .mul(bn(10).pow(18 - clDecimals)) + .mul(clRptData.answer.mul(bn(10).pow(18 - clRptDecimals))) + .div(fp('1')) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} +// eslint-disable-next-line @typescript-eslint/no-empty-function +const beforeEachRewardsTest = async () => {} + +export const defaultCBEthCollateralOpts: CollateralOpts = { + erc20: CB_ETH, + targetName: ethers.utils.formatBytes32String('ETH'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ETH_USD_PRICE_FEED, + oracleTimeout: ORACLE_TIMEOUT, + oracleError: ORACLE_ERROR, + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: fp('0'), +} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it.skip, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itHasRevenueHiding: it, + resetFork: resetFork, + collateralName: 'CBEthCollateral', + chainlinkDefaultAnswer, +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/cbeth/constants.ts b/test/plugins/individual-collateral/cbeth/constants.ts new file mode 100644 index 0000000000..887a8db617 --- /dev/null +++ b/test/plugins/individual-collateral/cbeth/constants.ts @@ -0,0 +1,19 @@ +import { bn, fp } from '../../../../common/numbers' +import { networkConfig } from '../../../../common/configuration' + +// Mainnet Addresses +export const ETH_USD_PRICE_FEED = networkConfig['31337'].chainlinkFeeds.ETH as string +export const CBETH_ETH_PRICE_FEED = networkConfig['31337'].chainlinkFeeds.cbETH as string +export const CB_ETH = networkConfig['31337'].tokens.cbETH as string +export const WETH = networkConfig['31337'].tokens.WETH as string +export const CB_ETH_MINTER = '0xd0F73E06E7b88c8e1da291bB744c4eEBAf9Af59f' +export const CB_ETH_ORACLE = '0x9b37180d847B27ADC13C2277299045C1237Ae281' + +export const PRICE_TIMEOUT = bn('604800') // 1 week +export const ORACLE_TIMEOUT = bn(86400) // 24 hours in seconds +export const ORACLE_ERROR = fp('0.005') +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 = 17479312 diff --git a/test/plugins/individual-collateral/cbeth/helpers.ts b/test/plugins/individual-collateral/cbeth/helpers.ts new file mode 100644 index 0000000000..0b90ef4285 --- /dev/null +++ b/test/plugins/individual-collateral/cbeth/helpers.ts @@ -0,0 +1,17 @@ +import { ethers } from 'hardhat' +import { CBEth } from '../../../../typechain' +import { BigNumberish } from 'ethers' +import { CB_ETH_MINTER, CB_ETH, FORK_BLOCK } from './constants' +import { getResetFork } from '../helpers' +import { whileImpersonating } from '#/utils/impersonation' +import hre from 'hardhat' +export const resetFork = getResetFork(FORK_BLOCK) + +export const mintCBETH = async (amount: BigNumberish, recipient: string) => { + const cbETH: CBEth = await ethers.getContractAt('CBEth', CB_ETH) + + await whileImpersonating(hre, CB_ETH_MINTER, async (minter) => { + await cbETH.connect(minter).configureMinter(CB_ETH_MINTER, amount) + await cbETH.connect(minter).mint(recipient, amount) + }) +} diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index c40542d338..17dffd8822 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -29,10 +29,12 @@ import { import { advanceBlocks, advanceTime, getLatestBlockTimestamp } from '../../../utils/time' import { Asset, + BadERC20, ComptrollerMock, CTokenFiatCollateral, CTokenMock, - CTokenVault, + CTokenWrapper, + CTokenWrapperMock, ERC20Mock, FacadeRead, FacadeTest, @@ -40,7 +42,6 @@ import { IAssetRegistry, InvalidMockV3Aggregator, MockV3Aggregator, - RewardableERC20Vault, RTokenAsset, TestIBackingManager, TestIBasketHandler, @@ -81,7 +82,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Tokens/Assets let dai: ERC20Mock let cDai: CTokenMock - let cDaiVault: RewardableERC20Vault + let cDaiVault: CTokenWrapper let cDaiCollateral: CTokenFiatCollateral let compToken: ERC20Mock let compAsset: Asset @@ -195,8 +196,8 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi ) ) - const cDaiVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenVault') - cDaiVault = ( + const cDaiVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenWrapper') + cDaiVault = ( await cDaiVaultFactory.deploy( cDai.address, 'cDAI RToken Vault', @@ -317,7 +318,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(await cDaiCollateral.referenceERC20Decimals()).to.equal(await dai.decimals()) expect(await cDaiCollateral.erc20()).to.equal(cDaiVault.address) expect(await cDai.decimals()).to.equal(8) - expect(await cDaiVault.decimals()).to.equal(17) + expect(await cDaiVault.decimals()).to.equal(8) expect(await cDaiCollateral.targetName()).to.equal(ethers.utils.formatBytes32String('USD')) expect(await cDaiCollateral.refPerTok()).to.be.closeTo(fp('0.022'), fp('0.001')) expect(await cDaiCollateral.targetPerRef()).to.equal(fp('1')) @@ -342,6 +343,10 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi .withArgs(compToken.address, anyValue) expect(await cDaiCollateral.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) + // Exchange rate + await cDaiVault.exchangeRateCurrent() + expect(await cDaiVault.exchangeRateStored()).to.equal(await cDaiVault.exchangeRateStored()) + // Should setup contracts expect(main.address).to.not.equal(ZERO_ADDRESS) }) @@ -382,7 +387,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Check RToken price const issueAmount: BigNumber = bn('10000e18') - await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) + await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) await advanceTime(3600) await expect(rToken.connect(addr1).issue(issueAmount)).to.emit(rToken, 'Issuance') await expectRTokenPrice( @@ -414,6 +419,42 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi REVENUE_HIDING ) ).to.be.revertedWith('missing erc20') + + // Check cToken with decimals = 0 in underlying + const token0decimals: BadERC20 = await ( + await ethers.getContractFactory('BadERC20') + ).deploy('Bad ERC20', 'BERC20') + await token0decimals.setDecimals(0) + + const CTokenWrapperMockFactory: ContractFactory = await ethers.getContractFactory( + 'CTokenWrapperMock' + ) + const vault: CTokenWrapperMock = ( + await CTokenWrapperMockFactory.deploy( + '0 Decimal Token', + '0 Decimal Token', + token0decimals.address, + compToken.address, + comptroller.address + ) + ) + + await expect( + CTokenCollateralFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI as string, + oracleError: ORACLE_ERROR, + erc20: vault.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold, + delayUntilDefault, + }, + REVENUE_HIDING + ) + ).to.be.revertedWith('referenceERC20Decimals missing') }) }) @@ -425,7 +466,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi const issueAmount: BigNumber = MIN_ISSUANCE_PER_BLOCK // instant issuance // Provide approvals for issuances - await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) + await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) await advanceTime(3600) @@ -533,12 +574,12 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi const newBalanceAddr1cDai: BigNumber = await cDaiVault.balanceOf(addr1.address) // Check received tokens represent ~10K in value at current prices - expect(newBalanceAddr1cDai.sub(balanceAddr1cDai)).to.be.closeTo(bn('303570e17'), bn('8e16')) // ~0.03294 * 303571 ~= 10K (100% of basket) + expect(newBalanceAddr1cDai.sub(balanceAddr1cDai)).to.be.closeTo(bn('303570e8'), bn('8e7')) // ~0.03294 * 303571 ~= 10K (100% of basket) // Check remainders in Backing Manager expect(await cDaiVault.balanceOf(backingManager.address)).to.be.closeTo( - bn('150663e17'), - bn('5e16') + bn('150663e8'), + bn('5e7') ) // ~= 4962.8 usd in value // Check total asset value (remainder) @@ -572,7 +613,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(await compToken.balanceOf(backingManager.address)).to.equal(0) // Provide approvals for issuances - await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 17).mul(100)) + await cDaiVault.connect(addr1).approve(rToken.address, toBNDecimals(issueAmount, 8).mul(100)) await advanceTime(3600) @@ -743,8 +784,8 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Set initial exchange rate to the new cDai Mock await cDaiMock.setExchangeRate(fp('0.02')) - const cDaiVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenVault') - const cDaiMockVault = ( + const cDaiVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenWrapper') + const cDaiMockVault = ( await cDaiVaultFactory.deploy( cDaiMock.address, 'cDAI Mock RToken Vault', diff --git a/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts b/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts index a09f88fbbc..8a44e447af 100644 --- a/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts @@ -2,6 +2,7 @@ import { expect } from 'chai' import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' import hre, { ethers, network } from 'hardhat' import { useEnv } from '#/utils/env' +import { whileImpersonating } from '../../../utils/impersonation' import { advanceTime, advanceBlocks } from '../../../utils/time' import { allocateUSDC, enableRewardsAccrual, mintWcUSDC, makewCSUDC, resetFork } from './helpers' import { COMP, REWARDS } from './constants' @@ -15,7 +16,7 @@ import { bn } from '../../../../common/numbers' import { getChainId } from '../../../../common/blockchain-utils' import { networkConfig } from '../../../../common/configuration' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { ZERO_ADDRESS } from '../../../../common/constants' +import { MAX_UINT256, ZERO_ADDRESS } from '../../../../common/constants' const describeFork = useEnv('FORK') ? describe : describe.skip @@ -52,6 +53,15 @@ describeFork('Wrapped CUSDCv3', () => { await expect(CusdcV3WrapperFactory.deploy(ZERO_ADDRESS, REWARDS, COMP)).to.be.reverted }) + it('configuration/state', async () => { + expect(await wcusdcV3.symbol()).to.equal('wcUSDCv3') + expect(await wcusdcV3.name()).to.equal('Wrapped cUSDCv3') + expect(await wcusdcV3.totalSupply()).to.equal(bn(0)) + + expect(await wcusdcV3.underlyingComet()).to.equal(cusdcV3.address) + expect(await wcusdcV3.rewardERC20()).to.equal(COMP) + }) + describe('deposit', () => { const amount = bn('20000e6') @@ -163,6 +173,12 @@ describeFork('Wrapped CUSDCv3', () => { wcusdcV3.connect(bob).deposit(ethers.constants.MaxUint256) ).to.be.revertedWithCustomError(wcusdcV3, 'BadAmount') }) + + it('desposit to zero address reverts', async () => { + await expect( + wcusdcV3.connect(bob).depositTo(ZERO_ADDRESS, ethers.constants.MaxUint256) + ).to.be.revertedWithCustomError(wcusdcV3, 'ZeroAddress') + }) }) describe('withdraw', () => { @@ -292,10 +308,92 @@ describeFork('Wrapped CUSDCv3', () => { await mintWcUSDC(usdc, cusdcV3, wcusdcV3, bob, bn('20000e6'), bob.address) }) + it('sets max allowance with approval', async () => { + expect(await wcusdcV3.allowance(bob.address, don.address)).to.equal(bn(0)) + + // set approve + await wcusdcV3.connect(bob).allow(don.address, true) + + expect(await wcusdcV3.allowance(bob.address, don.address)).to.equal(MAX_UINT256) + + // rollback approve + await wcusdcV3.connect(bob).allow(don.address, false) + + expect(await wcusdcV3.allowance(bob.address, don.address)).to.equal(bn(0)) + }) + it('does not transfer without approval', async () => { await expect( wcusdcV3.connect(bob).transferFrom(don.address, bob.address, bn('10000e6')) ).to.be.revertedWithCustomError(wcusdcV3, 'Unauthorized') + + // Perform approval + await wcusdcV3.connect(bob).allow(don.address, true) + + await expect( + wcusdcV3.connect(don).transferFrom(bob.address, don.address, bn('10000e6')) + ).to.emit(wcusdcV3, 'Transfer') + }) + + it('transfer from/to zero address revert', async () => { + await expect( + wcusdcV3.connect(bob).transfer(ZERO_ADDRESS, bn('100e6')) + ).to.be.revertedWithCustomError(wcusdcV3, 'ZeroAddress') + + await whileImpersonating(ZERO_ADDRESS, async (signer) => { + await expect( + wcusdcV3.connect(signer).transfer(don.address, bn('100e6')) + ).to.be.revertedWithCustomError(wcusdcV3, 'ZeroAddress') + }) + }) + + it('performs validation on transfer amount', async () => { + await expect( + wcusdcV3.connect(bob).transfer(don.address, bn('40000e6')) + ).to.be.revertedWithCustomError(wcusdcV3, 'ExceedsBalance') + }) + + it('supports IERC20.approve and performs validations', async () => { + expect(await wcusdcV3.allowance(bob.address, don.address)).to.equal(bn(0)) + expect(await wcusdcV3.hasPermission(bob.address, don.address)).to.equal(false) + + // Cannot set approve to the zero address + await expect( + wcusdcV3.connect(bob).approve(ZERO_ADDRESS, bn('10000e6')) + ).to.be.revertedWithCustomError(wcusdcV3, 'ZeroAddress') + + // Can set full allowance with max uint256 + await expect(wcusdcV3.connect(bob).approve(don.address, MAX_UINT256)).to.emit( + wcusdcV3, + 'Approval' + ) + expect(await wcusdcV3.allowance(bob.address, don.address)).to.equal(MAX_UINT256) + expect(await wcusdcV3.hasPermission(bob.address, don.address)).to.equal(true) + + // Can revert allowance with zero amount + await expect(wcusdcV3.connect(bob).approve(don.address, bn(0))).to.emit(wcusdcV3, 'Approval') + expect(await wcusdcV3.allowance(bob.address, don.address)).to.equal(bn(0)) + expect(await wcusdcV3.hasPermission(bob.address, don.address)).to.equal(false) + + // Any other amount reverts + await expect( + wcusdcV3.connect(bob).approve(don.address, bn('10000e6')) + ).to.be.revertedWithCustomError(wcusdcV3, 'BadAmount') + expect(await wcusdcV3.allowance(bob.address, don.address)).to.equal(bn(0)) + expect(await wcusdcV3.hasPermission(bob.address, don.address)).to.equal(false) + }) + + it('perform validations on allow', async () => { + await expect(wcusdcV3.connect(bob).allow(ZERO_ADDRESS, true)).to.be.revertedWithCustomError( + wcusdcV3, + 'ZeroAddress' + ) + + await whileImpersonating(ZERO_ADDRESS, async (signer) => { + await expect( + wcusdcV3.connect(signer).allow(don.address, true) + ).to.be.revertedWithCustomError(wcusdcV3, 'ZeroAddress') + }) }) it('updates balances and rewards in sender and receiver', async () => { diff --git a/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts b/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts deleted file mode 100644 index e73d306f90..0000000000 --- a/test/plugins/individual-collateral/convex/CvxStableMetapoolSuite.test.ts +++ /dev/null @@ -1,768 +0,0 @@ -import { - CollateralFixtureContext, - CollateralOpts, - CollateralStatus, - MintCollateralFunc, -} from '../pluginTestTypes' -import { makeWMIM3Pool, mintWMIM3Pool, WrappedMIM3PoolFixture, resetFork } from './helpers' -import hre, { ethers } from 'hardhat' -import { BigNumberish } from 'ethers' -import { - CvxStableMetapoolCollateral, - ERC20Mock, - InvalidMockV3Aggregator, - MockV3Aggregator, - MockV3Aggregator__factory, - TestICollateral, -} from '../../../../typechain' -import { bn, fp } from '../../../../common/numbers' -import { MAX_UINT192, MAX_UINT48, ZERO_ADDRESS } from '../../../../common/constants' -import { expect } from 'chai' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' -import { - PRICE_TIMEOUT, - THREE_POOL, - THREE_POOL_TOKEN, - THREE_POOL_DEFAULT_THRESHOLD, - CVX, - DAI_USD_FEED, - DAI_ORACLE_TIMEOUT, - DAI_ORACLE_ERROR, - MIM_DEFAULT_THRESHOLD, - MIM_USD_FEED, - MIM_ORACLE_TIMEOUT, - MIM_ORACLE_ERROR, - MIM_THREE_POOL, - MIM_THREE_POOL_HOLDER, - USDC_USD_FEED, - USDC_ORACLE_TIMEOUT, - USDC_ORACLE_ERROR, - USDT_USD_FEED, - USDT_ORACLE_TIMEOUT, - USDT_ORACLE_ERROR, - MAX_TRADE_VOL, - DELAY_UNTIL_DEFAULT, - CurvePoolType, - CRV, -} from './constants' -import { useEnv } from '#/utils/env' -import { getChainId } from '#/common/blockchain-utils' -import { networkConfig } from '#/common/configuration' -import { - advanceBlocks, - advanceTime, - getLatestBlockTimestamp, - setNextBlockTimestamp, -} from '#/test/utils/time' - -type Fixture = () => Promise - -/* - Define interfaces -*/ - -interface CvxStableMetapoolCollateralFixtureContext - extends CollateralFixtureContext, - WrappedMIM3PoolFixture { - usdcFeed: MockV3Aggregator - daiFeed: MockV3Aggregator - usdtFeed: MockV3Aggregator - cvx: ERC20Mock - crv: ERC20Mock -} - -interface CvxStableCollateralOpts extends CollateralOpts { - revenueHiding?: BigNumberish - nTokens?: BigNumberish - curvePool?: string - poolType?: CurvePoolType - feeds?: string[][] - oracleTimeouts?: BigNumberish[][] - oracleErrors?: BigNumberish[][] - lpToken?: string - pairedTokenDefaultThreshold?: BigNumberish - metapoolToken?: string -} - -/* - Define deployment functions -*/ - -export const defaultCvxStableCollateralOpts: CvxStableCollateralOpts = { - erc20: ZERO_ADDRESS, - targetName: ethers.utils.formatBytes32String('USD'), - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: MIM_USD_FEED, - oracleTimeout: MIM_ORACLE_TIMEOUT, - oracleError: MIM_ORACLE_ERROR, - maxTradeVolume: MAX_TRADE_VOL, - defaultThreshold: THREE_POOL_DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - revenueHiding: bn('0'), // TODO - nTokens: bn('3'), - curvePool: THREE_POOL, - lpToken: THREE_POOL_TOKEN, - poolType: CurvePoolType.Plain, - feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], - oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], - metapoolToken: MIM_THREE_POOL, - pairedTokenDefaultThreshold: MIM_DEFAULT_THRESHOLD, -} - -export const deployCollateral = async ( - opts: CvxStableCollateralOpts = {} -): Promise => { - if (!opts.erc20 && !opts.feeds && !opts.chainlinkFeed) { - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) - - // Substitute all 3 feeds: DAI, USDC, USDT - const daiFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const mimFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const fix = await makeWMIM3Pool() - - opts.feeds = [[daiFeed.address], [usdcFeed.address], [usdtFeed.address]] - opts.erc20 = fix.wPool.address - opts.chainlinkFeed = mimFeed.address - } - - opts = { ...defaultCvxStableCollateralOpts, ...opts } - - const CvxStableCollateralFactory = await ethers.getContractFactory('CvxStableMetapoolCollateral') - - const collateral = await CvxStableCollateralFactory.deploy( - { - erc20: opts.erc20!, - targetName: opts.targetName!, - priceTimeout: opts.priceTimeout!, - chainlinkFeed: opts.chainlinkFeed!, - oracleError: opts.oracleError!, - oracleTimeout: opts.oracleTimeout!, - maxTradeVolume: opts.maxTradeVolume!, - defaultThreshold: opts.defaultThreshold!, - delayUntilDefault: opts.delayUntilDefault!, - }, - opts.revenueHiding!, - { - nTokens: opts.nTokens!, - curvePool: opts.curvePool!, - poolType: opts.poolType!, - feeds: opts.feeds!, - oracleTimeouts: opts.oracleTimeouts!, - oracleErrors: opts.oracleErrors!, - lpToken: opts.lpToken!, - }, - opts.metapoolToken!, - opts.pairedTokenDefaultThreshold! - ) - await collateral.deployed() - - // sometimes we are trying to test a negative test case and we want this to fail silently - // fortunately this syntax fails silently because our tools are terrible - await expect(collateral.refresh()) - - return collateral -} - -const makeCollateralFixtureContext = ( - alice: SignerWithAddress, - opts: CvxStableCollateralOpts = {} -): Fixture => { - const collateralOpts = { ...defaultCvxStableCollateralOpts, ...opts } - - const makeCollateralFixtureContext = async () => { - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) - - // Substitute all 3 feeds: DAI, USDC, USDT - const daiFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const mimFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const fix = await makeWMIM3Pool() - - collateralOpts.erc20 = fix.wPool.address - collateralOpts.chainlinkFeed = mimFeed.address - collateralOpts.feeds = [[daiFeed.address], [usdcFeed.address], [usdtFeed.address]] - collateralOpts.curvePool = fix.curvePool.address - collateralOpts.metapoolToken = fix.metapoolToken.address - - const collateral = ((await deployCollateral(collateralOpts)) as unknown) - const rewardToken = await ethers.getContractAt('ERC20Mock', CVX) // use CVX - - const cvx = await ethers.getContractAt('ERC20Mock', CVX) - const crv = await ethers.getContractAt('ERC20Mock', CRV) - - return { - alice, - collateral, - chainlinkFeed: mimFeed, - metapoolToken: fix.metapoolToken, - realMetapool: fix.realMetapool, - curvePool: fix.curvePool, - wPool: fix.wPool, - dai: fix.dai, - usdc: fix.usdc, - usdt: fix.usdt, - mim: fix.mim, - tok: fix.wPool, - rewardToken, - daiFeed, - usdcFeed, - usdtFeed, - cvx, - crv, - } - } - - return makeCollateralFixtureContext -} - -/* - Define helper functions -*/ - -const mintCollateralTo: MintCollateralFunc = async ( - ctx: CvxStableMetapoolCollateralFixtureContext, - amount: BigNumberish, - user: SignerWithAddress, - recipient: string -) => { - await mintWMIM3Pool(ctx, amount, user, recipient, MIM_THREE_POOL_HOLDER) -} - -/* - Define collateral-specific tests -*/ - -const collateralSpecificConstructorTests = () => { - it('does not allow 0 defaultThreshold', async () => { - await expect(deployCollateral({ defaultThreshold: bn('0') })).to.be.revertedWith( - 'defaultThreshold zero' - ) - }) - - it('does not allow more than 4 tokens', async () => { - await expect(deployCollateral({ nTokens: 5 })).to.be.revertedWith('up to 4 tokens max') - }) - - it('does not allow empty curvePool', async () => { - await expect(deployCollateral({ curvePool: ZERO_ADDRESS })).to.be.revertedWith( - 'curvePool address is zero' - ) - }) - - it('does not allow more than 2 price feeds', async () => { - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[DAI_USD_FEED, DAI_USD_FEED, DAI_USD_FEED], [], []], - }) - ).to.be.revertedWith('price feeds limited to 2') - }) - - it('requires at least 1 price feed per token', async () => { - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[DAI_USD_FEED, DAI_USD_FEED], [USDC_USD_FEED], []], - }) - ).to.be.revertedWith('each token needs at least 1 price feed') - }) - - it('requires non-zero-address feeds', async () => { - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[ZERO_ADDRESS], [USDC_USD_FEED], [USDT_USD_FEED]], - }) - ).to.be.revertedWith('t0feed0 empty') - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[DAI_USD_FEED, ZERO_ADDRESS], [USDC_USD_FEED], [USDT_USD_FEED]], - }) - ).to.be.revertedWith('t0feed1 empty') - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[USDC_USD_FEED], [ZERO_ADDRESS], [USDT_USD_FEED]], - }) - ).to.be.revertedWith('t1feed0 empty') - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[DAI_USD_FEED], [USDC_USD_FEED, ZERO_ADDRESS], [USDT_USD_FEED]], - }) - ).to.be.revertedWith('t1feed1 empty') - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [ZERO_ADDRESS]], - }) - ).to.be.revertedWith('t2feed0 empty') - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED, ZERO_ADDRESS]], - }) - ).to.be.revertedWith('t2feed1 empty') - }) - - it('requires non-zero oracleTimeouts', async () => { - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - oracleTimeouts: [[bn('0')], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], - }) - ).to.be.revertedWith('t0timeout0 zero') - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - oracleTimeouts: [[USDC_ORACLE_TIMEOUT], [bn('0')], [USDT_ORACLE_TIMEOUT]], - }) - ).to.be.revertedWith('t1timeout0 zero') - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [bn('0')]], - }) - ).to.be.revertedWith('t2timeout0 zero') - }) - - it('requires non-zero oracleErrors', async () => { - await expect( - deployCollateral({ - oracleErrors: [[fp('1')], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], - }) - ).to.be.revertedWith('t0error0 too large') - await expect( - deployCollateral({ - oracleErrors: [[USDC_ORACLE_ERROR], [fp('1')], [USDT_ORACLE_ERROR]], - }) - ).to.be.revertedWith('t1error0 too large') - await expect( - deployCollateral({ oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [fp('1')]] }) - ).to.be.revertedWith('t2error0 too large') - }) -} - -/* - Run the test suite -*/ - -const describeFork = useEnv('FORK') ? describe : describe.skip - -describeFork(`Collateral: Convex - Stable Metapool (MIM+3Pool)`, () => { - before(resetFork) - describe('constructor validation', () => { - it('validates targetName', async () => { - await expect(deployCollateral({ targetName: ethers.constants.HashZero })).to.be.revertedWith( - 'targetName missing' - ) - }) - - it('does not allow missing ERC20', async () => { - await expect(deployCollateral({ erc20: ethers.constants.AddressZero })).to.be.revertedWith( - 'missing erc20' - ) - }) - - it('does not allow missing chainlink feed', async () => { - await expect( - deployCollateral({ chainlinkFeed: ethers.constants.AddressZero }) - ).to.be.revertedWith('missing chainlink feed') - }) - - it('max trade volume must be greater than zero', async () => { - await expect(deployCollateral({ maxTradeVolume: 0 })).to.be.revertedWith( - 'invalid max trade volume' - ) - }) - - it('does not allow oracle timeout at 0', async () => { - await expect(deployCollateral({ oracleTimeout: 0 })).to.be.revertedWith('oracleTimeout zero') - }) - - it('does not allow missing delayUntilDefault if defaultThreshold > 0', async () => { - await expect(deployCollateral({ delayUntilDefault: 0 })).to.be.revertedWith( - 'delayUntilDefault zero' - ) - }) - - describe('collateral-specific tests', collateralSpecificConstructorTests) - }) - - describe('collateral functionality', () => { - let ctx: CvxStableMetapoolCollateralFixtureContext - let alice: SignerWithAddress - - let wallet: SignerWithAddress - let chainId: number - - let collateral: TestICollateral - let chainlinkFeed: MockV3Aggregator - let usdcFeed: MockV3Aggregator - let daiFeed: MockV3Aggregator - let usdtFeed: MockV3Aggregator - - let crv: ERC20Mock - let cvx: ERC20Mock - - before(async () => { - ;[wallet] = (await ethers.getSigners()) as unknown as SignerWithAddress[] - - chainId = await getChainId(hre) - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - await resetFork() - }) - - beforeEach(async () => { - ;[, alice] = await ethers.getSigners() - ctx = await loadFixture(makeCollateralFixtureContext(alice, {})) - ;({ chainlinkFeed, collateral, usdcFeed, daiFeed, usdtFeed, crv, cvx } = ctx) - await mintCollateralTo(ctx, bn('100e18'), wallet, wallet.address) - }) - - describe('functions', () => { - it('returns the correct bal (18 decimals)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, alice.address) - - const aliceBal = await collateral.bal(alice.address) - expect(aliceBal).to.closeTo( - amount.mul(bn(10).pow(18 - (await ctx.tok.decimals()))), - bn('100').mul(bn(10).pow(18 - (await ctx.tok.decimals()))) - ) - }) - }) - - describe('rewards', () => { - it('does not revert', async () => { - await expect(collateral.claimRewards()).to.not.be.reverted - }) - - it('claims rewards (plugin)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, collateral.address) - - await advanceBlocks(1000) - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) - - const crvBefore = await crv.balanceOf(collateral.address) - const cvxBefore = await cvx.balanceOf(collateral.address) - await expect(collateral.claimRewards()).to.emit(ctx.wPool, 'RewardsClaimed') - const crvAfter = await crv.balanceOf(collateral.address) - const cvxAfter = await cvx.balanceOf(collateral.address) - expect(crvAfter).gt(crvBefore) - expect(cvxAfter).gt(cvxBefore) - }) - - it('claims rewards (wrapper)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, alice.address) - - await advanceBlocks(1000) - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) - - const crvBefore = await crv.balanceOf(alice.address) - const cvxBefore = await cvx.balanceOf(alice.address) - await expect(ctx.wPool.connect(alice).claimRewards()).to.emit(ctx.wPool, 'RewardsClaimed') - const crvAfter = await crv.balanceOf(alice.address) - const cvxAfter = await cvx.balanceOf(alice.address) - expect(crvAfter).gt(crvBefore) - expect(cvxAfter).gt(cvxBefore) - }) - }) - - describe('prices', () => { - it('prices change as feed price changes', async () => { - const feedData = await usdcFeed.latestRoundData() - const initialRefPerTok = await collateral.refPerTok() - - const [low, high] = await collateral.price() - - // Update values in Oracles increase by 10% - const newPrice = feedData.answer.mul(110).div(100) - - await Promise.all([ - usdcFeed.updateAnswer(newPrice).then((e) => e.wait()), - daiFeed.updateAnswer(newPrice).then((e) => e.wait()), - usdtFeed.updateAnswer(newPrice).then((e) => e.wait()), - chainlinkFeed.updateAnswer(newPrice).then((e) => e.wait()), - ]) - - const [newLow, newHigh] = await collateral.price() - - expect(newLow).to.be.closeTo(low.mul(110).div(100), bn('1e3')) // rounding - expect(newHigh).to.be.closeTo(high.mul(110).div(100), bn('1e3')) - - // Check refPerTok remains the same (because we have not refreshed) - const finalRefPerTok = await collateral.refPerTok() - expect(finalRefPerTok).to.equal(initialRefPerTok) - }) - - it('prices change as refPerTok changes', async () => { - const initRefPerTok = await collateral.refPerTok() - const curveVirtualPrice = await ctx.metapoolToken.get_virtual_price() - await ctx.metapoolToken.setVirtualPrice(curveVirtualPrice.add(1e4)) - await collateral.refresh() - expect(await collateral.refPerTok()).to.be.gt(initRefPerTok) - }) - - it('returns a 0 price', async () => { - await Promise.all([ - usdcFeed.updateAnswer(0).then((e) => e.wait()), - daiFeed.updateAnswer(0).then((e) => e.wait()), - usdtFeed.updateAnswer(0).then((e) => e.wait()), - chainlinkFeed.updateAnswer(0).then((e) => e.wait()), - ]) - - // (0, FIX_MAX) is returned - const [low, high] = await collateral.price() - expect(low).to.equal(0) - expect(high).to.equal(0) - - // When refreshed, sets status to Unpriced - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('reverts in case of invalid timestamp', async () => { - await usdcFeed.setInvalidTimestamp() - - // Check price of token - const [low, high] = await collateral.price() - expect(low).to.equal(0) - expect(high).to.equal(MAX_UINT192) - - // When refreshed, sets status to Unpriced - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('decays lotPrice over priceTimeout period', async () => { - // Prices should start out equal - const p = await collateral.price() - let lotP = await collateral.lotPrice() - expect(p.length).to.equal(lotP.length) - expect(p[0]).to.equal(lotP[0]) - expect(p[1]).to.equal(lotP[1]) - - // Should be roughly half, after half of priceTimeout - const priceTimeout = await collateral.priceTimeout() - await advanceTime(priceTimeout / 2) - lotP = await collateral.lotPrice() - expect(lotP[0]).to.be.closeTo(p[0].div(2), p[0].div(2).div(10000)) // 1 part in 10 thousand - expect(lotP[1]).to.be.closeTo(p[1].div(2), p[1].div(2).div(10000)) // 1 part in 10 thousand - - // Should be 0 after full priceTimeout - await advanceTime(priceTimeout / 2) - lotP = await collateral.lotPrice() - expect(lotP[0]).to.equal(0) - expect(lotP[1]).to.equal(0) - }) - }) - - describe('status', () => { - before(resetFork) - it('maintains status in normal situations', async () => { - // Check initial state - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Force updates (with no changes) - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - - // State remains the same - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - }) - - it('enters IFFY state when reference unit depegs below low threshold', async () => { - const delayUntilDefault = await collateral.delayUntilDefault() - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Depeg MIM:USD slightly, should remain SOUND - let updateAnswerTx = await chainlinkFeed.updateAnswer(bn('9.7e7')) - await updateAnswerTx.wait() - - // Set next block timestamp - for deterministic result - let nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - await setNextBlockTimestamp(nextBlockTimestamp) - let expectedDefaultTimestamp = nextBlockTimestamp + delayUntilDefault - - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - // Depeg MIM:USD - Reducing price by 20% from 1 to 0.94 - updateAnswerTx = await chainlinkFeed.updateAnswer(bn('9.4e7')) - await updateAnswerTx.wait() - - // Set next block timestamp - for deterministic result - nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - await setNextBlockTimestamp(nextBlockTimestamp) - expectedDefaultTimestamp = nextBlockTimestamp + delayUntilDefault - - await expect(collateral.refresh()) - .to.emit(collateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - expect(await collateral.whenDefault()).to.equal(expectedDefaultTimestamp) - }) - - it('enters IFFY state when reference unit depegs above high threshold', async () => { - const delayUntilDefault = await collateral.delayUntilDefault() - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Depeg MIM:USD - Raising price by 20% from 1 to 1.2 - const updateAnswerTx = await chainlinkFeed.updateAnswer(bn('1.2e8')) - await updateAnswerTx.wait() - - // Set next block timestamp - for deterministic result - const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - await setNextBlockTimestamp(nextBlockTimestamp) - const expectedDefaultTimestamp = nextBlockTimestamp + delayUntilDefault - - await expect(collateral.refresh()) - .to.emit(collateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - expect(await collateral.whenDefault()).to.equal(expectedDefaultTimestamp) - }) - - it('enters DISABLED state when reference unit depegs for too long', async () => { - const delayUntilDefault = await collateral.delayUntilDefault() - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Depeg MIM:USD - Reducing price by 20% from 1 to 0.8 - const updateAnswerTx = await chainlinkFeed.updateAnswer(bn('8e7')) - await updateAnswerTx.wait() - - // Set next block timestamp - for deterministic result - const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - await setNextBlockTimestamp(nextBlockTimestamp) - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - - // Move time forward past delayUntilDefault - await advanceTime(delayUntilDefault) - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - - // Nothing changes if attempt to refresh after default - const prevWhenDefault: bigint = (await collateral.whenDefault()).toBigInt() - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await collateral.whenDefault()).to.equal(prevWhenDefault) - }) - - it('enters DISABLED state when refPerTok() decreases', async () => { - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - await mintCollateralTo(ctx, bn('20000e6'), alice, alice.address) - - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - // State remains the same - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - const currentExchangeRate = await ctx.metapoolToken.get_virtual_price() - await ctx.metapoolToken.setVirtualPrice(currentExchangeRate.sub(1e3)).then((e) => e.wait()) - - // Collateral defaults due to refPerTok() going down - await expect(collateral.refresh()).to.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await collateral.whenDefault()).to.equal(await getLatestBlockTimestamp()) - }) - - it('enters IFFY state when price becomes stale', async () => { - const oracleTimeout = DAI_ORACLE_TIMEOUT.toNumber() - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('does revenue hiding correctly', async () => { - ctx = await loadFixture(makeCollateralFixtureContext(alice, { revenueHiding: fp('1e-6') })) - ;({ collateral } = ctx) - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - await mintCollateralTo(ctx, bn('20000e6'), alice, alice.address) - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - - // State remains the same - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Decrease refPerTok by 1 part in a million - const currentExchangeRate = await ctx.metapoolToken.get_virtual_price() - const newVirtualPrice = currentExchangeRate.sub(currentExchangeRate.div(bn('1e6'))) - await ctx.metapoolToken.setVirtualPrice(newVirtualPrice) - - // Collateral remains SOUND - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // One quanta more of decrease results in default - await ctx.metapoolToken.setVirtualPrice(newVirtualPrice.sub(1)) - await expect(collateral.refresh()).to.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await collateral.whenDefault()).to.equal(await getLatestBlockTimestamp()) - }) - - it('reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { - const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( - 'InvalidMockV3Aggregator' - ) - const invalidChainlinkFeed = ( - await InvalidMockV3AggregatorFactory.deploy(6, bn('1e6')) - ) - - const fix = await makeWMIM3Pool() - - const invalidCollateral = await deployCollateral({ - erc20: fix.wPool.address, - chainlinkFeed: invalidChainlinkFeed.address, // shouldn't be necessary - feeds: [ - [invalidChainlinkFeed.address], - [invalidChainlinkFeed.address], - [invalidChainlinkFeed.address], - ], - }) - - // Reverting with no reason - await invalidChainlinkFeed.setSimplyRevert(true) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - - // Runnning out of gas (same error) - await invalidChainlinkFeed.setSimplyRevert(false) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - }) - }) - }) -}) diff --git a/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts deleted file mode 100644 index 856c13bbc0..0000000000 --- a/test/plugins/individual-collateral/convex/CvxStableRTokenMetapoolTestSuite.test.ts +++ /dev/null @@ -1,771 +0,0 @@ -import { - CollateralFixtureContext, - CollateralOpts, - CollateralStatus, - MintCollateralFunc, -} from '../pluginTestTypes' -import { makeWeUSDFraxBP, mintWeUSDFraxBP, WrappedEUSDFraxBPFixture, resetFork } from './helpers' -import hre, { ethers } from 'hardhat' -import { ContractFactory, BigNumberish } from 'ethers' -import { - CvxStableRTokenMetapoolCollateral, - ERC20Mock, - InvalidMockV3Aggregator, - MockV3Aggregator, - MockV3Aggregator__factory, - TestICollateral, -} from '../../../../typechain' -import { bn, fp } from '../../../../common/numbers' -import { - MAX_UINT256, - MAX_UINT192, - MAX_UINT48, - ZERO_ADDRESS, - ONE_ADDRESS, -} from '../../../../common/constants' -import { expect } from 'chai' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' -import { - PRICE_TIMEOUT, - eUSD_FRAX_BP, - FRAX_BP, - FRAX_BP_TOKEN, - CVX, - USDC_USD_FEED, - USDC_ORACLE_TIMEOUT, - USDC_ORACLE_ERROR, - FRAX_USD_FEED, - FRAX_ORACLE_TIMEOUT, - FRAX_ORACLE_ERROR, - MAX_TRADE_VOL, - DEFAULT_THRESHOLD, - RTOKEN_DELAY_UNTIL_DEFAULT, - CurvePoolType, - CRV, - eUSD_FRAX_HOLDER, -} from './constants' -import { useEnv } from '#/utils/env' -import { getChainId } from '#/common/blockchain-utils' -import { networkConfig } from '#/common/configuration' -import { - advanceBlocks, - advanceTime, - getLatestBlockTimestamp, - setNextBlockTimestamp, -} from '#/test/utils/time' - -type Fixture = () => Promise - -/* - Define interfaces - */ - -interface CvxStableRTokenMetapoolCollateralFixtureContext - extends CollateralFixtureContext, - WrappedEUSDFraxBPFixture { - fraxFeed: MockV3Aggregator - usdcFeed: MockV3Aggregator - eusdFeed: MockV3Aggregator - cvx: ERC20Mock - crv: ERC20Mock -} - -// interface CometCollateralFixtureContextMockComet extends CollateralFixtureContext { -// cusdcV3: CometMock -// wcusdcV3: ICusdcV3Wrapper -// usdc: ERC20Mock -// wcusdcV3Mock: CusdcV3WrapperMock -// } - -interface CvxStableRTokenMetapoolCollateralOpts extends CollateralOpts { - revenueHiding?: BigNumberish - nTokens?: BigNumberish - curvePool?: string - poolType?: CurvePoolType // for underlying fraxBP pool - feeds?: string[][] - oracleTimeouts?: BigNumberish[][] - oracleErrors?: BigNumberish[][] - lpToken?: string - metapoolToken?: string -} - -/* - Define deployment functions - */ - -export const defaultCvxStableCollateralOpts: CvxStableRTokenMetapoolCollateralOpts = { - erc20: ZERO_ADDRESS, - targetName: ethers.utils.formatBytes32String('USD'), - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero - oracleError: bn('1'), // unused but cannot be zero - maxTradeVolume: MAX_TRADE_VOL, - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, - revenueHiding: bn('0'), // TODO - nTokens: bn('2'), - curvePool: FRAX_BP, - lpToken: FRAX_BP_TOKEN, - poolType: CurvePoolType.Plain, // for fraxBP, not the top-level pool - feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], - oracleTimeouts: [[FRAX_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT]], - oracleErrors: [[FRAX_ORACLE_ERROR], [USDC_ORACLE_ERROR]], - metapoolToken: eUSD_FRAX_BP, -} - -export const deployCollateral = async ( - opts: CvxStableRTokenMetapoolCollateralOpts = {} -): Promise => { - if (!opts.erc20 && !opts.feeds) { - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) - - // Substitute all 3 feeds: FRAX, USDC, eUSD - const fraxFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const eusdFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const fix = await makeWeUSDFraxBP(eusdFeed) - - opts.feeds = [[fraxFeed.address], [usdcFeed.address]] - opts.erc20 = fix.wPool.address - } - - opts = { ...defaultCvxStableCollateralOpts, ...opts } - - const CvxStableRTokenMetapoolCollateralFactory: ContractFactory = await ethers.getContractFactory( - 'CvxStableRTokenMetapoolCollateral' - ) - - const collateral = ( - await CvxStableRTokenMetapoolCollateralFactory.deploy( - { - erc20: opts.erc20, - targetName: opts.targetName, - priceTimeout: opts.priceTimeout, - chainlinkFeed: opts.chainlinkFeed, - oracleError: opts.oracleError, - oracleTimeout: opts.oracleTimeout, - maxTradeVolume: opts.maxTradeVolume, - defaultThreshold: opts.defaultThreshold, - delayUntilDefault: opts.delayUntilDefault, - }, - opts.revenueHiding, - { - nTokens: opts.nTokens, - curvePool: opts.curvePool, - poolType: opts.poolType, - feeds: opts.feeds, - oracleTimeouts: opts.oracleTimeouts, - oracleErrors: opts.oracleErrors, - lpToken: opts.lpToken, - }, - opts.metapoolToken, - opts.defaultThreshold // use same 2% value - ) - ) - await collateral.deployed() - - // sometimes we are trying to test a negative test case and we want this to fail silently - // fortunately this syntax fails silently because our tools are terrible - await expect(collateral.refresh()) - - return collateral -} - -const makeCollateralFixtureContext = ( - alice: SignerWithAddress, - opts: CvxStableRTokenMetapoolCollateralOpts = {} -): Fixture => { - const collateralOpts = { ...defaultCvxStableCollateralOpts, ...opts } - - const makeCollateralFixtureContext = async () => { - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) - - // Substitute all feeds: FRAX, USDC, RToken - const fraxFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const eusdFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - - const fix = await makeWeUSDFraxBP(eusdFeed) - collateralOpts.feeds = [[fraxFeed.address], [usdcFeed.address]] - - collateralOpts.erc20 = fix.wPool.address - collateralOpts.curvePool = fix.curvePool.address - collateralOpts.metapoolToken = fix.metapoolToken.address - - const collateral = ((await deployCollateral(collateralOpts)) as unknown) - const rewardToken = await ethers.getContractAt('ERC20Mock', CVX) // use CVX - - const cvx = await ethers.getContractAt('ERC20Mock', CVX) - const crv = await ethers.getContractAt('ERC20Mock', CRV) - - return { - alice, - collateral, - chainlinkFeed: usdcFeed, - metapoolToken: fix.metapoolToken, - realMetapool: fix.realMetapool, - curvePool: fix.curvePool, - wPool: fix.wPool, - frax: fix.frax, - usdc: fix.usdc, - eusd: fix.eusd, - tok: fix.wPool, - rewardToken, - fraxFeed, - usdcFeed, - eusdFeed, - cvx, - crv, - } - } - - return makeCollateralFixtureContext -} - -/* - Define helper functions - */ - -const mintCollateralTo: MintCollateralFunc< - CvxStableRTokenMetapoolCollateralFixtureContext -> = async ( - ctx: CvxStableRTokenMetapoolCollateralFixtureContext, - amount: BigNumberish, - user: SignerWithAddress, - recipient: string -) => { - await mintWeUSDFraxBP(ctx, amount, user, recipient, eUSD_FRAX_HOLDER) -} - -/* - Define collateral-specific tests - */ - -const collateralSpecificConstructorTests = () => { - it('does not allow 0 defaultThreshold', async () => { - await expect(deployCollateral({ defaultThreshold: bn('0') })).to.be.revertedWith( - 'defaultThreshold zero' - ) - }) - - it('does not allow more than 2 tokens', async () => { - await expect(deployCollateral({ nTokens: 1 })).to.be.reverted - await expect(deployCollateral({ nTokens: 3 })).to.be.reverted - }) - - it('does not allow empty curvePool', async () => { - await expect(deployCollateral({ curvePool: ZERO_ADDRESS })).to.be.revertedWith( - 'curvePool address is zero' - ) - }) - - it('does not allow more than 2 price feeds', async () => { - await expect( - deployCollateral({ - erc20: eUSD_FRAX_BP, // can be anything. - feeds: [[FRAX_USD_FEED, FRAX_USD_FEED, FRAX_USD_FEED], [], []], - }) - ).to.be.revertedWith('price feeds limited to 2') - }) - - it('requires at least 1 price feed per token', async () => { - await expect( - deployCollateral({ - erc20: eUSD_FRAX_BP, // can be anything. - feeds: [[FRAX_USD_FEED, FRAX_USD_FEED], [USDC_USD_FEED], []], - }) - ).to.be.revertedWith('each token needs at least 1 price feed') - }) - - it('requires non-zero-address feeds', async () => { - await expect( - deployCollateral({ - erc20: eUSD_FRAX_BP, // can be anything. - feeds: [[ZERO_ADDRESS], [FRAX_USD_FEED]], - }) - ).to.be.revertedWith('t0feed0 empty') - await expect( - deployCollateral({ - erc20: eUSD_FRAX_BP, // can be anything. - feeds: [[FRAX_USD_FEED, ZERO_ADDRESS], [USDC_USD_FEED]], - }) - ).to.be.revertedWith('t0feed1 empty') - await expect( - deployCollateral({ - erc20: eUSD_FRAX_BP, // can be anything. - feeds: [[FRAX_USD_FEED], [ZERO_ADDRESS]], - }) - ).to.be.revertedWith('t1feed0 empty') - await expect( - deployCollateral({ - erc20: eUSD_FRAX_BP, // can be anything. - feeds: [[FRAX_USD_FEED], [USDC_USD_FEED, ZERO_ADDRESS]], - }) - ).to.be.revertedWith('t1feed1 empty') - }) - - it('requires non-zero oracleTimeouts', async () => { - await expect( - deployCollateral({ - erc20: eUSD_FRAX_BP, // can be anything. - oracleTimeouts: [[bn('0')], [FRAX_ORACLE_TIMEOUT]], - }) - ).to.be.revertedWith('t0timeout0 zero') - await expect( - deployCollateral({ - erc20: eUSD_FRAX_BP, // can be anything. - oracleTimeouts: [[FRAX_ORACLE_TIMEOUT], [bn('0')]], - }) - ).to.be.revertedWith('t1timeout0 zero') - }) - - it('requires non-zero oracleErrors', async () => { - await expect( - deployCollateral({ - oracleErrors: [[fp('1')], [USDC_ORACLE_ERROR]], - }) - ).to.be.revertedWith('t0error0 too large') - await expect( - deployCollateral({ - oracleErrors: [[FRAX_ORACLE_ERROR], [fp('1')]], - }) - ).to.be.revertedWith('t1error0 too large') - }) -} - -/* - Run the test suite - */ - -const describeFork = useEnv('FORK') ? describe : describe.skip - -describeFork(`Collateral: Convex - RToken Metapool (eUSD/fraxBP)`, () => { - before(resetFork) - describe('constructor validation', () => { - it('validates targetName', async () => { - await expect(deployCollateral({ targetName: ethers.constants.HashZero })).to.be.revertedWith( - 'targetName missing' - ) - }) - - it('does not allow missing ERC20', async () => { - await expect(deployCollateral({ erc20: ethers.constants.AddressZero })).to.be.revertedWith( - 'missing erc20' - ) - }) - - it('does not allow missing chainlink feed', async () => { - await expect( - deployCollateral({ chainlinkFeed: ethers.constants.AddressZero }) - ).to.be.revertedWith('missing chainlink feed') - }) - - it('max trade volume must be greater than zero', async () => { - await expect(deployCollateral({ maxTradeVolume: 0 })).to.be.revertedWith( - 'invalid max trade volume' - ) - }) - - it('does not allow oracle timeout at 0', async () => { - await expect(deployCollateral({ oracleTimeout: 0 })).to.be.revertedWith('oracleTimeout zero') - }) - - it('does not allow missing delayUntilDefault if defaultThreshold > 0', async () => { - await expect(deployCollateral({ delayUntilDefault: 0 })).to.be.revertedWith( - 'delayUntilDefault zero' - ) - }) - - describe('collateral-specific tests', collateralSpecificConstructorTests) - }) - - describe('collateral functionality', () => { - let ctx: CvxStableRTokenMetapoolCollateralFixtureContext - let alice: SignerWithAddress - - let wallet: SignerWithAddress - let chainId: number - - let collateral: TestICollateral - let fraxFeed: MockV3Aggregator - let usdcFeed: MockV3Aggregator - let eusdFeed: MockV3Aggregator - - let crv: ERC20Mock - let cvx: ERC20Mock - - before(async () => { - ;[wallet] = (await ethers.getSigners()) as unknown as SignerWithAddress[] - - chainId = await getChainId(hre) - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - }) - - beforeEach(async () => { - ;[, alice] = await ethers.getSigners() - ctx = await loadFixture(makeCollateralFixtureContext(alice, {})) - ;({ collateral, fraxFeed, usdcFeed, eusdFeed, crv, cvx } = ctx) - - await mintCollateralTo(ctx, bn('100e18'), wallet, wallet.address) - }) - - describe('functions', () => { - it('returns the correct bal (18 decimals)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, alice.address) - - const aliceBal = await collateral.bal(alice.address) - expect(aliceBal).to.closeTo( - amount.mul(bn(10).pow(18 - (await ctx.tok.decimals()))), - bn('100').mul(bn(10).pow(18 - (await ctx.tok.decimals()))) - ) - }) - }) - - describe('rewards', () => { - it('does not revert', async () => { - await expect(collateral.claimRewards()).to.not.be.reverted - }) - - it('claims rewards (plugin)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, collateral.address) - - await advanceBlocks(1000) - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) - - const crvBefore = await crv.balanceOf(collateral.address) - const cvxBefore = await cvx.balanceOf(collateral.address) - await expect(collateral.claimRewards()).to.emit(ctx.wPool, 'RewardsClaimed') - const crvAfter = await crv.balanceOf(collateral.address) - const cvxAfter = await cvx.balanceOf(collateral.address) - expect(crvAfter).gt(crvBefore) - expect(cvxAfter).gt(cvxBefore) - }) - - it('claims rewards (wrapper)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, alice.address) - - await advanceBlocks(1000) - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) - - const crvBefore = await crv.balanceOf(alice.address) - const cvxBefore = await cvx.balanceOf(alice.address) - await expect(ctx.wPool.connect(alice).claimRewards()).to.emit(ctx.wPool, 'RewardsClaimed') - const crvAfter = await crv.balanceOf(alice.address) - const cvxAfter = await cvx.balanceOf(alice.address) - expect(crvAfter).gt(crvBefore) - expect(cvxAfter).gt(cvxBefore) - }) - }) - - describe('prices', () => { - it('prices change as feed price changes', async () => { - const feedData = await usdcFeed.latestRoundData() - const initialRefPerTok = await collateral.refPerTok() - - const [low, high] = await collateral.price() - - // Update values in Oracles increase by 10% - const newPrice = feedData.answer.mul(110).div(100) - - await Promise.all([ - fraxFeed.updateAnswer(newPrice).then((e) => e.wait()), - usdcFeed.updateAnswer(newPrice).then((e) => e.wait()), - eusdFeed.updateAnswer(newPrice).then((e) => e.wait()), - ]) - - // Appreciated 10% - const [newLow, newHigh] = await collateral.price() - expect(newLow).to.be.closeTo(low.mul(110).div(100), 1) - expect(newHigh).to.be.closeTo(high.mul(110).div(100), 1) - - // Check refPerTok remains the same - const finalRefPerTok = await collateral.refPerTok() - expect(finalRefPerTok).to.equal(initialRefPerTok) - }) - - it('prices change as refPerTok changes', async () => { - const initRefPerTok = await collateral.refPerTok() - const curveVirtualPrice = await ctx.metapoolToken.get_virtual_price() - await ctx.metapoolToken.setVirtualPrice(curveVirtualPrice.add(1e4)) - await collateral.refresh() - expect(await collateral.refPerTok()).to.be.gt(initRefPerTok) - }) - - it('returns a 0 price', async () => { - await Promise.all([ - fraxFeed.updateAnswer(0).then((e) => e.wait()), - usdcFeed.updateAnswer(0).then((e) => e.wait()), - eusdFeed.updateAnswer(0).then((e) => e.wait()), - ]) - - // (0, 0) is returned - const [low, high] = await collateral.price() - expect(low).to.equal(0) - expect(high).to.equal(0) - - // When refreshed, sets status to IFFY - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('still reads out pool token price when paired price is broken', async () => { - await eusdFeed.updateAnswer(MAX_UINT256.div(2).sub(1)) - - // (>0.5, +inf) is returned - const [low, high] = await collateral.price() - expect(low).to.be.gt(fp('0.5')) - expect(high).to.be.gt(fp('1e27')) // won't quite be FIX_MAX always - - // When refreshed, sets status to IFFY - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('reverts in case of invalid timestamp', async () => { - await usdcFeed.setInvalidTimestamp() - - // Check price of token - const [low, high] = await collateral.price() - expect(low).to.equal(0) - expect(high).to.equal(MAX_UINT192) - - // When refreshed, sets status to IFFY - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('decays lotPrice over priceTimeout period', async () => { - // Prices should start out equal - const p = await collateral.price() - let lotP = await collateral.lotPrice() - expect(p.length).to.equal(lotP.length) - expect(p[0]).to.equal(lotP[0]) - expect(p[1]).to.equal(lotP[1]) - - // Should be roughly half, after half of priceTimeout - const priceTimeout = await collateral.priceTimeout() - await advanceTime(priceTimeout / 2) - lotP = await collateral.lotPrice() - expect(lotP[0]).to.be.closeTo(p[0].div(2), p[0].div(2).div(10000)) // 1 part in 10 thousand - expect(lotP[1]).to.be.closeTo(p[1].div(2), p[1].div(2).div(10000)) // 1 part in 10 thousand - - // Should be 0 after full priceTimeout - await advanceTime(priceTimeout / 2) - lotP = await collateral.lotPrice() - expect(lotP[0]).to.equal(0) - expect(lotP[1]).to.equal(0) - }) - }) - - describe('status', () => { - before(resetFork) - - it('maintains status in normal situations', async () => { - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Force updates (with no changes) - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - - // State remains the same - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - }) - - it('enters IFFY state when reference unit depegs below low threshold', async () => { - const delayUntilDefault = await collateral.delayUntilDefault() - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Depeg USDC:USD - Reducing price by 20% from 1 to 0.8 - const updateAnswerTx = await usdcFeed.updateAnswer(bn('8e7')) - await updateAnswerTx.wait() - - // Set next block timestamp - for deterministic result - const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - await setNextBlockTimestamp(nextBlockTimestamp) - const expectedDefaultTimestamp = nextBlockTimestamp + delayUntilDefault - - await expect(collateral.refresh()) - .to.emit(collateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - expect(await collateral.whenDefault()).to.equal(expectedDefaultTimestamp) - }) - - it('enters IFFY state when reference unit depegs above high threshold', async () => { - const delayUntilDefault = await collateral.delayUntilDefault() - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Depeg USDC:USD - Raising price by 20% from 1 to 1.2 - const updateAnswerTx = await usdcFeed.updateAnswer(bn('1.2e8')) - await updateAnswerTx.wait() - - // Set next block timestamp - for deterministic result - const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - await setNextBlockTimestamp(nextBlockTimestamp) - const expectedDefaultTimestamp = nextBlockTimestamp + delayUntilDefault - - await expect(collateral.refresh()) - .to.emit(collateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - expect(await collateral.whenDefault()).to.equal(expectedDefaultTimestamp) - }) - - it('enters DISABLED state when reference unit depegs for too long', async () => { - const delayUntilDefault = await collateral.delayUntilDefault() - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Depeg USDC:USD - Reducing price by 20% from 1 to 0.8 - const updateAnswerTx = await usdcFeed.updateAnswer(bn('8e7')) - await updateAnswerTx.wait() - - // Set next block timestamp - for deterministic result - const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - await setNextBlockTimestamp(nextBlockTimestamp) - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - - // Move time forward past delayUntilDefault - await advanceTime(delayUntilDefault) - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - - // Nothing changes if attempt to refresh after default - const prevWhenDefault: bigint = (await collateral.whenDefault()).toBigInt() - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await collateral.whenDefault()).to.equal(prevWhenDefault) - }) - - // handy trick for dealing with expiring oracles - it('resets fork', async () => { - await resetFork() - }) - - it('enters DISABLED state when refPerTok() decreases', async () => { - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - await mintCollateralTo(ctx, bn('20000e6'), alice, alice.address) - - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - // State remains the same - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - const currentExchangeRate = await ctx.metapoolToken.get_virtual_price() - await ctx.metapoolToken.setVirtualPrice(currentExchangeRate.sub(1e3)) - - // Collateral defaults due to refPerTok() going down - await expect(collateral.refresh()).to.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await collateral.whenDefault()).to.equal(await getLatestBlockTimestamp()) - }) - - it('does revenue hiding correctly', async () => { - ctx = await loadFixture(makeCollateralFixtureContext(alice, { revenueHiding: fp('1e-6') })) - ;({ collateral } = ctx) - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - await mintCollateralTo(ctx, bn('20000e6'), alice, alice.address) - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - - // State remains the same - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Decrease refPerTok by 1 part in a million - const currentExchangeRate = await ctx.metapoolToken.get_virtual_price() - const newVirtualPrice = currentExchangeRate.sub(currentExchangeRate.div(bn('1e6'))) - await ctx.metapoolToken.setVirtualPrice(newVirtualPrice) - - // Collateral remains SOUND - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // One quanta more of decrease results in default - await ctx.metapoolToken.setVirtualPrice(newVirtualPrice.sub(1)) - await expect(collateral.refresh()).to.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await collateral.whenDefault()).to.equal(await getLatestBlockTimestamp()) - }) - - it('enters IFFY state when price becomes stale', async () => { - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - const oracleTimeout = FRAX_ORACLE_TIMEOUT.toNumber() - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('enters IFFY state when _only_ the RToken de-pegs for 72h', async () => { - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - // De-peg RToken to just below threshold of $0.98 - await eusdFeed.updateAnswer(fp('0.9799999')) - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - - // Advance 72h - await setNextBlockTimestamp( - RTOKEN_DELAY_UNTIL_DEFAULT.add(await getLatestBlockTimestamp()).toNumber() - ) - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - }) - - it('reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { - const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( - 'InvalidMockV3Aggregator' - ) - const invalidChainlinkFeed = ( - await InvalidMockV3AggregatorFactory.deploy(6, bn('1e6')) - ) - - const fix = await makeWeUSDFraxBP(eusdFeed) - - const invalidCollateral = await deployCollateral({ - erc20: fix.wPool.address, - feeds: [[invalidChainlinkFeed.address], [invalidChainlinkFeed.address]], - }) - - // Reverting with no reason - await invalidChainlinkFeed.setSimplyRevert(true) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - - // Runnning out of gas (same error) - await invalidChainlinkFeed.setSimplyRevert(false) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - }) - }) - }) -}) diff --git a/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts b/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts deleted file mode 100644 index 9cb9f7acbb..0000000000 --- a/test/plugins/individual-collateral/convex/CvxStableTestSuite.test.ts +++ /dev/null @@ -1,756 +0,0 @@ -import { - CollateralFixtureContext, - CollateralOpts, - CollateralStatus, - MintCollateralFunc, -} from '../pluginTestTypes' -import { mintW3Pool, makeW3PoolStable, Wrapped3PoolFixtureStable, resetFork } from './helpers' -import hre, { ethers } from 'hardhat' -import { ContractFactory, BigNumberish } from 'ethers' -import { - CvxStableCollateral, - ERC20Mock, - InvalidMockV3Aggregator, - MockV3Aggregator, - MockV3Aggregator__factory, - TestICollateral, -} from '../../../../typechain' -import { bn, fp } from '../../../../common/numbers' -import { MAX_UINT192, MAX_UINT48, ZERO_ADDRESS } from '../../../../common/constants' -import { expect } from 'chai' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' -import { - PRICE_TIMEOUT, - THREE_POOL, - THREE_POOL_TOKEN, - CVX, - DAI_USD_FEED, - DAI_ORACLE_TIMEOUT, - DAI_ORACLE_ERROR, - USDC_USD_FEED, - USDC_ORACLE_TIMEOUT, - USDC_ORACLE_ERROR, - USDT_USD_FEED, - USDT_ORACLE_TIMEOUT, - USDT_ORACLE_ERROR, - MAX_TRADE_VOL, - DEFAULT_THRESHOLD, - DELAY_UNTIL_DEFAULT, - CurvePoolType, - CRV, - THREE_POOL_HOLDER, -} from './constants' -import { useEnv } from '#/utils/env' -import { getChainId } from '#/common/blockchain-utils' -import { networkConfig } from '#/common/configuration' -import { - advanceBlocks, - advanceTime, - getLatestBlockTimestamp, - setNextBlockTimestamp, -} from '#/test/utils/time' - -type Fixture = () => Promise - -/* - Define interfaces -*/ - -interface CvxStableCollateralFixtureContext - extends CollateralFixtureContext, - Wrapped3PoolFixtureStable { - usdcFeed: MockV3Aggregator - daiFeed: MockV3Aggregator - usdtFeed: MockV3Aggregator - cvx: ERC20Mock - crv: ERC20Mock -} - -// interface CometCollateralFixtureContextMockComet extends CollateralFixtureContext { -// cusdcV3: CometMock -// wcusdcV3: ICusdcV3Wrapper -// usdc: ERC20Mock -// wcusdcV3Mock: CusdcV3WrapperMock -// } - -interface CvxStableCollateralOpts extends CollateralOpts { - revenueHiding?: BigNumberish - nTokens?: BigNumberish - curvePool?: string - poolType?: CurvePoolType - feeds?: string[][] - oracleTimeouts?: BigNumberish[][] - oracleErrors?: BigNumberish[][] - lpToken?: string -} - -/* - Define deployment functions -*/ - -export const defaultCvxStableCollateralOpts: CvxStableCollateralOpts = { - erc20: ZERO_ADDRESS, - targetName: ethers.utils.formatBytes32String('USD'), - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: DAI_USD_FEED, // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero - oracleError: bn('1'), // unused but cannot be zero - maxTradeVolume: MAX_TRADE_VOL, - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - revenueHiding: bn('0'), // TODO - nTokens: bn('3'), - curvePool: THREE_POOL, - lpToken: THREE_POOL_TOKEN, - poolType: CurvePoolType.Plain, - feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], - oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], -} - -export const deployCollateral = async ( - opts: CvxStableCollateralOpts = {} -): Promise => { - if (!opts.erc20 && !opts.feeds) { - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) - - // Substitute all 3 feeds: DAI, USDC, USDT - const daiFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const fix = await makeW3PoolStable() - - opts.feeds = [[daiFeed.address], [usdcFeed.address], [usdtFeed.address]] - opts.erc20 = fix.w3Pool.address - } - - opts = { ...defaultCvxStableCollateralOpts, ...opts } - - const CvxStableCollateralFactory: ContractFactory = await ethers.getContractFactory( - 'CvxStableCollateral' - ) - - const collateral = await CvxStableCollateralFactory.deploy( - { - erc20: opts.erc20, - targetName: opts.targetName, - priceTimeout: opts.priceTimeout, - chainlinkFeed: opts.chainlinkFeed, - oracleError: opts.oracleError, - oracleTimeout: opts.oracleTimeout, - maxTradeVolume: opts.maxTradeVolume, - defaultThreshold: opts.defaultThreshold, - delayUntilDefault: opts.delayUntilDefault, - }, - opts.revenueHiding, - { - nTokens: opts.nTokens, - curvePool: opts.curvePool, - poolType: opts.poolType, - feeds: opts.feeds, - oracleTimeouts: opts.oracleTimeouts, - oracleErrors: opts.oracleErrors, - lpToken: opts.lpToken, - } - ) - await collateral.deployed() - - // sometimes we are trying to test a negative test case and we want this to fail silently - // fortunately this syntax fails silently because our tools are terrible - await expect(collateral.refresh()) - - return collateral -} - -const makeCollateralFixtureContext = ( - alice: SignerWithAddress, - opts: CvxStableCollateralOpts = {} -): Fixture => { - const collateralOpts = { ...defaultCvxStableCollateralOpts, ...opts } - - const makeCollateralFixtureContext = async () => { - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) - - // Substitute all 3 feeds: DAI, USDC, USDT - const daiFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - collateralOpts.feeds = [[daiFeed.address], [usdcFeed.address], [usdtFeed.address]] - - const fix = await makeW3PoolStable() - - collateralOpts.erc20 = fix.w3Pool.address - collateralOpts.curvePool = fix.curvePool.address - const collateral = ((await deployCollateral(collateralOpts)) as unknown) - const rewardToken = await ethers.getContractAt('ERC20Mock', CVX) // use CVX - - const cvx = await ethers.getContractAt('ERC20Mock', CVX) - const crv = await ethers.getContractAt('ERC20Mock', CRV) - - return { - alice, - collateral, - chainlinkFeed: usdcFeed, - curvePool: fix.curvePool, - crv3Pool: fix.crv3Pool, - w3Pool: fix.w3Pool, - dai: fix.dai, - usdc: fix.usdc, - usdt: fix.usdt, - tok: fix.w3Pool, - rewardToken, - usdcFeed, - daiFeed, - usdtFeed, - cvx, - crv, - } - } - - return makeCollateralFixtureContext -} - -/* - Define helper functions -*/ - -const mintCollateralTo: MintCollateralFunc = async ( - ctx: CvxStableCollateralFixtureContext, - amount: BigNumberish, - user: SignerWithAddress, - recipient: string -) => { - await mintW3Pool(ctx, amount, user, recipient, THREE_POOL_HOLDER) -} - -/* - Define collateral-specific tests -*/ - -const collateralSpecificConstructorTests = () => { - it('does not allow 0 defaultThreshold', async () => { - await expect(deployCollateral({ defaultThreshold: bn('0') })).to.be.revertedWith( - 'defaultThreshold zero' - ) - }) - - it('does not allow more than 4 tokens', async () => { - await expect(deployCollateral({ nTokens: 5 })).to.be.revertedWith('up to 4 tokens max') - }) - - it('does not allow empty curvePool', async () => { - await expect(deployCollateral({ curvePool: ZERO_ADDRESS })).to.be.revertedWith( - 'curvePool address is zero' - ) - }) - - it('does not allow more than 2 price feeds', async () => { - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[DAI_USD_FEED, DAI_USD_FEED, DAI_USD_FEED], [], []], - }) - ).to.be.revertedWith('price feeds limited to 2') - }) - - it('requires at least 1 price feed per token', async () => { - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[DAI_USD_FEED, DAI_USD_FEED], [USDC_USD_FEED], []], - }) - ).to.be.revertedWith('each token needs at least 1 price feed') - }) - - it('requires non-zero-address feeds', async () => { - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[ZERO_ADDRESS], [USDC_USD_FEED], [USDT_USD_FEED]], - }) - ).to.be.revertedWith('t0feed0 empty') - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[DAI_USD_FEED, ZERO_ADDRESS], [USDC_USD_FEED], [USDT_USD_FEED]], - }) - ).to.be.revertedWith('t0feed1 empty') - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[USDC_USD_FEED], [ZERO_ADDRESS], [USDT_USD_FEED]], - }) - ).to.be.revertedWith('t1feed0 empty') - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[DAI_USD_FEED], [USDC_USD_FEED, ZERO_ADDRESS], [USDT_USD_FEED]], - }) - ).to.be.revertedWith('t1feed1 empty') - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [ZERO_ADDRESS]], - }) - ).to.be.revertedWith('t2feed0 empty') - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED, ZERO_ADDRESS]], - }) - ).to.be.revertedWith('t2feed1 empty') - }) - - it('requires non-zero oracleTimeouts', async () => { - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - oracleTimeouts: [[bn('0')], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], - }) - ).to.be.revertedWith('t0timeout0 zero') - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - oracleTimeouts: [[USDC_ORACLE_TIMEOUT], [bn('0')], [USDT_ORACLE_TIMEOUT]], - }) - ).to.be.revertedWith('t1timeout0 zero') - await expect( - deployCollateral({ - erc20: THREE_POOL_TOKEN, // can be anything. - oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [bn('0')]], - }) - ).to.be.revertedWith('t2timeout0 zero') - }) - - it('requires non-zero oracleErrors', async () => { - await expect( - deployCollateral({ - oracleErrors: [[fp('1')], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], - }) - ).to.be.revertedWith('t0error0 too large') - await expect( - deployCollateral({ - oracleErrors: [[USDC_ORACLE_ERROR], [fp('1')], [USDT_ORACLE_ERROR]], - }) - ).to.be.revertedWith('t1error0 too large') - await expect( - deployCollateral({ oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [fp('1')]] }) - ).to.be.revertedWith('t2error0 too large') - }) -} - -/* - Run the test suite -*/ - -const describeFork = useEnv('FORK') ? describe : describe.skip - -describeFork(`Collateral: Convex - Stable (3Pool)`, () => { - before(resetFork) - describe('constructor validation', () => { - it('validates targetName', async () => { - await expect(deployCollateral({ targetName: ethers.constants.HashZero })).to.be.revertedWith( - 'targetName missing' - ) - }) - - it('does not allow missing ERC20', async () => { - await expect(deployCollateral({ erc20: ethers.constants.AddressZero })).to.be.revertedWith( - 'missing erc20' - ) - }) - - it('does not allow missing chainlink feed', async () => { - await expect( - deployCollateral({ chainlinkFeed: ethers.constants.AddressZero }) - ).to.be.revertedWith('missing chainlink feed') - }) - - it('max trade volume must be greater than zero', async () => { - await expect(deployCollateral({ maxTradeVolume: 0 })).to.be.revertedWith( - 'invalid max trade volume' - ) - }) - - it('does not allow oracle timeout at 0', async () => { - await expect(deployCollateral({ oracleTimeout: 0 })).to.be.revertedWith('oracleTimeout zero') - }) - - it('does not allow missing delayUntilDefault if defaultThreshold > 0', async () => { - await expect(deployCollateral({ delayUntilDefault: 0 })).to.be.revertedWith( - 'delayUntilDefault zero' - ) - }) - - describe('collateral-specific tests', collateralSpecificConstructorTests) - }) - - describe('collateral functionality', () => { - let ctx: CvxStableCollateralFixtureContext - let alice: SignerWithAddress - - let wallet: SignerWithAddress - let chainId: number - - let collateral: TestICollateral - let chainlinkFeed: MockV3Aggregator - let usdcFeed: MockV3Aggregator - let daiFeed: MockV3Aggregator - let usdtFeed: MockV3Aggregator - - let crv: ERC20Mock - let cvx: ERC20Mock - - before(async () => { - ;[wallet] = (await ethers.getSigners()) as unknown as SignerWithAddress[] - - chainId = await getChainId(hre) - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - await resetFork() - }) - - beforeEach(async () => { - ;[, alice] = await ethers.getSigners() - ctx = await loadFixture(makeCollateralFixtureContext(alice, {})) - ;({ chainlinkFeed, collateral, usdcFeed, daiFeed, usdtFeed, crv, cvx } = ctx) - - await mintCollateralTo(ctx, bn('100e18'), wallet, wallet.address) - }) - - describe('functions', () => { - it('returns the correct bal (18 decimals)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, alice.address) - - const aliceBal = await collateral.bal(alice.address) - expect(aliceBal).to.closeTo( - amount.mul(bn(10).pow(18 - (await ctx.tok.decimals()))), - bn('100').mul(bn(10).pow(18 - (await ctx.tok.decimals()))) - ) - }) - }) - - describe('rewards', () => { - it('does not revert', async () => { - await expect(collateral.claimRewards()).to.not.be.reverted - }) - - it('claims rewards (plugin)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, collateral.address) - - await advanceBlocks(1000) - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) - - const crvBefore = await crv.balanceOf(collateral.address) - const cvxBefore = await cvx.balanceOf(collateral.address) - await expect(collateral.claimRewards()).to.emit(ctx.w3Pool, 'RewardsClaimed') - const crvAfter = await crv.balanceOf(collateral.address) - const cvxAfter = await cvx.balanceOf(collateral.address) - expect(crvAfter).gt(crvBefore) - expect(cvxAfter).gt(cvxBefore) - }) - - it('claims rewards (wrapper)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, alice.address) - - await advanceBlocks(1000) - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) - - const crvBefore = await crv.balanceOf(alice.address) - const cvxBefore = await cvx.balanceOf(alice.address) - await expect(ctx.w3Pool.connect(alice).claimRewards()).to.emit(ctx.w3Pool, 'RewardsClaimed') - const crvAfter = await crv.balanceOf(alice.address) - const cvxAfter = await cvx.balanceOf(alice.address) - expect(crvAfter).gt(crvBefore) - expect(cvxAfter).gt(cvxBefore) - }) - }) - - describe('prices', () => { - before(resetFork) - it('prices change as feed price changes', async () => { - const feedData = await usdcFeed.latestRoundData() - const initialRefPerTok = await collateral.refPerTok() - - const [low, high] = await collateral.price() - - // Update values in Oracles increase by 10% - const newPrice = feedData.answer.mul(110).div(100) - - await Promise.all([ - usdcFeed.updateAnswer(newPrice).then((e) => e.wait()), - daiFeed.updateAnswer(newPrice).then((e) => e.wait()), - usdtFeed.updateAnswer(newPrice).then((e) => e.wait()), - ]) - - const [newLow, newHigh] = await collateral.price() - - expect(newLow).to.be.closeTo(low.mul(110).div(100), 1) - expect(newHigh).to.be.closeTo(high.mul(110).div(100), 1) - - // Check refPerTok remains the same (because we have not refreshed) - const finalRefPerTok = await collateral.refPerTok() - expect(finalRefPerTok).to.equal(initialRefPerTok) - }) - - it('prices change as refPerTok changes', async () => { - const initRefPerTok = await collateral.refPerTok() - const [initLow, initHigh] = await collateral.price() - - const curveVirtualPrice = await ctx.curvePool.get_virtual_price() - await ctx.curvePool.setVirtualPrice(curveVirtualPrice.add(1e4)) - await ctx.curvePool.setBalances([ - await ctx.curvePool.balances(0).then((e) => e.add(1e4)), - await ctx.curvePool.balances(1).then((e) => e.add(2e4)), - await ctx.curvePool.balances(2).then((e) => e.add(3e4)), - ]) - - await collateral.refresh() - expect(await collateral.refPerTok()).to.be.gt(initRefPerTok) - - const [newLow, newHigh] = await collateral.price() - expect(newLow).to.be.gt(initLow) - expect(newHigh).to.be.gt(initHigh) - }) - - it('returns a 0 price', async () => { - await Promise.all([ - usdcFeed.updateAnswer(0).then((e) => e.wait()), - daiFeed.updateAnswer(0).then((e) => e.wait()), - usdtFeed.updateAnswer(0).then((e) => e.wait()), - ]) - - // (0, FIX_MAX) is returned - const [low, high] = await collateral.price() - expect(low).to.equal(0) - expect(high).to.equal(0) - - // When refreshed, sets status to Unpriced - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('reverts in case of invalid timestamp', async () => { - await usdcFeed.setInvalidTimestamp() - - // Check price of token - const [low, high] = await collateral.price() - expect(low).to.equal(0) - expect(high).to.equal(MAX_UINT192) - - // When refreshed, sets status to Unpriced - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('decays lotPrice over priceTimeout period', async () => { - // Prices should start out equal - const p = await collateral.price() - let lotP = await collateral.lotPrice() - expect(p.length).to.equal(lotP.length) - expect(p[0]).to.equal(lotP[0]) - expect(p[1]).to.equal(lotP[1]) - - // Should be roughly half, after half of priceTimeout - const priceTimeout = await collateral.priceTimeout() - await advanceTime(priceTimeout / 2) - lotP = await collateral.lotPrice() - expect(lotP[0]).to.be.closeTo(p[0].div(2), p[0].div(2).div(10000)) // 1 part in 10 thousand - expect(lotP[1]).to.be.closeTo(p[1].div(2), p[1].div(2).div(10000)) // 1 part in 10 thousand - - // Should be 0 after full priceTimeout - await advanceTime(priceTimeout / 2) - lotP = await collateral.lotPrice() - expect(lotP[0]).to.equal(0) - expect(lotP[1]).to.equal(0) - }) - }) - - describe('status', () => { - it('maintains status in normal situations', async () => { - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Force updates (with no changes) - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - - // State remains the same - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - }) - - it('enters IFFY state when reference unit depegs below low threshold', async () => { - const delayUntilDefault = await collateral.delayUntilDefault() - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Depeg USDC:USD - Reducing price by 20% from 1 to 0.8 - const updateAnswerTx = await chainlinkFeed.updateAnswer(bn('8e7')) - await updateAnswerTx.wait() - - // Set next block timestamp - for deterministic result - const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - await setNextBlockTimestamp(nextBlockTimestamp) - const expectedDefaultTimestamp = nextBlockTimestamp + delayUntilDefault - - await expect(collateral.refresh()) - .to.emit(collateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - expect(await collateral.whenDefault()).to.equal(expectedDefaultTimestamp) - }) - - it('enters IFFY state when reference unit depegs above high threshold', async () => { - const delayUntilDefault = await collateral.delayUntilDefault() - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Depeg USDC:USD - Raising price by 20% from 1 to 1.2 - const updateAnswerTx = await chainlinkFeed.updateAnswer(bn('1.2e8')) - await updateAnswerTx.wait() - - // Set next block timestamp - for deterministic result - const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - await setNextBlockTimestamp(nextBlockTimestamp) - const expectedDefaultTimestamp = nextBlockTimestamp + delayUntilDefault - - await expect(collateral.refresh()) - .to.emit(collateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - expect(await collateral.whenDefault()).to.equal(expectedDefaultTimestamp) - }) - - it('enters DISABLED state when reference unit depegs for too long', async () => { - const delayUntilDefault = await collateral.delayUntilDefault() - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Depeg USDC:USD - Reducing price by 20% from 1 to 0.8 - const updateAnswerTx = await chainlinkFeed.updateAnswer(bn('8e7')) - await updateAnswerTx.wait() - - // Set next block timestamp - for deterministic result - const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - await setNextBlockTimestamp(nextBlockTimestamp) - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - - // Move time forward past delayUntilDefault - await advanceTime(delayUntilDefault) - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - - // Nothing changes if attempt to refresh after default - const prevWhenDefault: bigint = (await collateral.whenDefault()).toBigInt() - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await collateral.whenDefault()).to.equal(prevWhenDefault) - }) - - it('enters DISABLED state when refPerTok() decreases', async () => { - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - await mintCollateralTo(ctx, bn('20000e6'), alice, alice.address) - - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - // State remains the same - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - const currentExchangeRate = await ctx.curvePool.get_virtual_price() - await ctx.curvePool.setVirtualPrice(currentExchangeRate.sub(1e3)).then((e) => e.wait()) - - // Collateral defaults due to refPerTok() going down - await expect(collateral.refresh()).to.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await collateral.whenDefault()).to.equal(await getLatestBlockTimestamp()) - }) - - it('enters IFFY state when price becomes stale', async () => { - const oracleTimeout = DAI_ORACLE_TIMEOUT.toNumber() - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('does revenue hiding correctly', async () => { - ctx = await loadFixture(makeCollateralFixtureContext(alice, { revenueHiding: fp('1e-6') })) - ;({ collateral } = ctx) - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - await mintCollateralTo(ctx, bn('20000e6'), alice, alice.address) - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - - // State remains the same - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Decrease refPerTok by 1 part in a million - const currentExchangeRate = await ctx.curvePool.get_virtual_price() - const newVirtualPrice = currentExchangeRate.sub(currentExchangeRate.div(bn('1e6'))) - await ctx.curvePool.setVirtualPrice(newVirtualPrice) - - // Collateral remains SOUND - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // One quanta more of decrease results in default - await ctx.curvePool.setVirtualPrice(newVirtualPrice.sub(1)) - await expect(collateral.refresh()).to.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await collateral.whenDefault()).to.equal(await getLatestBlockTimestamp()) - }) - - it('reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { - const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( - 'InvalidMockV3Aggregator' - ) - const invalidChainlinkFeed = ( - await InvalidMockV3AggregatorFactory.deploy(6, bn('1e6')) - ) - - const fix = await makeW3PoolStable() - - const invalidCollateral = await deployCollateral({ - erc20: fix.w3Pool.address, - feeds: [ - [invalidChainlinkFeed.address], - [invalidChainlinkFeed.address], - [invalidChainlinkFeed.address], - ], - }) - - // Reverting with no reason - await invalidChainlinkFeed.setSimplyRevert(true) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - - // Runnning out of gas (same error) - await invalidChainlinkFeed.setSimplyRevert(false) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - }) - }) - }) -}) diff --git a/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts b/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts deleted file mode 100644 index cbbd8780a0..0000000000 --- a/test/plugins/individual-collateral/convex/CvxVolatileTestSuite.test.ts +++ /dev/null @@ -1,799 +0,0 @@ -import { - CollateralFixtureContext, - CollateralOpts, - CollateralStatus, - MintCollateralFunc, -} from '../pluginTestTypes' -import { mintW3Pool, makeW3PoolVolatile, Wrapped3PoolFixtureVolatile, resetFork } from './helpers' -import hre, { ethers } from 'hardhat' -import { ContractFactory, BigNumberish } from 'ethers' -import { - CvxVolatileCollateral, - ERC20Mock, - InvalidMockV3Aggregator, - MockV3Aggregator, - MockV3Aggregator__factory, - TestICollateral, -} from '../../../../typechain' -import { bn, fp } from '../../../../common/numbers' -import { MAX_UINT192, MAX_UINT48, ZERO_ADDRESS } from '../../../../common/constants' -import { expect } from 'chai' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' -import { - PRICE_TIMEOUT, - CVX, - USDT_ORACLE_TIMEOUT, - USDT_ORACLE_ERROR, - MAX_TRADE_VOL, - DEFAULT_THRESHOLD, - DELAY_UNTIL_DEFAULT, - CurvePoolType, - CRV, - TRI_CRYPTO_HOLDER, - TRI_CRYPTO, - TRI_CRYPTO_TOKEN, - WBTC_BTC_FEED, - BTC_USD_FEED, - BTC_ORACLE_TIMEOUT, - WETH_USD_FEED, - WBTC_BTC_ORACLE_ERROR, - WBTC_ORACLE_TIMEOUT, - WETH_ORACLE_TIMEOUT, - USDT_USD_FEED, - BTC_USD_ORACLE_ERROR, - WETH_ORACLE_ERROR, -} from './constants' -import { useEnv } from '#/utils/env' -import { getChainId } from '#/common/blockchain-utils' -import { networkConfig } from '#/common/configuration' -import { - advanceBlocks, - advanceTime, - getLatestBlockTimestamp, - setNextBlockTimestamp, -} from '#/test/utils/time' - -type Fixture = () => Promise - -/* - Define interfaces -*/ - -interface CvxVolatileCollateralFixtureContext - extends CollateralFixtureContext, - Wrapped3PoolFixtureVolatile { - wethFeed: MockV3Aggregator - wbtcFeed: MockV3Aggregator - btcFeed: MockV3Aggregator - usdtFeed: MockV3Aggregator - cvx: ERC20Mock - crv: ERC20Mock -} - -// interface CometCollateralFixtureContextMockComet extends CollateralFixtureContext { -// cusdcV3: CometMock -// wcusdcV3: ICusdcV3Wrapper -// usdc: ERC20Mock -// wcusdcV3Mock: CusdcV3WrapperMock -// } - -interface CvxVolatileCollateralOpts extends CollateralOpts { - revenueHiding?: BigNumberish - nTokens?: BigNumberish - curvePool?: string - poolType?: CurvePoolType - feeds?: string[][] - oracleTimeouts?: BigNumberish[][] - oracleErrors?: BigNumberish[][] - lpToken?: string -} - -/* - Define deployment functions -*/ - -export const defaultCvxVolatileCollateralOpts: CvxVolatileCollateralOpts = { - erc20: ZERO_ADDRESS, - targetName: ethers.utils.formatBytes32String('TRICRYPTO'), - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: USDT_USD_FEED, // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero - oracleError: bn('1'), // unused but cannot be zero - maxTradeVolume: MAX_TRADE_VOL, - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - revenueHiding: bn('0'), // TODO - nTokens: bn('3'), - curvePool: TRI_CRYPTO, - lpToken: TRI_CRYPTO_TOKEN, - poolType: CurvePoolType.Plain, - feeds: [[USDT_USD_FEED], [WBTC_BTC_FEED, BTC_USD_FEED], [WETH_USD_FEED]], - oracleTimeouts: [ - [USDT_ORACLE_TIMEOUT], - [WBTC_ORACLE_TIMEOUT, BTC_ORACLE_TIMEOUT], - [WETH_ORACLE_TIMEOUT], - ], - oracleErrors: [ - [USDT_ORACLE_ERROR], - [WBTC_BTC_ORACLE_ERROR, BTC_USD_ORACLE_ERROR], - [WETH_ORACLE_ERROR], - ], -} - -const makeFeeds = async () => { - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) - - // Substitute all 3 feeds: DAI, USDC, USDT - const wethFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const wbtcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const btcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - - const wethFeedOrg = MockV3AggregatorFactory.attach(WETH_USD_FEED) - const wbtcFeedOrg = MockV3AggregatorFactory.attach(WBTC_BTC_FEED) - const btcFeedOrg = MockV3AggregatorFactory.attach(BTC_USD_FEED) - const usdtFeedOrg = MockV3AggregatorFactory.attach(USDT_USD_FEED) - - await wethFeed.updateAnswer(await wethFeedOrg.latestAnswer()) - await wbtcFeed.updateAnswer(await wbtcFeedOrg.latestAnswer()) - await btcFeed.updateAnswer(await btcFeedOrg.latestAnswer()) - await usdtFeed.updateAnswer(await usdtFeedOrg.latestAnswer()) - - return { wethFeed, wbtcFeed, btcFeed, usdtFeed } -} - -export const deployCollateral = async ( - opts: CvxVolatileCollateralOpts = {} -): Promise => { - if (!opts.erc20 && !opts.feeds) { - const { wethFeed, wbtcFeed, btcFeed, usdtFeed } = await makeFeeds() - - const fix = await makeW3PoolVolatile() - - opts.feeds = [[wethFeed.address], [wbtcFeed.address, btcFeed.address], [usdtFeed.address]] - opts.erc20 = fix.w3Pool.address - } - - opts = { ...defaultCvxVolatileCollateralOpts, ...opts } - - const CvxVolatileCollateralFactory: ContractFactory = await ethers.getContractFactory( - 'CvxVolatileCollateral' - ) - - const collateral = await CvxVolatileCollateralFactory.deploy( - { - erc20: opts.erc20, - targetName: opts.targetName, - priceTimeout: opts.priceTimeout, - chainlinkFeed: opts.chainlinkFeed, - oracleError: opts.oracleError, - oracleTimeout: opts.oracleTimeout, - maxTradeVolume: opts.maxTradeVolume, - defaultThreshold: opts.defaultThreshold, - delayUntilDefault: opts.delayUntilDefault, - }, - opts.revenueHiding, - { - nTokens: opts.nTokens, - curvePool: opts.curvePool, - poolType: opts.poolType, - feeds: opts.feeds, - oracleTimeouts: opts.oracleTimeouts, - oracleErrors: opts.oracleErrors, - lpToken: opts.lpToken, - } - ) - await collateral.deployed() - - return collateral -} - -const makeCollateralFixtureContext = ( - alice: SignerWithAddress, - opts: CvxVolatileCollateralOpts = {} -): Fixture => { - const collateralOpts = { ...defaultCvxVolatileCollateralOpts, ...opts } - - const makeCollateralFixtureContext = async () => { - const { wethFeed, wbtcFeed, btcFeed, usdtFeed } = await makeFeeds() - - collateralOpts.feeds = [ - [usdtFeed.address], - [wbtcFeed.address, btcFeed.address], - [wethFeed.address], - ] - - const fix = await makeW3PoolVolatile() - - collateralOpts.erc20 = fix.w3Pool.address - collateralOpts.curvePool = fix.curvePool.address - const collateral = ((await deployCollateral(collateralOpts)) as unknown) - const rewardToken = await ethers.getContractAt('ERC20Mock', CVX) // use CVX - - const cvx = await ethers.getContractAt('ERC20Mock', CVX) - const crv = await ethers.getContractAt('ERC20Mock', CRV) - - return { - alice, - collateral, - chainlinkFeed: usdtFeed, - curvePool: fix.curvePool, - crv3Pool: fix.crv3Pool, - w3Pool: fix.w3Pool, - usdt: fix.usdt, - wbtc: fix.wbtc, - weth: fix.weth, - tok: fix.w3Pool, - rewardToken, - wbtcFeed, - btcFeed, - wethFeed, - usdtFeed, - cvx, - crv, - } - } - - return makeCollateralFixtureContext -} - -/* - Define helper functions -*/ - -const mintCollateralTo: MintCollateralFunc = async ( - ctx: CvxVolatileCollateralFixtureContext, - amount: BigNumberish, - user: SignerWithAddress, - recipient: string -) => { - await mintW3Pool(ctx, amount, user, recipient, TRI_CRYPTO_HOLDER) -} - -/* - Define collateral-specific tests -*/ - -const collateralSpecificConstructorTests = () => { - it('does not allow 0 defaultThreshold', async () => { - await expect(deployCollateral({ defaultThreshold: bn('0') })).to.be.revertedWith( - 'defaultThreshold zero' - ) - }) - - it('does not allow more than 4 tokens', async () => { - await expect(deployCollateral({ nTokens: 5 })).to.be.revertedWith('up to 4 tokens max') - }) - - it('does not allow empty curvePool', async () => { - await expect(deployCollateral({ curvePool: ZERO_ADDRESS })).to.be.revertedWith( - 'curvePool address is zero' - ) - }) - - it('does not allow more than 2 price feeds', async () => { - await expect( - deployCollateral({ - erc20: TRI_CRYPTO_TOKEN, // can be anything. - feeds: [[WETH_USD_FEED, WBTC_BTC_FEED, WETH_USD_FEED], [], []], - }) - ).to.be.revertedWith('price feeds limited to 2') - }) - - it('requires at least 1 price feed per token', async () => { - await expect( - deployCollateral({ - erc20: TRI_CRYPTO_TOKEN, // can be anything. - feeds: [[WETH_USD_FEED], [WETH_USD_FEED], []], - }) - ).to.be.revertedWith('each token needs at least 1 price feed') - }) - - it('requires non-zero-address feeds', async () => { - await expect( - deployCollateral({ - erc20: TRI_CRYPTO_TOKEN, // can be anything. - feeds: [[ZERO_ADDRESS], [WBTC_BTC_FEED, BTC_USD_FEED], [WETH_USD_FEED]], - }) - ).to.be.revertedWith('t0feed0 empty') - await expect( - deployCollateral({ - erc20: TRI_CRYPTO_TOKEN, // can be anything. - feeds: [[WETH_USD_FEED, ZERO_ADDRESS], [WBTC_BTC_FEED, BTC_USD_FEED], [WETH_USD_FEED]], - }) - ).to.be.revertedWith('t0feed1 empty') - await expect( - deployCollateral({ - erc20: TRI_CRYPTO_TOKEN, // can be anything. - feeds: [[USDT_USD_FEED], [ZERO_ADDRESS], [WETH_USD_FEED]], - }) - ).to.be.revertedWith('t1feed0 empty') - await expect( - deployCollateral({ - erc20: TRI_CRYPTO_TOKEN, // can be anything. - feeds: [[USDT_USD_FEED], [USDT_USD_FEED, ZERO_ADDRESS], [WETH_USD_FEED]], - }) - ).to.be.revertedWith('t1feed1 empty') - await expect( - deployCollateral({ - erc20: TRI_CRYPTO_TOKEN, // can be anything. - feeds: [[USDT_USD_FEED], [USDT_USD_FEED], [ZERO_ADDRESS]], - }) - ).to.be.revertedWith('t2feed0 empty') - await expect( - deployCollateral({ - erc20: TRI_CRYPTO_TOKEN, // can be anything. - feeds: [[USDT_USD_FEED], [USDT_USD_FEED], [WETH_USD_FEED, ZERO_ADDRESS]], - }) - ).to.be.revertedWith('t2feed1 empty') - }) - - it('requires non-zero oracleTimeouts', async () => { - await expect( - deployCollateral({ - erc20: TRI_CRYPTO_TOKEN, // can be anything. - oracleTimeouts: [[bn('0')], [USDT_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], - }) - ).to.be.revertedWith('t0timeout0 zero') - await expect( - deployCollateral({ - erc20: TRI_CRYPTO_TOKEN, // can be anything. - oracleTimeouts: [[USDT_ORACLE_TIMEOUT], [bn('0')], [USDT_ORACLE_TIMEOUT]], - }) - ).to.be.revertedWith('t1timeout0 zero') - await expect( - deployCollateral({ - erc20: TRI_CRYPTO_TOKEN, // can be anything. - oracleTimeouts: [ - [USDT_ORACLE_TIMEOUT], - [USDT_ORACLE_TIMEOUT, USDT_ORACLE_TIMEOUT], - [bn('0')], - ], - }) - ).to.be.revertedWith('t2timeout0 zero') - }) - - it('requires non-zero oracleErrors', async () => { - await expect( - deployCollateral({ - oracleErrors: [[fp('1')], [USDT_ORACLE_ERROR], [USDT_ORACLE_ERROR]], - }) - ).to.be.revertedWith('t0error0 too large') - await expect( - deployCollateral({ - oracleErrors: [[USDT_ORACLE_ERROR], [fp('1')], [USDT_ORACLE_ERROR]], - }) - ).to.be.revertedWith('t1error0 too large') - await expect( - deployCollateral({ oracleErrors: [[USDT_ORACLE_ERROR], [USDT_ORACLE_ERROR], [fp('1')]] }) - ).to.be.revertedWith('t2error0 too large') - }) -} - -/* - Run the test suite -*/ - -const describeFork = useEnv('FORK') ? describe : describe.skip - -describeFork(`Collateral: Convex - Volatile`, () => { - before(resetFork) - - describe('constructor validation', () => { - it('validates targetName', async () => { - await expect(deployCollateral({ targetName: ethers.constants.HashZero })).to.be.revertedWith( - 'targetName missing' - ) - }) - - it('does not allow missing ERC20', async () => { - await expect(deployCollateral({ erc20: ethers.constants.AddressZero })).to.be.revertedWith( - 'missing erc20' - ) - }) - - it('does not allow missing chainlink feed', async () => { - await expect( - deployCollateral({ chainlinkFeed: ethers.constants.AddressZero }) - ).to.be.revertedWith('missing chainlink feed') - }) - - it('max trade volume must be greater than zero', async () => { - await expect(deployCollateral({ maxTradeVolume: 0 })).to.be.revertedWith( - 'invalid max trade volume' - ) - }) - - it('does not allow oracle timeout at 0', async () => { - await expect(deployCollateral({ oracleTimeout: 0 })).to.be.revertedWith('oracleTimeout zero') - }) - - it('does not allow missing delayUntilDefault if defaultThreshold > 0', async () => { - await expect(deployCollateral({ delayUntilDefault: 0 })).to.be.revertedWith( - 'delayUntilDefault zero' - ) - }) - - describe('collateral-specific tests', collateralSpecificConstructorTests) - }) - - describe('collateral functionality', () => { - let ctx: CvxVolatileCollateralFixtureContext - let alice: SignerWithAddress - - let wallet: SignerWithAddress - let chainId: number - - let collateral: TestICollateral - let chainlinkFeed: MockV3Aggregator - let wbtcFeed: MockV3Aggregator - let btcFeed: MockV3Aggregator - let wethFeed: MockV3Aggregator - let usdtFeed: MockV3Aggregator - - let crv: ERC20Mock - let cvx: ERC20Mock - - before(async () => { - ;[wallet] = (await ethers.getSigners()) as unknown as SignerWithAddress[] - - chainId = await getChainId(hre) - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - }) - - beforeEach(async () => { - ;[, alice] = await ethers.getSigners() - ctx = await loadFixture(makeCollateralFixtureContext(alice, {})) - ;({ chainlinkFeed, collateral, wbtcFeed, btcFeed, wethFeed, usdtFeed, crv, cvx } = ctx) - - await mintCollateralTo(ctx, bn('100e18'), wallet, wallet.address) - }) - - describe('functions', () => { - it('returns the correct bal (18 decimals)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, alice.address) - - const aliceBal = await collateral.bal(alice.address) - expect(aliceBal).to.closeTo( - amount.mul(bn(10).pow(18 - (await ctx.tok.decimals()))), - bn('100').mul(bn(10).pow(18 - (await ctx.tok.decimals()))) - ) - }) - }) - - describe('rewards', () => { - it('does not revert', async () => { - await expect(collateral.claimRewards()).to.not.be.reverted - }) - - it('claims rewards (plugin)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, collateral.address) - - await advanceBlocks(1000) - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) - - const crvBefore = await crv.balanceOf(collateral.address) - const cvxBefore = await cvx.balanceOf(collateral.address) - await expect(collateral.claimRewards()).to.emit(ctx.w3Pool, 'RewardsClaimed') - const crvAfter = await crv.balanceOf(collateral.address) - const cvxAfter = await cvx.balanceOf(collateral.address) - expect(crvAfter).gt(crvBefore) - expect(cvxAfter).gt(cvxBefore) - }) - - it('claims rewards (wrapper)', async () => { - const amount = bn('20000').mul(bn(10).pow(await ctx.tok.decimals())) - await mintCollateralTo(ctx, amount, alice, alice.address) - - await advanceBlocks(1000) - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) - - const crvBefore = await crv.balanceOf(alice.address) - const cvxBefore = await cvx.balanceOf(alice.address) - await expect(ctx.w3Pool.connect(alice).claimRewards()).to.emit(ctx.w3Pool, 'RewardsClaimed') - const crvAfter = await crv.balanceOf(alice.address) - const cvxAfter = await cvx.balanceOf(alice.address) - expect(crvAfter).gt(crvBefore) - expect(cvxAfter).gt(cvxBefore) - }) - }) - - describe('prices', () => { - it('prices change as feed price changes', async () => { - await collateral.refresh() - - const initialRefPerTok = await collateral.refPerTok() - const [low, high] = await collateral.price() - - await Promise.all([ - btcFeed - .updateAnswer(await btcFeed.latestRoundData().then((e) => e.answer.mul(110).div(100))) - .then((e) => e.wait()), - wethFeed - .updateAnswer(await wethFeed.latestRoundData().then((e) => e.answer.mul(110).div(100))) - .then((e) => e.wait()), - usdtFeed - .updateAnswer(await usdtFeed.latestRoundData().then((e) => e.answer.mul(110).div(100))) - .then((e) => e.wait()), - ]) - - const [newLow, newHigh] = await collateral.price() - const expectedNewLow = low.mul(110).div(100) - const expectedNewHigh = high.mul(110).div(100) - - // Expect price to be correct within 1 part in 100 million - // The rounding comes from the oracle .mul(110).div(100) calculations truncating - expect(newLow).to.be.closeTo(expectedNewLow, expectedNewLow.div(bn('1e8'))) - expect(newHigh).to.be.closeTo(expectedNewHigh, expectedNewHigh.div(bn('1e8'))) - expect(newLow).to.be.lt(expectedNewLow) - expect(newHigh).to.be.lt(expectedNewHigh) - - // Check refPerTok remains the same (because we have not refreshed) - const finalRefPerTok = await collateral.refPerTok() - expect(finalRefPerTok).to.equal(initialRefPerTok) - }) - - it('prices change as refPerTok changes', async () => { - await collateral.refresh() - - const initRefPerTok = await collateral.refPerTok() - const [initLow, initHigh] = await collateral.price() - - const curveVirtualPrice = await ctx.curvePool.get_virtual_price() - await ctx.curvePool.setVirtualPrice(curveVirtualPrice.add(1e4)) - await ctx.curvePool.setBalances([ - await ctx.curvePool.balances(0).then((e) => e.add(1e4)), - await ctx.curvePool.balances(1).then((e) => e.add(2e4)), - await ctx.curvePool.balances(2).then((e) => e.add(3e4)), - ]) - - await collateral.refresh() - expect(await collateral.refPerTok()).to.be.gt(initRefPerTok) - - const [newLow, newHigh] = await collateral.price() - expect(newLow).to.be.gt(initLow) - expect(newHigh).to.be.gt(initHigh) - }) - - it('returns a 0 price', async () => { - await collateral.refresh() - - await Promise.all([ - wbtcFeed.updateAnswer(0).then((e) => e.wait()), - wethFeed.updateAnswer(0).then((e) => e.wait()), - usdtFeed.updateAnswer(0).then((e) => e.wait()), - ]) - - // (0, FIX_MAX) is returned - const [low, high] = await collateral.price() - expect(low).to.equal(0) - expect(high).to.equal(0) - - // When refreshed, sets status to Unpriced - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('reverts in case of invalid timestamp', async () => { - await wbtcFeed.setInvalidTimestamp() - - // Check price of token - const [low, high] = await collateral.price() - expect(low).to.equal(0) - expect(high).to.equal(MAX_UINT192) - - // When refreshed, sets status to Unpriced - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('decays lotPrice over priceTimeout period', async () => { - // Prices should start out equal - await collateral.refresh() - const p = await collateral.price() - let lotP = await collateral.lotPrice() - expect(p.length).to.equal(lotP.length) - expect(p[0]).to.equal(lotP[0]) - expect(p[1]).to.equal(lotP[1]) - - // Should be roughly half, after half of priceTimeout - const priceTimeout = await collateral.priceTimeout() - await advanceTime(priceTimeout / 2) - lotP = await collateral.lotPrice() - expect(lotP[0]).to.be.closeTo(p[0].div(2), p[0].div(2).div(10000)) // 1 part in 10 thousand - expect(lotP[1]).to.be.closeTo(p[1].div(2), p[1].div(2).div(10000)) // 1 part in 10 thousand - - // Should be 0 after full priceTimeout - await advanceTime(priceTimeout / 2) - lotP = await collateral.lotPrice() - expect(lotP[0]).to.equal(0) - expect(lotP[1]).to.equal(0) - }) - }) - - describe('status', () => { - it('maintains status in normal situations', async () => { - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Force updates (with no changes) - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - - // State remains the same - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - }) - - it('enters IFFY state when reference unit depegs below low threshold', async () => { - const delayUntilDefault = await collateral.delayUntilDefault() - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Depeg USDC:USD - Reducing price by 20% from 1 to 0.8 - const updateAnswerTx = await chainlinkFeed.updateAnswer(bn('8e7')) - await updateAnswerTx.wait() - - // Set next block timestamp - for deterministic result - const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - await setNextBlockTimestamp(nextBlockTimestamp) - const expectedDefaultTimestamp = nextBlockTimestamp + delayUntilDefault - - await expect(collateral.refresh()) - .to.emit(collateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - expect(await collateral.whenDefault()).to.equal(expectedDefaultTimestamp) - }) - - it('enters IFFY state when reference unit depegs above high threshold', async () => { - const delayUntilDefault = await collateral.delayUntilDefault() - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Depeg USDC:USD - Raising price by 20% from 1 to 1.2 - const updateAnswerTx = await chainlinkFeed.updateAnswer(bn('1.2e8')) - await updateAnswerTx.wait() - - // Set next block timestamp - for deterministic result - const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - await setNextBlockTimestamp(nextBlockTimestamp) - const expectedDefaultTimestamp = nextBlockTimestamp + delayUntilDefault - - await expect(collateral.refresh()) - .to.emit(collateral, 'CollateralStatusChanged') - .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - expect(await collateral.whenDefault()).to.equal(expectedDefaultTimestamp) - }) - - it('enters DISABLED state when reference unit depegs for too long', async () => { - const delayUntilDefault = await collateral.delayUntilDefault() - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Depeg USDC:USD - Reducing price by 20% from 1 to 0.8 - const updateAnswerTx = await chainlinkFeed.updateAnswer(bn('8e7')) - await updateAnswerTx.wait() - - // Set next block timestamp - for deterministic result - const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 - await setNextBlockTimestamp(nextBlockTimestamp) - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - - // Move time forward past delayUntilDefault - await advanceTime(delayUntilDefault) - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - - // Nothing changes if attempt to refresh after default - const prevWhenDefault: bigint = (await collateral.whenDefault()).toBigInt() - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await collateral.whenDefault()).to.equal(prevWhenDefault) - }) - - it('enters DISABLED state when refPerTok() decreases', async () => { - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - await mintCollateralTo(ctx, bn('20000e6'), alice, alice.address) - - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - // State remains the same - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - const currentExchangeRate = await ctx.curvePool.get_virtual_price() - await ctx.curvePool.setVirtualPrice(currentExchangeRate.sub(1e3)).then((e) => e.wait()) - - // Collateral defaults due to refPerTok() going down - await expect(collateral.refresh()).to.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await collateral.whenDefault()).to.equal(await getLatestBlockTimestamp()) - }) - - it('enters IFFY state when price becomes stale', async () => { - const oracleTimeout = USDT_ORACLE_TIMEOUT.toNumber() - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - }) - - it('does revenue hiding correctly', async () => { - ctx = await loadFixture(makeCollateralFixtureContext(alice, { revenueHiding: fp('1e-6') })) - ;({ collateral } = ctx) - - // Check initial state - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - await mintCollateralTo(ctx, bn('20000e6'), alice, alice.address) - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - - // State remains the same - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Decrease refPerTok by 1 part in a million - const currentExchangeRate = await ctx.curvePool.get_virtual_price() - const newVirtualPrice = currentExchangeRate.sub(currentExchangeRate.div(bn('1e6'))) - await ctx.curvePool.setVirtualPrice(newVirtualPrice) - - // Collateral remains SOUND - await expect(collateral.refresh()).to.not.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - expect(await collateral.whenDefault()).to.equal(MAX_UINT48) - - // Two more quanta of decrease results in default - await ctx.curvePool.setVirtualPrice(newVirtualPrice.sub(2)) - await expect(collateral.refresh()).to.emit(collateral, 'CollateralStatusChanged') - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await collateral.whenDefault()).to.equal(await getLatestBlockTimestamp()) - }) - - it('reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { - const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( - 'InvalidMockV3Aggregator' - ) - const invalidChainlinkFeed = ( - await InvalidMockV3AggregatorFactory.deploy(6, bn('1e6')) - ) - - const fix = await makeW3PoolVolatile() - - const invalidCollateral = await deployCollateral({ - erc20: fix.w3Pool.address, - feeds: [ - [invalidChainlinkFeed.address], - [invalidChainlinkFeed.address], - [invalidChainlinkFeed.address], - ], - }) - - // Reverting with no reason - await invalidChainlinkFeed.setSimplyRevert(true) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - - // Runnning out of gas (same error) - await invalidChainlinkFeed.setSimplyRevert(false) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - }) - }) - }) -}) diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts new file mode 100644 index 0000000000..eeff7bdf43 --- /dev/null +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -0,0 +1,639 @@ +import { + CurveCollateralFixtureContext, + CurveCollateralOpts, + CurveCollateralTestSuiteFixtures, +} from './pluginTestTypes' +import { CollateralStatus } from '../pluginTestTypes' +import { ethers } from 'hardhat' +import { ERC20Mock, InvalidMockV3Aggregator } from '../../../../typechain' +import { BigNumber } from 'ethers' +import { bn, fp } from '../../../../common/numbers' +import { MAX_UINT48, ZERO_ADDRESS, ONE_ADDRESS } from '../../../../common/constants' +import { expect } from 'chai' +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { useEnv } from '#/utils/env' +import { expectUnpriced } from '../../../utils/oracles' +import { + advanceBlocks, + advanceTime, + getLatestBlockTimestamp, + setNextBlockTimestamp, +} from '#/test/utils/time' + +const describeFork = useEnv('FORK') ? describe : describe.skip + +export default function fn( + fixtures: CurveCollateralTestSuiteFixtures +) { + const { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + makeCollateralFixtureContext, + mintCollateralTo, + isMetapool, + resetFork, + collateralName, + } = fixtures + + describeFork(`Collateral: ${collateralName}`, () => { + let defaultOpts: CurveCollateralOpts + let mockERC20: ERC20Mock + + before(async () => { + ;[, defaultOpts] = await deployCollateral({}) + const ERC20Factory = await ethers.getContractFactory('ERC20Mock') + mockERC20 = await ERC20Factory.deploy('Mock ERC20', 'ERC20') + }) + + describe('constructor validation', () => { + it('does not allow 0 defaultThreshold', async () => { + await expect(deployCollateral({ defaultThreshold: bn('0') })).to.be.revertedWith( + 'defaultThreshold zero' + ) + }) + + it('does not allow more than 4 tokens', async () => { + await expect(deployCollateral({ nTokens: 5 })).to.be.revertedWith('up to 4 tokens max') + }) + + it('does not allow empty curvePool', async () => { + await expect(deployCollateral({ curvePool: ZERO_ADDRESS })).to.be.revertedWith( + 'curvePool address is zero' + ) + }) + + it('does not allow invalid Pool Type', async () => { + await expect(deployCollateral({ poolType: 1 })).to.be.revertedWith('invalid poolType') + }) + + it('does not allow more than 2 price feeds', async () => { + await expect( + deployCollateral({ + erc20: mockERC20.address, // can be anything. + feeds: [[ONE_ADDRESS, ONE_ADDRESS, ONE_ADDRESS], [], []], + }) + ).to.be.revertedWith('price feeds limited to 2') + }) + + it('supports up to 2 price feeds per token', async () => { + const nonzeroError = fp('0.01') // 1% + const nTokens = defaultOpts.nTokens || 0 + + const feeds: string[][] = [] + for (let i = 0; i < nTokens; i++) { + feeds.push([ONE_ADDRESS, ONE_ADDRESS]) + } + + const oracleTimeouts: BigNumber[][] = [] + for (let i = 0; i < nTokens; i++) { + oracleTimeouts.push([bn('1'), bn('1')]) + } + + const oracleErrors: BigNumber[][] = [] + for (let i = 0; i < nTokens; i++) { + oracleErrors.push([nonzeroError, bn(0)]) + } + + await expect( + deployCollateral({ + erc20: mockERC20.address, // can be anything. + feeds, + oracleTimeouts, + oracleErrors, + }) + ).to.not.be.reverted + }) + + it('requires at least 1 price feed per token', async () => { + await expect( + deployCollateral({ + erc20: mockERC20.address, // can be anything. + feeds: [[ONE_ADDRESS, ONE_ADDRESS], [ONE_ADDRESS], []], + }) + ).to.be.revertedWith('each token needs at least 1 price feed') + + await expect( + deployCollateral({ + erc20: mockERC20.address, // can be anything. + feeds: [[], [ONE_ADDRESS, ONE_ADDRESS], [ONE_ADDRESS]], + }) + ).to.be.revertedWith('each token needs at least 1 price feed') + + await expect( + deployCollateral({ + erc20: mockERC20.address, // can be anything. + feeds: [[ONE_ADDRESS], [], [ONE_ADDRESS, ONE_ADDRESS]], + }) + ).to.be.revertedWith('each token needs at least 1 price feed') + }) + + it('requires non-zero-address feeds', async () => { + const nonzeroTimeout = bn(defaultOpts.oracleTimeouts![0][0]) + const nonzeroError = bn(defaultOpts.oracleErrors![0][0]) + + // Complete all possible feeds + const allFeeds: string[][] = [] + const allOracleTimeouts: BigNumber[][] = [] + const allOracleErrors: BigNumber[][] = [] + + for (let i = 0; i < defaultOpts.nTokens!; i++) { + allFeeds[i] = [ONE_ADDRESS, ONE_ADDRESS] + allOracleTimeouts[i] = [nonzeroTimeout, nonzeroTimeout] + allOracleErrors[i] = [nonzeroError, nonzeroError] + } + + for (let i = 0; i < allFeeds.length; i++) { + for (let j = 0; j < allFeeds[i].length; j++) { + const feeds = allFeeds.map((f) => f.map(() => ONE_ADDRESS)) + feeds[i][j] = ZERO_ADDRESS + + await expect( + deployCollateral({ + erc20: mockERC20.address, // can be anything. + feeds, + oracleTimeouts: allOracleTimeouts, + oracleErrors: allOracleErrors, + }) + ).to.be.revertedWith(`t${i}feed${j} empty`) + } + } + }) + + it('requires non-zero oracleTimeouts', async () => { + const nonzeroError = bn(defaultOpts.oracleErrors![0][0]) + + // Complete all possible feeds + const allFeeds: string[][] = [] + const allOracleTimeouts: BigNumber[][] = [] + const allOracleErrors: BigNumber[][] = [] + + for (let i = 0; i < defaultOpts.nTokens!; i++) { + allFeeds[i] = [ONE_ADDRESS, ONE_ADDRESS] + allOracleTimeouts[i] = [bn('1'), bn('1')] + allOracleErrors[i] = [nonzeroError, nonzeroError] + } + + for (let i = 0; i < allFeeds.length; i++) { + for (let j = 0; j < allFeeds[i].length; j++) { + const oracleTimeouts = allOracleTimeouts.map((f) => f.map(() => bn('1'))) + oracleTimeouts[i][j] = bn('0') + + await expect( + deployCollateral({ + erc20: mockERC20.address, // can be anything. + feeds: allFeeds, + oracleTimeouts, + oracleErrors: allOracleErrors, + }) + ).to.be.revertedWith(`t${i}timeout${j} zero`) + } + } + }) + + it('requires non-large oracleErrors', async () => { + const nonlargeError = fp('0.01') // 1% + + // Complete all possible feeds + const allFeeds: string[][] = [] + const allOracleTimeouts: BigNumber[][] = [] + const allOracleErrors: BigNumber[][] = [] + + for (let i = 0; i < defaultOpts.nTokens!; i++) { + allFeeds[i] = [ONE_ADDRESS, ONE_ADDRESS] + allOracleTimeouts[i] = [bn('1'), bn('1')] + allOracleErrors[i] = [nonlargeError, nonlargeError] + } + + for (let i = 0; i < allFeeds.length; i++) { + for (let j = 0; j < allFeeds[i].length; j++) { + const oracleErrors = allOracleErrors.map((f) => f.map(() => nonlargeError)) + oracleErrors[i][j] = fp('1') + + await expect( + deployCollateral({ + erc20: mockERC20.address, // can be anything. + feeds: allFeeds, + oracleTimeouts: allOracleTimeouts, + oracleErrors, + }) + ).to.be.revertedWith(`t${i}error${j} too large`) + } + } + }) + + it('validates targetName', async () => { + await expect( + deployCollateral({ targetName: ethers.constants.HashZero }) + ).to.be.revertedWith('targetName missing') + }) + + it('does not allow missing ERC20', async () => { + await expect(deployCollateral({ erc20: ethers.constants.AddressZero })).to.be.revertedWith( + 'missing erc20' + ) + }) + + it('does not allow missing chainlink feed', async () => { + await expect( + deployCollateral({ chainlinkFeed: ethers.constants.AddressZero }) + ).to.be.revertedWith('missing chainlink feed') + }) + + it('max trade volume must be greater than zero', async () => { + await expect(deployCollateral({ maxTradeVolume: 0 })).to.be.revertedWith( + 'invalid max trade volume' + ) + }) + + it('does not allow oracle timeout at 0', async () => { + await expect(deployCollateral({ oracleTimeout: 0 })).to.be.revertedWith( + 'oracleTimeout zero' + ) + }) + + it('does not allow missing delayUntilDefault if defaultThreshold > 0', async () => { + await expect(deployCollateral({ delayUntilDefault: 0 })).to.be.revertedWith( + 'delayUntilDefault zero' + ) + }) + describe('collateral-specific constructor tests', collateralSpecificConstructorTests) + }) + + describe('collateral functionality', () => { + before(resetFork) + + let ctx: CurveCollateralFixtureContext + + beforeEach(async () => { + const [alice] = await ethers.getSigners() + ctx = await loadFixture(makeCollateralFixtureContext(alice, {})) + }) + + describe('functions', () => { + it('returns the correct bal (18 decimals)', async () => { + const amount = bn('20000').mul(bn(10).pow(await ctx.wrapper.decimals())) + await mintCollateralTo(ctx, amount, ctx.alice, ctx.alice.address) + + const aliceBal = await ctx.collateral.bal(ctx.alice.address) + expect(aliceBal).to.closeTo( + amount.mul(bn(10).pow(18 - (await ctx.wrapper.decimals()))), + bn('100').mul(bn(10).pow(18 - (await ctx.wrapper.decimals()))) + ) + }) + }) + + describe('rewards', () => { + it('does not revert', async () => { + await expect(ctx.collateral.claimRewards()).to.not.be.reverted + }) + + it('claims rewards (plugin)', async () => { + const amount = bn('20000').mul(bn(10).pow(await ctx.wrapper.decimals())) + await mintCollateralTo(ctx, amount, ctx.alice, ctx.collateral.address) + + await advanceBlocks(1000) + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) + + const before = await Promise.all( + ctx.rewardTokens.map((t) => t.balanceOf(ctx.wrapper.address)) + ) + await expect(ctx.wrapper.claimRewards()).to.emit(ctx.wrapper, 'RewardsClaimed') + const after = await Promise.all( + ctx.rewardTokens.map((t) => t.balanceOf(ctx.wrapper.address)) + ) + + // Each reward token should have grew + for (let i = 0; i < ctx.rewardTokens.length; i++) { + expect(after[i]).gt(before[i]) + } + }) + + it('claims rewards (wrapper)', async () => { + const amount = bn('20000').mul(bn(10).pow(await ctx.wrapper.decimals())) + await mintCollateralTo(ctx, amount, ctx.alice, ctx.alice.address) + + await advanceBlocks(1000) + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12000) + + const before = await Promise.all( + ctx.rewardTokens.map((t) => t.balanceOf(ctx.alice.address)) + ) + await expect(ctx.wrapper.connect(ctx.alice).claimRewards()).to.emit( + ctx.wrapper, + 'RewardsClaimed' + ) + const after = await Promise.all( + ctx.rewardTokens.map((t) => t.balanceOf(ctx.alice.address)) + ) + + // Each reward token should have grew + for (let i = 0; i < ctx.rewardTokens.length; i++) { + expect(after[i]).gt(before[i]) + } + }) + }) + + describe('prices', () => { + before(resetFork) + it('prices change as feed price changes', async () => { + const initialRefPerTok = await ctx.collateral.refPerTok() + const [low, high] = await ctx.collateral.price() + + // Update values in Oracles increase by 10% + const initialPrices = await Promise.all(ctx.feeds.map((f) => f.latestRoundData())) + for (const [i, feed] of ctx.feeds.entries()) { + await feed.updateAnswer(initialPrices[i].answer.mul(110).div(100)).then((e) => e.wait()) + } + + const [newLow, newHigh] = await ctx.collateral.price() + + // with 18 decimals of price precision a 1e-9 tolerance seems fine for a 10% change + // and without this kind of tolerance the Volatile pool tests fail due to small movements + expect(newLow).to.be.closeTo(low.mul(110).div(100), fp('1e-9')) + expect(newHigh).to.be.closeTo(high.mul(110).div(100), fp('1e-9')) + + // Check refPerTok remains the same (because we have not refreshed) + const finalRefPerTok = await ctx.collateral.refPerTok() + expect(finalRefPerTok).to.equal(initialRefPerTok) + }) + + it('prices change as refPerTok changes', async () => { + const initRefPerTok = await ctx.collateral.refPerTok() + const [initLow, initHigh] = await ctx.collateral.price() + + const curveVirtualPrice = await ctx.curvePool.get_virtual_price() + await ctx.collateral.refresh() + expect(await ctx.collateral.refPerTok()).to.equal(curveVirtualPrice) + + await ctx.curvePool.setVirtualPrice(curveVirtualPrice.add(1e4)) + + const newBalances = [ + await ctx.curvePool.balances(0).then((e) => e.add(1e4)), + await ctx.curvePool.balances(1).then((e) => e.add(2e4)), + ] + if (!isMetapool) { + newBalances.push(await ctx.curvePool.balances(2).then((e) => e.add(3e4))) + } + await ctx.curvePool.setBalances(newBalances) + + await ctx.collateral.refresh() + expect(await ctx.collateral.refPerTok()).to.be.gt(initRefPerTok) + + // if it's a metapool, then price may not be hooked up to the mock + if (!isMetapool) { + const [newLow, newHigh] = await ctx.collateral.price() + expect(newLow).to.be.gt(initLow) + expect(newHigh).to.be.gt(initHigh) + } + }) + + it('returns a 0 price', async () => { + for (const feed of ctx.feeds) { + await feed.updateAnswer(0).then((e) => e.wait()) + } + + // (0, 0) is returned + const [low, high] = await ctx.collateral.price() + expect(low).to.equal(0) + expect(high).to.equal(0) + + // When refreshed, sets status to Unpriced + await ctx.collateral.refresh() + expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) + }) + + it('does not revert in case of invalid timestamp', async () => { + await ctx.feeds[0].setInvalidTimestamp() + + // When refreshed, sets status to Unpriced + await ctx.collateral.refresh() + expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) + }) + + it('Handles stale price', async () => { + await advanceTime(await ctx.collateral.priceTimeout()) + + // (0, FIX_MAX) is returned + await expectUnpriced(ctx.collateral.address) + + // Refresh should mark status IFFY + await ctx.collateral.refresh() + expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) + }) + + it('decays lotPrice over priceTimeout period', async () => { + // Prices should start out equal + const p = await ctx.collateral.price() + let lotP = await ctx.collateral.lotPrice() + expect(p.length).to.equal(lotP.length) + expect(p[0]).to.equal(lotP[0]) + expect(p[1]).to.equal(lotP[1]) + + // Should be roughly half, after half of priceTimeout + const priceTimeout = await ctx.collateral.priceTimeout() + await advanceTime(priceTimeout / 2) + lotP = await ctx.collateral.lotPrice() + expect(lotP[0]).to.be.closeTo(p[0].div(2), p[0].div(2).div(10000)) // 1 part in 10 thousand + expect(lotP[1]).to.be.closeTo(p[1].div(2), p[1].div(2).div(10000)) // 1 part in 10 thousand + + // Should be 0 after full priceTimeout + await advanceTime(priceTimeout / 2) + lotP = await ctx.collateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) + }) + }) + + describe('status', () => { + it('maintains status in normal situations', async () => { + // Check initial state + expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) + expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) + + // Force updates (with no changes) + await expect(ctx.collateral.refresh()).to.not.emit( + ctx.collateral, + 'CollateralStatusChanged' + ) + // State remains the same + expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) + 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() + + // 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() + + // Set next block timestamp - for deterministic result + const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 + await setNextBlockTimestamp(nextBlockTimestamp) + 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) + }) + + it('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) + + // 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() + + // Set next block timestamp - for deterministic result + const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 + await setNextBlockTimestamp(nextBlockTimestamp) + 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) + }) + + it('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) + + // Depeg first feed - Reducing price by 20% from 1 to 0.8 + const updateAnswerTx = await ctx.feeds[0].updateAnswer(bn('8e7')) + await updateAnswerTx.wait() + + // Set next block timestamp - for deterministic result + const nextBlockTimestamp = (await getLatestBlockTimestamp()) + 1 + await setNextBlockTimestamp(nextBlockTimestamp) + 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) + + // 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 + expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) + expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) + + await mintCollateralTo(ctx, bn('20000e6'), ctx.alice, ctx.alice.address) + + await expect(ctx.collateral.refresh()).to.not.emit( + ctx.collateral, + 'CollateralStatusChanged' + ) + // State remains the same + expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) + expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) + + const currentExchangeRate = await ctx.curvePool.get_virtual_price() + await ctx.curvePool.setVirtualPrice(currentExchangeRate.sub(1e3)).then((e) => e.wait()) + + // Collateral defaults due to refPerTok() going down + await expect(ctx.collateral.refresh()).to.emit(ctx.collateral, 'CollateralStatusChanged') + expect(await ctx.collateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await ctx.collateral.whenDefault()).to.equal(await getLatestBlockTimestamp()) + }) + + it('enters IFFY state when price becomes stale', async () => { + const oracleTimeout = bn(defaultOpts.oracleTimeouts![0][0]) + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout.toNumber()) + await ctx.collateral.refresh() + expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) + }) + + it('does revenue hiding correctly', async () => { + ctx = await loadFixture( + makeCollateralFixtureContext(ctx.alice, { revenueHiding: fp('1e-6') }) + ) + + // Check initial state + expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) + expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) + await mintCollateralTo(ctx, bn('20000e6'), ctx.alice, ctx.alice.address) + await expect(ctx.collateral.refresh()).to.not.emit( + ctx.collateral, + 'CollateralStatusChanged' + ) + + // State remains the same + expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) + expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) + + // Decrease refPerTok by 1 part in a million + const currentExchangeRate = await ctx.curvePool.get_virtual_price() + const newVirtualPrice = currentExchangeRate.sub(currentExchangeRate.div(bn('1e6'))) + await ctx.curvePool.setVirtualPrice(newVirtualPrice) + + // Collateral remains SOUND + await expect(ctx.collateral.refresh()).to.not.emit( + ctx.collateral, + 'CollateralStatusChanged' + ) + expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) + expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) + + // One quanta more of decrease results in default + await ctx.curvePool.setVirtualPrice(newVirtualPrice.sub(2)) // sub 2 to compenstate for rounding + await expect(ctx.collateral.refresh()).to.emit(ctx.collateral, 'CollateralStatusChanged') + expect(await ctx.collateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await ctx.collateral.whenDefault()).to.equal(await getLatestBlockTimestamp()) + }) + + it('reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { + const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( + 'InvalidMockV3Aggregator' + ) + const invalidChainlinkFeed = ( + await InvalidMockV3AggregatorFactory.deploy(6, bn('1e6')) + ) + + ctx = await loadFixture(makeCollateralFixtureContext(ctx.alice, {})) + const [invalidCollateral] = await deployCollateral({ + erc20: ctx.wrapper.address, + feeds: defaultOpts.feeds!.map((f) => f.map(() => invalidChainlinkFeed.address)), + }) + + // Reverting with no reason + await invalidChainlinkFeed.setSimplyRevert(true) + await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() + expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) + + // Runnning out of gas (same error) + await invalidChainlinkFeed.setSimplyRevert(false) + await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() + expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) + }) + + describe('collateral-specific tests', collateralSpecificStatusTests) + }) + }) + }) +} diff --git a/test/plugins/individual-collateral/convex/constants.ts b/test/plugins/individual-collateral/curve/constants.ts similarity index 79% rename from test/plugins/individual-collateral/convex/constants.ts rename to test/plugins/individual-collateral/curve/constants.ts index 98c0df065a..9e68a63d9e 100644 --- a/test/plugins/individual-collateral/convex/constants.ts +++ b/test/plugins/individual-collateral/curve/constants.ts @@ -18,6 +18,11 @@ export const USDT_USD_FEED = networkConfig['1'].chainlinkFeeds.USDT as string export const USDT_ORACLE_TIMEOUT = bn('86400') export const USDT_ORACLE_ERROR = fp('0.0025') +// SUSD +export const SUSD_USD_FEED = networkConfig['1'].chainlinkFeeds.sUSD as string +export const SUSD_ORACLE_TIMEOUT = bn('86400') +export const SUSD_ORACLE_ERROR = fp('0.0025') + // FRAX export const FRAX_USD_FEED = networkConfig['1'].chainlinkFeeds.FRAX as string export const FRAX_ORACLE_TIMEOUT = bn('3600') @@ -46,6 +51,7 @@ export const MIM_DEFAULT_THRESHOLD = fp('0.055') // 5.5% export const DAI = networkConfig['1'].tokens.DAI as string export const USDC = networkConfig['1'].tokens.USDC as string export const USDT = networkConfig['1'].tokens.USDT as string +export const SUSD = networkConfig['1'].tokens.sUSD as string export const FRAX = networkConfig['1'].tokens.FRAX as string export const MIM = networkConfig['1'].tokens.MIM as string export const eUSD = networkConfig['1'].tokens.eUSD as string @@ -62,12 +68,21 @@ export const THREE_POOL_TOKEN = '0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490' export const THREE_POOL_CVX_POOL_ID = 9 export const THREE_POOL_HOLDER = '0xd632f22692fac7611d2aa1c0d552930d43caed3b' export const THREE_POOL_DEFAULT_THRESHOLD = fp('0.0125') // 1.25% +export const THREE_POOL_GAUGE = '0xbfcf63294ad7105dea65aa58f8ae5be2d9d0952a' // tricrypto2 - USDT, WBTC, ETH export const TRI_CRYPTO = '0xd51a44d3fae010294c616388b506acda1bfaae46' export const TRI_CRYPTO_TOKEN = '0xc4ad29ba4b3c580e6d59105fff484999997675ff' export const TRI_CRYPTO_CVX_POOL_ID = 38 export const TRI_CRYPTO_HOLDER = '0xDeFd8FdD20e0f34115C7018CCfb655796F6B2168' +export const TRI_CRYPTO_GAUGE = '0xdefd8fdd20e0f34115c7018ccfb655796f6b2168' + +// SUSD Pool - USDC, USDT, DAI, SUSD (used for tests only) +export const SUSD_POOL = '0xa5407eae9ba41422680e2e00537571bcc53efbfd' +export const SUSD_POOL_TOKEN = '0xC25a3A3b969415c80451098fa907EC722572917F' +export const SUSD_POOL_CVX_POOL_ID = 4 +export const SUSD_POOL_HOLDER = '0xDCB6A51eA3CA5d3Fd898Fd6564757c7aAeC3ca92' +export const SUSD_POOL_DEFAULT_THRESHOLD = fp('0.0125') // 1.25% // fraxBP export const FRAX_BP = '0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2' @@ -77,11 +92,16 @@ export const FRAX_BP_TOKEN = '0x3175Df0976dFA876431C2E9eE6Bc45b65d3473CC' 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' // MIM + 3pool export const MIM_THREE_POOL = '0x5a6A4D54456819380173272A5E8E9B9904BdF41B' export const MIM_THREE_POOL_POOL_ID = 40 export const MIM_THREE_POOL_HOLDER = '0x66C90baCE2B68955C875FdA89Ba2c5A94e672440' +export const MIM_THREE_POOL_GAUGE = '0xd8b712d29381748db89c36bca0138d7c75866ddf' + +// Curve-specific +export const CURVE_MINTER = '0xd061d61a4d941c39e5453435b6345dc261c2fce0' // RTokenMetapool-specific export const RTOKEN_DELAY_UNTIL_DEFAULT = bn('259200') // 72h @@ -99,5 +119,4 @@ export const FORK_BLOCK = 16915576 export enum CurvePoolType { Plain, Lending, - Metapool, } diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts new file mode 100644 index 0000000000..a7993bb369 --- /dev/null +++ b/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts @@ -0,0 +1,229 @@ +import collateralTests from '../collateralTests' +import { + CurveCollateralFixtureContext, + MintCurveCollateralFunc, + CurveMetapoolCollateralOpts, +} from '../pluginTestTypes' +import { makeWMIM3Pool, mintWMIM3Pool, resetFork } from './helpers' +import { ethers } from 'hardhat' +import { BigNumberish } from 'ethers' +import { + CurveStableMetapoolCollateral, + ERC20Mock, + MockV3Aggregator, + MockV3Aggregator__factory, + TestICollateral, +} from '../../../../../typechain' +import { bn } from '../../../../../common/numbers' +import { ZERO_ADDRESS } from '../../../../../common/constants' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + PRICE_TIMEOUT, + THREE_POOL, + THREE_POOL_TOKEN, + THREE_POOL_DEFAULT_THRESHOLD, + DAI_USD_FEED, + DAI_ORACLE_TIMEOUT, + DAI_ORACLE_ERROR, + MIM_DEFAULT_THRESHOLD, + MIM_USD_FEED, + MIM_ORACLE_TIMEOUT, + MIM_ORACLE_ERROR, + MIM_THREE_POOL, + MIM_THREE_POOL_HOLDER, + USDC_USD_FEED, + USDC_ORACLE_TIMEOUT, + USDC_ORACLE_ERROR, + USDT_USD_FEED, + USDT_ORACLE_TIMEOUT, + USDT_ORACLE_ERROR, + MAX_TRADE_VOL, + DELAY_UNTIL_DEFAULT, + CurvePoolType, + CRV, +} from '../constants' + +type Fixture = () => Promise + +export const defaultCrvStableCollateralOpts: CurveMetapoolCollateralOpts = { + erc20: ZERO_ADDRESS, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: MIM_USD_FEED, + oracleTimeout: MIM_ORACLE_TIMEOUT, + oracleError: MIM_ORACLE_ERROR, + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: THREE_POOL_DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: bn('0'), // TODO + nTokens: bn('3'), + curvePool: THREE_POOL, + lpToken: THREE_POOL_TOKEN, + poolType: CurvePoolType.Plain, + feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], + oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], + oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], + metapoolToken: MIM_THREE_POOL, + pairedTokenDefaultThreshold: MIM_DEFAULT_THRESHOLD, +} + +export const deployCollateral = async ( + opts: CurveMetapoolCollateralOpts = {} +): Promise<[TestICollateral, CurveMetapoolCollateralOpts]> => { + if (!opts.erc20 && !opts.feeds && !opts.chainlinkFeed) { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute all 3 feeds: DAI, USDC, USDT + const daiFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const mimFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const fix = await makeWMIM3Pool() + + opts.feeds = [[daiFeed.address], [usdcFeed.address], [usdtFeed.address]] + opts.erc20 = fix.wPool.address + opts.chainlinkFeed = mimFeed.address + } + + opts = { ...defaultCrvStableCollateralOpts, ...opts } + + const CrvStableCollateralFactory = await ethers.getContractFactory( + 'CurveStableMetapoolCollateral' + ) + + const collateral = await CrvStableCollateralFactory.deploy( + { + erc20: opts.erc20!, + targetName: opts.targetName!, + priceTimeout: opts.priceTimeout!, + chainlinkFeed: opts.chainlinkFeed!, + oracleError: opts.oracleError!, + oracleTimeout: opts.oracleTimeout!, + maxTradeVolume: opts.maxTradeVolume!, + defaultThreshold: opts.defaultThreshold!, + delayUntilDefault: opts.delayUntilDefault!, + }, + opts.revenueHiding!, + { + nTokens: opts.nTokens!, + curvePool: opts.curvePool!, + poolType: opts.poolType!, + feeds: opts.feeds!, + oracleTimeouts: opts.oracleTimeouts!, + oracleErrors: opts.oracleErrors!, + lpToken: opts.lpToken!, + }, + opts.metapoolToken!, + opts.pairedTokenDefaultThreshold! + ) + await collateral.deployed() + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return [collateral as unknown as TestICollateral, opts] +} + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CurveMetapoolCollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultCrvStableCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute all 3 feeds: DAI, USDC, USDT + const daiFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const mimFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const fix = await makeWMIM3Pool() + + collateralOpts.erc20 = fix.wPool.address + collateralOpts.chainlinkFeed = mimFeed.address + collateralOpts.feeds = [[daiFeed.address], [usdcFeed.address], [usdtFeed.address]] + collateralOpts.curvePool = fix.curvePool.address + collateralOpts.metapoolToken = fix.metapoolToken.address + + const collateral = ((await deployCollateral(collateralOpts))[0] as unknown) + const crv = await ethers.getContractAt('ERC20Mock', CRV) + + return { + alice, + collateral, + curvePool: fix.metapoolToken, + wrapper: fix.wPool, + rewardTokens: [crv], + poolTokens: [fix.dai, fix.usdc, fix.usdt], + feeds: [mimFeed, daiFeed, usdcFeed, usdtFeed], + } + } + + return makeCollateralFixtureContext +} + +/* + Define helper functions +*/ + +const mintCollateralTo: MintCurveCollateralFunc = async ( + ctx: CurveCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintWMIM3Pool(ctx, amount, user, recipient, MIM_THREE_POOL_HOLDER) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => { + it('does not allow empty metaPoolToken', async () => { + await expect(deployCollateral({ metapoolToken: ZERO_ADDRESS })).to.be.revertedWith( + 'metapoolToken address is zero' + ) + }) + + it('does not allow invalid pairedTokenDefaultThreshold', async () => { + await expect(deployCollateral({ pairedTokenDefaultThreshold: bn(0) })).to.be.revertedWith( + 'pairedTokenDefaultThreshold out of bounds' + ) + + await expect( + deployCollateral({ pairedTokenDefaultThreshold: bn('1.1e18') }) + ).to.be.revertedWith('pairedTokenDefaultThreshold out of bounds') + }) +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + makeCollateralFixtureContext, + mintCollateralTo, + itChecksTargetPerRefDefault: it, + itChecksRefPerTokDefault: it, + itHasRevenueHiding: it, + isMetapool: true, + resetFork, + collateralName: 'CurveStableMetapoolCollateral - CurveGaugeWrapper', +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts new file mode 100644 index 0000000000..86eed375ae --- /dev/null +++ b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts @@ -0,0 +1,219 @@ +import collateralTests from '../collateralTests' +import { + CurveCollateralFixtureContext, + CurveMetapoolCollateralOpts, + MintCurveCollateralFunc, +} from '../pluginTestTypes' +import { makeWeUSDFraxBP, mintWeUSDFraxBP, resetFork } from './helpers' +import { ethers } from 'hardhat' +import { ContractFactory, BigNumberish } from 'ethers' +import { + ERC20Mock, + MockV3Aggregator, + MockV3Aggregator__factory, + TestICollateral, +} from '../../../../../typechain' +import { bn } from '../../../../../common/numbers' +import { ZERO_ADDRESS, ONE_ADDRESS } from '../../../../../common/constants' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + PRICE_TIMEOUT, + eUSD_FRAX_BP, + FRAX_BP, + FRAX_BP_TOKEN, + USDC_USD_FEED, + USDC_ORACLE_TIMEOUT, + USDC_ORACLE_ERROR, + FRAX_USD_FEED, + FRAX_ORACLE_TIMEOUT, + FRAX_ORACLE_ERROR, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + RTOKEN_DELAY_UNTIL_DEFAULT, + CurvePoolType, + CRV, + eUSD_FRAX_HOLDER, +} from '../constants' + +type Fixture = () => Promise + +export const defaultCrvStableCollateralOpts: CurveMetapoolCollateralOpts = { + erc20: ZERO_ADDRESS, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleTimeout: bn('1'), // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, + revenueHiding: bn('0'), // TODO + nTokens: bn('2'), + curvePool: FRAX_BP, + lpToken: FRAX_BP_TOKEN, + poolType: CurvePoolType.Plain, // for fraxBP, not the top-level pool + feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], + oracleTimeouts: [[FRAX_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT]], + oracleErrors: [[FRAX_ORACLE_ERROR], [USDC_ORACLE_ERROR]], + metapoolToken: eUSD_FRAX_BP, + pairedTokenDefaultThreshold: DEFAULT_THRESHOLD, +} + +export const deployCollateral = async ( + opts: CurveMetapoolCollateralOpts = {} +): Promise<[TestICollateral, CurveMetapoolCollateralOpts]> => { + if (!opts.erc20 && !opts.feeds) { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute all 3 feeds: FRAX, USDC, eUSD + const fraxFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const eusdFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const fix = await makeWeUSDFraxBP(eusdFeed) + + opts.feeds = [[fraxFeed.address], [usdcFeed.address]] + opts.erc20 = fix.wPool.address + } + + opts = { ...defaultCrvStableCollateralOpts, ...opts } + + const CrvStableRTokenMetapoolCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'CurveStableRTokenMetapoolCollateral' + ) + + const collateral = await CrvStableRTokenMetapoolCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + { + nTokens: opts.nTokens, + curvePool: opts.curvePool, + poolType: opts.poolType, + feeds: opts.feeds, + oracleTimeouts: opts.oracleTimeouts, + oracleErrors: opts.oracleErrors, + lpToken: opts.lpToken, + }, + opts.metapoolToken, + opts.pairedTokenDefaultThreshold + ) + await collateral.deployed() + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return [collateral as unknown as TestICollateral, opts] +} + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CurveMetapoolCollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultCrvStableCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute all feeds: FRAX, USDC, RToken + const fraxFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const eusdFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + + const fix = await makeWeUSDFraxBP(eusdFeed) + collateralOpts.feeds = [[fraxFeed.address], [usdcFeed.address]] + + collateralOpts.erc20 = fix.wPool.address + collateralOpts.curvePool = fix.curvePool.address + collateralOpts.metapoolToken = fix.metapoolToken.address + + const collateral = ((await deployCollateral(collateralOpts))[0] as unknown) + const crv = await ethers.getContractAt('ERC20Mock', CRV) + + return { + alice, + collateral, + curvePool: fix.metapoolToken, + wrapper: fix.wPool, + rewardTokens: [crv], + chainlinkFeed: usdcFeed, + poolTokens: [fix.frax, fix.usdc], + feeds: [fraxFeed, usdcFeed, eusdFeed], + } + } + + return makeCollateralFixtureContext +} + +/* + Define helper functions +*/ + +const mintCollateralTo: MintCurveCollateralFunc = async ( + ctx: CurveCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintWeUSDFraxBP(ctx, amount, user, recipient, eUSD_FRAX_HOLDER) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => { + it('does not allow empty metaPoolToken', async () => { + await expect(deployCollateral({ metapoolToken: ZERO_ADDRESS })).to.be.revertedWith( + 'metapoolToken address is zero' + ) + }) + + it('does not allow invalid pairedTokenDefaultThreshold', async () => { + await expect(deployCollateral({ pairedTokenDefaultThreshold: bn(0) })).to.be.revertedWith( + 'pairedTokenDefaultThreshold out of bounds' + ) + + await expect( + deployCollateral({ pairedTokenDefaultThreshold: bn('1.1e18') }) + ).to.be.revertedWith('pairedTokenDefaultThreshold out of bounds') + }) +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + makeCollateralFixtureContext, + mintCollateralTo, + itChecksTargetPerRefDefault: it, + itChecksRefPerTokDefault: it, + itHasRevenueHiding: it, + isMetapool: true, + resetFork, + collateralName: 'CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper', +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts new file mode 100644 index 0000000000..7cac9495b4 --- /dev/null +++ b/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts @@ -0,0 +1,238 @@ +import collateralTests from '../collateralTests' +import { + CurveCollateralFixtureContext, + CurveCollateralOpts, + MintCurveCollateralFunc, +} from '../pluginTestTypes' +import { mintWPool, makeW3PoolStable, resetFork } from './helpers' +import { ethers } from 'hardhat' +import { ContractFactory, BigNumberish } from 'ethers' +import { + ERC20Mock, + MockV3Aggregator, + MockV3Aggregator__factory, + TestICollateral, +} from '../../../../../typechain' +import { bn } from '../../../../../common/numbers' +import { ZERO_ADDRESS } from '../../../../../common/constants' +import { whileImpersonating } from '../../../../utils/impersonation' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + PRICE_TIMEOUT, + THREE_POOL, + THREE_POOL_TOKEN, + DAI_USD_FEED, + DAI_ORACLE_TIMEOUT, + DAI_ORACLE_ERROR, + USDC_USD_FEED, + USDC_ORACLE_TIMEOUT, + USDC_ORACLE_ERROR, + USDT_USD_FEED, + USDT_ORACLE_TIMEOUT, + USDT_ORACLE_ERROR, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + CurvePoolType, + CRV, + THREE_POOL_HOLDER, + TRI_CRYPTO_TOKEN, + TRI_CRYPTO_GAUGE, + TRI_CRYPTO_HOLDER, +} from '../constants' + +type Fixture = () => Promise + +export const defaultCrvStableCollateralOpts: CurveCollateralOpts = { + erc20: ZERO_ADDRESS, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: DAI_USD_FEED, // unused but cannot be zero + oracleTimeout: bn('1'), // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: bn('0'), + nTokens: bn('3'), + curvePool: THREE_POOL, + lpToken: THREE_POOL_TOKEN, + poolType: CurvePoolType.Plain, + feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], + oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], + oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], +} + +export const deployCollateral = async ( + opts: CurveCollateralOpts = {} +): Promise<[TestICollateral, CurveCollateralOpts]> => { + if (!opts.erc20 && !opts.feeds) { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute all 3 feeds: DAI, USDC, USDT + const daiFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const fix = await makeW3PoolStable() + + opts.feeds = [[daiFeed.address], [usdcFeed.address], [usdtFeed.address]] + opts.erc20 = fix.wrapper.address + } + + opts = { ...defaultCrvStableCollateralOpts, ...opts } + + const CrvStableCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'CurveStableCollateral' + ) + + const collateral = await CrvStableCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + { + nTokens: opts.nTokens, + curvePool: opts.curvePool, + poolType: opts.poolType, + feeds: opts.feeds, + oracleTimeouts: opts.oracleTimeouts, + oracleErrors: opts.oracleErrors, + lpToken: opts.lpToken, + } + ) + await collateral.deployed() + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return [collateral, opts] +} + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CurveCollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultCrvStableCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute all 3 feeds: DAI, USDC, USDT + const daiFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + collateralOpts.feeds = [[daiFeed.address], [usdcFeed.address], [usdtFeed.address]] + + const fix = await makeW3PoolStable() + + collateralOpts.erc20 = fix.wrapper.address + collateralOpts.curvePool = fix.curvePool.address + const collateral = ((await deployCollateral(collateralOpts))[0] as unknown) + const crv = await ethers.getContractAt('ERC20Mock', CRV) + + return { + alice, + collateral, + curvePool: fix.curvePool, + wrapper: fix.wrapper, + rewardTokens: [crv], + poolTokens: [fix.dai, fix.usdc, fix.usdt], + feeds: [daiFeed, usdcFeed, usdtFeed], + } + } + + return makeCollateralFixtureContext +} + +/* + Define helper functions +*/ + +const mintCollateralTo: MintCurveCollateralFunc = async ( + ctx: CurveCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintWPool(ctx, amount, user, recipient, THREE_POOL_HOLDER) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => { + it('wrapper allows to deposit and withdraw', async () => { + const [alice] = await ethers.getSigners() + + // Deploy Wrapper + const wrapperFactory = await ethers.getContractFactory('CurveGaugeWrapper') + const wrapper = await wrapperFactory.deploy( + TRI_CRYPTO_TOKEN, + 'Wrapped Curve.fi USD-BTC-ETH', + 'wcrv3crypto', + CRV, + TRI_CRYPTO_GAUGE + ) + + const amount = bn('20000').mul(bn(10).pow(await wrapper.decimals())) + + const lpToken = await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', + await wrapper.underlying() + ) + await whileImpersonating(TRI_CRYPTO_HOLDER, async (signer) => { + await lpToken.connect(signer).transfer(alice.address, amount) + }) + + // Initial Balance + expect(await lpToken.balanceOf(alice.address)).to.equal(amount) + + // Deposit + await lpToken.connect(alice).approve(wrapper.address, amount) + await wrapper.connect(alice).deposit(amount, alice.address) + expect(await lpToken.balanceOf(alice.address)).to.equal(0) + + // Withdraw + await wrapper.connect(alice).withdraw(amount, alice.address) + expect(await lpToken.balanceOf(alice.address)).to.equal(amount) + }) +} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + makeCollateralFixtureContext, + mintCollateralTo, + itChecksTargetPerRefDefault: it, + itChecksRefPerTokDefault: it, + itHasRevenueHiding: it, + isMetapool: false, + resetFork, + collateralName: 'CurveStableCollateral - CurveGaugeWrapper', +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/curve/crv/CrvVolatileTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvVolatileTestSuite.test.ts new file mode 100644 index 0000000000..70a7905ca1 --- /dev/null +++ b/test/plugins/individual-collateral/curve/crv/CrvVolatileTestSuite.test.ts @@ -0,0 +1,225 @@ +import collateralTests from '../collateralTests' +import { + CurveCollateralFixtureContext, + CurveCollateralOpts, + MintCurveCollateralFunc, +} from '../pluginTestTypes' +import { mintWPool, makeWTricryptoPoolVolatile, resetFork } from './helpers' +import { ethers } from 'hardhat' +import { ContractFactory, BigNumberish } from 'ethers' +import { + ERC20Mock, + MockV3Aggregator, + MockV3Aggregator__factory, + TestICollateral, +} from '../../../../../typechain' +import { bn } from '../../../../../common/numbers' +import { ZERO_ADDRESS } from '../../../../../common/constants' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + PRICE_TIMEOUT, + USDT_ORACLE_TIMEOUT, + USDT_ORACLE_ERROR, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + CurvePoolType, + CRV, + TRI_CRYPTO_HOLDER, + TRI_CRYPTO, + TRI_CRYPTO_TOKEN, + WBTC_BTC_FEED, + BTC_USD_FEED, + BTC_ORACLE_TIMEOUT, + WETH_USD_FEED, + WBTC_BTC_ORACLE_ERROR, + WBTC_ORACLE_TIMEOUT, + WETH_ORACLE_TIMEOUT, + USDT_USD_FEED, + BTC_USD_ORACLE_ERROR, + WETH_ORACLE_ERROR, +} from '../constants' + +type Fixture = () => Promise + +export const defaultCrvVolatileCollateralOpts: CurveCollateralOpts = { + erc20: ZERO_ADDRESS, + targetName: ethers.utils.formatBytes32String('TRICRYPTO'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: USDT_USD_FEED, // unused but cannot be zero + oracleTimeout: bn('1'), // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: bn('0'), + nTokens: bn('3'), + curvePool: TRI_CRYPTO, + lpToken: TRI_CRYPTO_TOKEN, + poolType: CurvePoolType.Plain, + feeds: [[USDT_USD_FEED], [WBTC_BTC_FEED, BTC_USD_FEED], [WETH_USD_FEED]], + oracleTimeouts: [ + [USDT_ORACLE_TIMEOUT], + [WBTC_ORACLE_TIMEOUT, BTC_ORACLE_TIMEOUT], + [WETH_ORACLE_TIMEOUT], + ], + oracleErrors: [ + [USDT_ORACLE_ERROR], + [WBTC_BTC_ORACLE_ERROR, BTC_USD_ORACLE_ERROR], + [WETH_ORACLE_ERROR], + ], +} + +const makeFeeds = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute all 3 feeds: DAI, USDC, USDT + const wethFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const wbtcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const btcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + + const wethFeedOrg = MockV3AggregatorFactory.attach(WETH_USD_FEED) + const wbtcFeedOrg = MockV3AggregatorFactory.attach(WBTC_BTC_FEED) + const btcFeedOrg = MockV3AggregatorFactory.attach(BTC_USD_FEED) + const usdtFeedOrg = MockV3AggregatorFactory.attach(USDT_USD_FEED) + + await wethFeed.updateAnswer(await wethFeedOrg.latestAnswer()) + await wbtcFeed.updateAnswer(await wbtcFeedOrg.latestAnswer()) + await btcFeed.updateAnswer(await btcFeedOrg.latestAnswer()) + await usdtFeed.updateAnswer(await usdtFeedOrg.latestAnswer()) + + return { wethFeed, wbtcFeed, btcFeed, usdtFeed } +} + +export const deployCollateral = async ( + opts: CurveCollateralOpts = {} +): Promise<[TestICollateral, CurveCollateralOpts]> => { + if (!opts.erc20 && !opts.feeds) { + const { wethFeed, wbtcFeed, btcFeed, usdtFeed } = await makeFeeds() + + const fix = await makeWTricryptoPoolVolatile() + + opts.feeds = [[wethFeed.address], [wbtcFeed.address, btcFeed.address], [usdtFeed.address]] + opts.erc20 = fix.wrapper.address + } + + opts = { ...defaultCrvVolatileCollateralOpts, ...opts } + + const CrvVolatileCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'CurveVolatileCollateral' + ) + + const collateral = await CrvVolatileCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + { + nTokens: opts.nTokens, + curvePool: opts.curvePool, + poolType: opts.poolType, + feeds: opts.feeds, + oracleTimeouts: opts.oracleTimeouts, + oracleErrors: opts.oracleErrors, + lpToken: opts.lpToken, + } + ) + await collateral.deployed() + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return [collateral, opts] +} + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CurveCollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultCrvVolatileCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const { wethFeed, wbtcFeed, btcFeed, usdtFeed } = await makeFeeds() + + collateralOpts.feeds = [ + [usdtFeed.address], + [wbtcFeed.address, btcFeed.address], + [wethFeed.address], + ] + + const fix = await makeWTricryptoPoolVolatile() + + collateralOpts.erc20 = fix.wrapper.address + collateralOpts.curvePool = fix.curvePool.address + const collateral = ((await deployCollateral(collateralOpts))[0] as unknown) + const crv = await ethers.getContractAt('ERC20Mock', CRV) + + return { + alice, + collateral, + curvePool: fix.curvePool, + wrapper: fix.wrapper, + rewardTokens: [crv], + poolTokens: [fix.usdt, fix.wbtc, fix.weth], + feeds: [usdtFeed, btcFeed, wethFeed], // exclude wbtcFeed + } + } + + return makeCollateralFixtureContext +} + +/* + Define helper functions +*/ + +const mintCollateralTo: MintCurveCollateralFunc = async ( + ctx: CurveCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintWPool(ctx, amount, user, recipient, TRI_CRYPTO_HOLDER) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + makeCollateralFixtureContext, + mintCollateralTo, + itChecksTargetPerRefDefault: it, + itChecksRefPerTokDefault: it, + itHasRevenueHiding: it, + isMetapool: false, + resetFork, + collateralName: 'CurveVolatileCollateral - CurveGaugeWrapper', +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/curve/crv/helpers.ts b/test/plugins/individual-collateral/curve/crv/helpers.ts new file mode 100644 index 0000000000..e3105d88ad --- /dev/null +++ b/test/plugins/individual-collateral/curve/crv/helpers.ts @@ -0,0 +1,320 @@ +import { ethers } from 'hardhat' +import { BigNumberish } from 'ethers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { whileImpersonating } from '../../../../utils/impersonation' +import { bn, fp } from '../../../../../common/numbers' +import { + CurveGaugeWrapper, + CurvePoolMock, + CurveMetapoolMock, + ERC20Mock, + ICurvePool, + MockV3Aggregator, +} from '../../../../../typechain' +import { getResetFork } from '../../helpers' +import { + CRV, + DAI, + USDC, + USDT, + MIM, + THREE_POOL, + THREE_POOL_GAUGE, + THREE_POOL_TOKEN, + FORK_BLOCK, + WBTC, + WETH, + TRI_CRYPTO, + TRI_CRYPTO_GAUGE, + TRI_CRYPTO_TOKEN, + FRAX, + FRAX_BP, + eUSD, + eUSD_FRAX_BP, + eUSD_GAUGE, + eUSD_FRAX_HOLDER, + MIM_THREE_POOL, + MIM_THREE_POOL_GAUGE, + MIM_THREE_POOL_HOLDER, +} from '../constants' +import { CurveBase } from '../pluginTestTypes' + +export interface Wrapped3PoolFixtureStable extends CurveBase { + dai: ERC20Mock + usdc: ERC20Mock + usdt: ERC20Mock +} + +export const makeW3PoolStable = async (): Promise => { + // Use real reference ERC20s + const dai = await ethers.getContractAt('ERC20Mock', DAI) + const usdc = await ethers.getContractAt('ERC20Mock', USDC) + const usdt = await ethers.getContractAt('ERC20Mock', USDT) + + // Use mock curvePool seeded with initial balances + const CurvePoolMockFactory = await ethers.getContractFactory('CurvePoolMock') + const realCurvePool = await ethers.getContractAt('CurvePoolMock', THREE_POOL) + const curvePool = ( + await CurvePoolMockFactory.deploy( + [ + await realCurvePool.balances(0), + await realCurvePool.balances(1), + await realCurvePool.balances(2), + ], + [await realCurvePool.coins(0), await realCurvePool.coins(1), await realCurvePool.coins(2)] + ) + ) + await curvePool.setVirtualPrice(await realCurvePool.get_virtual_price()) + + // Deploy Wrapper + const wrapperFactory = await ethers.getContractFactory('CurveGaugeWrapper') + const wrapper = await wrapperFactory.deploy( + THREE_POOL_TOKEN, + 'Wrapped Staked Curve.fi DAI/USDC/USDT', + 'ws3Crv', + CRV, + THREE_POOL_GAUGE + ) + + return { + dai, + usdc, + usdt, + curvePool, + wrapper, + } +} + +export interface Wrapped3PoolFixtureVolatile extends CurveBase { + usdt: ERC20Mock + wbtc: ERC20Mock + weth: ERC20Mock +} + +export const makeWTricryptoPoolVolatile = async (): Promise => { + // Use real reference ERC20s + const usdt = await ethers.getContractAt('ERC20Mock', USDT) + const wbtc = await ethers.getContractAt('ERC20Mock', WBTC) + const weth = await ethers.getContractAt('ERC20Mock', WETH) + + // Use mock curvePool seeded with initial balances + const CurvePoolMockFactory = await ethers.getContractFactory('CurvePoolMock') + const realCurvePool = await ethers.getContractAt('CurvePoolMock', TRI_CRYPTO) + const curvePool = ( + await CurvePoolMockFactory.deploy( + [ + await realCurvePool.balances(0), + await realCurvePool.balances(1), + await realCurvePool.balances(2), + ], + [await realCurvePool.coins(0), await realCurvePool.coins(1), await realCurvePool.coins(2)] + ) + ) + await curvePool.setVirtualPrice(await realCurvePool.get_virtual_price()) + + // Deploy Wrapper + const wrapperFactory = await ethers.getContractFactory('CurveGaugeWrapper') + const wrapper = await wrapperFactory.deploy( + TRI_CRYPTO_TOKEN, + 'Wrapped Curve.fi USD-BTC-ETH', + 'wcrv3crypto', + CRV, + TRI_CRYPTO_GAUGE + ) + + return { + usdt, + wbtc, + weth, + curvePool, + wrapper, + } +} + +export const mintWPool = async ( + ctx: CurveBase, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string, + holder: string +) => { + const wrapper = ctx.wrapper as CurveGaugeWrapper + const lpToken = await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', + await wrapper.underlying() + ) + await whileImpersonating(holder, async (signer) => { + await lpToken.connect(signer).transfer(user.address, amount) + }) + + await lpToken.connect(user).approve(wrapper.address, amount) + await wrapper.connect(user).deposit(amount, recipient) +} + +export const resetFork = getResetFork(FORK_BLOCK) + +export type Numeric = number | bigint + +export const exp = (i: Numeric, d: Numeric = 0): bigint => { + return BigInt(i) * 10n ** BigInt(d) +} + +// ===== eUSD / fraxBP + +export interface WrappedEUSDFraxBPFixture { + usdc: ERC20Mock + frax: ERC20Mock + eusd: ERC20Mock + metapoolToken: CurveMetapoolMock + realMetapool: CurveMetapoolMock + curvePool: ICurvePool + wPool: CurveGaugeWrapper +} + +export const makeWeUSDFraxBP = async ( + eusdFeed: MockV3Aggregator +): Promise => { + // Make a fake RTokenAsset and register it with eUSD's assetRegistry + const AssetFactory = await ethers.getContractFactory('Asset') + const mockRTokenAsset = await AssetFactory.deploy( + bn('604800'), + eusdFeed.address, + fp('0.01'), + eUSD, + fp('1e6'), + bn('604800') + ) + const eUSDAssetRegistry = await ethers.getContractAt( + 'IAssetRegistry', + '0x9B85aC04A09c8C813c37de9B3d563C2D3F936162' + ) + await whileImpersonating('0xc8Ee187A5e5c9dC9b42414Ddf861FFc615446a2c', async (signer) => { + await eUSDAssetRegistry.connect(signer).swapRegistered(mockRTokenAsset.address) + }) + + // Use real reference ERC20s + const usdc = await ethers.getContractAt('ERC20Mock', USDC) + const frax = await ethers.getContractAt('ERC20Mock', FRAX) + const eusd = await ethers.getContractAt('ERC20Mock', eUSD) + + // Use real fraxBP pool + const curvePool = await ethers.getContractAt('ICurvePool', FRAX_BP) + + // Use mock curvePool seeded with initial balances + const CurveMetapoolMockFactory = await ethers.getContractFactory('CurveMetapoolMock') + const realMetapool = ( + await ethers.getContractAt('CurveMetapoolMock', eUSD_FRAX_BP) + ) + const metapoolToken = ( + await CurveMetapoolMockFactory.deploy( + [await realMetapool.balances(0), await realMetapool.balances(1)], + [await realMetapool.coins(0), await realMetapool.coins(1)] + ) + ) + await metapoolToken.setVirtualPrice(await realMetapool.get_virtual_price()) + await metapoolToken.mint(eUSD_FRAX_HOLDER, await realMetapool.balanceOf(eUSD_FRAX_HOLDER)) + + // Deploy Wrapper + const wrapperFactory = await ethers.getContractFactory('CurveGaugeWrapper') + const wrapper = await wrapperFactory.deploy( + eUSD_FRAX_BP, + 'Wrapped Curve eUSD+FRAX/USDC', + 'weUSDFRAXBP', + CRV, + eUSD_GAUGE + ) + + return { usdc, frax, eusd, metapoolToken, realMetapool, curvePool, wPool: wrapper } +} + +export const mintWeUSDFraxBP = async ( + ctx: CurveBase, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string, + holder: string +) => { + const wrapper = ctx.wrapper as CurveGaugeWrapper + const lpToken = await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', + await wrapper.underlying() + ) + await whileImpersonating(holder, async (signer) => { + await lpToken.connect(signer).transfer(user.address, amount) + }) + + await lpToken.connect(user).approve(wrapper.address, amount) + await wrapper.connect(user).deposit(amount, recipient) +} + +// === MIM + 3Pool + +export interface WrappedMIM3PoolFixture { + dai: ERC20Mock + usdc: ERC20Mock + usdt: ERC20Mock + mim: ERC20Mock + metapoolToken: CurveMetapoolMock + realMetapool: CurveMetapoolMock + curvePool: ICurvePool + wPool: CurveGaugeWrapper +} + +export const makeWMIM3Pool = async (): Promise => { + // Use real reference ERC20s + const dai = await ethers.getContractAt('ERC20Mock', DAI) + const usdc = await ethers.getContractAt('ERC20Mock', USDC) + const usdt = await ethers.getContractAt('ERC20Mock', USDT) + const mim = await ethers.getContractAt('ERC20Mock', MIM) + + // Use real MIM pool + const curvePool = await ethers.getContractAt('ICurvePool', THREE_POOL) + + // Use mock curvePool seeded with initial balances + const CurveMetapoolMockFactory = await ethers.getContractFactory('CurveMetapoolMock') + const realMetapool = ( + await ethers.getContractAt('CurveMetapoolMock', MIM_THREE_POOL) + ) + const metapoolToken = ( + await CurveMetapoolMockFactory.deploy( + [await realMetapool.balances(0), await realMetapool.balances(1)], + [await realMetapool.coins(0), await realMetapool.coins(1)] + ) + ) + await metapoolToken.setVirtualPrice(await realMetapool.get_virtual_price()) + await metapoolToken.mint( + MIM_THREE_POOL_HOLDER, + await realMetapool.balanceOf(MIM_THREE_POOL_HOLDER) + ) + // Deploy Wrapper + const wrapperFactory = await ethers.getContractFactory('CurveGaugeWrapper') + const wrapper = await wrapperFactory.deploy( + MIM_THREE_POOL, + 'Wrapped Curve MIM+3Pool', + 'wMIM3CRV', + CRV, + MIM_THREE_POOL_GAUGE + ) + + return { dai, usdc, usdt, mim, metapoolToken, realMetapool, curvePool, wPool: wrapper } +} + +export const mintWMIM3Pool = async ( + ctx: CurveBase, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string, + holder: string +) => { + const wrapper = ctx.wrapper as CurveGaugeWrapper + const lpToken = await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', + await wrapper.underlying() + ) + await whileImpersonating(holder, async (signer) => { + await lpToken.connect(signer).transfer(user.address, amount) + }) + + await lpToken.connect(user).approve(ctx.wrapper.address, amount) + await wrapper.connect(user).deposit(amount, recipient) +} diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts new file mode 100644 index 0000000000..b92ba7c1b8 --- /dev/null +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts @@ -0,0 +1,237 @@ +import collateralTests from '../collateralTests' +import { + CurveCollateralFixtureContext, + MintCurveCollateralFunc, + CurveMetapoolCollateralOpts, +} from '../pluginTestTypes' +import { makeWMIM3Pool, mintWMIM3Pool, resetFork } from './helpers' +import { ethers } from 'hardhat' +import { BigNumberish } from 'ethers' +import { + CurveStableMetapoolCollateral, + ERC20Mock, + MockV3Aggregator, + MockV3Aggregator__factory, + TestICollateral, +} from '../../../../../typechain' +import { bn } from '../../../../../common/numbers' +import { ZERO_ADDRESS } from '../../../../../common/constants' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + PRICE_TIMEOUT, + THREE_POOL, + THREE_POOL_TOKEN, + THREE_POOL_DEFAULT_THRESHOLD, + CVX, + DAI_USD_FEED, + DAI_ORACLE_TIMEOUT, + DAI_ORACLE_ERROR, + MIM_DEFAULT_THRESHOLD, + MIM_USD_FEED, + MIM_ORACLE_TIMEOUT, + MIM_ORACLE_ERROR, + MIM_THREE_POOL, + MIM_THREE_POOL_HOLDER, + USDC_USD_FEED, + USDC_ORACLE_TIMEOUT, + USDC_ORACLE_ERROR, + USDT_USD_FEED, + USDT_ORACLE_TIMEOUT, + USDT_ORACLE_ERROR, + MAX_TRADE_VOL, + DELAY_UNTIL_DEFAULT, + CurvePoolType, + CRV, +} from '../constants' + +type Fixture = () => Promise + +export const defaultCvxStableCollateralOpts: CurveMetapoolCollateralOpts = { + erc20: ZERO_ADDRESS, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: MIM_USD_FEED, + oracleTimeout: MIM_ORACLE_TIMEOUT, + oracleError: MIM_ORACLE_ERROR, + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: THREE_POOL_DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: bn('0'), // TODO + nTokens: bn('3'), + curvePool: THREE_POOL, + lpToken: THREE_POOL_TOKEN, + poolType: CurvePoolType.Plain, + feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], + oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], + oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], + metapoolToken: MIM_THREE_POOL, + pairedTokenDefaultThreshold: MIM_DEFAULT_THRESHOLD, +} + +export const deployCollateral = async ( + opts: CurveMetapoolCollateralOpts = {} +): Promise<[TestICollateral, CurveMetapoolCollateralOpts]> => { + if (!opts.erc20 && !opts.feeds && !opts.chainlinkFeed) { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute all 3 feeds: DAI, USDC, USDT + const daiFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const mimFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const fix = await makeWMIM3Pool() + + opts.feeds = [[daiFeed.address], [usdcFeed.address], [usdtFeed.address]] + opts.erc20 = fix.wPool.address + opts.chainlinkFeed = mimFeed.address + } + + opts = { ...defaultCvxStableCollateralOpts, ...opts } + + const CvxStableCollateralFactory = await ethers.getContractFactory( + 'CurveStableMetapoolCollateral' + ) + + const collateral = await CvxStableCollateralFactory.deploy( + { + erc20: opts.erc20!, + targetName: opts.targetName!, + priceTimeout: opts.priceTimeout!, + chainlinkFeed: opts.chainlinkFeed!, + oracleError: opts.oracleError!, + oracleTimeout: opts.oracleTimeout!, + maxTradeVolume: opts.maxTradeVolume!, + defaultThreshold: opts.defaultThreshold!, + delayUntilDefault: opts.delayUntilDefault!, + }, + opts.revenueHiding!, + { + nTokens: opts.nTokens!, + curvePool: opts.curvePool!, + poolType: opts.poolType!, + feeds: opts.feeds!, + oracleTimeouts: opts.oracleTimeouts!, + oracleErrors: opts.oracleErrors!, + lpToken: opts.lpToken!, + }, + opts.metapoolToken!, + opts.pairedTokenDefaultThreshold! + ) + await collateral.deployed() + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return [collateral as unknown as TestICollateral, opts] +} + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CurveMetapoolCollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultCvxStableCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute all 3 feeds: DAI, USDC, USDT + const daiFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const mimFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const fix = await makeWMIM3Pool() + + collateralOpts.erc20 = fix.wPool.address + collateralOpts.chainlinkFeed = mimFeed.address + collateralOpts.feeds = [[daiFeed.address], [usdcFeed.address], [usdtFeed.address]] + collateralOpts.curvePool = fix.curvePool.address + collateralOpts.metapoolToken = fix.metapoolToken.address + + const collateral = ((await deployCollateral(collateralOpts))[0] as unknown) + const cvx = await ethers.getContractAt('ERC20Mock', CVX) + const crv = await ethers.getContractAt('ERC20Mock', CRV) + + return { + alice, + collateral, + curvePool: fix.metapoolToken, + wrapper: fix.wPool, + rewardTokens: [cvx, crv], + poolTokens: [fix.dai, fix.usdc, fix.usdt], + feeds: [mimFeed, daiFeed, usdcFeed, usdtFeed], + } + } + + return makeCollateralFixtureContext +} + +/* + Define helper functions +*/ + +const mintCollateralTo: MintCurveCollateralFunc = async ( + ctx: CurveCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintWMIM3Pool(ctx, amount, user, recipient, MIM_THREE_POOL_HOLDER) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => { + it('does not allow empty metaPoolToken', async () => { + await expect(deployCollateral({ metapoolToken: ZERO_ADDRESS })).to.be.revertedWith( + 'metapoolToken address is zero' + ) + }) + + it('does not allow invalid pairedTokenDefaultThreshold', async () => { + await expect(deployCollateral({ pairedTokenDefaultThreshold: bn(0) })).to.be.revertedWith( + 'pairedTokenDefaultThreshold out of bounds' + ) + + await expect( + deployCollateral({ pairedTokenDefaultThreshold: bn('1.1e18') }) + ).to.be.revertedWith('pairedTokenDefaultThreshold out of bounds') + }) + + it('does not allow invalid Pool Type', async () => { + await expect(deployCollateral({ metapoolToken: ZERO_ADDRESS })).to.be.revertedWith( + 'metapoolToken address is zero' + ) + }) +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + makeCollateralFixtureContext, + mintCollateralTo, + itChecksTargetPerRefDefault: it, + itChecksRefPerTokDefault: it, + itHasRevenueHiding: it, + isMetapool: true, + resetFork, + collateralName: 'CurveStableMetapoolCollateral - ConvexStakingWrapper', +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts new file mode 100644 index 0000000000..fbb8b04f72 --- /dev/null +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts @@ -0,0 +1,221 @@ +import collateralTests from '../collateralTests' +import { + CurveCollateralFixtureContext, + CurveMetapoolCollateralOpts, + MintCurveCollateralFunc, +} from '../pluginTestTypes' +import { makeWeUSDFraxBP, mintWeUSDFraxBP, resetFork } from './helpers' +import { ethers } from 'hardhat' +import { ContractFactory, BigNumberish } from 'ethers' +import { + ERC20Mock, + MockV3Aggregator, + MockV3Aggregator__factory, + TestICollateral, +} from '../../../../../typechain' +import { bn } from '../../../../../common/numbers' +import { ZERO_ADDRESS, ONE_ADDRESS } from '../../../../../common/constants' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + PRICE_TIMEOUT, + eUSD_FRAX_BP, + FRAX_BP, + FRAX_BP_TOKEN, + CVX, + USDC_USD_FEED, + USDC_ORACLE_TIMEOUT, + USDC_ORACLE_ERROR, + FRAX_USD_FEED, + FRAX_ORACLE_TIMEOUT, + FRAX_ORACLE_ERROR, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + RTOKEN_DELAY_UNTIL_DEFAULT, + CurvePoolType, + CRV, + eUSD_FRAX_HOLDER, +} from '../constants' + +type Fixture = () => Promise + +export const defaultCvxStableCollateralOpts: CurveMetapoolCollateralOpts = { + erc20: ZERO_ADDRESS, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero + oracleTimeout: bn('1'), // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, + revenueHiding: bn('0'), // TODO + nTokens: bn('2'), + curvePool: FRAX_BP, + lpToken: FRAX_BP_TOKEN, + poolType: CurvePoolType.Plain, // for fraxBP, not the top-level pool + feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], + oracleTimeouts: [[FRAX_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT]], + oracleErrors: [[FRAX_ORACLE_ERROR], [USDC_ORACLE_ERROR]], + metapoolToken: eUSD_FRAX_BP, + pairedTokenDefaultThreshold: DEFAULT_THRESHOLD, +} + +export const deployCollateral = async ( + opts: CurveMetapoolCollateralOpts = {} +): Promise<[TestICollateral, CurveMetapoolCollateralOpts]> => { + if (!opts.erc20 && !opts.feeds) { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute all 3 feeds: FRAX, USDC, eUSD + const fraxFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const eusdFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const fix = await makeWeUSDFraxBP(eusdFeed) + + opts.feeds = [[fraxFeed.address], [usdcFeed.address]] + opts.erc20 = fix.wPool.address + } + + opts = { ...defaultCvxStableCollateralOpts, ...opts } + + const CvxStableRTokenMetapoolCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'CurveStableRTokenMetapoolCollateral' + ) + + const collateral = await CvxStableRTokenMetapoolCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + { + nTokens: opts.nTokens, + curvePool: opts.curvePool, + poolType: opts.poolType, + feeds: opts.feeds, + oracleTimeouts: opts.oracleTimeouts, + oracleErrors: opts.oracleErrors, + lpToken: opts.lpToken, + }, + opts.metapoolToken, + opts.pairedTokenDefaultThreshold + ) + await collateral.deployed() + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return [collateral as unknown as TestICollateral, opts] +} + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CurveMetapoolCollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultCvxStableCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute all feeds: FRAX, USDC, RToken + const fraxFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const eusdFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + + const fix = await makeWeUSDFraxBP(eusdFeed) + collateralOpts.feeds = [[fraxFeed.address], [usdcFeed.address]] + + collateralOpts.erc20 = fix.wPool.address + collateralOpts.curvePool = fix.curvePool.address + collateralOpts.metapoolToken = fix.metapoolToken.address + + const collateral = ((await deployCollateral(collateralOpts))[0] as unknown) + const cvx = await ethers.getContractAt('ERC20Mock', CVX) + const crv = await ethers.getContractAt('ERC20Mock', CRV) + + return { + alice, + collateral, + curvePool: fix.metapoolToken, + wrapper: fix.wPool, + rewardTokens: [cvx, crv], + chainlinkFeed: usdcFeed, + poolTokens: [fix.frax, fix.usdc], + feeds: [fraxFeed, usdcFeed, eusdFeed], + } + } + + return makeCollateralFixtureContext +} + +/* + Define helper functions +*/ + +const mintCollateralTo: MintCurveCollateralFunc = async ( + ctx: CurveCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintWeUSDFraxBP(ctx, amount, user, recipient, eUSD_FRAX_HOLDER) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => { + it('does not allow empty metaPoolToken', async () => { + await expect(deployCollateral({ metapoolToken: ZERO_ADDRESS })).to.be.revertedWith( + 'metapoolToken address is zero' + ) + }) + + it('does not allow invalid pairedTokenDefaultThreshold', async () => { + await expect(deployCollateral({ pairedTokenDefaultThreshold: bn(0) })).to.be.revertedWith( + 'pairedTokenDefaultThreshold out of bounds' + ) + + await expect( + deployCollateral({ pairedTokenDefaultThreshold: bn('1.1e18') }) + ).to.be.revertedWith('pairedTokenDefaultThreshold out of bounds') + }) +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + makeCollateralFixtureContext, + mintCollateralTo, + itChecksTargetPerRefDefault: it, + itChecksRefPerTokDefault: it, + itHasRevenueHiding: it, + isMetapool: true, + resetFork, + collateralName: 'CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper', +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts new file mode 100644 index 0000000000..eb9fc0eb40 --- /dev/null +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts @@ -0,0 +1,436 @@ +import collateralTests from '../collateralTests' +import { + CurveCollateralFixtureContext, + CurveCollateralOpts, + MintCurveCollateralFunc, +} from '../pluginTestTypes' +import { mintWPool, makeW3PoolStable, makeWSUSDPoolStable, resetFork } from './helpers' +import { ethers } from 'hardhat' +import { ContractFactory, BigNumberish } from 'ethers' +import { + ERC20Mock, + MockV3Aggregator, + MockV3Aggregator__factory, + TestICollateral, +} from '../../../../../typechain' +import { bn, fp } from '../../../../../common/numbers' +import { ZERO_ADDRESS } from '../../../../../common/constants' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + PRICE_TIMEOUT, + THREE_POOL, + THREE_POOL_TOKEN, + SUSD_POOL_TOKEN, + CVX, + DAI_USD_FEED, + DAI_ORACLE_TIMEOUT, + DAI_ORACLE_ERROR, + USDC_USD_FEED, + USDC_ORACLE_TIMEOUT, + USDC_ORACLE_ERROR, + USDT_USD_FEED, + USDT_ORACLE_TIMEOUT, + USDT_ORACLE_ERROR, + SUSD_USD_FEED, + SUSD_ORACLE_TIMEOUT, + SUSD_ORACLE_ERROR, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + CurvePoolType, + CRV, + THREE_POOL_HOLDER, +} from '../constants' + +type Fixture = () => Promise + +export const defaultCvxStableCollateralOpts: CurveCollateralOpts = { + erc20: ZERO_ADDRESS, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: DAI_USD_FEED, // unused but cannot be zero + oracleTimeout: bn('1'), // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: bn('0'), + nTokens: bn('3'), + curvePool: THREE_POOL, + lpToken: THREE_POOL_TOKEN, + poolType: CurvePoolType.Plain, + feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], + oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], + oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], +} + +export const deployCollateral = async ( + opts: CurveCollateralOpts = {} +): Promise<[TestICollateral, CurveCollateralOpts]> => { + if (!opts.erc20 && !opts.feeds) { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute all 3 feeds: DAI, USDC, USDT + const daiFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const fix = await makeW3PoolStable() + + opts.feeds = [[daiFeed.address], [usdcFeed.address], [usdtFeed.address]] + opts.erc20 = fix.wrapper.address + } + + opts = { ...defaultCvxStableCollateralOpts, ...opts } + + const CvxStableCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'CurveStableCollateral' + ) + + const collateral = await CvxStableCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + { + nTokens: opts.nTokens, + curvePool: opts.curvePool, + poolType: opts.poolType, + feeds: opts.feeds, + oracleTimeouts: opts.oracleTimeouts, + oracleErrors: opts.oracleErrors, + lpToken: opts.lpToken, + } + ) + await collateral.deployed() + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return [collateral, opts] +} + +export const deployMaxTokensCollateral = async ( + opts: CurveCollateralOpts = {} +): Promise<[TestICollateral, CurveCollateralOpts]> => { + const fix = await makeWSUSDPoolStable() + + // Set default options for max tokens case + const maxTokenCollOpts = { + ...defaultCvxStableCollateralOpts, + ...{ + nTokens: bn('4'), + erc20: fix.wrapper.address, + curvePool: fix.curvePool.address, + lpToken: SUSD_POOL_TOKEN, + feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED], [SUSD_USD_FEED, SUSD_USD_FEED]], + oracleTimeouts: [ + [DAI_ORACLE_TIMEOUT], + [USDC_ORACLE_TIMEOUT], + [USDT_ORACLE_TIMEOUT], + [SUSD_ORACLE_TIMEOUT, SUSD_ORACLE_TIMEOUT], + ], + oracleErrors: [ + [DAI_ORACLE_ERROR], + [USDC_ORACLE_ERROR], + [USDT_ORACLE_ERROR], + [SUSD_ORACLE_ERROR], + ], + }, + } + + opts = { ...maxTokenCollOpts, ...opts } + + const CvxStableCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'CurveStableCollateral' + ) + + const collateral = await CvxStableCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + { + nTokens: opts.nTokens, + curvePool: opts.curvePool, + poolType: opts.poolType, + feeds: opts.feeds, + oracleTimeouts: opts.oracleTimeouts, + oracleErrors: opts.oracleErrors, + lpToken: opts.lpToken, + } + ) + await collateral.deployed() + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return [collateral, opts] +} + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CurveCollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultCvxStableCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute all 3 feeds: DAI, USDC, USDT + const daiFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + collateralOpts.feeds = [[daiFeed.address], [usdcFeed.address], [usdtFeed.address]] + + const fix = await makeW3PoolStable() + + collateralOpts.erc20 = fix.wrapper.address + collateralOpts.curvePool = fix.curvePool.address + const collateral = ((await deployCollateral(collateralOpts))[0] as unknown) + const cvx = await ethers.getContractAt('ERC20Mock', CVX) + const crv = await ethers.getContractAt('ERC20Mock', CRV) + + return { + alice, + collateral, + curvePool: fix.curvePool, + wrapper: fix.wrapper, + rewardTokens: [cvx, crv], + poolTokens: [fix.dai, fix.usdc, fix.usdt], + feeds: [daiFeed, usdcFeed, usdtFeed], + } + } + + return makeCollateralFixtureContext +} + +/* + Define helper functions +*/ + +const mintCollateralTo: MintCurveCollateralFunc = async ( + ctx: CurveCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintWPool(ctx, amount, user, recipient, THREE_POOL_HOLDER) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => { + describe('Handles constructor with 4 tokens (max allowed) - sUSD', () => { + let collateral: TestICollateral + before(resetFork) + it('deploys plugin successfully', async () => { + ;[collateral] = await deployMaxTokensCollateral() + expect(await collateral.address).to.not.equal(ZERO_ADDRESS) + const [low, high] = await collateral.price() + expect(low).to.be.closeTo(fp('1.06'), fp('0.01')) // close to $1 + expect(high).to.be.closeTo(fp('1.07'), fp('0.01')) + expect(high).to.be.gt(low) + + // Token price + const cvxMultiFeedStableCollateral = await ethers.getContractAt( + 'CurveStableCollateral', + collateral.address + ) + for (let i = 0; i < 4; i++) { + const [lowTkn, highTkn] = await cvxMultiFeedStableCollateral.tokenPrice(i) + expect(lowTkn).to.be.closeTo(fp('1'), fp('0.01')) // close to $1 + expect(highTkn).to.be.closeTo(fp('1'), fp('0.01')) + expect(highTkn).to.be.gt(lowTkn) + } + + await expect(cvxMultiFeedStableCollateral.tokenPrice(5)).to.be.revertedWithCustomError( + cvxMultiFeedStableCollateral, + 'WrongIndex' + ) + }) + + it('validates non-zero-address for final token - edge case', async () => { + // Set empty the final feed + let feeds = [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED], [ZERO_ADDRESS, ZERO_ADDRESS]] + await expect(deployMaxTokensCollateral({ feeds })).to.be.revertedWith('t3feed0 empty') + + feeds = [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED], [SUSD_USD_FEED, ZERO_ADDRESS]] + await expect(deployMaxTokensCollateral({ feeds })).to.be.revertedWith('t3feed1 empty') + }) + + it('validates non-zero oracle timeout for final token - edge case', async () => { + // Set empty the final oracle timeouts + let oracleTimeouts = [ + [DAI_ORACLE_TIMEOUT], + [USDC_ORACLE_TIMEOUT], + [USDT_ORACLE_TIMEOUT], + [bn(0), bn(0)], + ] + await expect(deployMaxTokensCollateral({ oracleTimeouts })).to.be.revertedWith( + 't3timeout0 zero' + ) + + const feeds = [ + [DAI_USD_FEED], + [USDC_USD_FEED], + [USDT_USD_FEED], + [SUSD_USD_FEED, SUSD_USD_FEED], + ] + oracleTimeouts = [ + [DAI_ORACLE_TIMEOUT], + [USDC_ORACLE_TIMEOUT], + [USDT_ORACLE_TIMEOUT], + [SUSD_ORACLE_TIMEOUT, bn(0)], + ] + + await expect(deployMaxTokensCollateral({ feeds, oracleTimeouts })).to.be.revertedWith( + 't3timeout1 zero' + ) + }) + + it('validates non-large oracle error for final token - edge case', async () => { + // Set empty the final oracle errors + let oracleErrors = [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR], [fp('1')]] + await expect(deployMaxTokensCollateral({ oracleErrors })).to.be.revertedWith( + 't3error0 too large' + ) + + const feeds = [ + [DAI_USD_FEED], + [USDC_USD_FEED], + [USDT_USD_FEED], + [SUSD_USD_FEED, SUSD_USD_FEED], + ] + const oracleTimeouts = [ + [DAI_ORACLE_TIMEOUT], + [USDC_ORACLE_TIMEOUT], + [USDT_ORACLE_TIMEOUT], + [SUSD_ORACLE_TIMEOUT, SUSD_ORACLE_TIMEOUT], + ] + + oracleErrors = [ + [DAI_ORACLE_ERROR], + [USDC_ORACLE_ERROR], + [USDT_ORACLE_ERROR], + [SUSD_ORACLE_ERROR, fp('1')], + ] + + await expect( + deployMaxTokensCollateral({ feeds, oracleTimeouts, oracleErrors }) + ).to.be.revertedWith('t3error1 too large') + + // If we don't specify it it will use 0 + oracleErrors = [ + [DAI_ORACLE_ERROR], + [USDC_ORACLE_ERROR], + [USDT_ORACLE_ERROR], + [SUSD_ORACLE_ERROR], + ] + + await expect(deployMaxTokensCollateral({ feeds, oracleTimeouts, oracleErrors })).to.not.be + .reverted + }) + }) +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => { + it('handles properly multiple price feeds', async () => { + const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') + const feed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + + const feedStable = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + + const fix = await makeW3PoolStable() + + const opts: CurveCollateralOpts = { ...defaultCvxStableCollateralOpts } + const nonzeroError = opts.oracleTimeouts![0][0] + const nonzeroTimeout = bn(opts.oracleTimeouts![0][0]) + const feeds = [ + [feed.address, feedStable.address], + [feed.address, feedStable.address], + [feed.address, feedStable.address], + ] + const oracleTimeouts = [ + [nonzeroTimeout, nonzeroTimeout], + [nonzeroTimeout, nonzeroTimeout], + [nonzeroTimeout, nonzeroTimeout], + ] + const oracleErrors = [ + [nonzeroError, nonzeroError], + [nonzeroError, nonzeroError], + [nonzeroError, nonzeroError], + ] + + const [multiFeedCollateral] = await deployCollateral({ + erc20: fix.wrapper.address, + feeds, + oracleTimeouts, + oracleErrors, + }) + + const initialRefPerTok = await multiFeedCollateral.refPerTok() + const [low, high] = await multiFeedCollateral.price() + + // Update values in Oracles increase by 10% + const initialPrice = await feed.latestRoundData() + await (await feed.updateAnswer(initialPrice.answer.mul(110).div(100))).wait() + + const [newLow, newHigh] = await multiFeedCollateral.price() + + // with 18 decimals of price precision a 1e-9 tolerance seems fine for a 10% change + // and without this kind of tolerance the Volatile pool tests fail due to small movements + expect(newLow).to.be.closeTo(low.mul(110).div(100), fp('1e-9')) + expect(newHigh).to.be.closeTo(high.mul(110).div(100), fp('1e-9')) + + // Check refPerTok remains the same (because we have not refreshed) + const finalRefPerTok = await multiFeedCollateral.refPerTok() + expect(finalRefPerTok).to.equal(initialRefPerTok) + }) +} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + makeCollateralFixtureContext, + mintCollateralTo, + itChecksTargetPerRefDefault: it, + itChecksRefPerTokDefault: it, + itHasRevenueHiding: it, + isMetapool: false, + resetFork, + collateralName: 'CurveStableCollateral - ConvexStakingWrapper', +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/curve/cvx/CvxVolatileTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxVolatileTestSuite.test.ts new file mode 100644 index 0000000000..181a7fe59b --- /dev/null +++ b/test/plugins/individual-collateral/curve/cvx/CvxVolatileTestSuite.test.ts @@ -0,0 +1,227 @@ +import collateralTests from '../collateralTests' +import { + CurveCollateralFixtureContext, + CurveCollateralOpts, + MintCurveCollateralFunc, +} from '../pluginTestTypes' +import { mintWPool, makeWTricryptoPoolVolatile, resetFork } from './helpers' +import { ethers } from 'hardhat' +import { ContractFactory, BigNumberish } from 'ethers' +import { + ERC20Mock, + MockV3Aggregator, + MockV3Aggregator__factory, + TestICollateral, +} from '../../../../../typechain' +import { bn } from '../../../../../common/numbers' +import { ZERO_ADDRESS } from '../../../../../common/constants' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + PRICE_TIMEOUT, + CVX, + USDT_ORACLE_TIMEOUT, + USDT_ORACLE_ERROR, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + CurvePoolType, + CRV, + TRI_CRYPTO_HOLDER, + TRI_CRYPTO, + TRI_CRYPTO_TOKEN, + WBTC_BTC_FEED, + BTC_USD_FEED, + BTC_ORACLE_TIMEOUT, + WETH_USD_FEED, + WBTC_BTC_ORACLE_ERROR, + WBTC_ORACLE_TIMEOUT, + WETH_ORACLE_TIMEOUT, + USDT_USD_FEED, + BTC_USD_ORACLE_ERROR, + WETH_ORACLE_ERROR, +} from '../constants' + +type Fixture = () => Promise + +export const defaultCvxVolatileCollateralOpts: CurveCollateralOpts = { + erc20: ZERO_ADDRESS, + targetName: ethers.utils.formatBytes32String('TRICRYPTO'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: USDT_USD_FEED, // unused but cannot be zero + oracleTimeout: bn('1'), // unused but cannot be zero + oracleError: bn('1'), // unused but cannot be zero + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: bn('0'), + nTokens: bn('3'), + curvePool: TRI_CRYPTO, + lpToken: TRI_CRYPTO_TOKEN, + poolType: CurvePoolType.Plain, + feeds: [[USDT_USD_FEED], [WBTC_BTC_FEED, BTC_USD_FEED], [WETH_USD_FEED]], + oracleTimeouts: [ + [USDT_ORACLE_TIMEOUT], + [WBTC_ORACLE_TIMEOUT, BTC_ORACLE_TIMEOUT], + [WETH_ORACLE_TIMEOUT], + ], + oracleErrors: [ + [USDT_ORACLE_ERROR], + [WBTC_BTC_ORACLE_ERROR, BTC_USD_ORACLE_ERROR], + [WETH_ORACLE_ERROR], + ], +} + +const makeFeeds = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + // Substitute all 3 feeds: DAI, USDC, USDT + const wethFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const wbtcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const btcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + + const wethFeedOrg = MockV3AggregatorFactory.attach(WETH_USD_FEED) + const wbtcFeedOrg = MockV3AggregatorFactory.attach(WBTC_BTC_FEED) + const btcFeedOrg = MockV3AggregatorFactory.attach(BTC_USD_FEED) + const usdtFeedOrg = MockV3AggregatorFactory.attach(USDT_USD_FEED) + + await wethFeed.updateAnswer(await wethFeedOrg.latestAnswer()) + await wbtcFeed.updateAnswer(await wbtcFeedOrg.latestAnswer()) + await btcFeed.updateAnswer(await btcFeedOrg.latestAnswer()) + await usdtFeed.updateAnswer(await usdtFeedOrg.latestAnswer()) + + return { wethFeed, wbtcFeed, btcFeed, usdtFeed } +} + +export const deployCollateral = async ( + opts: CurveCollateralOpts = {} +): Promise<[TestICollateral, CurveCollateralOpts]> => { + if (!opts.erc20 && !opts.feeds) { + const { wethFeed, wbtcFeed, btcFeed, usdtFeed } = await makeFeeds() + + const fix = await makeWTricryptoPoolVolatile() + + opts.feeds = [[wethFeed.address], [wbtcFeed.address, btcFeed.address], [usdtFeed.address]] + opts.erc20 = fix.wrapper.address + } + + opts = { ...defaultCvxVolatileCollateralOpts, ...opts } + + const CvxVolatileCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'CurveVolatileCollateral' + ) + + const collateral = await CvxVolatileCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + { + nTokens: opts.nTokens, + curvePool: opts.curvePool, + poolType: opts.poolType, + feeds: opts.feeds, + oracleTimeouts: opts.oracleTimeouts, + oracleErrors: opts.oracleErrors, + lpToken: opts.lpToken, + } + ) + await collateral.deployed() + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return [collateral, opts] +} + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CurveCollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultCvxVolatileCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const { wethFeed, wbtcFeed, btcFeed, usdtFeed } = await makeFeeds() + + collateralOpts.feeds = [ + [usdtFeed.address], + [wbtcFeed.address, btcFeed.address], + [wethFeed.address], + ] + + const fix = await makeWTricryptoPoolVolatile() + + collateralOpts.erc20 = fix.wrapper.address + collateralOpts.curvePool = fix.curvePool.address + const collateral = ((await deployCollateral(collateralOpts))[0] as unknown) + const cvx = await ethers.getContractAt('ERC20Mock', CVX) + const crv = await ethers.getContractAt('ERC20Mock', CRV) + + return { + alice, + collateral, + curvePool: fix.curvePool, + wrapper: fix.wrapper, + rewardTokens: [cvx, crv], + poolTokens: [fix.usdt, fix.wbtc, fix.weth], + feeds: [usdtFeed, btcFeed, wethFeed], // exclude wbtcFeed + } + } + + return makeCollateralFixtureContext +} + +/* + Define helper functions +*/ + +const mintCollateralTo: MintCurveCollateralFunc = async ( + ctx: CurveCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintWPool(ctx, amount, user, recipient, TRI_CRYPTO_HOLDER) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + makeCollateralFixtureContext, + mintCollateralTo, + itChecksTargetPerRefDefault: it, + itChecksRefPerTokDefault: it, + itHasRevenueHiding: it, + isMetapool: false, + resetFork, + collateralName: 'CurveVolatileCollateral - ConvexStakingWrapper', +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/convex/helpers.ts b/test/plugins/individual-collateral/curve/cvx/helpers.ts similarity index 66% rename from test/plugins/individual-collateral/convex/helpers.ts rename to test/plugins/individual-collateral/curve/cvx/helpers.ts index a083da8abc..686a2d8f8f 100644 --- a/test/plugins/individual-collateral/convex/helpers.ts +++ b/test/plugins/individual-collateral/curve/cvx/helpers.ts @@ -1,30 +1,33 @@ import { ethers } from 'hardhat' import { BigNumberish } from 'ethers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { whileImpersonating } from '../../../utils/impersonation' -import { bn, fp } from '../../../../common/numbers' +import { whileImpersonating } from '../../../../utils/impersonation' +import { bn, fp } from '../../../../../common/numbers' import { ConvexStakingWrapper, CurvePoolMock, + CurvePoolMockVariantInt, CurveMetapoolMock, ERC20Mock, ICurvePool, MockV3Aggregator, -} from '../../../../typechain' -import { getResetFork } from '../helpers' + RewardableERC4626Vault, +} from '../../../../../typechain' +import { getResetFork } from '../../helpers' import { DAI, USDC, USDT, + SUSD, MIM, THREE_POOL, - THREE_POOL_TOKEN, THREE_POOL_CVX_POOL_ID, + SUSD_POOL, + SUSD_POOL_CVX_POOL_ID, FORK_BLOCK, WBTC, WETH, TRI_CRYPTO, - TRI_CRYPTO_TOKEN, TRI_CRYPTO_CVX_POOL_ID, FRAX, FRAX_BP, @@ -35,20 +38,19 @@ import { MIM_THREE_POOL, MIM_THREE_POOL_POOL_ID, MIM_THREE_POOL_HOLDER, -} from './constants' +} from '../constants' +import { CurveBase } from '../pluginTestTypes' -interface WrappedPoolBase { - curvePool: CurvePoolMock - crv3Pool: ERC20Mock - w3Pool: ConvexStakingWrapper -} - -export interface Wrapped3PoolFixtureStable extends WrappedPoolBase { +export interface Wrapped3PoolFixtureStable extends CurveBase { dai: ERC20Mock usdc: ERC20Mock usdt: ERC20Mock } +export interface WrappedSUSDPoolFixtureStable extends Wrapped3PoolFixtureStable { + susd: ERC20Mock +} + export const makeW3PoolStable = async (): Promise => { // Use real reference ERC20s const dai = await ethers.getContractAt('ERC20Mock', DAI) @@ -70,8 +72,58 @@ export const makeW3PoolStable = async (): Promise => ) await curvePool.setVirtualPrice(await realCurvePool.get_virtual_price()) - // Use real Curve/Convex contracts - const crv3Pool = await ethers.getContractAt('ERC20Mock', THREE_POOL_TOKEN) + // Deploy external cvxMining lib + const CvxMiningFactory = await ethers.getContractFactory('CvxMining') + const cvxMining = await CvxMiningFactory.deploy() + + // Deploy Wrapper + const wrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper', { + libraries: { CvxMining: cvxMining.address }, + }) + const wrapper = await wrapperFactory.deploy() + await wrapper.initialize(THREE_POOL_CVX_POOL_ID) + + return { + dai, + usdc, + usdt, + curvePool, + wrapper: wrapper as unknown as RewardableERC4626Vault, + } +} + +export const makeWSUSDPoolStable = async (): Promise => { + // Use real reference ERC20s + const dai = await ethers.getContractAt('ERC20Mock', DAI) + const usdc = await ethers.getContractAt('ERC20Mock', USDC) + const usdt = await ethers.getContractAt('ERC20Mock', USDT) + const susd = await ethers.getContractAt('ERC20Mock', SUSD) + + // Use mock curvePool seeded with initial balances + const CurvePoolMockFactory = await ethers.getContractFactory('CurvePoolMock') + // Requires a special interface to interact with the real sUSD Curve Pool + const realCurvePool = ( + await ethers.getContractAt('CurvePoolMockVariantInt', SUSD_POOL) + ) + + const curvePool = ( + await CurvePoolMockFactory.deploy( + [ + await realCurvePool.balances(0), + await realCurvePool.balances(1), + await realCurvePool.balances(2), + await realCurvePool.balances(3), + ], + [ + await realCurvePool.coins(0), + await realCurvePool.coins(1), + await realCurvePool.coins(2), + await realCurvePool.coins(3), + ] + ) + ) + + await curvePool.setVirtualPrice(await realCurvePool.get_virtual_price()) // Deploy external cvxMining lib const CvxMiningFactory = await ethers.getContractFactory('CvxMining') @@ -81,19 +133,19 @@ export const makeW3PoolStable = async (): Promise => const wrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper', { libraries: { CvxMining: cvxMining.address }, }) - const w3Pool = await wrapperFactory.deploy() - await w3Pool.initialize(THREE_POOL_CVX_POOL_ID) + const wrapper = await wrapperFactory.deploy() + await wrapper.initialize(SUSD_POOL_CVX_POOL_ID) - return { dai, usdc, usdt, curvePool, crv3Pool, w3Pool } + return { dai, usdc, usdt, susd, curvePool, wrapper: wrapper as unknown as RewardableERC4626Vault } } -export interface Wrapped3PoolFixtureVolatile extends WrappedPoolBase { +export interface Wrapped3PoolFixtureVolatile extends CurveBase { usdt: ERC20Mock wbtc: ERC20Mock weth: ERC20Mock } -export const makeW3PoolVolatile = async (): Promise => { +export const makeWTricryptoPoolVolatile = async (): Promise => { // Use real reference ERC20s const usdt = await ethers.getContractAt('ERC20Mock', USDT) const wbtc = await ethers.getContractAt('ERC20Mock', WBTC) @@ -114,9 +166,6 @@ export const makeW3PoolVolatile = async (): Promise ) await curvePool.setVirtualPrice(await realCurvePool.get_virtual_price()) - // Use real Curve/Convex contracts - const crv3Pool = await ethers.getContractAt('ERC20Mock', TRI_CRYPTO_TOKEN) - // Deploy external cvxMining lib const CvxMiningFactory = await ethers.getContractFactory('CvxMining') const cvxMining = await CvxMiningFactory.deploy() @@ -125,25 +174,36 @@ export const makeW3PoolVolatile = async (): Promise const wrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper', { libraries: { CvxMining: cvxMining.address }, }) - const w3Pool = await wrapperFactory.deploy() - await w3Pool.initialize(TRI_CRYPTO_CVX_POOL_ID) - - return { usdt, wbtc, weth, curvePool, crv3Pool, w3Pool } + const wrapper = await wrapperFactory.deploy() + await wrapper.initialize(TRI_CRYPTO_CVX_POOL_ID) + + return { + usdt, + wbtc, + weth, + curvePool, + wrapper: wrapper as unknown as RewardableERC4626Vault, + } } -export const mintW3Pool = async ( - ctx: WrappedPoolBase, +export const mintWPool = async ( + ctx: CurveBase, amount: BigNumberish, user: SignerWithAddress, recipient: string, holder: string ) => { + const cvxWrapper = ctx.wrapper as ConvexStakingWrapper + const lpToken = await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', + await cvxWrapper.curveToken() + ) await whileImpersonating(holder, async (signer) => { - await ctx.crv3Pool.connect(signer).transfer(user.address, amount) + await lpToken.connect(signer).transfer(user.address, amount) }) - await ctx.crv3Pool.connect(user).approve(ctx.w3Pool.address, amount) - await ctx.w3Pool.connect(user).deposit(amount, recipient) + await lpToken.connect(user).approve(ctx.wrapper.address, amount) + await ctx.wrapper.connect(user).deposit(amount, recipient) } export const resetFork = getResetFork(FORK_BLOCK) @@ -224,18 +284,23 @@ export const makeWeUSDFraxBP = async ( } export const mintWeUSDFraxBP = async ( - ctx: WrappedEUSDFraxBPFixture, + ctx: CurveBase, amount: BigNumberish, user: SignerWithAddress, recipient: string, holder: string ) => { + const cvxWrapper = ctx.wrapper as ConvexStakingWrapper + const lpToken = await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', + await cvxWrapper.curveToken() + ) await whileImpersonating(holder, async (signer) => { - await ctx.realMetapool.connect(signer).transfer(user.address, amount) + await lpToken.connect(signer).transfer(user.address, amount) }) - await ctx.realMetapool.connect(user).approve(ctx.wPool.address, amount) - await ctx.wPool.connect(user).deposit(amount, recipient) + await lpToken.connect(user).approve(ctx.wrapper.address, amount) + await ctx.wrapper.connect(user).deposit(amount, recipient) } // === MIM + 3Pool @@ -293,16 +358,21 @@ export const makeWMIM3Pool = async (): Promise => { } export const mintWMIM3Pool = async ( - ctx: WrappedMIM3PoolFixture, + ctx: CurveBase, amount: BigNumberish, user: SignerWithAddress, recipient: string, holder: string ) => { + const cvxWrapper = ctx.wrapper as ConvexStakingWrapper + const lpToken = await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', + await cvxWrapper.curveToken() + ) await whileImpersonating(holder, async (signer) => { - await ctx.realMetapool.connect(signer).transfer(user.address, amount) + await lpToken.connect(signer).transfer(user.address, amount) }) - await ctx.realMetapool.connect(user).approve(ctx.wPool.address, amount) - await ctx.wPool.connect(user).deposit(amount, recipient) + await lpToken.connect(user).approve(ctx.wrapper.address, amount) + await ctx.wrapper.connect(user).deposit(amount, recipient) } diff --git a/test/plugins/individual-collateral/curve/pluginTestTypes.ts b/test/plugins/individual-collateral/curve/pluginTestTypes.ts new file mode 100644 index 0000000000..230e6cd146 --- /dev/null +++ b/test/plugins/individual-collateral/curve/pluginTestTypes.ts @@ -0,0 +1,89 @@ +import { BigNumberish } from 'ethers' +import { + ConvexStakingWrapper, + CurveGaugeWrapper, + CurvePoolMock, + ERC20Mock, + MockV3Aggregator, + TestICollateral, +} from '../../../../typechain' +import { CollateralOpts } from '../pluginTestTypes' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { CurvePoolType } from './constants' + +type Fixture = () => Promise + +export interface CurveBase { + curvePool: CurvePoolMock + wrapper: CurveGaugeWrapper | ConvexStakingWrapper +} + +// The basic fixture context used in the Curve collateral plugin tests +export interface CurveCollateralFixtureContext extends CurveBase { + alice: SignerWithAddress + collateral: TestICollateral + rewardTokens: ERC20Mock[] // ie [CRV, CVX, FXS] + poolTokens: ERC20Mock[] // ie [USDC, DAI, USDT] + feeds: MockV3Aggregator[] // ie [USDC/USD feed, DAI/USD feed, USDT/USD feed] +} + +// The basic constructor arguments for a Curve collateral plugin -- extension +export interface CurveCollateralOpts extends CollateralOpts { + nTokens?: BigNumberish + curvePool?: string + poolType?: CurvePoolType + feeds?: string[][] + oracleTimeouts?: BigNumberish[][] + oracleErrors?: BigNumberish[][] + lpToken?: string +} + +export interface CurveMetapoolCollateralOpts extends CurveCollateralOpts { + metapoolToken?: string + pairedTokenDefaultThreshold?: BigNumberish +} + +// A function to deploy the collateral plugin and return the deployed instance of the contract +export type DeployCurveCollateralFunc = ( + opts: CurveCollateralOpts +) => Promise<[TestICollateral, CurveCollateralOpts]> + +// A function to deploy and return the plugin-specific test suite context +export type MakeCurveCollateralFixtureFunc = ( + alice: SignerWithAddress, + opts: CurveCollateralOpts +) => Fixture + +// A function to mint a certain amount of collateral to a target address +export type MintCurveCollateralFunc = ( + ctx: CurveCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => Promise + +// The interface that defines the test suite for the collateral plugin +export interface CurveCollateralTestSuiteFixtures { + // a function to deploy the collateral plugin and return the deployed instance of the contract + deployCollateral: DeployCurveCollateralFunc + + // a group of tests, specific to the collateral plugin, focused on the plugin's constructor + collateralSpecificConstructorTests: () => void + + // a group of tests, specific to the collateral plugin, focused on status checks + collateralSpecificStatusTests: () => void + + // a function to deploy and return the plugin-specific test suite context + makeCollateralFixtureContext: MakeCurveCollateralFixtureFunc + + // a function to mint a certain amount of collateral to a target address + mintCollateralTo: MintCurveCollateralFunc + + isMetapool: boolean + + // a function to reset the fork to a desired block + resetFork: () => void + + // the name of the collateral plugin being tested + collateralName: string +} diff --git a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts new file mode 100644 index 0000000000..4b50a53796 --- /dev/null +++ b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts @@ -0,0 +1,211 @@ +import collateralTests from '../collateralTests' +import { CollateralFixtureContext, CollateralOpts, MintCollateralFunc } from '../pluginTestTypes' +import { resetFork, mintSDAI } from './helpers' +import { ethers } from 'hardhat' +import { expect } from 'chai' +import { ContractFactory, BigNumberish, BigNumber } from 'ethers' +import { + ERC20Mock, + IERC20Metadata, + MockV3Aggregator, + MockV3Aggregator__factory, + PotMock, + TestICollateral, +} from '../../../../typechain' +import { bn, fp } from '../../../../common/numbers' +import { ZERO_ADDRESS } from '../../../../common/constants' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + POT, + SDAI, + ORACLE_ERROR, + ORACLE_TIMEOUT, + PRICE_TIMEOUT, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + DAI_USD_PRICE_FEED, +} from './constants' + +/* + Define deployment functions +*/ + +export const defaultSDaiCollateralOpts: CollateralOpts = { + erc20: SDAI, + targetName: ethers.utils.formatBytes32String('USD'), + rewardERC20: ZERO_ADDRESS, + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: DAI_USD_PRICE_FEED, + oracleTimeout: ORACLE_TIMEOUT, + oracleError: ORACLE_ERROR, + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: fp('0'), +} + +export const deployCollateral = async (opts: CollateralOpts = {}): Promise => { + opts = { ...defaultSDaiCollateralOpts, ...opts } + + const PotFactory: ContractFactory = await ethers.getContractFactory('PotMock') + const pot = await PotFactory.deploy(POT) + + const SDaiCollateralFactory: ContractFactory = await ethers.getContractFactory('SDaiCollateral') + const collateral = await SDaiCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + pot.address, + { gasLimit: 2000000000 } + ) + await collateral.deployed() + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return collateral +} + +const chainlinkDefaultAnswer = bn('1e8') + +type Fixture = () => Promise + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultSDaiCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, chainlinkDefaultAnswer) + ) + collateralOpts.chainlinkFeed = chainlinkFeed.address + + const sdai = (await ethers.getContractAt('IERC20Metadata', SDAI)) as IERC20Metadata + const rewardToken = (await ethers.getContractAt('ERC20Mock', ZERO_ADDRESS)) as ERC20Mock + const collateral = await deployCollateral(collateralOpts) + const tok = await ethers.getContractAt('IERC20Metadata', await collateral.erc20()) + + return { + alice, + collateral, + chainlinkFeed, + tok, + sdai, + rewardToken, + } + } + + return makeCollateralFixtureContext +} + +const mintCollateralTo: MintCollateralFunc = async ( + ctx: CollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintSDAI(ctx.tok, user, amount, recipient) +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const reduceTargetPerRef = async () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const increaseTargetPerRef = async () => {} + +// prettier-ignore +const reduceRefPerTok = async ( + ctx: CollateralFixtureContext, + pctDecrease: BigNumberish +) => { + const collateral = await ethers.getContractAt('SDaiCollateral', ctx.collateral.address) + const pot = await ethers.getContractAt('PotMock', await collateral.pot()) + await pot.drip() + + const chi = await pot.chi() + const newChi = chi.sub(chi.mul(bn(pctDecrease)).div(bn('100'))) + await pot.setChi(newChi) +} + +// prettier-ignore +const increaseRefPerTok = async ( + ctx: CollateralFixtureContext, + pctIncrease: BigNumberish + +) => { + const collateral = await ethers.getContractAt('SDaiCollateral', ctx.collateral.address) + const pot = await ethers.getContractAt('PotMock', await collateral.pot()) + await pot.drip() + + const chi = await pot.chi() + const newChi = chi.add(chi.mul(bn(pctIncrease)).div(bn('100'))) + await pot.setChi(newChi) +} + +const getExpectedPrice = async (ctx: CollateralFixtureContext): Promise => { + const clData = await ctx.chainlinkFeed.latestRoundData() + const clDecimals = await ctx.chainlinkFeed.decimals() + + const refPerTok = await ctx.collateral.refPerTok() + return clData.answer + .mul(bn(10).pow(18 - clDecimals)) + .mul(refPerTok) + .div(fp('1')) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const beforeEachRewardsTest = async () => {} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it.skip, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itHasRevenueHiding: it, + resetFork, + collateralName: 'SDaiCollateral', + chainlinkDefaultAnswer, +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/dsr/constants.ts b/test/plugins/individual-collateral/dsr/constants.ts new file mode 100644 index 0000000000..010a13f76f --- /dev/null +++ b/test/plugins/individual-collateral/dsr/constants.ts @@ -0,0 +1,19 @@ +import { bn, fp } from '../../../../common/numbers' +import { networkConfig } from '../../../../common/configuration' + +// Mainnet Addresses +export const RSR = networkConfig['31337'].tokens.RSR as string +export const DAI_USD_PRICE_FEED = networkConfig['31337'].chainlinkFeeds.DAI as string +export const DAI = networkConfig['31337'].tokens.DAI as string +export const SDAI = networkConfig['31337'].tokens.sDAI as string +export const SDAI_HOLDER = '0xa4108aA1Ec4967F8b52220a4f7e94A8201F2D906' +export const POT = '0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7' + +export const PRICE_TIMEOUT = bn('604800') // 1 week +export const ORACLE_TIMEOUT = bn(86400) // 24 hours in seconds +export const ORACLE_ERROR = fp('0.0025') // 0.25% +export const DEFAULT_THRESHOLD = fp('0.05') +export const DELAY_UNTIL_DEFAULT = bn(86400) +export const MAX_TRADE_VOL = bn(1000) + +export const FORK_BLOCK = 17439282 diff --git a/test/plugins/individual-collateral/dsr/helpers.ts b/test/plugins/individual-collateral/dsr/helpers.ts new file mode 100644 index 0000000000..695dfbfff0 --- /dev/null +++ b/test/plugins/individual-collateral/dsr/helpers.ts @@ -0,0 +1,19 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { IERC20Metadata } from '../../../../typechain' +import { whileImpersonating } from '../../../utils/impersonation' +import { BigNumberish } from 'ethers' +import { FORK_BLOCK, SDAI_HOLDER } from './constants' +import { getResetFork } from '../helpers' + +export const mintSDAI = async ( + sdai: IERC20Metadata, + account: SignerWithAddress, + amount: BigNumberish, + recipient: string +) => { + await whileImpersonating(SDAI_HOLDER, async (whale) => { + await sdai.connect(whale).transfer(recipient, amount) + }) +} + +export const resetFork = getResetFork(FORK_BLOCK) diff --git a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts index 77c8d1f439..9367ee577c 100644 --- a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts @@ -8,8 +8,8 @@ import { import { ethers } from 'hardhat' import { ContractFactory, BigNumberish } from 'ethers' import { - CTokenVault, - CTokenVaultMock, + CTokenWrapper, + CTokenWrapperMock, ICToken, MockV3Aggregator, MockV3Aggregator__factory, @@ -119,9 +119,9 @@ all.forEach((curr: FTokenEnumeration) => { if (erc20Address && erc20Address != ZERO_ADDRESS && erc20Address == curr.fToken) { const erc20 = await ethers.getContractAt('ERC20Mock', opts.erc20!) - const CTokenVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenVault') - const fTokenVault = ( - await CTokenVaultFactory.deploy( + const CTokenWrapperFactory: ContractFactory = await ethers.getContractFactory('CTokenWrapper') + const fTokenVault = ( + await CTokenWrapperFactory.deploy( opts.erc20, await erc20.name(), await erc20.symbol(), @@ -176,7 +176,10 @@ all.forEach((curr: FTokenEnumeration) => { collateralOpts.chainlinkFeed = chainlinkFeed.address const collateral = await deployCollateral(collateralOpts) - const erc20 = await ethers.getContractAt('CTokenVault', (await collateral.erc20()) as string) // the fToken + const erc20 = await ethers.getContractAt( + 'CTokenWrapper', + (await collateral.erc20()) as string + ) // the fToken return { alice, @@ -211,8 +214,8 @@ all.forEach((curr: FTokenEnumeration) => { curr.underlying ) - const CTokenVaultMockFactory: ContractFactory = await ethers.getContractFactory( - 'CTokenVaultMock' + const CTokenWrapperMockFactory: ContractFactory = await ethers.getContractFactory( + 'CTokenWrapperMock' ) let compAddress = ZERO_ADDRESS @@ -221,8 +224,8 @@ all.forEach((curr: FTokenEnumeration) => { // eslint-disable-next-line no-empty } catch {} - const fTokenVault = ( - await CTokenVaultMockFactory.deploy( + const fTokenVault = ( + await CTokenWrapperMockFactory.deploy( await underlyingFToken.name(), await underlyingFToken.symbol(), underlyingFToken.address, @@ -252,8 +255,8 @@ all.forEach((curr: FTokenEnumeration) => { user: SignerWithAddress, recipient: string ) => { - const tok = ctx.tok as CTokenVault - const fToken = await ethers.getContractAt('ICToken', await tok.asset()) + const tok = ctx.tok as CTokenWrapper + const fToken = await ethers.getContractAt('ICToken', await tok.underlying()) const underlying = await ethers.getContractAt('IERC20Metadata', await fToken.underlying()) await mintFToken(underlying, curr.holderUnderlying, fToken, tok, amount, recipient) } @@ -272,21 +275,21 @@ all.forEach((curr: FTokenEnumeration) => { const rate = fp('2') const rateAsRefPerTok = rate.div(50) - await (tok as CTokenVaultMock).setExchangeRate(rate) // above current + await (tok as CTokenWrapperMock).setExchangeRate(rate) // above current await collateral.refresh() const before = await collateral.refPerTok() expect(before).to.equal(rateAsRefPerTok.mul(fp('0.99')).div(fp('1'))) expect(await collateral.status()).to.equal(CollateralStatus.SOUND) // Should be SOUND if drops just under 1% - await (tok as CTokenVaultMock).setExchangeRate(rate.mul(fp('0.99001')).div(fp('1'))) + await (tok as CTokenWrapperMock).setExchangeRate(rate.mul(fp('0.99001')).div(fp('1'))) await collateral.refresh() let after = await collateral.refPerTok() expect(before).to.eq(after) expect(await collateral.status()).to.equal(CollateralStatus.SOUND) // Should be DISABLED if drops just over 1% - await (tok as CTokenVaultMock).setExchangeRate(before.mul(fp('0.98999')).div(fp('1'))) + await (tok as CTokenWrapperMock).setExchangeRate(before.mul(fp('0.98999')).div(fp('1'))) await collateral.refresh() after = await collateral.refPerTok() expect(before).to.be.gt(after) diff --git a/test/plugins/individual-collateral/flux-finance/helpers.ts b/test/plugins/individual-collateral/flux-finance/helpers.ts index 9327270bbb..74cb8d9614 100644 --- a/test/plugins/individual-collateral/flux-finance/helpers.ts +++ b/test/plugins/individual-collateral/flux-finance/helpers.ts @@ -1,4 +1,4 @@ -import { CTokenVault, ICToken, IERC20Metadata } from '../../../../typechain' +import { CTokenWrapper, ICToken, IERC20Metadata } from '../../../../typechain' import { whileImpersonating } from '../../../utils/impersonation' import { BigNumberish } from 'ethers' import { getResetFork } from '../helpers' @@ -8,7 +8,7 @@ export const mintFToken = async ( underlying: IERC20Metadata, holderUnderlying: string, fToken: ICToken, - fTokenVault: CTokenVault, + fTokenVault: CTokenWrapper, amount: BigNumberish, recipient: string ) => { @@ -17,7 +17,7 @@ export const mintFToken = async ( await underlying.connect(signer).approve(fToken.address, balUnderlying) await fToken.connect(signer).mint(balUnderlying) await fToken.connect(signer).approve(fTokenVault.address, amount) - await fTokenVault.connect(signer).mint(amount, recipient) + await fTokenVault.connect(signer).deposit(amount, recipient) }) } diff --git a/test/plugins/individual-collateral/pluginTestTypes.ts b/test/plugins/individual-collateral/pluginTestTypes.ts index a25ea47e1f..445a237284 100644 --- a/test/plugins/individual-collateral/pluginTestTypes.ts +++ b/test/plugins/individual-collateral/pluginTestTypes.ts @@ -70,16 +70,16 @@ export interface CollateralTestSuiteFixtures mintCollateralTo: MintCollateralFunc // a function to reduce the value of `targetPerRef` - reduceTargetPerRef: (ctx: T, pctDecrease: BigNumberish) => void + reduceTargetPerRef: (ctx: T, pctDecrease: BigNumberish) => Promise | void // a function to increase the value of `targetPerRef` - increaseTargetPerRef: (ctx: T, pctIncrease: BigNumberish) => void + increaseTargetPerRef: (ctx: T, pctIncrease: BigNumberish) => Promise | void // a function to reduce the value of `refPerTok` - reduceRefPerTok: (ctx: T, pctDecrease: BigNumberish) => void + reduceRefPerTok: (ctx: T, pctDecrease: BigNumberish) => Promise | void // a function to increase the value of `refPerTok` - increaseRefPerTok: (ctx: T, pctIncrease: BigNumberish) => void + increaseRefPerTok: (ctx: T, pctIncrease: BigNumberish) => Promise | void // a function to calculate the expected price (ignoring oracle error) // that should be returned from `plugin.price()` diff --git a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts index 70194a2350..21b019a34c 100644 --- a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts @@ -273,7 +273,19 @@ const getExpectedPrice = async (ctx: RethCollateralFixtureContext): Promise {} +const collateralSpecificConstructorTests = () => { + it('does not allow missing refPerTok chainlink feed', async () => { + await expect( + deployCollateral({ refPerTokChainlinkFeed: ethers.constants.AddressZero }) + ).to.be.revertedWith('missing refPerTok feed') + }) + + it('does not allow refPerTok oracle timeout at 0', async () => { + await expect(deployCollateral({ refPerTokChainlinkTimeout: 0 })).to.be.revertedWith( + 'refPerTokChainlinkTimeout zero' + ) + }) +} // eslint-disable-next-line @typescript-eslint/no-empty-function const collateralSpecificStatusTests = () => {} diff --git a/test/scenario/ComplexBasket.test.ts b/test/scenario/ComplexBasket.test.ts index 50fbae5bff..c00f465cb2 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -9,7 +9,7 @@ import { bn, fp, pow10, toBNDecimals } from '../../common/numbers' import { Asset, ComptrollerMock, - CTokenVaultMock, + CTokenWrapperMock, ERC20Mock, FacadeRead, FacadeTest, @@ -83,11 +83,11 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { let usdToken: ERC20Mock let eurToken: ERC20Mock - let cUSDTokenVault: CTokenVaultMock + let cUSDTokenVault: CTokenWrapperMock let aUSDToken: StaticATokenMock let wbtc: ERC20Mock - let cWBTCVault: CTokenVaultMock - let cETHVault: CTokenVaultMock + let cWBTCVault: CTokenWrapperMock + let cETHVault: CTokenWrapperMock let weth: WETH9 @@ -156,7 +156,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // Setup Factories const ERC20: ContractFactory = await ethers.getContractFactory('ERC20Mock') const WETH: ContractFactory = await ethers.getContractFactory('WETH9') - const CToken: ContractFactory = await ethers.getContractFactory('CTokenVaultMock') + const CToken: ContractFactory = await ethers.getContractFactory('CTokenWrapperMock') const MockV3AggregatorFactory: ContractFactory = await ethers.getContractFactory( 'MockV3Aggregator' ) @@ -241,7 +241,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { collateral.push(await ethers.getContractAt('EURFiatCollateral', fiatEUR)) // 3. CTokenFiatCollateral against USD - cUSDTokenVault = erc20s[4] // cDAI Token + cUSDTokenVault = erc20s[4] // cDAI Token const { collateral: cUSDCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: PRICE_TIMEOUT.toString(), priceFeed: usdFeed.address, @@ -307,7 +307,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { collateral.push(await ethers.getContractAt('NonFiatCollateral', wBTCCollateral)) // 6. CTokenNonFiatCollateral cWBTCVault against BTC - cWBTCVault = ( + cWBTCVault = ( await CToken.deploy( 'cWBTCVault Token', 'cWBTCVault', @@ -363,7 +363,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // 8. CTokenSelfReferentialCollateral cETHVault against ETH // Give higher maxTradeVolume: MAX_TRADE_VOLUME.toString(), - cETHVault = ( + cETHVault = ( await CToken.deploy( 'cETHVault Token', 'cETHVault', diff --git a/test/scenario/MaxBasketSize.test.ts b/test/scenario/MaxBasketSize.test.ts index 79c5c1e3b3..a3ab632140 100644 --- a/test/scenario/MaxBasketSize.test.ts +++ b/test/scenario/MaxBasketSize.test.ts @@ -10,7 +10,7 @@ import { ATokenFiatCollateral, ComptrollerMock, CTokenFiatCollateral, - CTokenVaultMock, + CTokenWrapperMock, ERC20Mock, FacadeRead, FacadeTest, @@ -211,10 +211,10 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { return atoken } - const makeCToken = async (tokenName: string): Promise => { + const makeCToken = async (tokenName: string): Promise => { const ERC20MockFactory: ContractFactory = await ethers.getContractFactory('ERC20Mock') - const CTokenVaultMockFactory: ContractFactory = await ethers.getContractFactory( - 'CTokenVaultMock' + const CTokenWrapperMockFactory: ContractFactory = await ethers.getContractFactory( + 'CTokenWrapperMock' ) const CTokenCollateralFactory: ContractFactory = await ethers.getContractFactory( 'CTokenFiatCollateral' @@ -224,8 +224,8 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { await ERC20MockFactory.deploy(tokenName, `${tokenName} symbol`) ) - const ctoken: CTokenVaultMock = ( - await CTokenVaultMockFactory.deploy( + const ctoken: CTokenWrapperMock = ( + await CTokenWrapperMockFactory.deploy( 'c' + tokenName, `${'c' + tokenName} symbol`, erc20.address, @@ -491,7 +491,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { await assetRegistry.toColl(backing[0]) ) for (let i = maxBasketSize - tokensToDefault; i < backing.length; i++) { - const erc20 = await ethers.getContractAt('CTokenVaultMock', backing[i]) + const erc20 = await ethers.getContractAt('CTokenWrapperMock', backing[i]) // Decrease rate to cause default in Ctoken await erc20.setExchangeRate(fp('0.8')) diff --git a/test/scenario/RevenueHiding.test.ts b/test/scenario/RevenueHiding.test.ts index 6035603056..6412328602 100644 --- a/test/scenario/RevenueHiding.test.ts +++ b/test/scenario/RevenueHiding.test.ts @@ -7,7 +7,7 @@ import { bn, fp, divCeil } from '../../common/numbers' import { IConfig } from '../../common/configuration' import { CollateralStatus, TradeKind } from '../../common/constants' import { - CTokenVaultMock, + CTokenWrapperMock, CTokenFiatCollateral, ERC20Mock, IAssetRegistry, @@ -44,7 +44,7 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME // Tokens and Assets let dai: ERC20Mock let daiCollateral: SelfReferentialCollateral - let cDAI: CTokenVaultMock + let cDAI: CTokenWrapperMock let cDAICollateral: CTokenFiatCollateral // Config values @@ -106,7 +106,7 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME // Main ERC20 dai = erc20s[0] daiCollateral = collateral[0] - cDAI = erc20s[4] + cDAI = erc20s[4] cDAICollateral = await ( await ethers.getContractFactory('CTokenFiatCollateral') ).deploy( diff --git a/test/scenario/cETH.test.ts b/test/scenario/cETH.test.ts index 164109c2a0..870f11dcc0 100644 --- a/test/scenario/cETH.test.ts +++ b/test/scenario/cETH.test.ts @@ -21,7 +21,7 @@ import { TestIRevenueTrader, TestIRToken, WETH9, - CTokenVaultMock, + CTokenWrapperMock, } from '../../typechain' import { advanceTime } from '../utils/time' import { getTrade } from '../utils/trades' @@ -53,7 +53,7 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, // Tokens and Assets let weth: WETH9 let wethCollateral: SelfReferentialCollateral - let cETH: CTokenVaultMock + let cETH: CTokenWrapperMock let cETHCollateral: CTokenSelfReferentialCollateral let token0: CTokenMock let collateral0: Collateral @@ -123,7 +123,7 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, // cETH cETH = await ( - await ethers.getContractFactory('CTokenVaultMock') + await ethers.getContractFactory('CTokenWrapperMock') ).deploy('cETH Token', 'cETH', weth.address, compToken.address, compoundMock.address) cETHCollateral = await ( diff --git a/test/scenario/cWBTC.test.ts b/test/scenario/cWBTC.test.ts index 2754cdce81..35d916c2f6 100644 --- a/test/scenario/cWBTC.test.ts +++ b/test/scenario/cWBTC.test.ts @@ -8,7 +8,7 @@ import { advanceTime } from '../utils/time' import { IConfig } from '../../common/configuration' import { CollateralStatus, TradeKind } from '../../common/constants' import { - CTokenVaultMock, + CTokenWrapperMock, CTokenNonFiatCollateral, ComptrollerMock, ERC20Mock, @@ -52,9 +52,9 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => // Tokens and Assets let wbtc: ERC20Mock let wBTCCollateral: SelfReferentialCollateral - let cWBTC: CTokenVaultMock + let cWBTC: CTokenWrapperMock let cWBTCCollateral: CTokenNonFiatCollateral - let token0: CTokenVaultMock + let token0: CTokenWrapperMock let collateral0: Collateral let backupToken: ERC20Mock let backupCollateral: Collateral @@ -101,7 +101,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => } = await loadFixture(defaultFixtureNoBasket)) // Main ERC20 - token0 = erc20s[4] // cDai + token0 = erc20s[4] // cDai collateral0 = collateral[4] wbtc = await (await ethers.getContractFactory('ERC20Mock')).deploy('WBTC Token', 'WBTC') @@ -131,7 +131,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => // cWBTC cWBTC = await ( - await ethers.getContractFactory('CTokenVaultMock') + await ethers.getContractFactory('CTokenWrapperMock') ).deploy('cWBTC Token', 'cWBTC', wbtc.address, compToken.address, compoundMock.address) cWBTCCollateral = await ( await ethers.getContractFactory('CTokenNonFiatCollateral') diff --git a/test/utils/tokens.ts b/test/utils/tokens.ts index 0d24fba285..3ceebb8626 100644 --- a/test/utils/tokens.ts +++ b/test/utils/tokens.ts @@ -1,5 +1,5 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { CTokenVaultMock } from '@typechain/CTokenVaultMock' +import { CTokenWrapperMock } from '@typechain/CTokenWrapperMock' import { ERC20Mock } from '@typechain/ERC20Mock' import { StaticATokenMock } from '@typechain/StaticATokenMock' import { USDCMock } from '@typechain/USDCMock' @@ -18,8 +18,8 @@ export const mintCollaterals = async ( const token2 = ( await ethers.getContractAt('StaticATokenMock', await basket[2].erc20()) ) - const token3 = ( - await ethers.getContractAt('CTokenVaultMock', await basket[3].erc20()) + const token3 = ( + await ethers.getContractAt('CTokenWrapperMock', await basket[3].erc20()) ) for (const recipient of recipients) { diff --git a/utils/subgraph.ts b/utils/subgraph.ts index b276547e2f..76cd7b6a4b 100644 --- a/utils/subgraph.ts +++ b/utils/subgraph.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { BigNumber, BigNumberish } from 'ethers' import { gql, GraphQLClient } from 'graphql-request' import { useEnv } from './env' diff --git a/yarn.lock b/yarn.lock index f1b32350fa..fc614f5e36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1029,7 +1029,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/solidity@npm:5.7.0": +"@ethersproject/solidity@npm:5.7.0, @ethersproject/solidity@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/solidity@npm:5.7.0" dependencies: @@ -1110,7 +1110,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/wallet@npm:5.7.0": +"@ethersproject/wallet@npm:5.7.0, @ethersproject/wallet@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/wallet@npm:5.7.0" dependencies: @@ -1753,7 +1753,7 @@ __metadata: languageName: node linkType: hard -"@nomiclabs/hardhat-ethers@npm:^2.2.3": +"@nomiclabs/hardhat-ethers@npm:^2.1.1, @nomiclabs/hardhat-ethers@npm:^2.2.3": version: 2.2.3 resolution: "@nomiclabs/hardhat-ethers@npm:2.2.3" peerDependencies: @@ -1996,6 +1996,25 @@ __metadata: languageName: node linkType: hard +"@tenderly/hardhat-tenderly@npm:^1.7.7": + version: 1.7.7 + resolution: "@tenderly/hardhat-tenderly@npm:1.7.7" + dependencies: + "@ethersproject/bignumber": ^5.7.0 + "@nomiclabs/hardhat-ethers": ^2.1.1 + axios: ^0.27.2 + ethers: ^5.7.0 + fs-extra: ^10.1.0 + hardhat-deploy: ^0.11.14 + js-yaml: ^4.1.0 + tenderly: ^0.5.3 + tslog: ^4.3.1 + peerDependencies: + hardhat: ^2.10.2 + checksum: bd43441c6b121cb23c42f1f02b2aafa8c59c3a8e6b9bdcd980d594b91c15e02d7a239772b705b9cf1c0c292e7e8430a7ea24d7d1af24f54202e979e828bb9be6 + languageName: node + linkType: hard + "@tootallnate/once@npm:2": version: 2.0.0 resolution: "@tootallnate/once@npm:2.0.0" @@ -2252,7 +2271,7 @@ __metadata: languageName: node linkType: hard -"@types/qs@npm:^6.2.31": +"@types/qs@npm:^6.2.31, @types/qs@npm:^6.9.7": version: 6.9.7 resolution: "@types/qs@npm:6.9.7" checksum: 7fd6f9c25053e9b5bb6bc9f9f76c1d89e6c04f7707a7ba0e44cc01f17ef5284adb82f230f542c2d5557d69407c9a40f0f3515e8319afd14e1e16b5543ac6cdba @@ -2456,6 +2475,16 @@ __metadata: languageName: node linkType: hard +"accepts@npm:~1.3.8": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: ~2.1.34 + negotiator: 0.6.3 + checksum: 50c43d32e7b50285ebe84b613ee4a3aa426715a7d131b65b786e2ead0fd76b6b60091b9916d3478a75f11f162628a2139991b6c03ab3f1d9ab7c86075dc8eab4 + languageName: node + linkType: hard + "acorn-jsx@npm:^5.0.0, acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -2752,6 +2781,13 @@ __metadata: languageName: node linkType: hard +"array-flatten@npm:1.1.1": + version: 1.1.1 + resolution: "array-flatten@npm:1.1.1" + checksum: a9925bf3512d9dce202112965de90c222cd59a4fbfce68a0951d25d965cf44642931f40aac72309c41f12df19afa010ecadceb07cfff9ccc1621e99d89ab5f3b + languageName: node + linkType: hard + "array-includes@npm:^3.1.4": version: 3.1.5 resolution: "array-includes@npm:3.1.5" @@ -2899,7 +2935,7 @@ __metadata: languageName: node linkType: hard -"axios@npm:^0.21.2": +"axios@npm:^0.21.1, axios@npm:^0.21.2": version: 0.21.4 resolution: "axios@npm:0.21.4" dependencies: @@ -2917,6 +2953,16 @@ __metadata: languageName: node linkType: hard +"axios@npm:^0.27.2": + version: 0.27.2 + resolution: "axios@npm:0.27.2" + dependencies: + follow-redirects: ^1.14.9 + form-data: ^4.0.0 + checksum: 38cb7540465fe8c4102850c4368053c21683af85c5fdf0ea619f9628abbcb59415d1e22ebc8a6390d2bbc9b58a9806c874f139767389c862ec9b772235f06854 + languageName: node + linkType: hard + "babel-plugin-istanbul@npm:^6.1.1": version: 6.1.1 resolution: "babel-plugin-istanbul@npm:6.1.1" @@ -3056,6 +3102,26 @@ __metadata: languageName: node linkType: hard +"body-parser@npm:1.20.1": + version: 1.20.1 + resolution: "body-parser@npm:1.20.1" + dependencies: + bytes: 3.1.2 + content-type: ~1.0.4 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.1 + type-is: ~1.6.18 + unpipe: 1.0.0 + checksum: f1050dbac3bede6a78f0b87947a8d548ce43f91ccc718a50dd774f3c81f2d8b04693e52acf62659fad23101827dd318da1fb1363444ff9a8482b886a3e4a5266 + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -3448,7 +3514,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:3.5.3, chokidar@npm:^3.4.0": +"chokidar@npm:3.5.3, chokidar@npm:^3.4.0, chokidar@npm:^3.5.2": version: 3.5.3 resolution: "chokidar@npm:3.5.3" dependencies: @@ -3555,6 +3621,19 @@ __metadata: languageName: node linkType: hard +"cli-table3@npm:^0.6.2": + version: 0.6.3 + resolution: "cli-table3@npm:0.6.3" + dependencies: + "@colors/colors": 1.5.0 + string-width: ^4.2.0 + dependenciesMeta: + "@colors/colors": + optional: true + checksum: 09897f68467973f827c04e7eaadf13b55f8aec49ecd6647cc276386ea660059322e2dd8020a8b6b84d422dbdd619597046fa89cbbbdc95b2cea149a2df7c096c + languageName: node + linkType: hard + "cli-width@npm:^2.0.0": version: 2.2.1 resolution: "cli-width@npm:2.2.1" @@ -3682,6 +3761,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^9.4.0": + version: 9.5.0 + resolution: "commander@npm:9.5.0" + checksum: c7a3e27aa59e913b54a1bafd366b88650bc41d6651f0cbe258d4ff09d43d6a7394232a4dadd0bf518b3e696fdf595db1028a0d82c785b88bd61f8a440cecfade + languageName: node + linkType: hard + "compare-versions@npm:^5.0.0": version: 5.0.1 resolution: "compare-versions@npm:5.0.1" @@ -3715,6 +3801,22 @@ __metadata: languageName: node linkType: hard +"content-disposition@npm:0.5.4": + version: 0.5.4 + resolution: "content-disposition@npm:0.5.4" + dependencies: + safe-buffer: 5.2.1 + checksum: afb9d545e296a5171d7574fcad634b2fdf698875f4006a9dd04a3e1333880c5c0c98d47b560d01216fb6505a54a2ba6a843ee3a02ec86d7e911e8315255f56c3 + languageName: node + linkType: hard + +"content-type@npm:~1.0.4": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 566271e0a251642254cde0f845f9dd4f9856e52d988f4eb0d0dcffbb7a1f8ec98de7a5215fc628f3bce30fe2fb6fd2bc064b562d721658c59b544e2d34ea2766 + languageName: node + linkType: hard + "convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.7.0": version: 1.8.0 resolution: "convert-source-map@npm:1.8.0" @@ -3724,6 +3826,20 @@ __metadata: languageName: node linkType: hard +"cookie-signature@npm:1.0.6": + version: 1.0.6 + resolution: "cookie-signature@npm:1.0.6" + checksum: f4e1b0a98a27a0e6e66fd7ea4e4e9d8e038f624058371bf4499cfcd8f3980be9a121486995202ba3fca74fbed93a407d6d54d43a43f96fd28d0bd7a06761591a + languageName: node + linkType: hard + +"cookie@npm:0.5.0": + version: 0.5.0 + resolution: "cookie@npm:0.5.0" + checksum: 1f4bd2ca5765f8c9689a7e8954183f5332139eb72b6ff783d8947032ec1fdf43109852c178e21a953a30c0dd42257828185be01b49d1eb1a67fd054ca588a180 + languageName: node + linkType: hard + "cookie@npm:^0.4.1": version: 0.4.2 resolution: "cookie@npm:0.4.2" @@ -3856,6 +3972,15 @@ __metadata: languageName: node linkType: hard +"debug@npm:2.6.9, debug@npm:^2.6.0, debug@npm:^2.6.9": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: 2.0.0 + checksum: d2f51589ca66df60bf36e1fa6e4386b318c3f1e06772280eea5b1ae9fd3d05e9c2b7fd8a7d862457d00853c75b00451aa2d7459b924629ee385287a650f58fe6 + languageName: node + linkType: hard + "debug@npm:3.2.6": version: 3.2.6 resolution: "debug@npm:3.2.6" @@ -3877,15 +4002,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:^2.6.0, debug@npm:^2.6.9": - version: 2.6.9 - resolution: "debug@npm:2.6.9" - dependencies: - ms: 2.0.0 - checksum: d2f51589ca66df60bf36e1fa6e4386b318c3f1e06772280eea5b1ae9fd3d05e9c2b7fd8a7d862457d00853c75b00451aa2d7459b924629ee385287a650f58fe6 - languageName: node - linkType: hard - "debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" @@ -3967,6 +4083,13 @@ __metadata: languageName: node linkType: hard +"define-lazy-prop@npm:^2.0.0": + version: 2.0.0 + resolution: "define-lazy-prop@npm:2.0.0" + checksum: 0115fdb065e0490918ba271d7339c42453d209d4cb619dfe635870d906731eff3e1ade8028bb461ea27ce8264ec5e22c6980612d332895977e89c1bbc80fcee2 + languageName: node + linkType: hard + "define-properties@npm:^1.1.2, define-properties@npm:^1.1.3, define-properties@npm:^1.1.4": version: 1.1.4 resolution: "define-properties@npm:1.1.4" @@ -4005,6 +4128,13 @@ __metadata: languageName: node linkType: hard +"destroy@npm:1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: 0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 + languageName: node + linkType: hard + "detect-port@npm:^1.3.0": version: 1.3.0 resolution: "detect-port@npm:1.3.0" @@ -4099,6 +4229,13 @@ __metadata: languageName: node linkType: hard +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: 1b4cac778d64ce3b582a7e26b218afe07e207a0f9bfe13cc7395a6d307849cfe361e65033c3251e00c27dd060cab43014c2d6b2647676135e18b77d2d05b3f4f + languageName: node + linkType: hard + "electron-to-chromium@npm:^1.4.172": version: 1.4.180 resolution: "electron-to-chromium@npm:1.4.180" @@ -4142,6 +4279,20 @@ __metadata: languageName: node linkType: hard +"encode-utf8@npm:^1.0.2": + version: 1.0.3 + resolution: "encode-utf8@npm:1.0.3" + checksum: 550224bf2a104b1d355458c8a82e9b4ea07f9fc78387bc3a49c151b940ad26473de8dc9e121eefc4e84561cb0b46de1e4cd2bc766f72ee145e9ea9541482817f + languageName: node + linkType: hard + +"encodeurl@npm:~1.0.2": + version: 1.0.2 + resolution: "encodeurl@npm:1.0.2" + checksum: e50e3d508cdd9c4565ba72d2012e65038e5d71bdc9198cb125beb6237b5b1ade6c0d343998da9e170fb2eae52c1bed37d4d6d98a46ea423a0cddbed5ac3f780c + languageName: node + linkType: hard + "encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -4151,7 +4302,7 @@ __metadata: languageName: node linkType: hard -"enquirer@npm:^2.3.0": +"enquirer@npm:^2.3.0, enquirer@npm:^2.3.6": version: 2.3.6 resolution: "enquirer@npm:2.3.6" dependencies: @@ -4248,6 +4399,13 @@ __metadata: languageName: node linkType: hard +"escape-html@npm:~1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 6213ca9ae00d0ab8bccb6d8d4e0a98e76237b2410302cf7df70aaa6591d509a2a37ce8998008cbecae8fc8ffaadf3fb0229535e6a145f3ce0b211d060decbb24 + languageName: node + linkType: hard + "escape-string-regexp@npm:1.0.5, escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" @@ -4665,6 +4823,13 @@ __metadata: languageName: node linkType: hard +"etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 571aeb3dbe0f2bbd4e4fadbdb44f325fc75335cd5f6f6b6a091e6a06a9f25ed5392f0863c5442acb0646787446e816f13cbfc6edce5b07658541dff573cab1ff + languageName: node + linkType: hard + "eth-gas-reporter@npm:^0.2.24": version: 0.2.25 resolution: "eth-gas-reporter@npm:0.2.25" @@ -4801,7 +4966,7 @@ __metadata: languageName: node linkType: hard -"ethers@npm:^5.7.1, ethers@npm:^5.7.2": +"ethers@npm:^5.5.3, ethers@npm:^5.7.0, ethers@npm:^5.7.1, ethers@npm:^5.7.2": version: 5.7.2 resolution: "ethers@npm:5.7.2" dependencies: @@ -4890,6 +5055,45 @@ __metadata: languageName: node linkType: hard +"express@npm:^4.18.1": + version: 4.18.2 + resolution: "express@npm:4.18.2" + dependencies: + accepts: ~1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.1 + content-disposition: 0.5.4 + content-type: ~1.0.4 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: ~1.0.2 + escape-html: ~1.0.3 + etag: ~1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: ~1.1.2 + on-finished: 2.4.1 + parseurl: ~1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: ~2.0.7 + qs: 6.11.0 + range-parser: ~1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: ~1.6.18 + utils-merge: 1.0.1 + vary: ~1.1.2 + checksum: 3c4b9b076879442f6b968fe53d85d9f1eeacbb4f4c41e5f16cc36d77ce39a2b0d81b3f250514982110d815b2f7173f5561367f9110fcc541f9371948e8c8b037 + languageName: node + linkType: hard + "extend@npm:~3.0.2": version: 3.0.2 resolution: "extend@npm:3.0.2" @@ -5040,6 +5244,21 @@ __metadata: languageName: node linkType: hard +"finalhandler@npm:1.2.0": + version: 1.2.0 + resolution: "finalhandler@npm:1.2.0" + dependencies: + debug: 2.6.9 + encodeurl: ~1.0.2 + escape-html: ~1.0.3 + on-finished: 2.4.1 + parseurl: ~1.3.3 + statuses: 2.0.1 + unpipe: ~1.0.0 + checksum: 92effbfd32e22a7dff2994acedbd9bcc3aa646a3e919ea6a53238090e87097f8ef07cced90aa2cc421abdf993aefbdd5b00104d55c7c5479a8d00ed105b45716 + languageName: node + linkType: hard + "find-package-json@npm:^1.2.0": version: 1.2.0 resolution: "find-package-json@npm:1.2.0" @@ -5150,6 +5369,15 @@ __metadata: languageName: node linkType: hard +"fmix@npm:^0.1.0": + version: 0.1.0 + resolution: "fmix@npm:0.1.0" + dependencies: + imul: ^1.0.0 + checksum: c465344d4f169eaf10d45c33949a1e7a633f09dba2ac7063ce8ae8be743df5979d708f7f24900163589f047f5194ac5fc2476177ce31175e8805adfa7b8fb7a4 + languageName: node + linkType: hard + "follow-redirects@npm:^1.12.1, follow-redirects@npm:^1.14.4": version: 1.15.1 resolution: "follow-redirects@npm:1.15.1" @@ -5160,7 +5388,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.14.0": +"follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.9": version: 1.15.2 resolution: "follow-redirects@npm:1.15.2" peerDependenciesMeta: @@ -5199,6 +5427,17 @@ __metadata: languageName: node linkType: hard +"form-data@npm:^4.0.0": + version: 4.0.0 + resolution: "form-data@npm:4.0.0" + dependencies: + asynckit: ^0.4.0 + combined-stream: ^1.0.8 + mime-types: ^2.1.12 + checksum: 01135bf8675f9d5c61ff18e2e2932f719ca4de964e3be90ef4c36aacfc7b9cb2fceb5eca0b7e0190e3383fe51c5b37f4cb80b62ca06a99aaabfcfd6ac7c9328c + languageName: node + linkType: hard + "form-data@npm:~2.3.2": version: 2.3.3 resolution: "form-data@npm:2.3.3" @@ -5210,6 +5449,13 @@ __metadata: languageName: node linkType: hard +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: fd27e2394d8887ebd16a66ffc889dc983fbbd797d5d3f01087c020283c0f019a7d05ee85669383d8e0d216b116d720fc0cef2f6e9b7eb9f4c90c6e0bc7fd28e6 + languageName: node + linkType: hard + "fp-ts@npm:1.19.3": version: 1.19.3 resolution: "fp-ts@npm:1.19.3" @@ -5224,6 +5470,13 @@ __metadata: languageName: node linkType: hard +"fresh@npm:0.5.2": + version: 0.5.2 + resolution: "fresh@npm:0.5.2" + checksum: 13ea8b08f91e669a64e3ba3a20eb79d7ca5379a81f1ff7f4310d54e2320645503cc0c78daedc93dfb6191287295f6479544a649c64d8e41a1c0fb0c221552346 + languageName: node + linkType: hard + "fs-extra@npm:^0.30.0": version: 0.30.0 resolution: "fs-extra@npm:0.30.0" @@ -5237,6 +5490,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^10.0.0, fs-extra@npm:^10.1.0": + version: 10.1.0 + resolution: "fs-extra@npm:10.1.0" + dependencies: + graceful-fs: ^4.2.0 + jsonfile: ^6.0.1 + universalify: ^2.0.0 + checksum: dc94ab37096f813cc3ca12f0f1b5ad6744dfed9ed21e953d72530d103cea193c2f81584a39e9dee1bea36de5ee66805678c0dddc048e8af1427ac19c00fffc50 + languageName: node + linkType: hard + "fs-extra@npm:^7.0.0, fs-extra@npm:^7.0.1": version: 7.0.1 resolution: "fs-extra@npm:7.0.1" @@ -5692,6 +5956,38 @@ fsevents@~2.1.1: languageName: node linkType: hard +"hardhat-deploy@npm:^0.11.14": + version: 0.11.30 + resolution: "hardhat-deploy@npm:0.11.30" + dependencies: + "@ethersproject/abi": ^5.7.0 + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/contracts": ^5.7.0 + "@ethersproject/providers": ^5.7.2 + "@ethersproject/solidity": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + "@ethersproject/wallet": ^5.7.0 + "@types/qs": ^6.9.7 + axios: ^0.21.1 + chalk: ^4.1.2 + chokidar: ^3.5.2 + debug: ^4.3.2 + enquirer: ^2.3.6 + ethers: ^5.5.3 + form-data: ^4.0.0 + fs-extra: ^10.0.0 + match-all: ^1.2.6 + murmur-128: ^0.2.1 + qs: ^6.9.4 + zksync-web3: ^0.14.3 + checksum: 7b9ac9d856097be1df88ed86cbec88e5bdeb6258c7167c097d6ad4e80a1131b9288fc7704ff6457253f293f57c9992d83383f15ce8f22190d94966b8bb05d832 + languageName: node + linkType: hard + "hardhat-gas-reporter@npm:^1.0.8": version: 1.0.8 resolution: "hardhat-gas-reporter@npm:1.0.8" @@ -5991,6 +6287,13 @@ fsevents@~2.1.1: languageName: node linkType: hard +"hyperlinker@npm:^1.0.0": + version: 1.0.0 + resolution: "hyperlinker@npm:1.0.0" + checksum: f6d020ac552e9d048668206c805a737262b4c395546c773cceea3bc45252c46b4fa6eeb67c5896499dad00d21cb2f20f89fdd480a4529cfa3d012da2957162f9 + languageName: node + linkType: hard + "iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" @@ -6057,6 +6360,13 @@ fsevents@~2.1.1: languageName: node linkType: hard +"imul@npm:^1.0.0": + version: 1.0.1 + resolution: "imul@npm:1.0.1" + checksum: 6c2af3d5f09e2135e14d565a2c108412b825b221eb2c881f9130467f2adccf7ae201773ae8bcf1be169e2d090567a1fdfa9cf20d3b7da7b9cecb95b920ff3e52 + languageName: node + linkType: hard + "imurmurhash@npm:^0.1.4": version: 0.1.4 resolution: "imurmurhash@npm:0.1.4" @@ -6157,6 +6467,13 @@ fsevents@~2.1.1: languageName: node linkType: hard +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: f88d3825981486f5a1942414c8d77dd6674dd71c065adcfa46f578d677edcb99fda25af42675cb59db492fdf427b34a5abfcde3982da11a8fd83a500b41cfe77 + languageName: node + linkType: hard + "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -6231,6 +6548,15 @@ fsevents@~2.1.1: languageName: node linkType: hard +"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": + version: 2.2.1 + resolution: "is-docker@npm:2.2.1" + bin: + is-docker: cli.js + checksum: 3fef7ddbf0be25958e8991ad941901bf5922ab2753c46980b60b05c1bf9c9c2402d35e6dc32e4380b980ef5e1970a5d9d5e5aa2e02d77727c3b6b5e918474c56 + languageName: node + linkType: hard + "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -6365,6 +6691,15 @@ fsevents@~2.1.1: languageName: node linkType: hard +"is-wsl@npm:^2.2.0": + version: 2.2.0 + resolution: "is-wsl@npm:2.2.0" + dependencies: + is-docker: ^2.0.0 + checksum: 20849846ae414997d290b75e16868e5261e86ff5047f104027026fd61d8b5a9b0b3ade16239f35e1a067b3c7cc02f70183cb661010ed16f4b6c7c93dad1b19d8 + languageName: node + linkType: hard + "isarray@npm:^1.0.0, isarray@npm:~1.0.0": version: 1.0.0 resolution: "isarray@npm:1.0.0" @@ -6785,6 +7120,13 @@ fsevents@~2.1.1: languageName: node linkType: hard +"kleur@npm:^3.0.3": + version: 3.0.3 + resolution: "kleur@npm:3.0.3" + checksum: df82cd1e172f957bae9c536286265a5cdbd5eeca487cb0a3b2a7b41ef959fc61f8e7c0e9aeea9c114ccf2c166b6a8dd45a46fd619c1c569d210ecd2765ad5169 + languageName: node + linkType: hard + "level-supports@npm:^4.0.0": version: 4.0.1 resolution: "level-supports@npm:4.0.1" @@ -7005,6 +7347,13 @@ fsevents@~2.1.1: languageName: node linkType: hard +"match-all@npm:^1.2.6": + version: 1.2.6 + resolution: "match-all@npm:1.2.6" + checksum: 3d4f16b8fd082f2fd10e362f4a8b71c62f8a767591b3db831ca2bdcf726337e9a64e4abc30e2ef053dc2bcfb875a9ed80bd78e006ad5ef11380a7158d0cb00e1 + languageName: node + linkType: hard + "mcl-wasm@npm:^0.7.1": version: 0.7.9 resolution: "mcl-wasm@npm:0.7.9" @@ -7023,6 +7372,13 @@ fsevents@~2.1.1: languageName: node linkType: hard +"media-typer@npm:0.3.0": + version: 0.3.0 + resolution: "media-typer@npm:0.3.0" + checksum: af1b38516c28ec95d6b0826f6c8f276c58aec391f76be42aa07646b4e39d317723e869700933ca6995b056db4b09a78c92d5440dc23657e6764be5d28874bba1 + languageName: node + linkType: hard + "memory-level@npm:^1.0.0": version: 1.0.0 resolution: "memory-level@npm:1.0.0" @@ -7041,6 +7397,13 @@ fsevents@~2.1.1: languageName: node linkType: hard +"merge-descriptors@npm:1.0.1": + version: 1.0.1 + resolution: "merge-descriptors@npm:1.0.1" + checksum: 5abc259d2ae25bb06d19ce2b94a21632583c74e2a9109ee1ba7fd147aa7362b380d971e0251069f8b3eb7d48c21ac839e21fa177b335e82c76ec172e30c31a26 + languageName: node + linkType: hard + "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -7055,6 +7418,13 @@ fsevents@~2.1.1: languageName: node linkType: hard +"methods@npm:~1.1.2": + version: 1.1.2 + resolution: "methods@npm:1.1.2" + checksum: 0917ff4041fa8e2f2fda5425a955fe16ca411591fbd123c0d722fcf02b73971ed6f764d85f0a6f547ce49ee0221ce2c19a5fa692157931cecb422984f1dcd13a + languageName: node + linkType: hard + "micromatch@npm:^4.0.4": version: 4.0.5 resolution: "micromatch@npm:4.0.5" @@ -7072,7 +7442,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"mime-types@npm:^2.1.12, mime-types@npm:~2.1.19": +"mime-types@npm:^2.1.12, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -7081,6 +7451,15 @@ fsevents@~2.1.1: languageName: node linkType: hard +"mime@npm:1.6.0": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: fef25e39263e6d207580bdc629f8872a3f9772c923c7f8c7e793175cee22777bbe8bba95e5d509a40aaa292d8974514ce634ae35769faa45f22d17edda5e8557 + languageName: node + linkType: hard + "mimic-fn@npm:^1.0.0": version: 1.2.0 resolution: "mimic-fn@npm:1.2.0" @@ -7409,6 +7788,17 @@ fsevents@~2.1.1: languageName: node linkType: hard +"murmur-128@npm:^0.2.1": + version: 0.2.1 + resolution: "murmur-128@npm:0.2.1" + dependencies: + encode-utf8: ^1.0.2 + fmix: ^0.1.0 + imul: ^1.0.0 + checksum: 94ff8b39bf1a1a7bde83b6d13f656bbe591e0a5b5ffe4384c39470120ab70e9eadf0af38557742a30d24421ddc63aea6bba1028a1d6b66553038ee86a660dd92 + languageName: node + linkType: hard + "mute-stream@npm:0.0.7": version: 0.0.7 resolution: "mute-stream@npm:0.0.7" @@ -7439,7 +7829,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"negotiator@npm:^0.6.3": +"negotiator@npm:0.6.3, negotiator@npm:^0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" checksum: b8ffeb1e262eff7968fc90a2b6767b04cfd9842582a9d0ece0af7049537266e7b2506dfb1d107a32f06dd849ab2aea834d5830f7f4d0e5cb7d36e1ae55d021d9 @@ -7708,6 +8098,15 @@ fsevents@~2.1.1: languageName: node linkType: hard +"on-finished@npm:2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: 1.1.1 + checksum: d20929a25e7f0bb62f937a425b5edeb4e4cde0540d77ba146ec9357f00b0d497cdb3b9b05b9c8e46222407d1548d08166bff69cc56dfa55ba0e4469228920ff0 + languageName: node + linkType: hard + "once@npm:1.x, once@npm:^1.3.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -7726,6 +8125,17 @@ fsevents@~2.1.1: languageName: node linkType: hard +"open@npm:^8.4.0": + version: 8.4.2 + resolution: "open@npm:8.4.2" + dependencies: + define-lazy-prop: ^2.0.0 + is-docker: ^2.1.1 + is-wsl: ^2.2.0 + checksum: 6388bfff21b40cb9bd8f913f9130d107f2ed4724ea81a8fd29798ee322b361ca31fa2cdfb491a5c31e43a3996cfe9566741238c7a741ada8d7af1cb78d85cf26 + languageName: node + linkType: hard + "optionator@npm:^0.8.1, optionator@npm:^0.8.2": version: 0.8.3 resolution: "optionator@npm:0.8.3" @@ -7880,6 +8290,13 @@ fsevents@~2.1.1: languageName: node linkType: hard +"parseurl@npm:~1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2 + languageName: node + linkType: hard + "path-exists@npm:^3.0.0": version: 3.0.0 resolution: "path-exists@npm:3.0.0" @@ -7929,6 +8346,13 @@ fsevents@~2.1.1: languageName: node linkType: hard +"path-to-regexp@npm:0.1.7": + version: 0.1.7 + resolution: "path-to-regexp@npm:0.1.7" + checksum: 69a14ea24db543e8b0f4353305c5eac6907917031340e5a8b37df688e52accd09e3cebfe1660b70d76b6bd89152f52183f28c74813dbf454ba1a01c82a38abce + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -8122,6 +8546,16 @@ fsevents@~2.1.1: languageName: node linkType: hard +"prompts@npm:^2.4.2": + version: 2.4.2 + resolution: "prompts@npm:2.4.2" + dependencies: + kleur: ^3.0.3 + sisteransi: ^1.0.5 + checksum: d8fd1fe63820be2412c13bfc5d0a01909acc1f0367e32396962e737cb2fc52d004f3302475d5ce7d18a1e8a79985f93ff04ee03007d091029c3f9104bffc007d + languageName: node + linkType: hard + "proper-lockfile@npm:^4.1.1": version: 4.1.2 resolution: "proper-lockfile@npm:4.1.2" @@ -8133,6 +8567,16 @@ fsevents@~2.1.1: languageName: node linkType: hard +"proxy-addr@npm:~2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + checksum: 29c6990ce9364648255454842f06f8c46fcd124d3e6d7c5066df44662de63cdc0bad032e9bf5a3d653ff72141cc7b6019873d685708ac8210c30458ad99f2b74 + languageName: node + linkType: hard + "psl@npm:^1.1.28": version: 1.9.0 resolution: "psl@npm:1.9.0" @@ -8154,7 +8598,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"qs@npm:^6.4.0, qs@npm:^6.7.0": +"qs@npm:6.11.0, qs@npm:^6.4.0, qs@npm:^6.7.0": version: 6.11.0 resolution: "qs@npm:6.11.0" dependencies: @@ -8163,6 +8607,15 @@ fsevents@~2.1.1: languageName: node linkType: hard +"qs@npm:^6.9.4": + version: 6.11.2 + resolution: "qs@npm:6.11.2" + dependencies: + side-channel: ^1.0.4 + checksum: e812f3c590b2262548647d62f1637b6989cc56656dc960b893fe2098d96e1bd633f36576f4cd7564dfbff9db42e17775884db96d846bebe4f37420d073ecdc0b + languageName: node + linkType: hard + "qs@npm:~6.5.2": version: 6.5.3 resolution: "qs@npm:6.5.3" @@ -8186,7 +8639,14 @@ fsevents@~2.1.1: languageName: node linkType: hard -"raw-body@npm:^2.4.1": +"range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 0a268d4fea508661cf5743dfe3d5f47ce214fd6b7dec1de0da4d669dd4ef3d2144468ebe4179049eff253d9d27e719c88dae55be64f954e80135a0cada804ec9 + languageName: node + linkType: hard + +"raw-body@npm:2.5.1, raw-body@npm:^2.4.1": version: 2.5.1 resolution: "raw-body@npm:2.5.1" dependencies: @@ -8398,6 +8858,7 @@ fsevents@~2.1.1: "@openzeppelin/contracts": ~4.7.3 "@openzeppelin/contracts-upgradeable": ~4.7.3 "@openzeppelin/hardhat-upgrades": ^1.23.0 + "@tenderly/hardhat-tenderly": ^1.7.7 "@typechain/ethers-v5": ^7.2.0 "@typechain/hardhat": ^2.3.1 "@types/chai": ^4.3.0 @@ -8649,7 +9110,7 @@ resolve@1.17.0: languageName: node linkType: hard -"safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 @@ -8749,6 +9210,27 @@ resolve@1.17.0: languageName: node linkType: hard +"send@npm:0.18.0": + version: 0.18.0 + resolution: "send@npm:0.18.0" + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: ~1.0.2 + escape-html: ~1.0.3 + etag: ~1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: ~1.2.1 + statuses: 2.0.1 + checksum: 74fc07ebb58566b87b078ec63e5a3e41ecd987e4272ba67b7467e86c6ad51bc6b0b0154133b6d8b08a2ddda360464f71382f7ef864700f34844a76c8027817a8 + languageName: node + linkType: hard + "serialize-javascript@npm:6.0.0": version: 6.0.0 resolution: "serialize-javascript@npm:6.0.0" @@ -8758,6 +9240,18 @@ resolve@1.17.0: languageName: node linkType: hard +"serve-static@npm:1.15.0": + version: 1.15.0 + resolution: "serve-static@npm:1.15.0" + dependencies: + encodeurl: ~1.0.2 + escape-html: ~1.0.3 + parseurl: ~1.3.3 + send: 0.18.0 + checksum: af57fc13be40d90a12562e98c0b7855cf6e8bd4c107fe9a45c212bf023058d54a1871b1c89511c3958f70626fff47faeb795f5d83f8cf88514dbaeb2b724464d + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -8871,6 +9365,13 @@ resolve@1.17.0: languageName: node linkType: hard +"sisteransi@npm:^1.0.5": + version: 1.0.5 + resolution: "sisteransi@npm:1.0.5" + checksum: aba6438f46d2bfcef94cf112c835ab395172c75f67453fe05c340c770d3c402363018ae1ab4172a1026a90c47eaccf3af7b6ff6fa749a680c2929bd7fa2b37a4 + languageName: node + linkType: hard + "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" @@ -9375,6 +9876,31 @@ resolve@1.17.0: languageName: node linkType: hard +"tenderly@npm:^0.5.3": + version: 0.5.3 + resolution: "tenderly@npm:0.5.3" + dependencies: + axios: ^0.27.2 + cli-table3: ^0.6.2 + commander: ^9.4.0 + express: ^4.18.1 + hyperlinker: ^1.0.0 + js-yaml: ^4.1.0 + open: ^8.4.0 + prompts: ^2.4.2 + tslog: ^4.4.0 + peerDependencies: + ts-node: "*" + typescript: "*" + peerDependenciesMeta: + ts-node: + optional: true + typescript: + optional: true + checksum: 167f132d20acb12b841ac29f0e0cdd428e1329fed6c37beaf1e2d05a0ff48ab860b40a734d581d595b053c5bdb80622f3c37409fccba6148af63092a1eb67359 + languageName: node + linkType: hard + "test-exclude@npm:^6.0.0": version: 6.0.0 resolution: "test-exclude@npm:6.0.0" @@ -9587,6 +10113,13 @@ resolve@1.17.0: languageName: node linkType: hard +"tslog@npm:^4.3.1, tslog@npm:^4.4.0": + version: 4.8.2 + resolution: "tslog@npm:4.8.2" + checksum: 04d2c2c68be53578b090ff1729b08ee23f3b5c8c509d6ffa332c43d25bcb9599ede8cce1db2c9dcb29ea3647b21c47c1ba11180ee9652a03c901702199dc87cd + languageName: node + linkType: hard + "tsort@npm:0.0.1": version: 0.0.1 resolution: "tsort@npm:0.0.1" @@ -9681,6 +10214,16 @@ resolve@1.17.0: languageName: node linkType: hard +"type-is@npm:~1.6.18": + version: 1.6.18 + resolution: "type-is@npm:1.6.18" + dependencies: + media-typer: 0.3.0 + mime-types: ~2.1.24 + checksum: 2c8e47675d55f8b4e404bcf529abdf5036c537a04c2b20177bcf78c9e3c1da69da3942b1346e6edb09e823228c0ee656ef0e033765ec39a70d496ef601a0c657 + languageName: node + linkType: hard + "typechain@npm:^5.2.0": version: 5.2.0 resolution: "typechain@npm:5.2.0" @@ -9813,7 +10356,7 @@ typescript@^4.4.2: languageName: node linkType: hard -"unpipe@npm:1.0.0": +"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0" checksum: 4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2 @@ -9857,6 +10400,13 @@ typescript@^4.4.2: languageName: node linkType: hard +"utils-merge@npm:1.0.1": + version: 1.0.1 + resolution: "utils-merge@npm:1.0.1" + checksum: c81095493225ecfc28add49c106ca4f09cdf56bc66731aa8dabc2edbbccb1e1bfe2de6a115e5c6a380d3ea166d1636410b62ef216bb07b3feb1cfde1d95d5080 + languageName: node + linkType: hard + "uuid@npm:2.0.1": version: 2.0.1 resolution: "uuid@npm:2.0.1" @@ -9896,6 +10446,13 @@ typescript@^4.4.2: languageName: node linkType: hard +"vary@npm:~1.1.2": + version: 1.1.2 + resolution: "vary@npm:1.1.2" + checksum: ae0123222c6df65b437669d63dfa8c36cee20a504101b2fcd97b8bf76f91259c17f9f2b4d70a1e3c6bbcee7f51b28392833adb6b2770b23b01abec84e369660b + languageName: node + linkType: hard + "verror@npm:1.10.0": version: 1.10.0 resolution: "verror@npm:1.10.0" @@ -10264,3 +10821,12 @@ typescript@^4.4.2: checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 languageName: node linkType: hard + +"zksync-web3@npm:^0.14.3": + version: 0.14.4 + resolution: "zksync-web3@npm:0.14.4" + peerDependencies: + ethers: ^5.7.0 + checksum: f702a3437f48a8d42c4bb35b8dd13671a168aadfc4e23ce723d62959220ccb6bf9c529c60331fe5b91afaa622147c6a37490551474fe3e35c06ac476524b5160 + languageName: node + linkType: hard From 498819c99928ad74c87ab56087126086c98efdf6 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 3 Jul 2023 17:32:22 -0400 Subject: [PATCH 333/462] 3.0.0 rc4 (#857) Co-authored-by: Julian R <56316686+julianmrodri@users.noreply.github.com> --- CHANGELOG.md | 48 ++- common/configuration.ts | 4 +- contracts/facade/FacadeAct.sol | 236 ++++++------ contracts/facade/FacadeRead.sol | 94 ++++- contracts/facade/FacadeTest.sol | 50 ++- contracts/interfaces/IBroker.sol | 14 +- contracts/interfaces/IDeployer.sol | 4 + contracts/interfaces/IDistributor.sol | 3 +- contracts/interfaces/IFacadeAct.sol | 44 ++- contracts/interfaces/IFacadeRead.sol | 17 +- contracts/interfaces/IRToken.sol | 2 +- contracts/interfaces/IRevenueTrader.sol | 20 +- contracts/libraries/NetworkConfigLib.sol | 26 ++ contracts/libraries/Throttle.sol | 10 +- contracts/libraries/test/FixedCallerMock.sol | 3 + contracts/mixins/Auth.sol | 4 +- contracts/p0/AssetRegistry.sol | 13 +- contracts/p0/BackingManager.sol | 10 +- contracts/p0/BasketHandler.sol | 35 +- contracts/p0/Broker.sol | 17 +- contracts/p0/Deployer.sol | 5 + contracts/p0/Distributor.sol | 8 + contracts/p0/Furnace.sol | 3 +- contracts/p0/Main.sol | 3 +- contracts/p0/RToken.sol | 27 +- contracts/p0/RevenueTrader.sol | 109 +++--- contracts/p0/StRSR.sol | 15 +- contracts/p0/mixins/Trading.sol | 8 +- contracts/p0/mixins/TradingLib.sol | 145 ++++---- contracts/p1/AssetRegistry.sol | 11 +- contracts/p1/BackingManager.sol | 10 +- contracts/p1/BasketHandler.sol | 36 +- contracts/p1/Broker.sol | 17 +- contracts/p1/Deployer.sol | 6 + contracts/p1/Distributor.sol | 29 +- contracts/p1/Furnace.sol | 3 +- contracts/p1/Main.sol | 4 +- contracts/p1/RToken.sol | 29 +- contracts/p1/RevenueTrader.sol | 165 +++++---- contracts/p1/StRSR.sol | 14 +- .../p1/mixins/RecollateralizationLib.sol | 92 ++--- contracts/p1/mixins/TradeLib.sol | 75 ++-- contracts/p1/mixins/Trading.sol | 9 +- contracts/plugins/assets/Asset.sol | 19 +- contracts/plugins/mocks/InvalidBrokerMock.sol | 12 +- .../plugins/mocks/InvalidRevTraderP1Mock.sol | 2 +- .../plugins/mocks/RevenueTraderBackComp.sol | 6 +- contracts/plugins/trading/DutchTrade.sol | 42 ++- contracts/plugins/trading/GnosisTrade.sol | 2 +- docs/mev.md | 57 ++- docs/recollateralization.md | 4 +- docs/solidity-style.md | 2 +- docs/system-design.md | 2 +- .../mainnet-3.0.0/1-tmp-deployments.json | 2 +- .../deploy_convex_rToken_metapool_plugin.ts | 2 +- .../deploy_convex_stable_plugin.ts | 2 +- .../deploy_convex_volatile_plugin.ts | 2 +- .../deploy_curve_rToken_metapool_plugin.ts | 2 +- .../collaterals/deploy_curve_stable_plugin.ts | 2 +- .../deploy_curve_volatile_plugin.ts | 2 +- scripts/verify_etherscan.ts | 2 +- .../testing/upgrade-checker-utils/rewards.ts | 4 +- test/Broker.test.ts | 147 +++++--- test/Deployer.test.ts | 13 + test/Facade.test.ts | 234 +++++++++--- test/Furnace.test.ts | 4 +- test/Main.test.ts | 88 ++--- test/RToken.test.ts | 64 +++- test/Recollateralization.test.ts | 48 ++- test/Revenues.test.ts | 337 ++++++++++++++---- test/ZZStRSR.test.ts | 122 ++++--- .../Recollateralization.test.ts.snap | 12 +- test/__snapshots__/Revenues.test.ts.snap | 22 +- test/integration/EasyAuction.test.ts | 150 +++----- test/integration/fork-block-numbers.ts | 1 + .../mainnet-test/FacadeActVersion.test.ts | 156 ++++++++ .../mainnet-test/StaticATokens.test.ts | 4 +- test/libraries/Fixed.test.ts | 39 +- test/plugins/Asset.test.ts | 32 +- .../individual-collateral/collateralTests.ts | 2 + .../curve/collateralTests.ts | 2 +- .../curve/crv/CrvStableMetapoolSuite.test.ts | 2 +- .../CrvStableRTokenMetapoolTestSuite.test.ts | 4 +- .../curve/crv/CrvStableTestSuite.test.ts | 4 +- .../curve/crv/CrvVolatileTestSuite.test.ts | 4 +- .../curve/cvx/CvxStableMetapoolSuite.test.ts | 2 +- .../CvxStableRTokenMetapoolTestSuite.test.ts | 4 +- .../curve/cvx/CvxStableTestSuite.test.ts | 4 +- .../curve/cvx/CvxVolatileTestSuite.test.ts | 4 +- .../curve/pluginTestTypes.ts | 2 +- test/scenario/BadERC20.test.ts | 4 +- test/scenario/ComplexBasket.test.ts | 8 +- test/scenario/EURT.test.ts | 12 +- test/scenario/RevenueHiding.test.ts | 4 +- test/scenario/WBTC.test.ts | 12 +- test/scenario/WETH.test.ts | 12 +- test/scenario/cETH.test.ts | 14 +- test/scenario/cWBTC.test.ts | 10 +- 98 files changed, 2179 insertions(+), 1049 deletions(-) create mode 100644 contracts/libraries/NetworkConfigLib.sol create mode 100644 test/integration/mainnet-test/FacadeActVersion.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c8b49994a4..163413e8af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,32 @@ # Changelog -## 3.0.0 - Unreleased +# 3.0.0 - Unreleased -Warning: RTokens upgrading to this major release should proceed carefully. In addition to updating all component addresses: +### Upgrade Steps -- `cacheComponents()` MUST be called to prevent certain functions from reverting, on both the BackingManager and both RevenueTraders -- `setWarmupPeriod()` can be called on the BasketHandler to turn on the warmup period, optionally -- `setWithdrawalLeak()` can be called on StRSR to start saving gas on withdrawals, optionally -- `setDutchAuctionLength()` can be called (with a value such as 1800) or left at duration 0s to keep dutch auctions disabled +#### Required Steps -Collateral / Asset plugins from 2.1.0 do not need to be upgraded +Update _all_ component contracts, including Main. -#### Core Protocol Contracts +Call the following functions: + +- `BackingManager.cacheComponents()` +- `RevenueTrader.cacheComponents()` (for both rsrTrader and rTokenTrader) +- `Distributor.cacheComponents()` + +Collateral / Asset plugins from 2.1.0 do not need to be upgraded with the exception of Compound V2 cToken collateral ([CTokenFiatCollateral.sol](contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol)), which needs to be swapped in via `AssetRegistry.swapRegistered()`. Skipping this step will result in COMP rewards becoming unclaimable. Note that this will change the ERC20 for the collateral plugin, causing the protocol to trade out of the old ERC20. Since COMP rewards are claimed on every transfer, COMP does not need to be claimed beforehand. + +#### Optional Steps + +Call the following functions, once it is desired to turn on the new features: + +- `BaasketHandler.setWarmupPeriod()` +- `StRSR.setWithdrawalLeak()` +- `Broker.setDutchAuctionLength()` + +### Core Protocol Contracts + +Bump solidity version to 0.8.19 Bump solidity version to 0.8.19 @@ -119,7 +134,7 @@ Bump solidity version to 0.8.19 - `StRSRVotes` [+0 slots] - Add `stakeAndDelegate(uint256 rsrAmount, address delegate)` function, to encourage people to receive voting weight upon staking -#### Facades +### Facades - `FacadeWrite` Summary: More expressive and fine-grained control over the set of pausers and freezers @@ -147,9 +162,9 @@ Bump solidity version to 0.8.19 - Remove `FacadeMonitor` - redundant with `nextRecollateralizationAuction()` and `revenueOverview()` -### Plugins +## Plugins -#### DutchTrade +### DutchTrade A cheaper, simpler, trading method. Intended to be the new dominant trading method, with GnosisTrade (batch auctions) available as a faster-but-more-gas-expensive backup option. @@ -159,13 +174,14 @@ Over the last 60% of the auction, the price falls linearly from the best-case pr Duration: 30 min (default) -#### Assets and Collateral +### Assets and Collateral +- Bugfix: `lotPrice()` now begins at 100% the lastSavedPrice, instead of below 100%. It can be at 100% for up to the oracleTimeout in the worst-case. - Add `version() return (string)` getter to pave way for separation of asset versioning and core protocol versioning - Update `claimRewards()` on all assets to 3.0.0-style, without `delegatecall` - Add `lastSave()` to `RTokenAsset` -## 2.1.0 +# 2.1.0 ### Core protocol contracts @@ -221,7 +237,7 @@ Across all collateral, `tryPrice()` was updated to exclude revenueHiding conside - Add `docs/plugin-addresses.md` as well as accompanying script for generation at `scripts/collateral-params.ts` - Add `docs/exhaustive-tests.md` to document running exhaustive tests on GCP -## 2.0.0 +# 2.0.0 Candidate release for the "all clear" milestone. There wasn't any real usage of the 1.0.0/1.1.0 releases; this is the first release that we are going to spend real effort to remain backwards compatible with. @@ -287,7 +303,7 @@ Candidate release for the "all clear" milestone. There wasn't any real usage of - Add `FacadeRead.redeem(IRToken rToken, uint256 amount, uint48 basketNonce)` to return the expected redemption quantities on the basketNonce, or revert - Integrate with OZ 4.7.3 Governance (changes to `quorum()`/t`proposalThreshold()`) -## 1.1.0 +# 1.1.0 - Introduce semantic versioning to the Deployer and RToken - `RTokenCreated` event: added `version` argument @@ -319,7 +335,7 @@ event RTokenCreated( [d757d3a5a6097ae42c71fc03a7c787ec001d2efc](https://github.com/reserve-protocol/protocol/commit/d757d3a5a6097ae42c71fc03a7c787ec001d2efc) -## 1.0.0 +# 1.0.0 (This release is the one from the canonical lauch onstage in Bogota. We were missing semantic versioning at the time, but we call this the 1.0.0 release retroactively.) diff --git a/common/configuration.ts b/common/configuration.ts index 6f14eff79a..4084935193 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -325,7 +325,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { EUR: '0x12336777de46b9a6Edd7176E532810149C787bcD', rETH: '0xeb1cDb6C2F18173eaC53fEd1DC03fe13286f86ec', stETHUSD: '0x6dCCE86FFb3c1FC44Ded9a6E200eF12d0D4256a3', - stETHETH: '0x81ff01E93F86f41d3DFf66283Be2aD0a3C284604' + stETHETH: '0x81ff01E93F86f41d3DFf66283Be2aD0a3C284604', }, AAVE_LENDING_POOL: '0x3e9E33B84C1cD9037be16AA45A0B296ae5F185AD', // mock GNOSIS_EASY_AUCTION: '0x1fbab40c338e2e7243da945820ba680c92ef8281', // canonical @@ -442,7 +442,7 @@ export interface IGovRoles { export const MAX_TRADE_SLIPPAGE = BigNumber.from(10).pow(18) export const MAX_BACKING_BUFFER = BigNumber.from(10).pow(18) export const MAX_TARGET_AMT = BigNumber.from(10).pow(21) -export const MAX_RATIO = BigNumber.from(10).pow(18) +export const MAX_RATIO = BigNumber.from(10).pow(14) export const MAX_TRADE_VOLUME = BigNumber.from(10).pow(48) export const MAX_MIN_TRADE_VOLUME = BigNumber.from(10).pow(29) export const MIN_THROTTLE_AMT_RATE = BigNumber.from(10).pow(18) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 283d9cb385..7c6d952a8c 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/utils/Multicall.sol"; import "../plugins/trading/DutchTrade.sol"; import "../interfaces/IBackingManager.sol"; @@ -14,6 +15,7 @@ import "../interfaces/IFacadeRead.sol"; * For use with ^3.0.0 RTokens. */ contract FacadeAct is IFacadeAct, Multicall { + using Address for address; using SafeERC20 for IERC20; using FixLib for uint192; @@ -31,65 +33,49 @@ contract FacadeAct is IFacadeAct, Multicall { /// Logic: /// For each ERC20 in `toSettle`: /// - Settle any open ERC20 trades - /// For each ERC20 in `toStart`: + /// Then: /// - Transfer any revenue for that ERC20 from the backingManager to revenueTrader - /// - Call `revenueTrader.manageToken(ERC20)` to start an auction + /// - Call `revenueTrader.manageTokens(ERC20)` to start an auction function runRevenueAuctions( IRevenueTrader revenueTrader, - IERC20[] memory toSettle, - IERC20[] memory toStart, - TradeKind kind + IERC20[] calldata toSettle, + IERC20[] calldata toStart, + TradeKind[] calldata kinds ) external { // Settle auctions for (uint256 i = 0; i < toSettle.length; ++i) { - revenueTrader.settleTrade(toSettle[i]); + _settleTrade(revenueTrader, toSettle[i]); } - // Transfer revenue backingManager -> revenueTrader - { - IBackingManager bm = revenueTrader.main().backingManager(); - bytes1 majorVersion = bytes(bm.version())[0]; - - if (majorVersion == MAJOR_VERSION_3) { - // solhint-disable-next-line no-empty-blocks - try bm.forwardRevenue(toStart) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = address(bm).call{ value: 0 }( - abi.encodeWithSignature("manageTokens(address[])", toStart) - ); - success = success; // hush warning - } else { - revertUnrecognizedVersion(); - } + // if 2.1.0, distribute tokenToBuy + bytes1 majorVersion = bytes(revenueTrader.version())[0]; + if ( + toSettle.length > 0 && + (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) + ) { + address(revenueTrader).functionCall( + abi.encodeWithSignature("manageToken(address)", revenueTrader.tokenToBuy()) + ); } - // Start auctions - for (uint256 i = 0; i < toStart.length; ++i) { - bytes1 majorVersion = bytes(revenueTrader.version())[0]; + // Transfer revenue backingManager -> revenueTrader + _forwardRevenue(revenueTrader.main().backingManager(), toStart); - if (majorVersion == MAJOR_VERSION_3) { - // solhint-disable-next-line no-empty-blocks - try revenueTrader.manageToken(toStart[i], kind) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = address(revenueTrader).call{ value: 0 }( - abi.encodeWithSignature("manageToken(address)", toStart[i]) - ); - success = success; // hush warning - } else { - revertUnrecognizedVersion(); - } - } + // Start RevenueTrader auctions + _runRevenueAuctions(revenueTrader, toStart, kinds); } // === Static Calls === /// To use this, call via callStatic. + /// Includes consideration of when to distribute the RevenueTrader tokenToBuy /// @return erc20s The ERC20s that have auctions that can be started /// @return canStart If the ERC20 auction can be started - /// @return surpluses {qTok} The surplus amount + /// @return surpluses {qTok} The surplus amounts currently held, ignoring reward balances /// @return minTradeAmounts {qTok} The minimum amount worth trading + /// @return bmRewards {qTok} The amounts would be claimed by backingManager.claimRewards() + /// @return revTraderRewards {qTok} The amounts that would be claimed by trader.claimRewards() + /// @dev Note that `surpluses` + `bmRewards` + `revTraderRewards` /// @custom:static-call function revenueOverview(IRevenueTrader revenueTrader) external @@ -97,78 +83,72 @@ contract FacadeAct is IFacadeAct, Multicall { IERC20[] memory erc20s, bool[] memory canStart, uint256[] memory surpluses, - uint256[] memory minTradeAmounts + uint256[] memory minTradeAmounts, + uint256[] memory bmRewards, + uint256[] memory revTraderRewards ) { + IBackingManager bm = revenueTrader.main().backingManager(); uint192 minTradeVolume = revenueTrader.minTradeVolume(); // {UoA} Registry memory reg = revenueTrader.main().assetRegistry().getRegistry(); // Forward ALL revenue - { - IBackingManager bm = revenueTrader.main().backingManager(); - bytes1 majorVersion = bytes(bm.version())[0]; - - if (majorVersion == MAJOR_VERSION_3) { - // solhint-disable-next-line no-empty-blocks - try bm.forwardRevenue(reg.erc20s) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = address(bm).call{ value: 0 }( - abi.encodeWithSignature("manageTokens(address[])", reg.erc20s) - ); - success = success; // hush warning - } else { - revertUnrecognizedVersion(); - } - } + _forwardRevenue(bm, reg.erc20s); erc20s = new IERC20[](reg.erc20s.length); canStart = new bool[](reg.erc20s.length); surpluses = new uint256[](reg.erc20s.length); minTradeAmounts = new uint256[](reg.erc20s.length); - // Calculate which erc20s can have auctions started + bmRewards = new uint256[](reg.erc20s.length); + revTraderRewards = new uint256[](reg.erc20s.length); + + // Calculate which erc20s should have auctions started for (uint256 i = 0; i < reg.erc20s.length; ++i) { + erc20s[i] = reg.erc20s[i]; + // Settle first if possible. Required so we can assess full available balance - ITrade trade = revenueTrader.trades(reg.erc20s[i]); + ITrade trade = revenueTrader.trades(erc20s[i]); if (address(trade) != address(0) && trade.canSettle()) { - revenueTrader.settleTrade(reg.erc20s[i]); + _settleTrade(revenueTrader, erc20s[i]); } - uint48 tradesOpen = revenueTrader.tradesOpen(); - erc20s[i] = reg.erc20s[i]; - surpluses[i] = reg.erc20s[i].balanceOf(address(revenueTrader)); - + surpluses[i] = erc20s[i].balanceOf(address(revenueTrader)); (uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/tok} if (lotLow == 0) continue; // {qTok} = {UoA} / {UoA/tok} - minTradeAmounts[i] = minTradeVolume.div(lotLow).shiftl_toUint( + minTradeAmounts[i] = minTradeVolume.safeDiv(lotLow, FLOOR).shiftl_toUint( int8(reg.assets[i].erc20Decimals()) ); - bytes1 majorVersion = bytes(revenueTrader.version())[0]; if ( - reg.erc20s[i].balanceOf(address(revenueTrader)) > minTradeAmounts[i] && - revenueTrader.trades(reg.erc20s[i]) == ITrade(address(0)) + surpluses[i] > minTradeAmounts[i] && + revenueTrader.trades(erc20s[i]) == ITrade(address(0)) ) { - if (majorVersion == MAJOR_VERSION_3) { - // solhint-disable-next-line no-empty-blocks - try revenueTrader.manageToken(erc20s[i], TradeKind.DUTCH_AUCTION) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = address(revenueTrader).call{ value: 0 }( - abi.encodeWithSignature("manageToken(address)", erc20s[i]) - ); - success = success; // hush warning - } else { - revertUnrecognizedVersion(); - } - - if (revenueTrader.tradesOpen() - tradesOpen > 0) { - canStart[i] = true; - } + canStart[i] = true; } } + + // Calculate rewards + // Reward counts are disjoint with `surpluses` and `canStart` + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + bmRewards[i] = reg.erc20s[i].balanceOf(address(bm)); + } + // solhint-disable-next-line no-empty-blocks + try bm.claimRewards() {} catch {} // same between 2.1.0 and 3.0.0 + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + bmRewards[i] = reg.erc20s[i].balanceOf(address(bm)) - bmRewards[i]; + } + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + revTraderRewards[i] = reg.erc20s[i].balanceOf(address(revenueTrader)); + } + // solhint-disable-next-line no-empty-blocks + try revenueTrader.claimRewards() {} catch {} // same between 2.1.0 and 3.0.0 + for (uint256 i = 0; i < reg.erc20s.length; ++i) { + revTraderRewards[i] = + reg.erc20s[i].balanceOf(address(revenueTrader)) - + revTraderRewards[i]; + } } /// To use this, call via callStatic. @@ -195,29 +175,15 @@ contract FacadeAct is IFacadeAct, Multicall { for (uint256 i = 0; i < erc20s.length; ++i) { ITrade trade = bm.trades(erc20s[i]); if (address(trade) != address(0) && trade.canSettle()) { - bm.settleTrade(erc20s[i]); + _settleTrade(bm, erc20s[i]); break; // backingManager can only have 1 trade open at a time } } } - // If no auctions ongoing, try to find a new auction to start + // If no auctions ongoing, to find a new auction to start if (bm.tradesOpen() == 0) { - bytes1 majorVersion = bytes(bm.version())[0]; - - if (majorVersion == MAJOR_VERSION_3) { - // solhint-disable-next-line no-empty-blocks - try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch {} - } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { - IERC20[] memory emptyERC20s = new IERC20[](0); - // solhint-disable-next-line avoid-low-level-calls - (bool success, ) = address(bm).call{ value: 0 }( - abi.encodeWithSignature("manageTokens(address[])", emptyERC20s) - ); - success = success; // hush warning - } else { - revertUnrecognizedVersion(); - } + _rebalance(bm); // Find the started auction for (uint256 i = 0; i < erc20s.length; ++i) { @@ -234,7 +200,71 @@ contract FacadeAct is IFacadeAct, Multicall { // === Private === - function revertUnrecognizedVersion() private pure { + function _settleTrade(ITrading trader, IERC20 toSettle) private { + bytes1 majorVersion = bytes(trader.version())[0]; + if (majorVersion == MAJOR_VERSION_3) { + // Settle auctions + trader.settleTrade(toSettle); + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + address(trader).functionCall(abi.encodeWithSignature("settleTrade(address)", toSettle)); + } else { + _revertUnrecognizedVersion(); + } + } + + function _forwardRevenue(IBackingManager bm, IERC20[] memory toStart) private { + bytes1 majorVersion = bytes(bm.version())[0]; + // Need to use try-catch here in order to still show revenueOverview when basket not ready + if (majorVersion == MAJOR_VERSION_3) { + // solhint-disable-next-line no-empty-blocks + try bm.forwardRevenue(toStart) {} catch {} + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = address(bm).call{ value: 0 }( + abi.encodeWithSignature("manageTokens(address[])", toStart) + ); + success = success; // hush warning + } else { + _revertUnrecognizedVersion(); + } + } + + function _runRevenueAuctions( + IRevenueTrader revenueTrader, + IERC20[] memory toStart, + TradeKind[] memory kinds + ) private { + bytes1 majorVersion = bytes(revenueTrader.version())[0]; + + if (majorVersion == MAJOR_VERSION_3) { + revenueTrader.manageTokens(toStart, kinds); + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + for (uint256 i = 0; i < toStart.length; ++i) { + address(revenueTrader).functionCall( + abi.encodeWithSignature("manageToken(address)", toStart[i]) + ); + } + } else { + _revertUnrecognizedVersion(); + } + } + + function _rebalance(IBackingManager bm) private { + bytes1 majorVersion = bytes(bm.version())[0]; + + if (majorVersion == MAJOR_VERSION_3) { + bm.rebalance(TradeKind.DUTCH_AUCTION); + } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { + IERC20[] memory emptyERC20s = new IERC20[](0); + address(bm).functionCall( + abi.encodeWithSignature("manageTokens(address[])", emptyERC20s) + ); + } else { + _revertUnrecognizedVersion(); + } + } + + function _revertUnrecognizedVersion() private pure { revert("unrecognized version"); } } diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 29f076e408..4896b2d756 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -27,9 +27,14 @@ contract FacadeRead is IFacadeRead { /// @custom:static-call function maxIssuable(IRToken rToken, address account) external returns (uint256) { IMain main = rToken.main(); - main.poke(); - // {BU} + require(!main.frozen(), "frozen"); + + // Poke Main + main.assetRegistry().refresh(); + main.furnace().melt(); + + // {BU} BasketRange memory basketsHeld = main.basketHandler().basketsHeldBy(account); uint192 needed = rToken.basketsNeeded(); @@ -57,11 +62,17 @@ contract FacadeRead is IFacadeRead { ) { IMain main = rToken.main(); - main.poke(); + require(!main.frozen(), "frozen"); + + // Cache components IRToken rTok = rToken; IBasketHandler bh = main.basketHandler(); IAssetRegistry reg = main.assetRegistry(); + // Poke Main + reg.refresh(); + main.furnace().melt(); + // Compute # of baskets to create `amount` qRTok uint192 baskets = (rTok.totalSupply() > 0) // {BU} ? rTok.basketsNeeded().muluDivu(amount, rTok.totalSupply()) // {BU * qRTok / qRTok} @@ -85,42 +96,89 @@ contract FacadeRead is IFacadeRead { } /// @return tokens The erc20s returned for the redemption - /// @return withdrawals The balances necessary to issue `amount` RToken - /// @return isProrata True if the redemption is prorata and not full + /// @return withdrawals The balances the reedemer would receive after a full redemption + /// @return available The amount actually available, for each token + /// @dev If available[i] < withdrawals[i], then RToken.redeem() would revert /// @custom:static-call function redeem(IRToken rToken, uint256 amount) external returns ( address[] memory tokens, uint256[] memory withdrawals, - bool isProrata + uint256[] memory available ) { IMain main = rToken.main(); - main.poke(); + require(!main.frozen(), "frozen"); + + // Cache Components IRToken rTok = rToken; IBasketHandler bh = main.basketHandler(); + + // Poke Main + main.assetRegistry().refresh(); + main.furnace().melt(); + uint256 supply = rTok.totalSupply(); // D18{BU} = D18{BU} * {qRTok} / {qRTok} uint192 basketsRedeemed = rTok.basketsNeeded().muluDivu(amount, supply); - (tokens, withdrawals) = bh.quote(basketsRedeemed, FLOOR); + available = new uint256[](tokens.length); - // Bound each withdrawal by the prorata share, in case we're currently under-collateralized - address backingManager = address(main.backingManager()); - for (uint256 i = 0; i < tokens.length; ++i) { + // Calculate prorata amounts + for (uint256 i = 0; i < tokens.length; i++) { // {qTok} = {qTok} * {qRTok} / {qRTok} - uint256 prorata = mulDiv256( - IERC20Upgradeable(tokens[i]).balanceOf(backingManager), + available[i] = mulDiv256( + IERC20(tokens[i]).balanceOf(address(main.backingManager())), amount, supply ); // FLOOR + } + } - if (prorata < withdrawals[i]) { - withdrawals[i] = prorata; - isProrata = true; - } + /// @return tokens The erc20s returned for the redemption + /// @return withdrawals The balances necessary to issue `amount` RToken + /// @custom:static-call + function redeemCustom( + IRToken rToken, + uint256 amount, + uint48[] memory basketNonces, + uint192[] memory portions + ) external returns (address[] memory tokens, uint256[] memory withdrawals) { + IMain main = rToken.main(); + require(!main.frozen(), "frozen"); + + // Call collective state keepers. + main.poke(); + + uint256 supply = rToken.totalSupply(); + + // === Get basket redemption amounts === + uint256 portionsSum; + for (uint256 i = 0; i < portions.length; ++i) { + portionsSum += portions[i]; + } + require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); + + // D18{BU} = D18{BU} * {qRTok} / {qRTok} + uint192 basketsRedeemed = rToken.basketsNeeded().muluDivu(amount, supply); + (tokens, withdrawals) = main.basketHandler().quoteCustomRedemption( + basketNonces, + portions, + basketsRedeemed + ); + + // ==== Prorate redemption ==== + // Bound each withdrawal by the prorata share, in case currently under-collateralized + for (uint256 i = 0; i < tokens.length; i++) { + // {qTok} = {qTok} * {qRTok} / {qRTok} + uint256 prorata = mulDiv256( + IERC20(tokens[i]).balanceOf(address(main.backingManager())), + amount, + supply + ); // FLOOR + if (prorata < withdrawals[i]) withdrawals[i] = prorata; } } @@ -140,8 +198,6 @@ contract FacadeRead is IFacadeRead { IAssetRegistry assetRegistry = rToken.main().assetRegistry(); IBasketHandler basketHandler = rToken.main().basketHandler(); - // (erc20s, deposits) = issue(rToken, FIX_ONE); - // solhint-disable-next-line no-empty-blocks try rToken.main().furnace().melt() {} catch {} diff --git a/contracts/facade/FacadeTest.sol b/contracts/facade/FacadeTest.sol index 749a5bf422..e0683265fe 100644 --- a/contracts/facade/FacadeTest.sol +++ b/contracts/facade/FacadeTest.sol @@ -53,10 +53,17 @@ contract FacadeTest is IFacadeTest { // solhint-disable no-empty-blocks try main.backingManager().rebalance(TradeKind.BATCH_AUCTION) {} catch {} try main.backingManager().forwardRevenue(erc20s) {} catch {} - for (uint256 i = 0; i < erc20s.length; i++) { - try rsrTrader.manageToken(erc20s[i], TradeKind.BATCH_AUCTION) {} catch {} - try rTokenTrader.manageToken(erc20s[i], TradeKind.BATCH_AUCTION) {} catch {} - } + + // Start exact RSR auctions + (IERC20[] memory rsrERC20s, TradeKind[] memory rsrKinds) = traderERC20s(rsrTrader, erc20s); + try main.rsrTrader().manageTokens(rsrERC20s, rsrKinds) {} catch {} + + // Start exact RToken auctions + (IERC20[] memory rTokenERC20s, TradeKind[] memory rTokenKinds) = traderERC20s( + rTokenTrader, + erc20s + ); + try main.rTokenTrader().manageTokens(rTokenERC20s, rTokenKinds) {} catch {} // solhint-enable no-empty-blocks } @@ -74,8 +81,14 @@ contract FacadeTest is IFacadeTest { /// @custom:static-call function totalAssetValue(IRToken rToken) external returns (uint192 total) { IMain main = rToken.main(); - main.poke(); IAssetRegistry reg = main.assetRegistry(); + + require(!main.frozen(), "frozen"); + + // Poke Main + reg.refresh(); + main.furnace().melt(); + address backingManager = address(main.backingManager()); IERC20 rsr = main.rsr(); @@ -99,4 +112,31 @@ contract FacadeTest is IFacadeTest { BasketRange memory range = rToken.main().basketHandler().basketsHeldBy(account); return range.bottom; } + + // === Private === + + function traderERC20s(IRevenueTrader trader, IERC20[] memory erc20sAll) + private + view + returns (IERC20[] memory erc20s, TradeKind[] memory kinds) + { + uint256 len; + IERC20[] memory traderERC20sAll = new IERC20[](erc20sAll.length); + for (uint256 i = 0; i < erc20sAll.length; ++i) { + if ( + address(trader.trades(erc20sAll[i])) == address(0) && + erc20sAll[i].balanceOf(address(trader)) > 1 + ) { + traderERC20sAll[len] = erc20sAll[i]; + ++len; + } + } + + erc20s = new IERC20[](len); + kinds = new TradeKind[](len); + for (uint256 i = 0; i < len; ++i) { + erc20s[i] = traderERC20sAll[i]; + kinds[i] = TradeKind.BATCH_AUCTION; + } + } } diff --git a/contracts/interfaces/IBroker.sol b/contracts/interfaces/IBroker.sol index c02fab9192..cbb2f9cbdf 100644 --- a/contracts/interfaces/IBroker.sol +++ b/contracts/interfaces/IBroker.sol @@ -11,6 +11,14 @@ enum TradeKind { BATCH_AUCTION } +/// Cache of all (lot) prices for a pair to prevent re-lookup +struct TradePrices { + uint192 sellLow; // {UoA/sellTok} can be 0 + uint192 sellHigh; // {UoA/sellTok} should not be 0 + uint192 buyLow; // {UoA/buyTok} should not be 0 + uint192 buyHigh; // {UoA/buyTok} should not be 0 or FIX_MAX +} + /// The data format that describes a request for trade with the Broker struct TradeRequest { IAsset sell; @@ -45,7 +53,11 @@ interface IBroker is IComponent { /// Request a trade from the broker /// @dev Requires setting an allowance in advance /// @custom:interaction - function openTrade(TradeKind kind, TradeRequest memory req) external returns (ITrade); + function openTrade( + TradeKind kind, + TradeRequest memory req, + TradePrices memory prices + ) external returns (ITrade); /// Only callable by one of the trading contracts the broker deploys function reportViolation() external; diff --git a/contracts/interfaces/IDeployer.sol b/contracts/interfaces/IDeployer.sol index 6eb344aa02..164c60a8c1 100644 --- a/contracts/interfaces/IDeployer.sol +++ b/contracts/interfaces/IDeployer.sol @@ -101,6 +101,10 @@ interface IDeployer is IVersioned { address owner, DeploymentParams calldata params ) external returns (address); + + /// Deploys a new RTokenAsset instance. Not needed during normal deployment flow + /// @param maxTradeVolume {UoA} The maximum trade volume for the RTokenAsset + function deployRTokenAsset(IRToken rToken, uint192 maxTradeVolume) external returns (IAsset); } interface TestIDeployer is IDeployer { diff --git a/contracts/interfaces/IDistributor.sol b/contracts/interfaces/IDistributor.sol index 10606c6cba..e3c7de6ace 100644 --- a/contracts/interfaces/IDistributor.sol +++ b/contracts/interfaces/IDistributor.sol @@ -43,7 +43,8 @@ interface IDistributor is IComponent { function setDistribution(address dest, RevenueShare memory share) external; /// Distribute the `erc20` token across all revenue destinations - /// @custom:interaction + /// Only callable by RevenueTraders + /// @custom:protected function distribute(IERC20 erc20, uint256 amount) external; /// @return revTotals The total of all destinations diff --git a/contracts/interfaces/IFacadeAct.sol b/contracts/interfaces/IFacadeAct.sol index b3c77eea04..61085f0a72 100644 --- a/contracts/interfaces/IFacadeAct.sol +++ b/contracts/interfaces/IFacadeAct.sol @@ -25,16 +25,39 @@ interface IFacadeAct { /// Logic: /// For each ERC20 in `toSettle`: /// - Settle any open ERC20 trades - /// For each ERC20 in `toStart`: + /// Then: /// - Transfer any revenue for that ERC20 from the backingManager to revenueTrader - /// - Call `revenueTrader.manageToken(ERC20)` to start an auction, if possible + /// - Call `revenueTrader.manageTokens(ERC20)` to start an auction function runRevenueAuctions( IRevenueTrader revenueTrader, IERC20[] memory toSettle, IERC20[] memory toStart, - TradeKind kind + TradeKind[] memory kinds ) external; + // === Static Calls === + + /// To use this, call via callStatic. + /// Includes consideration of when to distribute the RevenueTrader tokenToBuy + /// @return erc20s The ERC20s that have auctions that can be started + /// @return canStart If the ERC20 auction can be started + /// @return surpluses {qTok} The surplus amounts currently held, ignoring reward balances + /// @return minTradeAmounts {qTok} The minimum amount worth trading + /// @return bmRewards {qTok} The amounts would be claimed by backingManager.claimRewards() + /// @return revTraderRewards {qTok} The amounts that would be claimed by trader.claimRewards() + /// @dev Note that `surpluses` + `bmRewards` + `revTraderRewards` + /// @custom:static-call + function revenueOverview(IRevenueTrader revenueTrader) + external + returns ( + IERC20[] memory erc20s, + bool[] memory canStart, + uint256[] memory surpluses, + uint256[] memory minTradeAmounts, + uint256[] memory bmRewards, + uint256[] memory revTraderRewards + ); + /// To use this, call via callStatic. /// If canStart is true, call backingManager.rebalance(). May require settling a /// trade first; see auctionsSettleable. @@ -51,19 +74,4 @@ interface IFacadeAct { IERC20 buy, uint256 sellAmount ); - - /// To use this, call via callStatic. - /// @return erc20s The ERC20s that have auctions that can be started - /// @return canStart If the ERC20 auction can be started - /// @return surpluses {qTok} The surplus amount - /// @return minTradeAmounts {qTok} The minimum amount worth trading - /// @custom:static-call - function revenueOverview(IRevenueTrader revenueTrader) - external - returns ( - IERC20[] memory erc20s, - bool[] memory canStart, - uint256[] memory surpluses, - uint256[] memory minTradeAmounts - ); } diff --git a/contracts/interfaces/IFacadeRead.sol b/contracts/interfaces/IFacadeRead.sol index 61e2dbd521..44af758dec 100644 --- a/contracts/interfaces/IFacadeRead.sol +++ b/contracts/interfaces/IFacadeRead.sol @@ -31,17 +31,28 @@ interface IFacadeRead { ); /// @return tokens The erc20s returned for the redemption - /// @return withdrawals The balances necessary to issue `amount` RToken - /// @return isProrata True if the redemption is prorata and not full + /// @return withdrawals The balances the reedemer would receive after a full redemption + /// @return available The amount actually available, for each token + /// @dev If available[i] < withdrawals[i], then RToken.redeem() would revert /// @custom:static-call function redeem(IRToken rToken, uint256 amount) external returns ( address[] memory tokens, uint256[] memory withdrawals, - bool isProrata + uint256[] memory available ); + /// @return tokens The erc20s returned for the redemption + /// @return withdrawals The balances the reedemer would receive after redemption + /// @custom:static-call + function redeemCustom( + IRToken rToken, + uint256 amount, + uint48[] memory basketNonces, + uint192[] memory portions + ) external returns (address[] memory tokens, uint256[] memory withdrawals); + /// @return erc20s The ERC20 addresses in the current basket /// @return uoaShares The proportion of the basket associated with each ERC20 /// @return targets The bytes32 representations of the target unit associated with each ERC20 diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index 31bf6cc9f9..3111563053 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -107,7 +107,7 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea uint192[] memory portions, address[] memory expectedERC20sOut, uint256[] memory minAmounts - ) external returns (address[] memory erc20sOut, uint256[] memory amountsOut); + ) external; /// Mint an amount of RToken equivalent to baskets BUs, scaling basketsNeeded up /// Callable only by BackingManager diff --git a/contracts/interfaces/IRevenueTrader.sol b/contracts/interfaces/IRevenueTrader.sol index aaff95bc30..4b07ff70bc 100644 --- a/contracts/interfaces/IRevenueTrader.sol +++ b/contracts/interfaces/IRevenueTrader.sol @@ -20,20 +20,22 @@ interface IRevenueTrader is IComponent, ITrading { uint192 minTradeVolume_ ) external; - /// Process a single token - /// @dev Intended to be used with multicall - /// @param erc20 The ERC20 token to manage; can be tokenToBuy or anything registered - /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION - /// @custom:interaction - function manageToken(IERC20 erc20, TradeKind kind) external; - /// Distribute tokenToBuy to its destinations - /// @dev Special-case of manageToken(tokenToBuy, *) + /// @dev Special-case of manageTokens() /// @custom:interaction function distributeTokenToBuy() external; + + /// Process some number of tokens + /// If the tokenToBuy is included in erc20s, RevenueTrader will distribute it at end of the tx + /// @param erc20s The ERC20s to manage; can be tokenToBuy or anything registered + /// @param kinds The kinds of auctions to launch: DUTCH_AUCTION | BATCH_AUCTION + /// @custom:interaction + function manageTokens(IERC20[] memory erc20s, TradeKind[] memory kinds) external; + + function tokenToBuy() external view returns (IERC20); } // solhint-disable-next-line no-empty-blocks interface TestIRevenueTrader is IRevenueTrader, TestITrading { - function tokenToBuy() external view returns (IERC20); + } diff --git a/contracts/libraries/NetworkConfigLib.sol b/contracts/libraries/NetworkConfigLib.sol new file mode 100644 index 0000000000..b347bec484 --- /dev/null +++ b/contracts/libraries/NetworkConfigLib.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +/** + * @title NetworkConfigLib + * @notice Provides network-specific configuration parameters + */ +library NetworkConfigLib { + error InvalidNetwork(); + + // Returns the blocktime based on the current network (e.g. 12s for Ethereum PoS) + // See docs/system-design.md for discussion of handling longer or shorter times + function blocktime() internal view returns (uint48) { + uint256 chainId = block.chainid; + // untestable: + // most of the branches will be shown as uncovered, because we only run coverage + // on local Ethereum PoS network (31337). Manual testing was performed. + if (chainId == 1 || chainId == 5 || chainId == 31337) { + return 12; // Ethereum PoS, Goerli, HH (tests) + } else if (chainId == 8453 || chainId == 84531) { + return 2; // Base, Base Goerli + } else { + revert InvalidNetwork(); + } + } +} diff --git a/contracts/libraries/Throttle.sol b/contracts/libraries/Throttle.sol index 258796be29..7314325aa8 100644 --- a/contracts/libraries/Throttle.sol +++ b/contracts/libraries/Throttle.sol @@ -48,7 +48,12 @@ library ThrottleLib { // Calculate available amount before supply change uint256 available = currentlyAvailable(throttle, limit); - // Calculate available amount after supply change + // Update throttle.timestamp if available amount changed or at limit + if (available != throttle.lastAvailable || available == limit) { + throttle.lastTimestamp = uint48(block.timestamp); + } + + // Update throttle.lastAvailable if (amount > 0) { require(uint256(amount) <= available, "supply change throttled"); available -= uint256(amount); @@ -56,10 +61,7 @@ library ThrottleLib { } else if (amount < 0) { available += uint256(-amount); } - - // Update cached values throttle.lastAvailable = available; - throttle.lastTimestamp = uint48(block.timestamp); } /// @param limit {qRTok/hour} The hourly limit diff --git a/contracts/libraries/test/FixedCallerMock.sol b/contracts/libraries/test/FixedCallerMock.sol index 98701eb580..4c3cc1dd5f 100644 --- a/contracts/libraries/test/FixedCallerMock.sol +++ b/contracts/libraries/test/FixedCallerMock.sol @@ -240,6 +240,9 @@ contract FixedCallerMock { ) public pure returns (uint192) { return FixLib.safeMul(a, b, rnd); } + function safeDiv_(uint192 a, uint192 b, RoundingMode rnd) public pure returns (uint192) { + return FixLib.safeDiv(a, b, rnd); + } function safeDiv( uint192 x, diff --git a/contracts/mixins/Auth.sol b/contracts/mixins/Auth.sol index 24cd81d709..ffdc714816 100644 --- a/contracts/mixins/Auth.sol +++ b/contracts/mixins/Auth.sol @@ -16,9 +16,9 @@ uint48 constant MAX_LONG_FREEZE = 31536000; // 1 year abstract contract Auth is AccessControlUpgradeable, IAuth { /** * System-wide states (does not impact ERC20 functions) - * - Frozen: only allow OWNER actions and staking + * - Frozen: only allow OWNER actions and staking. * - Trading Paused: only allow OWNER actions, issuance, redemption, staking, - * and rewards payout + * and rewards payout. * - Issuance Paused: disallow issuance * * Typically freezing thaws on its own in a predetermined number of blocks. diff --git a/contracts/p0/AssetRegistry.sol b/contracts/p0/AssetRegistry.sol index 91b9bb1a2f..223c691154 100644 --- a/contracts/p0/AssetRegistry.sol +++ b/contracts/p0/AssetRegistry.sol @@ -12,7 +12,8 @@ contract AssetRegistryP0 is ComponentP0, IAssetRegistry { using FixLib for uint192; using EnumerableSet for EnumerableSet.AddressSet; - uint256 public constant GAS_TO_RESERVE = 900000; // just enough to disable basket on n=128 + uint256 public constant GAS_FOR_BH_QTY = 100_000; // enough to call bh.quantity + uint256 public constant GAS_FOR_DISABLE_BASKET = 900_000; // enough to disable basket on n=128 // Registered ERC20s EnumerableSet.AddressSet private _erc20s; @@ -37,8 +38,7 @@ contract AssetRegistryP0 is ComponentP0, IAssetRegistry { assets[IERC20(_erc20s.at(i))].refresh(); } - IBasketHandler basketHandler = main.basketHandler(); - basketHandler.trackStatus(); + main.basketHandler().trackStatus(); lastRefresh = uint48(block.timestamp); } @@ -166,7 +166,10 @@ contract AssetRegistryP0 is ComponentP0, IAssetRegistry { function _reserveGas() private view returns (uint256) { uint256 gas = gasleft(); - require(gas > GAS_TO_RESERVE, "not enough gas to unregister safely"); - return gas - GAS_TO_RESERVE; + require( + gas > GAS_FOR_DISABLE_BASKET + GAS_FOR_BH_QTY, + "not enough gas to unregister safely" + ); + return gas - GAS_FOR_DISABLE_BASKET; } } diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index cd7c063ca1..0bdc2249c7 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -11,7 +11,7 @@ import "../interfaces/IBroker.sol"; import "../interfaces/IMain.sol"; import "../libraries/Array.sol"; import "../libraries/Fixed.sol"; -import "../mixins/NetworkConfigLib.sol"; +import "../libraries/NetworkConfigLib.sol"; /** * @title BackingManager @@ -123,10 +123,8 @@ contract BackingManagerP0 is TradingP0, IBackingManager { */ BasketRange memory basketsHeld = main.basketHandler().basketsHeldBy(address(this)); - (bool doTrade, TradeRequest memory req) = TradingLibP0.prepareRecollateralizationTrade( - this, - basketsHeld - ); + (bool doTrade, TradeRequest memory req, TradePrices memory prices) = TradingLibP0 + .prepareRecollateralizationTrade(this, basketsHeld); if (doTrade) { // Seize RSR if needed @@ -136,7 +134,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { } // Execute Trade - ITrade trade = tryTrade(kind, req); + ITrade trade = tryTrade(kind, req, prices); tradeEnd[kind] = trade.endTime(); } else { // Haircut time diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 3fc647432d..20464dc0d8 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -453,9 +453,10 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { uint192[] memory portions, uint192 amount ) external view returns (address[] memory erc20s, uint256[] memory quantities) { - require(basketNonces.length == portions.length, "portions does not mirror basketNonces"); + require(basketNonces.length == portions.length, "bad portions len"); IERC20[] memory erc20sAll = new IERC20[](main.assetRegistry().size()); + ICollateral[] memory collsAll = new ICollateral[](erc20sAll.length); uint192[] memory refAmtsAll = new uint192[](erc20sAll.length); uint256 len; // length of return arrays @@ -467,26 +468,38 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // Add-in refAmts contribution from historical basket for (uint256 j = 0; j < b.erc20s.length; ++j) { - IERC20 erc20 = b.erc20s[j]; - if (address(erc20) == address(0)) continue; + if (address(b.erc20s[j]) == address(0)) continue; - // Ugly search through erc20sAll + // Search through erc20sAll uint256 erc20Index = type(uint256).max; for (uint256 k = 0; k < len; ++k) { - if (erc20 == erc20sAll[k]) { + if (b.erc20s[j] == erc20sAll[k]) { erc20Index = k; continue; } } // Add new ERC20 entry if not found - uint192 amt = portions[i].mul(b.refAmts[erc20], FLOOR); + uint192 amt = portions[i].mul(b.refAmts[b.erc20s[j]], FLOOR); if (erc20Index == type(uint256).max) { - erc20sAll[len] = erc20; - - // {ref} = {1} * {ref} - refAmtsAll[len] = amt; - ++len; + // New entry found + + try main.assetRegistry().toAsset(b.erc20s[j]) returns (IAsset asset) { + if (!asset.isCollateral()) continue; // skip token if not collateral + + erc20sAll[len] = b.erc20s[j]; + collsAll[len] = ICollateral(address(asset)); + + // {ref} = {1} * {ref} + refAmtsAll[len] = amt; + ++len; + } catch (bytes memory errData) { + // untested: + // OOG pattern tested in other contracts, cost to test here is high + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + // skip token if no longer registered or other non-gas issue + } } else { // {ref} = {1} * {ref} refAmtsAll[erc20Index] += amt; diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 033ef0bf9b..c1c7da1452 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -10,6 +10,7 @@ import "../interfaces/IBroker.sol"; import "../interfaces/IMain.sol"; import "../interfaces/ITrade.sol"; import "../libraries/Fixed.sol"; +import "../libraries/NetworkConfigLib.sol"; import "./mixins/Component.sol"; // Gnosis: uint96 ~= 7e28 @@ -62,7 +63,11 @@ contract BrokerP0 is ComponentP0, IBroker { /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @dev Requires setting an allowance in advance /// @custom:protected - function openTrade(TradeKind kind, TradeRequest memory req) external returns (ITrade) { + function openTrade( + TradeKind kind, + TradeRequest memory req, + TradePrices memory prices + ) external returns (ITrade) { require(!disabled, "broker disabled"); assert(req.sellAmount > 0); @@ -79,7 +84,7 @@ contract BrokerP0 is ComponentP0, IBroker { return newBatchAuction(req, caller); } else { // kind == TradeKind.DUTCH_AUCTION - return newDutchAuction(req, ITrading(caller)); + return newDutchAuction(req, prices, ITrading(caller)); } } @@ -191,7 +196,11 @@ contract BrokerP0 is ComponentP0, IBroker { return trade; } - function newDutchAuction(TradeRequest memory req, ITrading caller) private returns (ITrade) { + function newDutchAuction( + TradeRequest memory req, + TradePrices memory prices, + ITrading caller + ) private returns (ITrade) { require(dutchAuctionLength > 0, "dutch auctions not enabled"); DutchTrade trade = new DutchTrade(); trades[address(trade)] = true; @@ -202,7 +211,7 @@ contract BrokerP0 is ComponentP0, IBroker { req.sellAmount ); - trade.init(caller, req.sell, req.buy, req.sellAmount, dutchAuctionLength); + trade.init(caller, req.sell, req.buy, req.sellAmount, dutchAuctionLength, prices); return trade; } diff --git a/contracts/p0/Deployer.sol b/contracts/p0/Deployer.sol index 2b089b2242..8c8d124d2e 100644 --- a/contracts/p0/Deployer.sol +++ b/contracts/p0/Deployer.sol @@ -158,4 +158,9 @@ contract DeployerP0 is IDeployer, Versioned { emit RTokenCreated(main, components.rToken, components.stRSR, owner, version()); return (address(components.rToken)); } + + /// @param maxTradeVolume {UoA} The maximum trade volume for the RTokenAsset + function deployRTokenAsset(IRToken rToken, uint192 maxTradeVolume) external returns (IAsset) { + return new RTokenAsset(rToken, maxTradeVolume); + } } diff --git a/contracts/p0/Distributor.sol b/contracts/p0/Distributor.sol index 877485598d..d305e9b521 100644 --- a/contracts/p0/Distributor.sol +++ b/contracts/p0/Distributor.sol @@ -41,9 +41,17 @@ contract DistributorP0 is ComponentP0, IDistributor { /// Distribute revenue, in rsr or rtoken, per the distribution table. /// Requires that this contract has an allowance of at least /// `amount` tokens, from `from`, of the token at `erc20`. + /// Only callable by RevenueTraders function distribute(IERC20 erc20, uint256 amount) external { + // Intentionally do not check notTradingPausedOrFrozen, since handled by caller + IERC20 rsr = main.rsr(); + require( + _msgSender() == address(main.rsrTrader()) || + _msgSender() == address(main.rTokenTrader()), + "RevenueTraders only" + ); require(erc20 == rsr || erc20 == IERC20(address(main.rToken())), "RSR or RToken"); bool isRSR = erc20 == rsr; // if false: isRToken uint256 tokensPerShare; diff --git a/contracts/p0/Furnace.sol b/contracts/p0/Furnace.sol index 9301b3b0f9..16b0d4377c 100644 --- a/contracts/p0/Furnace.sol +++ b/contracts/p0/Furnace.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.19; import "../libraries/Fixed.sol"; +import "../libraries/NetworkConfigLib.sol"; import "../interfaces/IFurnace.sol"; import "./mixins/Component.sol"; import "../mixins/NetworkConfigLib.sol"; @@ -13,7 +14,7 @@ import "../mixins/NetworkConfigLib.sol"; contract FurnaceP0 is ComponentP0, IFurnace { using FixLib for uint192; - uint192 public constant MAX_RATIO = FIX_ONE; // {1} 100% + uint192 public constant MAX_RATIO = 1e14; // {1} 0.01% // solhint-disable-next-line var-name-mixedcase uint48 public immutable PERIOD; // {seconds} 1 block based on network diff --git a/contracts/p0/Main.sol b/contracts/p0/Main.sol index 3681f2da52..9493b72c5c 100644 --- a/contracts/p0/Main.sol +++ b/contracts/p0/Main.sol @@ -37,9 +37,8 @@ contract MainP0 is Versioned, Initializable, Auth, ComponentRegistry, IMain { /// @custom:refresher function poke() external { - require(!frozen(), "frozen"); assetRegistry.refresh(); - furnace.melt(); + if (!frozen()) furnace.melt(); stRSR.payoutRewards(); // NOT basketHandler.refreshBasket } diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 2dc33334a0..a943324371 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -186,12 +186,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { uint192[] memory portions, address[] memory expectedERC20sOut, uint256[] memory minAmounts - ) - external - notFrozen - exchangeRateIsValidAfter - returns (address[] memory erc20sOut, uint256[] memory amountsOut) - { + ) external notFrozen exchangeRateIsValidAfter { require(amount > 0, "Cannot redeem zero"); require(amount <= balanceOf(_msgSender()), "insufficient balance"); @@ -218,11 +213,9 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { require(portionsSum == FIX_ONE, "portions do not add up to FIX_ONE"); } - (erc20sOut, amountsOut) = main.basketHandler().quoteCustomRedemption( - basketNonces, - portions, - basketsRedeemed - ); + (address[] memory erc20s, uint256[] memory amounts) = main + .basketHandler() + .quoteCustomRedemption(basketNonces, portions, basketsRedeemed); // === Save initial recipient balances === @@ -235,21 +228,21 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { { bool allZero = true; // Bound each withdrawal by the prorata share, in case currently under-collateralized - for (uint256 i = 0; i < erc20sOut.length; i++) { + for (uint256 i = 0; i < erc20s.length; i++) { // {qTok} = {qTok} * {qRTok} / {qRTok} uint256 prorata = mulDiv256( - IERC20(erc20sOut[i]).balanceOf(address(main.backingManager())), + IERC20(erc20s[i]).balanceOf(address(main.backingManager())), amount, supply ); // FLOOR - if (prorata < amountsOut[i]) amountsOut[i] = prorata; + if (prorata < amounts[i]) amounts[i] = prorata; // Send withdrawal - if (amountsOut[i] > 0) { - IERC20(erc20sOut[i]).safeTransferFrom( + if (amounts[i] > 0) { + IERC20(erc20s[i]).safeTransferFrom( address(main.backingManager()), recipient, - amountsOut[i] + amounts[i] ); allZero = false; } diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index d9a1ed6e49..4196cc7916 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -39,62 +39,77 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { returns (ITrade trade) { trade = super.settleTrade(sell); - distributeTokenToBuy(); + _distributeTokenToBuy(); // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } /// Distribute tokenToBuy to its destinations - /// @dev Special-case of manageToken(tokenToBuy, *) + /// @dev Special-case of manageTokens([tokenToBuy], *) /// @custom:interaction - function distributeTokenToBuy() public { - uint256 bal = tokenToBuy.balanceOf(address(this)); - tokenToBuy.safeApprove(address(main.distributor()), 0); - tokenToBuy.safeApprove(address(main.distributor()), bal); - main.distributor().distribute(tokenToBuy, bal); + function distributeTokenToBuy() external notTradingPausedOrFrozen { + _distributeTokenToBuy(); } - /// Processes a single token; unpermissioned - /// @dev Intended to be used with multicall - /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION + /// Process some number of tokens + /// @param erc20s The ERC20s to manage; can be tokenToBuy or anything registered + /// @param kinds The kinds of auctions to launch: DUTCH_AUCTION | BATCH_AUCTION /// @custom:interaction - function manageToken(IERC20 erc20, TradeKind kind) external notTradingPausedOrFrozen { - if (erc20 == tokenToBuy) { - distributeTokenToBuy(); - return; + function manageTokens(IERC20[] memory erc20s, TradeKind[] memory kinds) + external + notTradingPausedOrFrozen + { + require(erc20s.length > 0, "empty erc20s list"); + require(erc20s.length == kinds.length, "length mismatch"); + main.assetRegistry().refresh(); + + IAsset assetToBuy = main.assetRegistry().toAsset(tokenToBuy); + (uint192 buyLow, uint192 buyHigh) = assetToBuy.lotPrice(); // {UoA/tok} + require(buyHigh > 0 && buyHigh < FIX_MAX, "buy asset price unknown"); + + // For each ERC20: start auction of given kind + for (uint256 i = 0; i < erc20s.length; ++i) { + IERC20 erc20 = erc20s[i]; + if (erc20 == tokenToBuy) { + _distributeTokenToBuy(); + continue; + } + + IAsset assetToSell = main.assetRegistry().toAsset(erc20); + + require(address(trades[erc20]) == address(0), "trade open"); + require(erc20.balanceOf(address(this)) > 0, "0 balance"); + + (uint192 sellLow, uint192 sellHigh) = assetToSell.lotPrice(); // {UoA/tok} + + TradingLibP0.TradeInfo memory trade = TradingLibP0.TradeInfo({ + sell: assetToSell, + buy: assetToBuy, + sellAmount: assetToSell.bal(address(this)), + buyAmount: 0, + prices: TradePrices(sellLow, sellHigh, buyLow, buyHigh) + }); + + // Whether dust or not, trade the non-target asset for the target asset + // Any asset with a broken price feed will trigger a revert here + (, TradeRequest memory req) = TradingLibP0.prepareTradeSell( + trade, + minTradeVolume, + maxTradeSlippage + ); + require(req.sellAmount > 1, "sell amount too low"); + + tryTrade(kinds[i], req, trade.prices); } + } - main.assetRegistry().refresh(); - main.furnace().melt(); - - require(address(trades[erc20]) == address(0), "trade open"); - require(erc20.balanceOf(address(this)) > 0, "0 balance"); - - IAssetRegistry reg = main.assetRegistry(); - IAsset sell = reg.toAsset(erc20); - IAsset buy = reg.toAsset(tokenToBuy); - (uint192 sellPrice, ) = sell.price(); // {UoA/tok} - (, uint192 buyPrice) = buy.price(); // {UoA/tok} - - require(buyPrice > 0 && buyPrice < FIX_MAX, "buy asset price unknown"); - - TradingLibP0.TradeInfo memory trade = TradingLibP0.TradeInfo({ - sell: sell, - buy: buy, - sellAmount: sell.bal(address(this)), - buyAmount: 0, - sellPrice: sellPrice, - buyPrice: buyPrice - }); - - // Whether dust or not, trade the non-target asset for the target asset - // Any asset with a broken price feed will trigger a revert here - (, TradeRequest memory req) = TradingLibP0.prepareTradeSell( - trade, - minTradeVolume, - maxTradeSlippage - ); - require(req.sellAmount > 1, "sell amount too low"); - - tryTrade(kind, req); + // === Internal === + + /// Distribute tokenToBuy to its destinations + /// @dev Assumes notTradingPausedOrFrozen has already been checked! + function _distributeTokenToBuy() internal { + uint256 bal = tokenToBuy.balanceOf(address(this)); + tokenToBuy.safeApprove(address(main.distributor()), 0); + tokenToBuy.safeApprove(address(main.distributor()), bal); + main.distributor().distribute(tokenToBuy, bal); } } diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index f0e2f1e733..926861473e 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -14,6 +14,7 @@ import "../interfaces/IBasketHandler.sol"; import "../interfaces/IStRSR.sol"; import "../interfaces/IMain.sol"; import "../libraries/Fixed.sol"; +import "../libraries/NetworkConfigLib.sol"; import "../libraries/Permit.sol"; import "./mixins/Component.sol"; import "../mixins/NetworkConfigLib.sol"; @@ -37,7 +38,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { // solhint-disable-next-line var-name-mixedcase uint48 public immutable MIN_UNSTAKING_DELAY; // {s} based on network uint48 public constant MAX_UNSTAKING_DELAY = 31536000; // {s} 1 year - uint192 public constant MAX_REWARD_RATIO = 1e18; + uint192 public constant MAX_REWARD_RATIO = 1e14; // {1} 0.01% uint192 public constant MAX_WITHDRAWAL_LEAK = 3e17; // {1} 30% // ==== ERC20Permit ==== @@ -136,7 +137,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { /// Assign reward payouts to the staker pool /// @custom:refresher - function payoutRewards() external notFrozen { + function payoutRewards() external { _payoutRewards(); } @@ -149,7 +150,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { address account = _msgSender(); require(rsrAmount > 0, "Cannot stake zero"); - if (!main.frozen()) _payoutRewards(); + _payoutRewards(); uint256 stakeAmount = rsrAmount; // The next line is _not_ an overflow risk, in our expected ranges: @@ -242,6 +243,10 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { function cancelUnstake(uint256 endId) external notFrozen { address account = _msgSender(); + // Call state keepers + _payoutRewards(); + + // We specifically allow unstaking when under collateralized // IBasketHandler bh = main.basketHandler(); // require(bh.fullyCollateralized(), "RToken uncollateralized"); // require(bh.isReady(), "basket not ready"); @@ -250,6 +255,8 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { if (endId == 0) return; require(endId <= queue.length, "index out-of-bounds"); + + // Cancelling unstake does not require checking if the unstaking was available // require(queue[endId - 1].availableAt <= block.timestamp, "withdrawal unavailable"); // Skip executed withdrawals - Both amounts should be 0 @@ -613,7 +620,7 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { } function setRewardRatio(uint192 val) public governance { - if (!main.frozen()) _payoutRewards(); + _payoutRewards(); require(val <= MAX_REWARD_RATIO, "invalid rewardRatio"); emit RewardRatioSet(rewardRatio, val); rewardRatio = val; diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index 871092986b..4df64445c8 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -60,7 +60,11 @@ abstract contract TradingP0 is RewardableP0, ITrading { /// Try to initiate a trade with a trading partner provided by the broker /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @return trade The trade contract created - function tryTrade(TradeKind kind, TradeRequest memory req) internal returns (ITrade trade) { + function tryTrade( + TradeKind kind, + TradeRequest memory req, + TradePrices memory prices + ) internal returns (ITrade trade) { IBroker broker = main.broker(); assert(address(trades[req.sell.erc20()]) == address(0)); require(!broker.disabled(), "broker disabled"); @@ -68,7 +72,7 @@ abstract contract TradingP0 is RewardableP0, ITrading { req.sell.erc20().safeApprove(address(broker), 0); req.sell.erc20().safeApprove(address(broker), req.sellAmount); - trade = broker.openTrade(kind, req); + trade = broker.openTrade(kind, req, prices); trades[req.sell.erc20()] = trade; tradesOpen++; tradesNonce++; diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index 271c6610f1..f1ed5e5bc8 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -25,18 +25,18 @@ library TradingLibP0 { using TradingLibP0 for IBackingManager; /// Prepare a trade to sell `trade.sellAmount` that guarantees a reasonable closing price, - /// without explicitly aiming at a particular quantity to purchase. + /// without explicitly aiming at a particular buy amount. /// @param trade: - /// sell != 0, sellAmount >= 0 {sellTok}, sellPrice >= 0 {UoA/sellTok} - /// buy != 0, buyAmount (unused) {buyTok}, buyPrice > 0 {UoA/buyTok} + /// sell != 0, sellAmount >= 0 {sellTok}, prices.sellLow >= 0 {UoA/sellTok} + /// buy != 0, buyAmount (unused) {buyTok}, prices.buyHigh > 0 {UoA/buyTok} /// @return notDust True when the trade is larger than the dust amount /// @return req The prepared trade request to send to the Broker // // If notDust is true, then the returned trade request satisfies: // req.sell == trade.sell and req.buy == trade.buy, - // req.minBuyAmount * trade.buyPrice ~= - // trade.sellAmount * trade.sellPrice * (1-maxTradeSlippage), - // req.sellAmount == min(trade.sell.maxTradeSize().toQTok(), trade.sellAmount.toQTok(sell) + // req.minBuyAmount * trade.prices.buyHigh ~= + // trade.sellAmount * trade.prices.sellLow * (1-maxTradeSlippage), + // req.sellAmount == min(trade.sell.maxTradeSize(), trade.sellAmount) // 1 < req.sellAmount // // If notDust is false, no trade exists that satisfies those constraints. @@ -46,14 +46,21 @@ library TradingLibP0 { uint192 maxTradeSlippage ) internal view returns (bool notDust, TradeRequest memory req) { // checked for in RevenueTrader / CollateralizatlionLib - assert(trade.buyPrice > 0 && trade.buyPrice < FIX_MAX && trade.sellPrice < FIX_MAX); - - (uint192 lotLow, uint192 lotHigh) = trade.sell.lotPrice(); + assert( + trade.prices.buyHigh > 0 && + trade.prices.buyHigh < FIX_MAX && + trade.prices.sellLow < FIX_MAX + ); - notDust = isEnoughToSell(trade.sell, trade.sellAmount, lotLow, minTradeVolume); + notDust = isEnoughToSell( + trade.sell, + trade.sellAmount, + trade.prices.sellLow, + minTradeVolume + ); // Cap sell amount - uint192 maxSell = maxTradeSize(trade.sell, trade.buy, lotHigh); // {sellTok} + uint192 maxSell = maxTradeSize(trade.sell, trade.buy, trade.prices.sellHigh); // {sellTok} uint192 s = trade.sellAmount > maxSell ? maxSell : trade.sellAmount; // {sellTok} // Calculate equivalent buyAmount within [0, FIX_MAX] @@ -69,35 +76,35 @@ library TradingLibP0 { req.minBuyAmount = b.shiftl_toUint(int8(trade.buy.erc20Decimals()), CEIL); req.sell = trade.sell; req.buy = trade.buy; + return (notDust, req); } /// Assuming we have `trade.sellAmount` sell tokens available, prepare a trade to cover as /// much of our deficit of `trade.buyAmount` buy tokens as possible, given expected trade - /// slippage and the sell asset's maxTradeVolume(). + /// slippage and maxTradeVolume(). /// @param trade: /// sell != 0 /// buy != 0 /// sellAmount (unused) {sellTok} /// buyAmount >= 0 {buyTok} - /// sellPrice > 0 {UoA/sellTok} - /// buyPrice > 0 {UoA/buyTok} + /// prices.sellLow > 0 {UoA/sellTok} + /// prices.buyHigh > 0 {UoA/buyTok} /// @return notDust Whether the prepared trade is large enough to be worth trading /// @return req The prepared trade request to send to the Broker // // Returns prepareTradeSell(trade, rules), where // req.sellAmount = min(trade.sellAmount, - // trade.buyAmount * (trade.buyPrice / trade.sellPrice) / (1-maxTradeSlippage)) + // trade.buyAmount * (buyHigh / sellLow) / (1-maxTradeSlippage)) // i.e, the minimum of trade.sellAmount and (a sale amount that, at current prices and // maximum slippage, will yield at least the requested trade.buyAmount) // // Which means we should get that, if notDust is true, then: // req.sell = sell and req.buy = buy // - // 1 <= req.minBuyAmount <= max(trade.buyAmount, buy.minTradeSize()).toQTok(trade.buy) - // 1 < req.sellAmount <= min(trade.sellAmount.toQTok(trade.sell), - // sell.maxTradeSize().toQTok(trade.sell)) - // req.minBuyAmount ~= trade.sellAmount * sellPrice / buyPrice * (1-maxTradeSlippage) + // 1 <= req.minBuyAmount <= max(trade.buyAmount, buy.minTradeSize())) + // 1 < req.sellAmount <= min(trade.sellAmount, sell.maxTradeSize()) + // req.minBuyAmount ~= trade.sellAmount * sellLow / buyHigh * (1-maxTradeSlippage) // // req.sellAmount (and req.minBuyAmount) are maximal satisfying all these conditions function prepareTradeToCoverDeficit( @@ -106,17 +113,24 @@ library TradingLibP0 { uint192 maxTradeSlippage ) internal view returns (bool notDust, TradeRequest memory req) { assert( - trade.sellPrice > 0 && - trade.sellPrice < FIX_MAX && - trade.buyPrice > 0 && - trade.buyPrice < FIX_MAX + trade.prices.sellLow > 0 && + trade.prices.sellLow < FIX_MAX && + trade.prices.buyHigh > 0 && + trade.prices.buyHigh < FIX_MAX ); // Don't buy dust. - trade.buyAmount = fixMax(trade.buyAmount, minTradeSize(minTradeVolume, trade.buyPrice)); + trade.buyAmount = fixMax( + trade.buyAmount, + minTradeSize(minTradeVolume, trade.prices.buyHigh) + ); // {sellTok} = {buyTok} * {UoA/buyTok} / {UoA/sellTok} - uint192 exactSellAmount = trade.buyAmount.mulDiv(trade.buyPrice, trade.sellPrice, CEIL); + uint192 exactSellAmount = trade.buyAmount.mulDiv( + trade.prices.buyHigh, + trade.prices.sellLow, + CEIL + ); // exactSellAmount: Amount to sell to buy `deficitAmount` if there's no slippage // slippedSellAmount: Amount needed to sell to buy `deficitAmount`, counting slippage @@ -153,8 +167,7 @@ library TradingLibP0 { IAsset buy; uint192 sellAmount; // {sellTok} uint192 buyAmount; // {buyTok} - uint192 sellPrice; // {UoA/sellTok} can be 0 - uint192 buyPrice; // {UoA/buyTok} + TradePrices prices; } /// Select and prepare a trade that moves us closer to capitalization, using the @@ -164,11 +177,15 @@ library TradingLibP0 { // let range = basketRange(all erc20s) // let trade = nextTradePair(...) // if trade.sell is not a defaulted collateral, prepareTradeToCoverDeficit(...) - // otherwise, prepareTradeSell(trade) with a 0 minBuyAmount + // otherwise, prepareTradeSell(...) taking the minBuyAmount as the dependent variable function prepareRecollateralizationTrade(IBackingManager bm, BasketRange memory basketsHeld) external view - returns (bool doTrade, TradeRequest memory req) + returns ( + bool doTrade, + TradeRequest memory req, + TradePrices memory prices + ) { // === Prepare cached values === @@ -196,12 +213,12 @@ library TradingLibP0 { // Don't trade if no pair is selected if (address(trade.sell) == address(0) || address(trade.buy) == address(0)) { - return (false, req); + return (false, req, prices); } // If we are selling an unpriced asset or UNSOUND collateral, do not try to cover deficit if ( - trade.sellPrice == 0 || + trade.prices.sellLow == 0 || (trade.sell.isCollateral() && ICollateral(address(trade.sell)).status() != CollateralStatus.SOUND) ) { @@ -216,16 +233,16 @@ library TradingLibP0 { // At this point doTrade _must_ be true, otherwise nextTradePair assumptions are broken assert(doTrade); - return (doTrade, req); + return (doTrade, req, trade.prices); } // Compute the target basket range // Algorithm intuition: Trade conservatively. Quantify uncertainty based on the proportion of - // token balances requiring trading vs not requiring trading. Decrease uncertainty the largest - // amount possible with each trade. + // token balances requiring trading vs not requiring trading. Seek to decrease uncertainty + // the largest amount possible with each trade. // // How do we know this algorithm converges? - // Assumption: constant prices + // Assumption: constant oracle prices; monotonically increasing refPerTok() // Any volume traded narrows the BU band. Why: // - We might increase `basketsHeld.bottom` from run-to-run, but will never decrease it // - We might decrease the UoA amount of excess balances beyond `basketsHeld.bottom` from @@ -234,8 +251,8 @@ library TradingLibP0 { // run-to-run, but will never increase it // // Preconditions: - // - ctx is correctly populated with current basketsHeld.bottom + basketsHeld.top - // - reg contains erc20 + asset arrays in same order and without duplicates + // - ctx is correctly populated, with current basketsHeld.bottom + basketsHeld.top + // - reg contains erc20 + asset + quantities arrays in same order and without duplicates // Trading Strategy: // - We will not aim to hold more than rToken.basketsNeeded() BUs // - No double trades: if we buy B in one trade, we won't sell B in another trade @@ -250,14 +267,14 @@ library TradingLibP0 { // - range.top = min(rToken.basketsNeeded, basketsHeld.top - least baskets missing // + most baskets surplus) // - range.bottom = min(rToken.basketsNeeded, basketsHeld.bottom + least baskets purchaseable) - // where "least baskets purchaseable" involves trading at unfavorable prices, - // incurring maxTradeSlippage, and taking up to a minTradeVolume loss due to dust. + // where "least baskets purchaseable" involves trading at the worst price, + // incurring the full maxTradeSlippage, and taking up to a minTradeVolume loss due to dust. function basketRange(TradingContext memory ctx, IERC20[] memory erc20s) internal view returns (BasketRange memory range) { - (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.price(); // {UoA/BU} + (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.lotPrice(); // {UoA/BU} // Cap ctx.basketsHeld.top if (ctx.basketsHeld.top > ctx.rToken.basketsNeeded()) { @@ -296,7 +313,11 @@ library TradingLibP0 { !isEnoughToSell(asset, bal, lotLow, ctx.minTradeVolume) ) continue; } + (uint192 low, uint192 high) = asset.price(); // {UoA/tok} + // price() is better than lotPrice() here: it's important to not underestimate how + // much value could be in a token that is unpriced by using a decaying high lotPrice. + // price() will return [0, FIX_MAX] in this case, which is preferable. // throughout these sections +/- is same as Fix.plus/Fix.minus and is Fix.gt/.lt @@ -387,8 +408,10 @@ library TradingLibP0 { /// deficit Deficit collateral OR address(0) /// sellAmount {sellTok} Surplus amount (whole tokens) /// buyAmount {buyTok} Deficit amount (whole tokens) - /// sellPrice {UoA/sellTok} The worst-case price of the sell token on secondary markets - /// buyPrice {UoA/sellTok} The worst-case price of the buy token on secondary markets + /// prices.sellLow {UoA/sellTok} The worst-case price of the sell token on secondary markets + /// prices.sellHigh {UoA/sellTok} The best-case price of the sell token on secondary markets + /// prices.buyLow {UoA/buyTok} The best-case price of the buy token on secondary markets + /// prices.buyHigh {UoA/buyTok} The worst-case price of the buy token on secondary markets /// // Defining "sell" and "buy": // If bal(e) > (quantity(e) * range.top), then e is in surplus by the difference @@ -398,16 +421,17 @@ library TradingLibP0 { // `trade.sell` is the token from erc20s with the greatest surplus value (in UoA), // and sellAmount is the quantity of that token that it's in surplus (in qTok). // if `trade.sell` == 0, then no token is in surplus by at least minTradeSize, - // and `trade.sellAmount` and `trade.sellPrice` are unset. + // and `trade.sellAmount` and `trade.sellLow` / `trade.sellHigh are unset. // // `trade.buy` is the token from erc20s with the greatest deficit value (in UoA), // and buyAmount is the quantity of that token that it's in deficit (in qTok). // if `trade.buy` == 0, then no token is in deficit at all, - // and `trade.buyAmount` and `trade.buyPrice` are unset. + // and `trade.buyAmount` and `trade.buyLow` / `trade.buyHigh` are unset. // // Then, just if we have a buy asset and no sell asset, consider selling available RSR. // // Prefer selling assets in this order: DISABLED -> SOUND -> IFFY. + // Sell IFFY last because it may recover value in the future. // All collateral in the basket have already been guaranteed to be SOUND by upstream checks. function nextTradePair( TradingContext memory ctx, @@ -429,7 +453,8 @@ library TradingLibP0 { // {tok} = {BU} * {tok/BU} uint192 needed = range.top.mul(ctx.bh.quantity(erc20s[i]), CEIL); // {tok} if (bal.gt(needed)) { - (uint192 lotLow, ) = asset.lotPrice(); // {UoA/sellTok} + (uint192 lotLow, uint192 lotHigh) = asset.lotPrice(); // {UoA/sellTok} + if (lotHigh == 0) continue; // Skip worthless assets // by calculating this early we can duck the stack limit but be less gas-efficient bool enoughToSell = isEnoughToSell( @@ -439,11 +464,6 @@ library TradingLibP0 { ctx.minTradeVolume ); - (uint192 low, uint192 high) = asset.price(); // {UoA/sellTok} - - // Skip worthless assets - if (high == 0) continue; - // {UoA} = {sellTok} * {UoA/sellTok} uint192 delta = bal.minus(needed).mul(lotLow, FLOOR); @@ -456,7 +476,8 @@ library TradingLibP0 { if (isBetterSurplus(maxes, status, delta) && enoughToSell) { trade.sell = asset; trade.sellAmount = bal.minus(needed); - trade.sellPrice = low; + trade.prices.sellLow = lotLow; + trade.prices.sellHigh = lotHigh; maxes.surplusStatus = status; maxes.surplus = delta; @@ -466,16 +487,17 @@ library TradingLibP0 { needed = range.bottom.mul(ctx.bh.quantity(erc20s[i]), CEIL); // {buyTok}; if (bal.lt(needed)) { uint192 amtShort = needed.minus(bal); // {buyTok} - (, uint192 high) = asset.price(); // {UoA/buyTok} + (uint192 lotLow, uint192 lotHigh) = asset.lotPrice(); // {UoA/buyTok} // {UoA} = {buyTok} * {UoA/buyTok} - uint192 delta = amtShort.mul(high, CEIL); + uint192 delta = amtShort.mul(lotHigh, CEIL); // The best asset to buy is whichever asset has the largest deficit if (delta.gt(maxes.deficit)) { trade.buy = ICollateral(address(asset)); trade.buyAmount = amtShort; - trade.buyPrice = high; + trade.prices.buyLow = lotLow; + trade.prices.buyHigh = lotHigh; maxes.deficit = delta; } @@ -490,13 +512,13 @@ library TradingLibP0 { uint192 rsrAvailable = rsrAsset.bal(address(ctx.bm)).plus( rsrAsset.bal(address(ctx.stRSR)) ); - (uint192 low, uint192 high) = rsrAsset.price(); // {UoA/tok} - (uint192 lotLow, ) = rsrAsset.lotPrice(); // {UoA/sellTok} + (uint192 lotLow, uint192 lotHigh) = rsrAsset.lotPrice(); // {UoA/RSR} - if (high > 0 && isEnoughToSell(rsrAsset, rsrAvailable, lotLow, ctx.minTradeVolume)) { + if (lotHigh > 0 && isEnoughToSell(rsrAsset, rsrAvailable, lotLow, ctx.minTradeVolume)) { trade.sell = rsrAsset; trade.sellAmount = rsrAvailable; - trade.sellPrice = low; + trade.prices.sellLow = lotLow; + trade.prices.sellHigh = lotHigh; } } } @@ -558,11 +580,8 @@ library TradingLibP0 { IAsset buy, uint192 price ) private view returns (uint192) { - // untestable: - // Price cannot be 0, it would've been filtered before in `prepareTradeSell` - uint192 size = price == 0 - ? FIX_MAX - : fixMin(sell.maxTradeVolume(), buy.maxTradeVolume()).div(price, FLOOR); + // D18{tok} = D18{UoA} / D18{UoA/tok} + uint192 size = fixMin(sell.maxTradeVolume(), buy.maxTradeVolume()).safeDiv(price, FLOOR); return size > 0 ? size : 1; } } diff --git a/contracts/p1/AssetRegistry.sol b/contracts/p1/AssetRegistry.sol index 6ae765525c..3474d171f8 100644 --- a/contracts/p1/AssetRegistry.sol +++ b/contracts/p1/AssetRegistry.sol @@ -12,7 +12,8 @@ import "./mixins/Component.sol"; contract AssetRegistryP1 is ComponentP1, IAssetRegistry { using EnumerableSet for EnumerableSet.AddressSet; - uint256 public constant GAS_TO_RESERVE = 900000; // just enough to disable basket on n=128 + uint256 public constant GAS_FOR_BH_QTY = 100_000; // enough to call bh.quantity + uint256 public constant GAS_FOR_DISABLE_BASKET = 900_000; // enough to disable basket on n=128 // Peer-component addresses IBasketHandler private basketHandler; @@ -42,6 +43,7 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { __Component_init(main_); basketHandler = main_.basketHandler(); backingManager = main_.backingManager(); + uint256 length = assets_.length; for (uint256 i = 0; i < length; ++i) { _register(assets_[i]); @@ -207,8 +209,11 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { function _reserveGas() private view returns (uint256) { uint256 gas = gasleft(); - require(gas > GAS_TO_RESERVE, "not enough gas to unregister safely"); - return gas - GAS_TO_RESERVE; + require( + gas > GAS_FOR_DISABLE_BASKET + GAS_FOR_BH_QTY, + "not enough gas to unregister safely" + ); + return gas - GAS_FOR_DISABLE_BASKET; } /** diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index bc0df7db20..de60a1d0a5 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -8,6 +8,7 @@ import "../interfaces/IBackingManager.sol"; import "../interfaces/IMain.sol"; import "../libraries/Array.sol"; import "../libraries/Fixed.sol"; +import "../libraries/NetworkConfigLib.sol"; import "./mixins/Trading.sol"; import "./mixins/RecollateralizationLib.sol"; import "../mixins/NetworkConfigLib.sol"; @@ -150,8 +151,11 @@ contract BackingManagerP1 is TradingP1, IBackingManager { * rToken.basketsNeeded to the current basket holdings. Haircut time. */ - (bool doTrade, TradeRequest memory req) = RecollateralizationLibP1 - .prepareRecollateralizationTrade(this, basketsHeld); + ( + bool doTrade, + TradeRequest memory req, + TradePrices memory prices + ) = RecollateralizationLibP1.prepareRecollateralizationTrade(this, basketsHeld); if (doTrade) { // Seize RSR if needed @@ -161,7 +165,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { } // Execute Trade - ITrade trade = tryTrade(kind, req); + ITrade trade = tryTrade(kind, req, prices); tradeEnd[kind] = trade.endTime(); } else { // Haircut time diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 568d3d7651..5d8655df70 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -323,6 +323,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } /// Returns the price of a BU, using the lot prices if `useLotPrice` is true + /// @param useLotPrice Whether to use lotPrice() or price() /// @return low {UoA/BU} The lower end of the price estimate /// @return high {UoA/BU} The upper end of the price estimate function _price(bool useLotPrice) internal view returns (uint192 low, uint192 high) { @@ -395,9 +396,10 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { uint192[] memory portions, uint192 amount ) external view returns (address[] memory erc20s, uint256[] memory quantities) { - require(basketNonces.length == portions.length, "portions does not mirror basketNonces"); + require(basketNonces.length == portions.length, "bad portions len"); IERC20[] memory erc20sAll = new IERC20[](assetRegistry.size()); + ICollateral[] memory collsAll = new ICollateral[](erc20sAll.length); uint192[] memory refAmtsAll = new uint192[](erc20sAll.length); uint256 len; // length of return arrays @@ -409,28 +411,40 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // Add-in refAmts contribution from historical basket for (uint256 j = 0; j < b.erc20s.length; ++j) { - IERC20 erc20 = b.erc20s[j]; // untestable: // previous baskets erc20s do not contain the zero address - if (address(erc20) == address(0)) continue; + if (address(b.erc20s[j]) == address(0)) continue; - // Ugly search through erc20sAll + // Search through erc20sAll uint256 erc20Index = type(uint256).max; for (uint256 k = 0; k < len; ++k) { - if (erc20 == erc20sAll[k]) { + if (b.erc20s[j] == erc20sAll[k]) { erc20Index = k; continue; } } // Add new ERC20 entry if not found - uint192 amt = portions[i].mul(b.refAmts[erc20], FLOOR); + uint192 amt = portions[i].mul(b.refAmts[b.erc20s[j]], FLOOR); if (erc20Index == type(uint256).max) { - erc20sAll[len] = erc20; - - // {ref} = {1} * {ref} - refAmtsAll[len] = amt; - ++len; + // New entry found + + try assetRegistry.toAsset(b.erc20s[j]) returns (IAsset asset) { + if (!asset.isCollateral()) continue; // skip token if not collateral + + erc20sAll[len] = b.erc20s[j]; + collsAll[len] = ICollateral(address(asset)); + + // {ref} = {1} * {ref} + refAmtsAll[len] = amt; + ++len; + } catch (bytes memory errData) { + // untested: + // OOG pattern tested in other contracts, cost to test here is high + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + // skip token if no longer registered or other non-gas issue + } } else { // {ref} = {1} * {ref} refAmtsAll[erc20Index] += amt; diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 666c604b46..abd9ec9ec9 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -8,6 +8,7 @@ import "../interfaces/IBroker.sol"; import "../interfaces/IMain.sol"; import "../interfaces/ITrade.sol"; import "../libraries/Fixed.sol"; +import "../libraries/NetworkConfigLib.sol"; import "./mixins/Component.sol"; import "../mixins/NetworkConfigLib.sol"; import "../plugins/trading/DutchTrade.sol"; @@ -101,7 +102,11 @@ contract BrokerP1 is ComponentP1, IBroker { // actions: // Transfers req.sellAmount of req.sell.erc20 from caller to `trade` // Calls trade.init() with appropriate parameters - function openTrade(TradeKind kind, TradeRequest memory req) external returns (ITrade) { + function openTrade( + TradeKind kind, + TradeRequest memory req, + TradePrices memory prices + ) external returns (ITrade) { require(!disabled, "broker disabled"); address caller = _msgSender(); @@ -116,7 +121,7 @@ contract BrokerP1 is ComponentP1, IBroker { if (kind == TradeKind.BATCH_AUCTION) { return newBatchAuction(req, caller); } - return newDutchAuction(req, ITrading(caller)); + return newDutchAuction(req, prices, ITrading(caller)); } /// Disable the broker until re-enabled by governance @@ -215,7 +220,11 @@ contract BrokerP1 is ComponentP1, IBroker { return trade; } - function newDutchAuction(TradeRequest memory req, ITrading caller) private returns (ITrade) { + function newDutchAuction( + TradeRequest memory req, + TradePrices memory prices, + ITrading caller + ) private returns (ITrade) { require(dutchAuctionLength > 0, "dutch auctions not enabled"); DutchTrade trade = DutchTrade(address(dutchTradeImplementation).clone()); trades[address(trade)] = true; @@ -227,7 +236,7 @@ contract BrokerP1 is ComponentP1, IBroker { req.sellAmount ); - trade.init(caller, req.sell, req.buy, req.sellAmount, dutchAuctionLength); + trade.init(caller, req.sell, req.buy, req.sellAmount, dutchAuctionLength, prices); return trade; } diff --git a/contracts/p1/Deployer.sol b/contracts/p1/Deployer.sol index 9dd23363a4..23f0b34ac1 100644 --- a/contracts/p1/Deployer.sol +++ b/contracts/p1/Deployer.sol @@ -248,4 +248,10 @@ contract DeployerP1 is IDeployer, Versioned { emit RTokenCreated(main, components.rToken, components.stRSR, owner, version()); return (address(components.rToken)); } + + /// Deploys a new RTokenAsset instance. Not needed during normal deployment flow + /// @param maxTradeVolume {UoA} The maximum trade volume for the RTokenAsset + function deployRTokenAsset(IRToken rToken, uint192 maxTradeVolume) external returns (IAsset) { + return new RTokenAsset(rToken, maxTradeVolume); + } } diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index 734c702d29..c46ed858b7 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -37,14 +37,12 @@ contract DistributorP1 is ComponentP1, IDistributor { IERC20 private rToken; address private furnace; address private stRSR; + address private rTokenTrader; + address private rsrTrader; function init(IMain main_, RevenueShare calldata dist) external initializer { __Component_init(main_); - - rsr = main_.rsr(); - rToken = IERC20(address(main_.rToken())); - furnace = address(main_.furnace()); - stRSR = address(main_.stRSR()); + cacheComponents(); _ensureNonZeroDistribution(dist.rTokenDist, dist.rsrDist); _setDistribution(FURNACE, RevenueShare(dist.rTokenDist, 0)); @@ -73,7 +71,8 @@ contract DistributorP1 is ComponentP1, IDistributor { /// Distribute revenue, in rsr or rtoken, per the distribution table. /// Requires that this contract has an allowance of at least /// `amount` tokens, from `from`, of the token at `erc20`. - /// @custom:interaction CEI + /// Only callable by RevenueTraders + /// @custom:protected CEI // let: // w = the map such that w[dest] = distribution[dest].{erc20}Shares // tokensPerShare = floor(amount / sum(values(w))) @@ -85,6 +84,10 @@ contract DistributorP1 is ComponentP1, IDistributor { // for dest where w[dest] != 0: // erc20.transferFrom(from, addrOf(dest), tokensPerShare * w[dest]) function distribute(IERC20 erc20, uint256 amount) external { + // Intentionally do not check notTradingPausedOrFrozen, since handled by caller + + address caller = _msgSender(); + require(caller == rsrTrader || caller == rTokenTrader, "RevenueTraders only"); require(erc20 == rsr || erc20 == rToken, "RSR or RToken"); bool isRSR = erc20 == rsr; // if false: isRToken uint256 tokensPerShare; @@ -123,12 +126,12 @@ contract DistributorP1 is ComponentP1, IDistributor { }); numTransfers++; } - emit RevenueDistributed(erc20, _msgSender(), amount); + emit RevenueDistributed(erc20, caller, amount); // == Interactions == for (uint256 i = 0; i < numTransfers; i++) { Transfer memory t = transfers[i]; - IERC20Upgradeable(address(t.erc20)).safeTransferFrom(_msgSender(), t.addrTo, t.amount); + IERC20Upgradeable(address(t.erc20)).safeTransferFrom(caller, t.addrTo, t.amount); } } @@ -182,6 +185,16 @@ contract DistributorP1 is ComponentP1, IDistributor { require(rTokenDist > 0 || rsrDist > 0, "no distribution defined"); } + /// Call after upgrade to >= 3.0.0 + function cacheComponents() public { + rsr = main.rsr(); + rToken = IERC20(address(main.rToken())); + furnace = address(main.furnace()); + stRSR = address(main.stRSR()); + rTokenTrader = address(main.rTokenTrader()); + rsrTrader = address(main.rsrTrader()); + } + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. diff --git a/contracts/p1/Furnace.sol b/contracts/p1/Furnace.sol index a261f217cc..c670f11235 100644 --- a/contracts/p1/Furnace.sol +++ b/contracts/p1/Furnace.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.19; import "../libraries/Fixed.sol"; +import "../libraries/NetworkConfigLib.sol"; import "../interfaces/IFurnace.sol"; import "./mixins/Component.sol"; import "../mixins/NetworkConfigLib.sol"; @@ -13,7 +14,7 @@ import "../mixins/NetworkConfigLib.sol"; contract FurnaceP1 is ComponentP1, IFurnace { using FixLib for uint192; - uint192 public constant MAX_RATIO = FIX_ONE; // {1} 100% + uint192 public constant MAX_RATIO = 1e14; // {1} 0.01% /// @custom:oz-upgrades-unsafe-allow state-variable-immutable // solhint-disable-next-line var-name-mixedcase uint48 public immutable PERIOD; // {seconds} 1 block based on network diff --git a/contracts/p1/Main.sol b/contracts/p1/Main.sol index e4922caa9b..43bddcaed7 100644 --- a/contracts/p1/Main.sol +++ b/contracts/p1/Main.sol @@ -40,13 +40,13 @@ contract MainP1 is Versioned, Initializable, Auth, ComponentRegistry, UUPSUpgrad /// @custom:refresher /// @custom:interaction CEI + /// @dev Not intended to be used in production, only for equivalence with P0 function poke() external { // == Refresher == assetRegistry.refresh(); // == CE block == - require(!frozen(), "frozen"); - furnace.melt(); + if (!frozen()) furnace.melt(); stRSR.payoutRewards(); } diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 75cbcc3b6b..942e2d2501 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -96,7 +96,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// Issue an RToken on the current basket, to a particular recipient /// @param recipient The address to receive the issued RTokens /// @param amount {qRTok} The quantity of RToken to issue - /// @custom:interaction + /// @custom:interaction RCEI // BU exchange rate cannot decrease, and it can only increase when < FIX_ONE. function issueTo(address recipient, uint256 amount) public notIssuancePausedOrFrozen { require(amount > 0, "Cannot issue zero"); @@ -104,6 +104,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // == Refresh == assetRegistry.refresh(); + furnace.melt(); // == Checks-effects block == @@ -111,8 +112,6 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // Ensure basket is ready, SOUND and not in warmup period require(basketHandler.isReady(), "basket not ready"); - - furnace.melt(); uint256 supply = totalSupply(); // Revert if issuance exceeds either supply throttle @@ -178,7 +177,6 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// @custom:interaction RCEI function redeemTo(address recipient, uint256 amount) public notFrozen { // == Refresh == - assetRegistry.refresh(); // solhint-disable-next-line no-empty-blocks try main.furnace().melt() {} catch {} // nice for the redeemer, but not necessary @@ -249,12 +247,11 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { uint192[] memory portions, address[] memory expectedERC20sOut, uint256[] memory minAmounts - ) external notFrozen returns (address[] memory erc20sOut, uint256[] memory amountsOut) { + ) external notFrozen { // == Refresh == - assetRegistry.refresh(); // solhint-disable-next-line no-empty-blocks - try main.furnace().melt() {} catch {} + try main.furnace().melt() {} catch {} // nice for the redeemer, but not necessary // == Checks and Effects == @@ -278,7 +275,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // === Get basket redemption amounts === - (erc20sOut, amountsOut) = basketHandler.quoteCustomRedemption( + (address[] memory erc20s, uint256[] memory amounts) = basketHandler.quoteCustomRedemption( basketNonces, portions, baskets @@ -286,18 +283,18 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // ==== Prorate redemption ==== // i.e, set amounts = min(amounts, balances * amount / totalSupply) - // where balances[i] = erc20sOut[i].balanceOf(backingManager) + // where balances[i] = erc20s[i].balanceOf(backingManager) // Bound each withdrawal by the prorata share, in case we're currently under-collateralized - for (uint256 i = 0; i < erc20sOut.length; ++i) { + for (uint256 i = 0; i < erc20s.length; ++i) { // {qTok} = {qTok} * {qRTok} / {qRTok} uint256 prorata = mulDiv256( - IERC20(erc20sOut[i]).balanceOf(address(backingManager)), + IERC20(erc20s[i]).balanceOf(address(backingManager)), amount, supply ); // FLOOR - if (prorata < amountsOut[i]) amountsOut[i] = prorata; + if (prorata < amounts[i]) amounts[i] = prorata; } // === Save initial recipient balances === @@ -313,15 +310,15 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // Distribute tokens; revert if empty redemption { bool allZero = true; - for (uint256 i = 0; i < erc20sOut.length; ++i) { - if (amountsOut[i] == 0) continue; // unregistered ERC20s will have 0 amount + for (uint256 i = 0; i < erc20s.length; ++i) { + if (amounts[i] == 0) continue; // unregistered ERC20s will have 0 amount if (allZero) allZero = false; // Send withdrawal - IERC20Upgradeable(erc20sOut[i]).safeTransferFrom( + IERC20Upgradeable(erc20s[i]).safeTransferFrom( address(backingManager), recipient, - amountsOut[i] + amounts[i] ); } if (allZero) revert("empty redemption"); diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index d588b7e5f7..9c7346edef 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -9,7 +9,7 @@ import "./mixins/Trading.sol"; import "./mixins/TradeLib.sol"; /// Trader Component that converts all asset balances at its address to a -/// single target asset and sends this asset to the Distributor. +/// single target asset and distributes this asset through the Distributor. /// @custom:oz-upgrades-unsafe-allow external-library-linking contract RevenueTraderP1 is TradingP1, IRevenueTrader { using FixLib for uint192; @@ -36,6 +36,15 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { cacheComponents(); } + /// Call after upgrade to >= 3.0.0 + function cacheComponents() public { + assetRegistry = main.assetRegistry(); + distributor = main.distributor(); + backingManager = main.backingManager(); + furnace = main.furnace(); + rToken = main.rToken(); + } + /// Settle a single trade + distribute revenue /// @param sell The sell token in the trade /// @return trade The ITrade contract settled @@ -47,104 +56,112 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { returns (ITrade trade) { trade = super.settleTrade(sell); // nonReentrant - distributeTokenToBuy(); + _distributeTokenToBuy(); // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } /// Distribute tokenToBuy to its destinations - /// @dev Special-case of manageToken(tokenToBuy, *) + /// @dev Special-case of manageTokens([tokenToBuy], *) /// @custom:interaction - function distributeTokenToBuy() public { - uint256 bal = tokenToBuy.balanceOf(address(this)); - tokenToBuy.safeApprove(address(distributor), 0); - tokenToBuy.safeApprove(address(distributor), bal); - distributor.distribute(tokenToBuy, bal); + function distributeTokenToBuy() external notTradingPausedOrFrozen { + _distributeTokenToBuy(); } - /// If erc20 is tokenToBuy, distribute it; else, sell it for tokenToBuy - /// @dev Intended to be used with multicall - /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION - /// @custom:interaction RCEI and nonReentrant + /// Process some number of tokens + /// If the tokenToBuy is included in erc20s, RevenueTrader will distribute it at end of the tx + /// @param erc20s The ERC20s to manage; can be tokenToBuy or anything registered + /// @param kinds The kinds of auctions to launch: DUTCH_AUCTION | BATCH_AUCTION + /// @custom:interaction not strictly RCEI; nonReentrant // let bal = this contract's balance of erc20 // checks: !paused (trading), !frozen // does nothing if erc20 == addr(0) or bal == 0 // - // If erc20 is tokenToBuy: - // actions: - // erc20.increaseAllowance(distributor, bal) - two safeApprove calls to support USDT - // distributor.distribute(erc20, this, bal) - // - // If erc20 is any other registered asset (checked): - // actions: - // tryTrade(kind, prepareTradeSell(toAsset(erc20), toAsset(tokenToBuy), bal)) - // (i.e, start a trade, selling as much of our bal of erc20 as we can, to buy tokenToBuy) + // For each ERC20: + // if erc20 is tokenToBuy: distribute it + // else: sell erc20 for tokenToBuy // untested: // OZ nonReentrant line is assumed to be working. cost/benefit of direct testing is high - function manageToken(IERC20 erc20, TradeKind kind) + function manageTokens(IERC20[] calldata erc20s, TradeKind[] calldata kinds) external nonReentrant notTradingPausedOrFrozen { - if (erc20 == tokenToBuy) { - distributeTokenToBuy(); - return; + uint256 len = erc20s.length; + require(len > 0, "empty erc20s list"); + require(len == kinds.length, "length mismatch"); + + // Calculate if the trade involves any RToken + // Distribute tokenToBuy if supplied in ERC20s list + bool involvesRToken = tokenToBuy == IERC20(address(rToken)); + for (uint256 i = 0; i < len; ++i) { + if (erc20s[i] == IERC20(address(rToken))) involvesRToken = true; + if (erc20s[i] == tokenToBuy) { + _distributeTokenToBuy(); + if (len == 1) return; // return early if tokenToBuy is only entry + } } - // == Refresh == - // Skip refresh() if data is from current block - if (erc20 != IERC20(address(rToken)) && tokenToBuy != IERC20(address(rToken))) { - IAsset sell_ = assetRegistry.toAsset(erc20); - IAsset buy_ = assetRegistry.toAsset(tokenToBuy); - if (sell_.lastSave() != uint48(block.timestamp)) sell_.refresh(); - if (buy_.lastSave() != uint48(block.timestamp)) buy_.refresh(); - } else if (assetRegistry.lastRefresh() != uint48(block.timestamp)) { - // Refresh everything only if RToken is being traded + // Cache assetToBuy + IAsset assetToBuy = assetRegistry.toAsset(tokenToBuy); + + // Refresh everything if RToken is involved + if (involvesRToken) { assetRegistry.refresh(); furnace.melt(); + } else { + // Otherwise: refresh just the needed assets and nothing more + for (uint256 i = 0; i < len; ++i) { + assetRegistry.toAsset(erc20s[i]).refresh(); + } + assetToBuy.refresh(); // invariant: can never be the RTokenAsset } - // == Checks/Effects == - // Above calls should not have changed registered assets, but just to be safe... - IAsset sell = assetRegistry.toAsset(erc20); - IAsset buy = assetRegistry.toAsset(tokenToBuy); - - require(address(trades[erc20]) == address(0), "trade open"); - require(erc20.balanceOf(address(this)) > 0, "0 balance"); - - (uint192 sellPrice, ) = sell.price(); // {UoA/tok} - (, uint192 buyPrice) = buy.price(); // {UoA/tok} - - require(buyPrice > 0 && buyPrice < FIX_MAX, "buy asset price unknown"); - - TradeInfo memory trade = TradeInfo({ - sell: sell, - buy: buy, - sellAmount: sell.bal(address(this)), - buyAmount: 0, - sellPrice: sellPrice, - buyPrice: buyPrice - }); - - // Whether dust or not, trade the non-target asset for the target asset - // Any asset with a broken price feed will trigger a revert here - (, TradeRequest memory req) = TradeLib.prepareTradeSell( - trade, - minTradeVolume, - maxTradeSlippage - ); - require(req.sellAmount > 1, "sell amount too low"); - - // == Interactions == - tryTrade(kind, req); + // Cache and validate buyHigh + (uint192 buyLow, uint192 buyHigh) = assetToBuy.lotPrice(); // {UoA/tok} + require(buyHigh > 0 && buyHigh < FIX_MAX, "buy asset price unknown"); + + // For each ERC20 that isn't the tokenToBuy, start an auction of the given kind + for (uint256 i = 0; i < len; ++i) { + IERC20 erc20 = erc20s[i]; + if (erc20 == tokenToBuy) continue; + + require(address(trades[erc20]) == address(0), "trade open"); + require(erc20.balanceOf(address(this)) > 0, "0 balance"); + + IAsset assetToSell = assetRegistry.toAsset(erc20); + (uint192 sellLow, uint192 sellHigh) = assetToSell.lotPrice(); // {UoA/tok} + + TradeInfo memory trade = TradeInfo({ + sell: assetToSell, + buy: assetToBuy, + sellAmount: assetToSell.bal(address(this)), + buyAmount: 0, + prices: TradePrices(sellLow, sellHigh, buyLow, buyHigh) + }); + + // Whether dust or not, trade the non-target asset for the target asset + // Any asset with a broken price feed will trigger a revert here + (, TradeRequest memory req) = TradeLib.prepareTradeSell( + trade, + minTradeVolume, + maxTradeSlippage + ); + require(req.sellAmount > 1, "sell amount too low"); + + // Launch trade + tryTrade(kinds[i], req, trade.prices); + } } - /// Call after upgrade to >= 3.0.0 - function cacheComponents() public { - assetRegistry = main.assetRegistry(); - distributor = main.distributor(); - backingManager = main.backingManager(); - furnace = main.furnace(); - rToken = main.rToken(); + // === Internal === + + /// Distribute tokenToBuy to its destinations + /// @dev Assumes notTradingPausedOrFrozen has already been checked! + function _distributeTokenToBuy() internal { + uint256 bal = tokenToBuy.balanceOf(address(this)); + tokenToBuy.safeApprove(address(distributor), 0); + tokenToBuy.safeApprove(address(distributor), bal); + distributor.distribute(tokenToBuy, bal); } /** diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 99cbe10d4b..8cde9097a2 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -11,6 +11,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../interfaces/IStRSR.sol"; import "../interfaces/IMain.sol"; import "../libraries/Fixed.sol"; +import "../libraries/NetworkConfigLib.sol"; import "../libraries/Permit.sol"; import "./mixins/Component.sol"; import "../mixins/NetworkConfigLib.sol"; @@ -42,7 +43,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab // solhint-disable-next-line var-name-mixedcase uint48 public immutable MIN_UNSTAKING_DELAY; // {s} based on network uint48 public constant MAX_UNSTAKING_DELAY = 31536000; // {s} 1 year - uint192 public constant MAX_REWARD_RATIO = FIX_ONE; // {1} 100% + uint192 public constant MAX_REWARD_RATIO = 1e14; // {1} 0.01% // === ERC20 === string public name; // immutable @@ -210,14 +211,13 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab /// Assign reward payouts to the staker pool /// @custom:refresher function payoutRewards() external { - requireNotFrozen(); _payoutRewards(); } /// Stakes an RSR `amount` on the corresponding RToken to earn yield and over-collateralize /// the system /// @param rsrAmount {qRSR} - /// @dev Staking continues while paused/frozen, without reward handouts + /// @dev Staking continues while paused/frozen, with reward handouts /// @custom:interaction CEI // checks: // 0 < rsrAmount @@ -233,7 +233,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab function stake(uint256 rsrAmount) public { require(rsrAmount > 0, "Cannot stake zero"); - if (!main.frozen()) _payoutRewards(); + _payoutRewards(); // Mint new stakes mintStakes(_msgSender(), rsrAmount); @@ -382,10 +382,12 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab if (rsrAmount == 0) return; + // Payout rewards before updating draftRSR + _payoutRewards(); + // ==== Transfer RSR from the draft pool totalDrafts = newTotalDrafts; draftRSR = newDraftRSR; - emit UnstakingCancelled(firstId, endId, draftEra, account, rsrAmount); // Mint new stakes @@ -953,7 +955,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab /// @custom:governance function setRewardRatio(uint192 val) public { requireGovernanceOnly(); - if (!main.frozen()) _payoutRewards(); + _payoutRewards(); require(val <= MAX_REWARD_RATIO, "invalid rewardRatio"); emit RewardRatioSet(rewardRatio, val); rewardRatio = val; diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index a4162df47a..8a9e0990f3 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -33,14 +33,14 @@ struct TradingContext { /** * @title RecollateralizationLibP1 - * @notice An informal extension of the Trading mixin that provides trade preparation views + * @notice An informal extension of BackingManager that implements the rebalancing logic * Users: * - BackingManager - * - RTokenAsset + * - RTokenAsset (uses `basketRange()`) * * Interface: - * 1. prepareRecollateralizationTrade (external) - * 2. basketRange (internal) + * 1. prepareRecollateralizationTrade() (external) + * 2. basketRange() (internal) */ library RecollateralizationLibP1 { using FixLib for uint192; @@ -49,16 +49,21 @@ library RecollateralizationLibP1 { /// Select and prepare a trade that moves us closer to capitalization, using the /// basket range to avoid overeager/duplicate trading. + /// The basket range is the full range of projected outcomes for the rebalancing process. // This is the "main loop" for recollateralization trading: // actions: // let range = basketRange(...) // let trade = nextTradePair(...) // if trade.sell is not a defaulted collateral, prepareTradeToCoverDeficit(...) - // otherwise, prepareTradeSell(...) with a 0 minBuyAmount + // otherwise, prepareTradeSell(...) taking the minBuyAmount as the dependent variable function prepareRecollateralizationTrade(IBackingManager bm, BasketRange memory basketsHeld) external view - returns (bool doTrade, TradeRequest memory req) + returns ( + bool doTrade, + TradeRequest memory req, + TradePrices memory prices + ) { IMain main = bm.main(); @@ -92,17 +97,23 @@ library RecollateralizationLibP1 { // Don't trade if no pair is selected if (address(trade.sell) == address(0) || address(trade.buy) == address(0)) { - return (false, req); + return (false, req, prices); } - // If we are selling an unpriced asset or UNSOUND collateral, do not try to cover deficit + // If we are selling a fully unpriced asset or UNSOUND collateral, do not cover deficit + // untestable: + // sellLow will not be zero, those assets are skipped in nextTradePair if ( - trade.sellPrice == 0 || + trade.prices.sellLow == 0 || (trade.sell.isCollateral() && ICollateral(address(trade.sell)).status() != CollateralStatus.SOUND) ) { + // Emergency case + // Set minBuyAmount as a function of sellAmount (doTrade, req) = trade.prepareTradeSell(ctx.minTradeVolume, ctx.maxTradeSlippage); } else { + // Normal case + // Set sellAmount as a function of minBuyAmount (doTrade, req) = trade.prepareTradeToCoverDeficit( ctx.minTradeVolume, ctx.maxTradeSlippage @@ -112,16 +123,16 @@ library RecollateralizationLibP1 { // At this point doTrade _must_ be true, otherwise nextTradePair assumptions are broken assert(doTrade); - return (doTrade, req); + return (doTrade, req, trade.prices); } // Compute the target basket range // Algorithm intuition: Trade conservatively. Quantify uncertainty based on the proportion of - // token balances requiring trading vs not requiring trading. Decrease uncertainty the largest - // amount possible with each trade. + // token balances requiring trading vs not requiring trading. Seek to decrease uncertainty + // the largest amount possible with each trade. // // How do we know this algorithm converges? - // Assumption: constant prices + // Assumption: constant oracle prices; monotonically increasing refPerTok() // Any volume traded narrows the BU band. Why: // - We might increase `basketsHeld.bottom` from run-to-run, but will never decrease it // - We might decrease the UoA amount of excess balances beyond `basketsHeld.bottom` from @@ -146,14 +157,14 @@ library RecollateralizationLibP1 { // - range.top = min(rToken.basketsNeeded, basketsHeld.top - least baskets missing // + most baskets surplus) // - range.bottom = min(rToken.basketsNeeded, basketsHeld.bottom + least baskets purchaseable) - // where "least baskets purchaseable" involves trading at unfavorable prices, - // incurring maxTradeSlippage, and taking up to a minTradeVolume loss due to dust. + // where "least baskets purchaseable" involves trading at the worst price, + // incurring the full maxTradeSlippage, and taking up to a minTradeVolume loss due to dust. function basketRange(TradingContext memory ctx, Registry memory reg) internal view returns (BasketRange memory range) { - (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.price(); // {UoA/BU} + (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.lotPrice(); // {UoA/BU} uint192 basketsNeeded = ctx.rToken.basketsNeeded(); // {BU} // Cap ctx.basketsHeld.top @@ -192,7 +203,11 @@ library RecollateralizationLibP1 { !TradeLib.isEnoughToSell(reg.assets[i], bal, lotLow, ctx.minTradeVolume) ) continue; } + (uint192 low, uint192 high) = reg.assets[i].price(); // {UoA/tok} + // price() is better than lotPrice() here: it's important to not underestimate how + // much value could be in a token that is unpriced by using a decaying high lotPrice. + // price() will return [0, FIX_MAX] in this case, which is preferable. // throughout these sections +/- is same as Fix.plus/Fix.minus and is Fix.gt/.lt @@ -283,8 +298,10 @@ library RecollateralizationLibP1 { /// deficit Deficit collateral OR address(0) /// sellAmount {sellTok} Surplus amount (whole tokens) /// buyAmount {buyTok} Deficit amount (whole tokens) - /// sellPrice {UoA/sellTok} The worst-case price of the sell token on secondary markets - /// buyPrice {UoA/sellTok} The worst-case price of the buy token on secondary markets + /// prices.sellLow {UoA/sellTok} The worst-case price of the sell token on secondary markets + /// prices.sellHigh {UoA/sellTok} The best-case price of the sell token on secondary markets + /// prices.buyLow {UoA/buyTok} The best-case price of the buy token on secondary markets + /// prices.buyHigh {UoA/buyTok} The worst-case price of the buy token on secondary markets /// // Defining "sell" and "buy": // If bal(e) > (quantity(e) * range.top), then e is in surplus by the difference @@ -294,16 +311,17 @@ library RecollateralizationLibP1 { // `trade.sell` is the token from erc20s with the greatest surplus value (in UoA), // and sellAmount is the quantity of that token that it's in surplus (in qTok). // if `trade.sell` == 0, then no token is in surplus by at least minTradeSize, - // and `trade.sellAmount` and `trade.sellPrice` are unset. + // and `trade.sellAmount` and `trade.sellLow` / `trade.sellHigh are unset. // // `trade.buy` is the token from erc20s with the greatest deficit value (in UoA), // and buyAmount is the quantity of that token that it's in deficit (in qTok). // if `trade.buy` == 0, then no token is in deficit at all, - // and `trade.buyAmount` and `trade.buyPrice` are unset. + // and `trade.buyAmount` and `trade.buyLow` / `trade.buyHigh` are unset. // // Then, just if we have a buy asset and no sell asset, consider selling available RSR. // // Prefer selling assets in this order: DISABLED -> SOUND -> IFFY. + // Sell IFFY last because it may recover value in the future. // All collateral in the basket have already been guaranteed to be SOUND by upstream checks. function nextTradePair( TradingContext memory ctx, @@ -325,18 +343,8 @@ library RecollateralizationLibP1 { uint192 needed = range.top.mul(ctx.quantities[i], CEIL); // {tok} if (bal.gt(needed)) { - uint192 low; // {UoA/sellTok} - - // this wonky block is just for getting around the stack limit - { - uint192 high; // {UoA/sellTok} - (low, high) = reg.assets[i].price(); // {UoA/sellTok} - - // Skip worthless assets - if (high == 0) continue; - } - - (uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/sellTok} + (uint192 lotLow, uint192 lotHigh) = reg.assets[i].lotPrice(); // {UoA/sellTok} + if (lotHigh == 0) continue; // skip over worthless assets // {UoA} = {sellTok} * {UoA/sellTok} uint192 delta = bal.minus(needed).mul(lotLow, FLOOR); @@ -360,7 +368,8 @@ library RecollateralizationLibP1 { ) { trade.sell = reg.assets[i]; trade.sellAmount = bal.minus(needed); - trade.sellPrice = low; + trade.prices.sellLow = lotLow; + trade.prices.sellHigh = lotHigh; maxes.surplusStatus = status; maxes.surplus = delta; @@ -371,16 +380,17 @@ library RecollateralizationLibP1 { if (bal.lt(needed)) { uint192 amtShort = needed.minus(bal); // {buyTok} - (, uint192 high) = reg.assets[i].price(); // {UoA/buyTok} + (uint192 lotLow, uint192 lotHigh) = reg.assets[i].lotPrice(); // {UoA/buyTok} // {UoA} = {buyTok} * {UoA/buyTok} - uint192 delta = amtShort.mul(high, CEIL); + uint192 delta = amtShort.mul(lotHigh, CEIL); // The best asset to buy is whichever asset has the largest deficit if (delta.gt(maxes.deficit)) { - trade.buy = ICollateral(address(reg.assets[i])); + trade.buy = reg.assets[i]; trade.buyAmount = amtShort; - trade.buyPrice = high; + trade.prices.buyLow = lotLow; + trade.prices.buyHigh = lotHigh; maxes.deficit = delta; } @@ -395,16 +405,16 @@ library RecollateralizationLibP1 { uint192 rsrAvailable = rsrAsset.bal(address(ctx.bm)).plus( rsrAsset.bal(address(ctx.stRSR)) ); - (uint192 low, uint192 high) = rsrAsset.price(); // {UoA/tok} - (uint192 lotLow, ) = rsrAsset.lotPrice(); // {UoA/tok} + (uint192 lotLow, uint192 lotHigh) = rsrAsset.lotPrice(); // {UoA/RSR} if ( - high > 0 && + lotHigh > 0 && TradeLib.isEnoughToSell(rsrAsset, rsrAvailable, lotLow, ctx.minTradeVolume) ) { trade.sell = rsrAsset; trade.sellAmount = rsrAvailable; - trade.sellPrice = low; + trade.prices.sellLow = lotLow; + trade.prices.sellHigh = lotHigh; } } } diff --git a/contracts/p1/mixins/TradeLib.sol b/contracts/p1/mixins/TradeLib.sol index 22f612ef86..769cd569ce 100644 --- a/contracts/p1/mixins/TradeLib.sol +++ b/contracts/p1/mixins/TradeLib.sol @@ -13,33 +13,32 @@ struct TradeInfo { IAsset buy; uint192 sellAmount; // {sellTok} uint192 buyAmount; // {buyTok} - uint192 sellPrice; // {UoA/sellTok} can be 0 - uint192 buyPrice; // {UoA/buyTok} + TradePrices prices; } /** * @title TradeLib * @notice An internal lib for preparing individual trades on particular asset pairs * Users: - * - BackingManagerLib + * - RecollateralizationLib * - RevenueTrader */ library TradeLib { using FixLib for uint192; /// Prepare a trade to sell `trade.sellAmount` that guarantees a reasonable closing price, - /// without explicitly aiming at a particular quantity to purchase. + /// without explicitly aiming at a particular buy amount. /// @param trade: - /// sell != 0, sellAmount >= 0 {sellTok}, sellPrice >= 0 {UoA/sellTok} - /// buy != 0, buyAmount (unused) {buyTok}, buyPrice > 0 {UoA/buyTok} + /// sell != 0, sellAmount >= 0 {sellTok}, prices.sellLow >= 0 {UoA/sellTok} + /// buy != 0, buyAmount (unused) {buyTok}, prices.buyHigh > 0 {UoA/buyTok} /// @return notDust True when the trade is larger than the dust amount /// @return req The prepared trade request to send to the Broker // // If notDust is true, then the returned trade request satisfies: // req.sell == trade.sell and req.buy == trade.buy, - // req.minBuyAmount * trade.buyPrice ~= - // trade.sellAmount * trade.sellPrice * (1-maxTradeSlippage), - // req.sellAmount == min(trade.sell.maxTradeSize().toQTok(), trade.sellAmount.toQTok(sell) + // req.minBuyAmount * trade.prices.buyHigh ~= + // trade.sellAmount * trade.prices.sellLow * (1-maxTradeSlippage), + // req.sellAmount == min(trade.sell.maxTradeSize(), trade.sellAmount) // 1 < req.sellAmount // // If notDust is false, no trade exists that satisfies those constraints. @@ -49,14 +48,21 @@ library TradeLib { uint192 maxTradeSlippage ) internal view returns (bool notDust, TradeRequest memory req) { // checked for in RevenueTrader / CollateralizatlionLib - assert(trade.buyPrice > 0 && trade.buyPrice < FIX_MAX && trade.sellPrice < FIX_MAX); - - (uint192 lotLow, uint192 lotHigh) = trade.sell.lotPrice(); + assert( + trade.prices.buyHigh > 0 && + trade.prices.buyHigh < FIX_MAX && + trade.prices.sellLow < FIX_MAX + ); - notDust = isEnoughToSell(trade.sell, trade.sellAmount, lotLow, minTradeVolume); + notDust = isEnoughToSell( + trade.sell, + trade.sellAmount, + trade.prices.sellLow, + minTradeVolume + ); // Cap sell amount - uint192 maxSell = maxTradeSize(trade.sell, trade.buy, lotHigh); // {sellTok} + uint192 maxSell = maxTradeSize(trade.sell, trade.buy, trade.prices.sellHigh); // {sellTok} uint192 s = trade.sellAmount > maxSell ? maxSell : trade.sellAmount; // {sellTok} // Calculate equivalent buyAmount within [0, FIX_MAX] @@ -78,30 +84,29 @@ library TradeLib { /// Assuming we have `trade.sellAmount` sell tokens available, prepare a trade to cover as /// much of our deficit of `trade.buyAmount` buy tokens as possible, given expected trade - /// slippage and the sell asset's maxTradeVolume(). + /// slippage and maxTradeVolume(). /// @param trade: /// sell != 0 /// buy != 0 /// sellAmount (unused) {sellTok} /// buyAmount >= 0 {buyTok} - /// sellPrice > 0 {UoA/sellTok} - /// buyPrice > 0 {UoA/buyTok} + /// prices.sellLow > 0 {UoA/sellTok} + /// prices.buyHigh > 0 {UoA/buyTok} /// @return notDust Whether the prepared trade is large enough to be worth trading /// @return req The prepared trade request to send to the Broker // // Returns prepareTradeSell(trade, rules), where // req.sellAmount = min(trade.sellAmount, - // trade.buyAmount * (trade.buyPrice / trade.sellPrice) / (1-maxTradeSlippage)) + // trade.buyAmount * (buyHigh / sellLow) / (1-maxTradeSlippage)) // i.e, the minimum of trade.sellAmount and (a sale amount that, at current prices and // maximum slippage, will yield at least the requested trade.buyAmount) // // Which means we should get that, if notDust is true, then: // req.sell = sell and req.buy = buy // - // 1 <= req.minBuyAmount <= max(trade.buyAmount, buy.minTradeSize()).toQTok(trade.buy) - // 1 < req.sellAmount <= min(trade.sellAmount.toQTok(trade.sell), - // sell.maxTradeSize().toQTok(trade.sell)) - // req.minBuyAmount ~= trade.sellAmount * sellPrice / buyPrice * (1-maxTradeSlippage) + // 1 <= req.minBuyAmount <= max(trade.buyAmount, buy.minTradeSize())) + // 1 < req.sellAmount <= min(trade.sellAmount, sell.maxTradeSize()) + // req.minBuyAmount ~= trade.sellAmount * sellLow / buyHigh * (1-maxTradeSlippage) // // req.sellAmount (and req.minBuyAmount) are maximal satisfying all these conditions function prepareTradeToCoverDeficit( @@ -110,17 +115,24 @@ library TradeLib { uint192 maxTradeSlippage ) internal view returns (bool notDust, TradeRequest memory req) { assert( - trade.sellPrice > 0 && - trade.sellPrice < FIX_MAX && - trade.buyPrice > 0 && - trade.buyPrice < FIX_MAX + trade.prices.sellLow > 0 && + trade.prices.sellLow < FIX_MAX && + trade.prices.buyHigh > 0 && + trade.prices.buyHigh < FIX_MAX ); // Don't buy dust. - trade.buyAmount = fixMax(trade.buyAmount, minTradeSize(minTradeVolume, trade.buyPrice)); + trade.buyAmount = fixMax( + trade.buyAmount, + minTradeSize(minTradeVolume, trade.prices.buyHigh) + ); // {sellTok} = {buyTok} * {UoA/buyTok} / {UoA/sellTok} - uint192 exactSellAmount = trade.buyAmount.mulDiv(trade.buyPrice, trade.sellPrice, CEIL); + uint192 exactSellAmount = trade.buyAmount.mulDiv( + trade.prices.buyHigh, + trade.prices.sellLow, + CEIL + ); // exactSellAmount: Amount to sell to buy `deficitAmount` if there's no slippage // slippedSellAmount: Amount needed to sell to buy `deficitAmount`, counting slippage @@ -166,11 +178,8 @@ library TradeLib { IAsset buy, uint192 price ) private view returns (uint192) { - // untestable: - // Price cannot be 0, it would've been filtered before in `prepareTradeSell` - uint192 size = price == 0 - ? FIX_MAX - : fixMin(sell.maxTradeVolume(), buy.maxTradeVolume()).div(price, FLOOR); + // D18{tok} = D18{UoA} / D18{UoA/tok} + uint192 size = fixMin(sell.maxTradeVolume(), buy.maxTradeVolume()).safeDiv(price, FLOOR); return size > 0 ? size : 1; } } diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 9a84b1d726..1cfa49c191 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -112,14 +112,19 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl // effects: // trades' = trades.set(req.sell, tradeID) // tradesOpen' = tradesOpen + 1 - function tryTrade(TradeKind kind, TradeRequest memory req) internal returns (ITrade trade) { + function tryTrade( + TradeKind kind, + TradeRequest memory req, + TradePrices memory prices + ) internal returns (ITrade trade) { + /* */ IERC20 sell = req.sell.erc20(); assert(address(trades[sell]) == address(0)); IERC20Upgradeable(address(sell)).safeApprove(address(broker), 0); IERC20Upgradeable(address(sell)).safeApprove(address(broker), req.sellAmount); - trade = broker.openTrade(kind, req); + trade = broker.openTrade(kind, req, prices); trades[sell] = trade; tradesOpen++; tradesNonce++; diff --git a/contracts/plugins/assets/Asset.sol b/contracts/plugins/assets/Asset.sol index b35c7941e5..ba6908c8a8 100644 --- a/contracts/plugins/assets/Asset.sol +++ b/contracts/plugins/assets/Asset.sol @@ -137,14 +137,21 @@ contract Asset is IAsset, VersionedAsset { // if the price feed is broken, use a decayed historical value uint48 delta = uint48(block.timestamp) - lastSave; // {s} - if (delta >= priceTimeout) return (0, 0); // no price after timeout elapses + if (delta <= oracleTimeout) { + lotLow = savedLowPrice; + lotHigh = savedHighPrice; + } else if (delta >= oracleTimeout + priceTimeout) { + return (0, 0); // no price after full timeout + } else { + // oracleTimeout <= delta <= oracleTimeout + priceTimeout - // {1} = {s} / {s} - uint192 lotMultiplier = divuu(priceTimeout - delta, priceTimeout); + // {1} = {s} / {s} + uint192 lotMultiplier = divuu(oracleTimeout + priceTimeout - delta, priceTimeout); - // {UoA/tok} = {UoA/tok} * {1} - lotLow = savedLowPrice.mul(lotMultiplier); - lotHigh = savedHighPrice.mul(lotMultiplier); + // {UoA/tok} = {UoA/tok} * {1} + lotLow = savedLowPrice.mul(lotMultiplier); + lotHigh = savedHighPrice.mul(lotMultiplier); + } } assert(lotLow <= lotHigh); } diff --git a/contracts/plugins/mocks/InvalidBrokerMock.sol b/contracts/plugins/mocks/InvalidBrokerMock.sol index 67e4ae98c7..525d1f7559 100644 --- a/contracts/plugins/mocks/InvalidBrokerMock.sol +++ b/contracts/plugins/mocks/InvalidBrokerMock.sol @@ -39,14 +39,12 @@ contract InvalidBrokerMock is ComponentP0, IBroker { } /// Invalid implementation - Reverts - function openTrade(TradeKind, TradeRequest memory req) - external - view - notTradingPausedOrFrozen - returns (ITrade) - { + function openTrade( + TradeKind, + TradeRequest memory, + TradePrices memory + ) external view notTradingPausedOrFrozen returns (ITrade) { require(!disabled, "broker disabled"); - req; // Revert when opening trades revert("Failure opening trade"); diff --git a/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol b/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol index b19793ffaf..64b3904230 100644 --- a/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol +++ b/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol @@ -44,7 +44,7 @@ contract RevenueTraderP1InvalidReverts is TradingP1, IRevenueTrader { /// Processes a single token; unpermissioned /// Reverts for testing purposes - function manageToken(IERC20, TradeKind) external notTradingPausedOrFrozen { + function manageTokens(IERC20[] memory, TradeKind[] memory) external notTradingPausedOrFrozen { rToken = rToken; // silence warning revert(); } diff --git a/contracts/plugins/mocks/RevenueTraderBackComp.sol b/contracts/plugins/mocks/RevenueTraderBackComp.sol index f7f8a0855b..ed76f53346 100644 --- a/contracts/plugins/mocks/RevenueTraderBackComp.sol +++ b/contracts/plugins/mocks/RevenueTraderBackComp.sol @@ -10,8 +10,12 @@ interface IRevenueTraderComp { // RevenueTrader compatible with version 2 contract RevenueTraderCompatibleV2 is RevenueTraderP1, IRevenueTraderComp { function manageToken(IERC20 sell) external notTradingPausedOrFrozen { + IERC20[] memory erc20s = new IERC20[](1); + erc20s[0] = sell; + TradeKind[] memory kinds = new TradeKind[](1); + kinds[0] = TradeKind.DUTCH_AUCTION; // Mirror V3 logic (only the section relevant to tests) - this.manageToken(sell, TradeKind.DUTCH_AUCTION); + this.manageTokens(erc20s, kinds); } function version() public pure virtual override(Versioned, IVersioned) returns (string memory) { diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 6c8f90fc72..057262f7c5 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -4,18 +4,20 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../../libraries/Fixed.sol"; +import "../../libraries/NetworkConfigLib.sol"; import "../../interfaces/IAsset.sol"; +import "../../interfaces/IBroker.sol"; import "../../interfaces/ITrade.sol"; import "../../mixins/NetworkConfigLib.sol"; uint192 constant FORTY_PERCENT = 4e17; // {1} 0.4 uint192 constant SIXTY_PERCENT = 6e17; // {1} 0.6 -// Exponential price decay with base (999999/1000000). Price starts at 1000x and decays to 1x +// Exponential price decay with base (999999/1000000). Price starts at 1000x and decays to <1x // A 30-minute auction on a chain with a 12-second blocktime has a ~10.87% price drop per block -// during the geometric/exponential period and a 0.05% drop during the linear period. +// during the geometric/exponential period and a 0.05% drop per block during the linear period. // 30-minutes is the recommended length of auction for a chain with 12-second blocktimes, but -// longer and shorter times can be used as well. The pricing algorithm does not degrade +// longer and shorter times can be used as well. The pricing method does not degrade // beyond the degree to which less overall blocktime means necessarily larger price drops. uint192 constant MAX_EXP = 6907752 * FIX_ONE; // {1} (1000000/999999)^6907752 = ~1000x uint192 constant BASE = 999999e12; // {1} (999999/1000000) @@ -32,12 +34,13 @@ uint192 constant BASE = 999999e12; // {1} (999999/1000000) * Over the last 60% of the auction the price falls from the best plausible price to the worst * price, linearly. The worst price is further discounted by the maxTradeSlippage as a fraction * of how far from minTradeVolume to maxTradeVolume the trade lies. - * At maxTradeVolume, no further discount is applied. + * At maxTradeVolume, no additonal discount beyond the oracle errors is applied. * * To bid: - * - Call `bidAmount()` view to check prices at various timestamps - * - Wait until a desirable block is reached (hopefully not in the first 40% of the auction) - * - Provide approval of buy tokens and call bid(). The swap will be atomic + * 1. Call `bidAmount()` view to check prices at various timestamps + * 2. Provide approval of sell tokens for precisely the `bidAmount()` desired + * 3. Wait until a desirable block is reached (hopefully not in the first 40% of the auction) + * 4. Call bid() */ contract DutchTrade is ITrade { using FixLib for uint192; @@ -110,7 +113,8 @@ contract DutchTrade is ITrade { IAsset sell_, IAsset buy_, uint256 sellAmount_, - uint48 auctionLength + uint48 auctionLength, + TradePrices memory prices ) external stateTransition(TradeStatus.NOT_STARTED, TradeStatus.OPEN) { assert( address(sell_) != address(0) && @@ -119,10 +123,8 @@ contract DutchTrade is ITrade { ); // misuse by caller // Only start dutch auctions under well-defined prices - (uint192 sellLow, uint192 sellHigh) = sell_.price(); // {UoA/sellTok} - (uint192 buyLow, uint192 buyHigh) = buy_.price(); // {UoA/buyTok} - require(sellLow > 0 && sellHigh < FIX_MAX, "bad sell pricing"); - require(buyLow > 0 && buyHigh < FIX_MAX, "bad buy pricing"); + require(prices.sellLow > 0 && prices.sellHigh < FIX_MAX, "bad sell pricing"); + require(prices.buyLow > 0 && prices.buyHigh < FIX_MAX, "bad buy pricing"); origin = origin_; sell = sell_.erc20(); @@ -135,14 +137,14 @@ contract DutchTrade is ITrade { // {1} uint192 slippage = _slippage( - sellAmount.mul(sellHigh, FLOOR), // auctionVolume + sellAmount.mul(prices.sellHigh, FLOOR), // auctionVolume origin.minTradeVolume(), // minTradeVolume fixMin(sell_.maxTradeVolume(), buy_.maxTradeVolume()) // maxTradeVolume ); // {buyTok/sellTok} = {UoA/sellTok} * {1} / {UoA/buyTok} - lowPrice = sellLow.mulDiv(FIX_ONE - slippage, buyHigh, FLOOR); - middlePrice = sellHigh.div(buyLow, CEIL); // no additional slippage + lowPrice = prices.sellLow.mulDiv(FIX_ONE - slippage, prices.buyHigh, FLOOR); + middlePrice = prices.sellHigh.div(prices.buyLow, CEIL); // no additional slippage // highPrice = 1000 * middlePrice assert(lowPrice <= middlePrice); @@ -205,12 +207,17 @@ contract DutchTrade is ITrade { erc20.safeTransfer(address(origin), erc20.balanceOf(address(this))); } - /// @return True if the trade can be settled. + /// @return true iff the trade can be settled. // Guaranteed to be true some time after init(), until settle() is called function canSettle() external view returns (bool) { return status == TradeStatus.OPEN && (bidder != address(0) || block.timestamp > endTime); } + /// @return {qSellTok} The size of the lot being sold, in token quanta + function lot() external view returns (uint256) { + return sellAmount.shiftl_toUint(int8(sell.decimals())); + } + // === Private === /// Return a sliding % from 0 (at maxTradeVolume) to maxTradeSlippage (at minTradeVolume) @@ -225,6 +232,9 @@ contract DutchTrade is ITrade { ) private view returns (uint192 slippage) { slippage = origin.maxTradeSlippage(); // {1} if (maxTradeVolume <= minTradeVolume || auctionVolume < minTradeVolume) return slippage; + + // untestable: + // auctionVolume already sized based on maxTradeVolume, so this will not be true if (auctionVolume > maxTradeVolume) return 0; // 0% slippage beyond maxTradeVolume // {1} = {1} * ({UoA} - {UoA}} / ({UoA} - {UoA}) diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index eb93b62a9c..ab954dbe8a 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -22,7 +22,7 @@ contract GnosisTrade is ITrade { // Upper bound for the max number of orders we're happy to have the auction clear in; // When we have good price information, this determines the minimum buy amount per order. - uint96 public constant MAX_ORDERS = 1e5; + uint96 public constant MAX_ORDERS = 5000; // bounded to avoid going beyond block gas limit // raw "/" for compile-time const uint192 public constant DEFAULT_MIN_BID = FIX_ONE / 100; // {tok} diff --git a/docs/mev.md b/docs/mev.md index 12eda2189d..8d2cfdf714 100644 --- a/docs/mev.md +++ b/docs/mev.md @@ -4,4 +4,59 @@ This document is intended to serve as a resource for MEV searchers and others lo ## Overview -TODO +Like any protocol, the Reserve Protocol causes some amount of MEV. While the full extent is not necessarily described here, there are two obvious examples of MEV opportunities in the protocol: + +1. Issuance/Redemption +2. Auctions + +### 1. Issuance/Redemption + +MEV searchers can arb an RToken's issuance/redemption price against the broader market, whether that be AMM pools or CEX prices. This is a fairly standard MEV opportunity and it works the way an MEV searcher would expect. All that one needs to be able to do to participate is execute `issue()` or `redeem()` on the `RToken.sol` contract. The issuance requires approvals in advance, while the `redeem()` does not. You can find more documentation elsewhere in the repo about the properties of our `issue()`/`redeem()`/`redeemCustom()` functions. In short, they are atomic and work the way a searcher would expect, with the caveat that `redeem()` will revert during rebalancing (`redeemCustom()` does not). + +### 2. Auctions + +To bid in the protocol's single-lot, atomic, falling-price dutch auctions, an MEV searcher needs to monitor all `Broker` instances associated with RTokens. Whenever a `Broker` emits a `TradeStarted(ITrade indexed trade, IERC20 indexed sell, IERC20 indexed buy, uint256 sellAmount, uint256 minBuyAmount)` event, the `trade.KIND()` can be checked to see what kind of trade it is. + +- if trade.KIND() == 0, then it is a [DutchTrade](../contracts/plugins/trading/DutchTrade.sol) +- if trade.KIND() == 1, then it is a [GnosisTrade](../contracts/plugins/trading/GnosisTrade.sol) + +#### DutchTrade + +Bidding instructions from the `DutchTrade` contract: + +`DutchTrade` (relevant) interface: + +```solidity +function bid() external; // execute a bid at the current block timestamp + +function sell() external view returns (IERC20); + +function buy() external view returns (IERC20); + +function status() external view returns (uint8); // 0: not_started, 1: active, 2: closed, 3: mid-tx only + +function lot() external view returns (uint256); // {qSellTok} the number of tokens being sold + +function bidAmount(uint48 timestamp) external view returns (uint256); // {qBuyTok} the number of tokens required to buy the lot, at a particular timestamp + +``` + +To participate: + +1. Call `status()` view; the auction is ongoing if return value is 1 +2. Call `lot()` to see the number of tokens being sold +3. Call `bidAmount()` to see the number of tokens required to buy the lot, at various timestamps +4. After finding an attractive bidAmount, provide an approval for the `buy()` token. The spender should be the `DutchTrade` contract. + **Note**: it is very important to set tight approvals! Do not set more than the `bidAmount()` for the desired bidding block else reorgs present risk. +5. Wait until the desired block is reached (hopefully not in the first 40% of the auction) +6. Call `bid()`. If someone else completes the auction first, this will revert with the error message "bid already received". Approvals do not have to be revoked in the event that another MEV searcher wins the auction. (Though ideally the searcher includes the approval in the same tx they `bid()`) + +#### GnosisTrade + +`GnosisTrade.sol` implements a batch auction on top of Gnosis's [EasyAuction](https://github.com/gnosis/ido-contracts/blob/main/contracts/EasyAuction.sol) platform. In general a batch auction is designed to minimize MEV, and indeed that's why it was chosen in the first place. Both types of auctions (batch + dutch) can be opened at anytime, but the expectation is that dutch auctions will be preferred by MEV searchers because they are more likely to be profitable. + +However, if a batch auction is launched, an MEV searcher may still be able to profit. In order to bid in the auction, the searcher must call `function placeSellOrders(uint256 auctionId, uint96[] memory _minBuyAmounts, uint96[] memory _sellAmounts, bytes32[] memory _prevSellOrders, bytes calldata allowListCallData)`, providing an approval in advance. This call will escrow `_sellAmounts` tokens in EasyAuction for the remaining duration of the auction. Once the auction is over, anyone can settle the auction directly in EasyAuction via `settleAuction(uint256 auctionId)`, or by calling `settleTrade(IERC20 erc20)` on the `ITrading` instance in our system that started the trade (either BackingManager or a RevenueTrader). + +Since the opportunity is not atomic, it is not likely MEV searchers will be very interested in this option. Still, there may be batch auctions that clear with money left on the table, so it is worth mentioning. + +**Note**: Atomic settlement will always be set to disabled in EasyAuction, which makes the MEV opportunity further unattractive. diff --git a/docs/recollateralization.md b/docs/recollateralization.md index 3a7dde392e..67c9067592 100644 --- a/docs/recollateralization.md +++ b/docs/recollateralization.md @@ -3,7 +3,7 @@ Recollateralization takes place in the central loop of [`BackingManager.rebalance()`](../contracts/p1/BackingManager). Since the BackingManager can only have open 1 trade at a time, it needs to know which tokens to try to trade and how much. This algorithm should not be gameable and should not result in unnecessary losses. ```solidity -(bool doTrade, TradeRequest memory req) = RecollateralizationLibP1.prepareRecollateralizationTrade(...); +(bool doTrade, TradeRequest memory req, TradePrices memory prices) = RecollateralizationLibP1.prepareRecollateralizationTrade(...); ``` The trading algorithm is isolated in [RecollateralizationLib.sol](../contracts/p1/mixins/RecollateralizationLib.sol). This document describes the algorithm implemented by the library at a high-level, as well as the concepts required to evaluate the correctness of the implementation. @@ -76,7 +76,7 @@ function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh); ``` -All trades have a worst-case exchange rate that is a function of (among other things) the selling asset's `price().low` and the buying asset's `price().high`. However, the protocol must also be able to sell assets that do not currently have known prices. This can occur either due to an oracle contract reverting, or because the oracle value is stale. In this case `price()` returns `[0, FIX_MAX]` while `lotPrice()` returns nonzero values, at least for some period of time. If an asset's oracle goes offline forever, its `lotPrice()` will eventually reach `[0, 0]` and the protocol will completely stop trading this asset. +All trades have a worst-case exchange rate that is a function of (among other things) the selling asset's `lotPrice().low` and the buying asset's `lotPrice().high`. #### Trade Examples diff --git a/docs/solidity-style.md b/docs/solidity-style.md index 37309aeef8..ad3d1a5f2e 100644 --- a/docs/solidity-style.md +++ b/docs/solidity-style.md @@ -162,7 +162,7 @@ For each `external` or `public` function, one of these tags MUST be in the corre - backingManager.grantRTokenAllowances() - backingManager.rebalance\*() - backingManager.forwardRevenue\*() -- {rsrTrader,rTokenTrader}.manageToken() +- {rsrTrader,rTokenTrader}.manageTokens() ### `@custom:governance` diff --git a/docs/system-design.md b/docs/system-design.md index 21ae313e5c..8fe8d0e8dd 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -128,7 +128,7 @@ On the other hand, while a redemption is pending in the mempool, the quantities - `tradingPaused`: all interactions disabled EXCEPT ERC20 functions + RToken.issue + RToken.redeem + StRSR.stake + StRSR.payoutRewards - `issuancePaused`: all interactions enabled EXCEPT RToken.issue -- `frozen`: all interactions disabled EXCEPT ERC20 functions + StRSR.stake +- `frozen`: all interactions disabled EXCEPT ERC20 functions + StRSR.stake + StRSR.payoutRewards Freezing can occur over two timescales: short freezing + long freezing. diff --git a/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json b/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json index 2f8fb364f5..48f2121bd0 100644 --- a/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json +++ b/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json @@ -7,7 +7,7 @@ "tradingLib": "", "cvxMiningLib": "", "facadeRead": "0xcb71dDa3BdB1208B8bf18554Aab8CCF9bDe3e53D", - "facadeAct": "0xeaCaF85eA2df99e56053FD0250330C148D582547", + "facadeAct": "0x98f292e6Bb4722664fEffb81448cCFB5B7211469", "facadeWriteLib": "", "basketLib": "", "facadeWrite": "", diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts index e66b89dcd0..dae7875b30 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts @@ -87,7 +87,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts index cc3b31856a..97a8fc4f2b 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts @@ -88,7 +88,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts index 71ff38d29e..ab71316676 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts @@ -93,7 +93,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: oracleTimeout(chainId, USDT_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts index a52f03d7c4..e886d6d806 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts @@ -88,7 +88,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts index 8aba2f5aaf..3bc89cb18f 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts @@ -89,7 +89,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts index 938c9e1683..f3b6e3e615 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts @@ -93,7 +93,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: oracleTimeout(chainId, USDT_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index 857d72645a..0769abafa8 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -42,7 +42,7 @@ async function main() { '3_verify_deployer.ts', '4_verify_facade.ts', '5_verify_facadeWrite.ts', - '6_verify_collateral.ts', + '6_verify.ts', '7_verify_rToken.ts', '8_verify_governance.ts', 'collateral-plugins/verify_convex_stable.ts', diff --git a/tasks/testing/upgrade-checker-utils/rewards.ts b/tasks/testing/upgrade-checker-utils/rewards.ts index 4e69385cd8..4281738bc4 100644 --- a/tasks/testing/upgrade-checker-utils/rewards.ts +++ b/tasks/testing/upgrade-checker-utils/rewards.ts @@ -42,9 +42,9 @@ export const claimRsrRewards = async (hre: HardhatRuntimeEnvironment, rtokenAddr await compContract.connect(compWhale).transfer(rsrTrader.address, fp('1e5')) }) - await rsrTrader.manageToken(comp, TradeKind.BATCH_AUCTION) + await rsrTrader.manageTokens([comp], [TradeKind.BATCH_AUCTION]) await runTrade(hre, rsrTrader, comp, false) - await rsrTrader.manageToken(rsr.address, TradeKind.BATCH_AUCTION) + await rsrTrader.manageTokens([rsr.address], [TradeKind.BATCH_AUCTION]) await strsr.payoutRewards() await advanceBlocks(hre, 100) await advanceTime(hre, 1200) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index a0eccff666..57e3f29087 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -5,7 +5,14 @@ import { expect } from 'chai' import { BigNumber, ContractFactory } from 'ethers' import { ethers, upgrades } from 'hardhat' import { IConfig, MAX_AUCTION_LENGTH } from '../common/configuration' -import { MAX_UINT96, TradeKind, TradeStatus, ZERO_ADDRESS, ONE_ADDRESS } from '../common/constants' +import { + MAX_UINT96, + MAX_UINT192, + TradeKind, + TradeStatus, + ZERO_ADDRESS, + ONE_ADDRESS, +} from '../common/constants' import { bn, fp, divCeil, toBNDecimals } from '../common/numbers' import { DutchTrade, @@ -76,6 +83,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { let basket: Collateral[] let collateral: Collateral[] + let prices: { sellLow: BigNumber; sellHigh: BigNumber; buyLow: BigNumber; buyHigh: BigNumber } + beforeEach(async () => { ;[owner, addr1, mock, other] = await ethers.getSigners() // Deploy fixture @@ -101,6 +110,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { tokenZ = ( await ethers.getContractAt('ZeroDecimalMock', await collateralZ.erc20()) ) + prices = { sellLow: fp('1'), sellHigh: fp('1'), buyLow: fp('1'), buyHigh: fp('1') } }) describe('Deployment', () => { @@ -387,10 +397,10 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await whileImpersonating(backingManager.address, async (bmSigner) => { await expect( - broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest, prices) ).to.be.revertedWith('broker disabled') await expect( - broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) ).to.be.revertedWith('broker disabled') }) }) @@ -414,29 +424,32 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // Attempt to open trade from non-trader await token0.connect(addr1).approve(broker.address, amount) await expect( - broker.connect(addr1).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + broker.connect(addr1).openTrade(TradeKind.BATCH_AUCTION, tradeRequest, prices) ).to.be.revertedWith('only traders') // Open from traders - Should work // Backing Manager await whileImpersonating(backingManager.address, async (bmSigner) => { await token0.connect(bmSigner).approve(broker.address, amount) - await expect(broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest)).to - .not.be.reverted + await expect( + broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest, prices) + ).to.not.be.reverted }) // RSR Trader await whileImpersonating(rsrTrader.address, async (rsrSigner) => { await token0.connect(rsrSigner).approve(broker.address, amount) - await expect(broker.connect(rsrSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest)).to - .not.be.reverted + await expect( + broker.connect(rsrSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest, prices) + ).to.not.be.reverted }) // RToken Trader await whileImpersonating(rTokenTrader.address, async (rtokSigner) => { await token0.connect(rtokSigner).approve(broker.address, amount) - await expect(broker.connect(rtokSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest)).to - .not.be.reverted + await expect( + broker.connect(rtokSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest, prices) + ).to.not.be.reverted }) }) @@ -950,7 +963,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { collateral0.address, collateral1.address, amount, - config.dutchAuctionLength + config.dutchAuctionLength, + prices ) ).to.not.be.reverted @@ -965,10 +979,10 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await trade.endTime()).to.equal( (await trade.startTime()) + config.dutchAuctionLength.toNumber() ) - const [sellLow, sellHigh] = await collateral0.price() - const [buyLow, buyHigh] = await collateral1.price() - expect(await trade.middlePrice()).to.equal(divCeil(sellHigh.mul(fp('1')), buyLow)) - expect(await trade.lowPrice()).to.equal(sellLow.mul(fp('1')).div(buyHigh)) + expect(await trade.middlePrice()).to.equal( + divCeil(prices.sellHigh.mul(fp('1')), prices.buyLow) + ) + expect(await trade.lowPrice()).to.equal(prices.sellLow.mul(fp('1')).div(prices.buyHigh)) expect(await trade.canSettle()).to.equal(false) // Attempt to initialize again @@ -978,7 +992,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { collateral0.address, collateral1.address, amount, - config.dutchAuctionLength + config.dutchAuctionLength, + prices ) ).to.be.revertedWith('Invalid trade state') }) @@ -987,27 +1002,21 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // Fund trade await token0.connect(owner).mint(trade.address, amount) - // Set bad price for sell token - await setOraclePrice(collateral0.address, bn(0)) - await collateral0.refresh() - // Attempt to initialize with bad sell price + prices.sellLow = bn('0') await expect( trade.init( backingManager.address, collateral0.address, collateral1.address, amount, - config.dutchAuctionLength + config.dutchAuctionLength, + prices ) ).to.be.revertedWith('bad sell pricing') - // Fix sell price, set bad buy price - await setOraclePrice(collateral0.address, bn(1e8)) - await collateral0.refresh() - - await setOraclePrice(collateral1.address, bn(0)) - await collateral1.refresh() + prices.sellLow = fp('1') + prices.buyHigh = MAX_UINT192 await expect( trade.init( @@ -1015,7 +1024,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { collateral0.address, collateral1.address, amount, - config.dutchAuctionLength + config.dutchAuctionLength, + prices ) ).to.be.revertedWith('bad buy pricing') }) @@ -1031,15 +1041,59 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { collateral0.address, collateral1.address, amount, - config.dutchAuctionLength + config.dutchAuctionLength, + prices ) ).to.not.be.reverted // Check trade values - const [sellLow, sellHigh] = await collateral0.price() - const [buyLow, buyHigh] = await collateral1.price() - expect(await trade.middlePrice()).to.equal(divCeil(sellHigh.mul(fp('1')), buyLow)) - const withoutSlippage = sellLow.mul(fp('1')).div(buyHigh) + expect(await trade.middlePrice()).to.equal( + divCeil(prices.sellHigh.mul(fp('1')), prices.buyLow) + ) + const withoutSlippage = prices.sellLow.mul(fp('1')).div(prices.buyHigh) + const withSlippage = withoutSlippage.sub( + withoutSlippage.mul(config.maxTradeSlippage).div(fp('1')) + ) + expect(await trade.lowPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) + }) + + it('Should apply full maxTradeSlippage with low maxTradeVolume', async () => { + // Set low maxTradeVolume for collateral + const FiatCollateralFactory = await ethers.getContractFactory('FiatCollateral') + const newCollateral0: FiatCollateral = await FiatCollateralFactory.deploy({ + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await collateral0.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: token0.address, + maxTradeVolume: bn(500), + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }) + + // Refresh and swap collateral + await newCollateral0.refresh() + await assetRegistry.connect(owner).swapRegistered(newCollateral0.address) + + // Fund trade and initialize + await token0.connect(owner).mint(trade.address, amount) + await expect( + trade.init( + backingManager.address, + newCollateral0.address, + collateral1.address, + amount, + config.dutchAuctionLength, + prices + ) + ).to.not.be.reverted + + // Check trade values + expect(await trade.middlePrice()).to.equal( + divCeil(prices.sellHigh.mul(fp('1')), prices.buyLow) + ) + const withoutSlippage = prices.sellLow.mul(fp('1')).div(prices.buyHigh) const withSlippage = withoutSlippage.sub( withoutSlippage.mul(config.maxTradeSlippage).div(fp('1')) ) @@ -1096,7 +1150,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { collateral0.address, collateral1.address, amount, - config.dutchAuctionLength + config.dutchAuctionLength, + prices ) ).to.be.revertedWith('unfunded trade') }) @@ -1110,7 +1165,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { collateral0.address, collateral1.address, amount, - config.dutchAuctionLength + config.dutchAuctionLength, + prices ) ).to.not.be.reverted @@ -1144,7 +1200,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { collateral0.address, collateral1.address, amount, - config.dutchAuctionLength + config.dutchAuctionLength, + prices ) ).to.not.be.reverted @@ -1226,7 +1283,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await whileImpersonating(backingManager.address, async (bmSigner) => { await token0.connect(bmSigner).approve(broker.address, amount) await snapshotGasCost( - broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest, prices) ) }) @@ -1234,7 +1291,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await whileImpersonating(rsrTrader.address, async (rsrSigner) => { await token0.connect(rsrSigner).approve(broker.address, amount) await snapshotGasCost( - broker.connect(rsrSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + broker.connect(rsrSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest, prices) ) }) @@ -1242,7 +1299,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await whileImpersonating(rTokenTrader.address, async (rtokSigner) => { await token0.connect(rtokSigner).approve(broker.address, amount) await snapshotGasCost( - broker.connect(rtokSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest) + broker.connect(rtokSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest, prices) ) }) }) @@ -1319,7 +1376,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await whileImpersonating(backingManager.address, async (bmSigner) => { await token0.connect(bmSigner).approve(broker.address, amount) await snapshotGasCost( - broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) ) }) @@ -1327,7 +1384,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await whileImpersonating(rsrTrader.address, async (rsrSigner) => { await token0.connect(rsrSigner).approve(broker.address, amount) await snapshotGasCost( - broker.connect(rsrSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + broker.connect(rsrSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) ) }) @@ -1335,7 +1392,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await whileImpersonating(rTokenTrader.address, async (rtokSigner) => { await token0.connect(rtokSigner).approve(broker.address, amount) await snapshotGasCost( - broker.connect(rtokSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest) + broker.connect(rtokSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) ) }) }) @@ -1349,7 +1406,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { collateral0.address, collateral1.address, amount, - config.dutchAuctionLength + config.dutchAuctionLength, + prices ) ) }) @@ -1362,7 +1420,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { collateral0.address, collateral1.address, amount, - config.dutchAuctionLength + config.dutchAuctionLength, + prices ) // Advance time till trade can be settled diff --git a/test/Deployer.test.ts b/test/Deployer.test.ts index 1f14da64ea..e46f5adfca 100644 --- a/test/Deployer.test.ts +++ b/test/Deployer.test.ts @@ -365,4 +365,17 @@ describe(`DeployerP${IMPLEMENTATION} contract #fast`, () => { expect(await stRSR.main()).to.equal(main.address) }) }) + + describe('deployRTokenAsset', () => { + it('Should deploy new RTokenAsset', async () => { + expect(await rTokenAsset.maxTradeVolume()).to.equal(bn('1e24')) // fp('1e6') + const newRTokenAssetAddr = await deployer.callStatic.deployRTokenAsset( + rToken.address, + bn('1e27') + ) + await deployer.deployRTokenAsset(rToken.address, bn('1e27')) // fp('1e9') + const newRTokenAsset = await ethers.getContractAt('RTokenAsset', newRTokenAssetAddr) + expect(await newRTokenAsset.maxTradeVolume()).to.equal(bn('1e27')) // fp('1e9') + }) + }) }) diff --git a/test/Facade.test.ts b/test/Facade.test.ts index ffc8bea931..947f554576 100644 --- a/test/Facade.test.ts +++ b/test/Facade.test.ts @@ -24,7 +24,10 @@ import { RecollateralizationLibP1, RevenueTraderCompatibleV1, RevenueTraderCompatibleV2, +<<<<<<< HEAD +======= RevenueTraderP1InvalidReverts, +>>>>>>> 3.0.0 RevenueTraderInvalidVersion, RevenueTraderP1, StaticATokenMock, @@ -106,7 +109,10 @@ describe('FacadeRead + FacadeAct contracts', () => { let RevenueTraderV2ImplFactory: ContractFactory let RevenueTraderV1ImplFactory: ContractFactory let RevenueTraderInvalidVerImplFactory: ContractFactory +<<<<<<< HEAD +======= let RevenueTraderRevertsImplFactory: ContractFactory +>>>>>>> 3.0.0 let BackingMgrV2ImplFactory: ContractFactory let BackingMgrV1ImplFactory: ContractFactory let BackingMgrInvalidVerImplFactory: ContractFactory @@ -158,10 +164,13 @@ describe('FacadeRead + FacadeAct contracts', () => { 'RevenueTraderInvalidVersion' ) +<<<<<<< HEAD +======= RevenueTraderRevertsImplFactory = await ethers.getContractFactory( 'RevenueTraderP1InvalidReverts' ) +>>>>>>> 3.0.0 const tradingLib: RecollateralizationLibP1 = ( await (await ethers.getContractFactory('RecollateralizationLibP1')).deploy() ) @@ -248,6 +257,13 @@ describe('FacadeRead + FacadeAct contracts', () => { ) }) + it('Should revert maxIssuable when frozen', async () => { + await main.connect(owner).freezeShort() + await expect(facade.callStatic.maxIssuable(rToken.address, addr1.address)).to.be.revertedWith( + 'frozen' + ) + }) + it('Should return issuable quantities correctly', async () => { const [toks, quantities, uoas] = await facade.callStatic.issue(rToken.address, issueAmount) expect(toks.length).to.equal(4) @@ -289,8 +305,18 @@ describe('FacadeRead + FacadeAct contracts', () => { expect(uoas[3]).to.equal(0) }) +<<<<<<< HEAD + it('Should revert when returning issuable quantities if frozen', async () => { + await main.connect(owner).freezeShort() + await expect(facade.callStatic.issue(rToken.address, issueAmount)).to.be.revertedWith( + 'frozen' + ) + }) + +======= +>>>>>>> 3.0.0 it('Should return redeemable quantities correctly', async () => { - const [toks, quantities, isProrata] = await facade.callStatic.redeem( + const [toks, quantities, available] = await facade.callStatic.redeem( rToken.address, issueAmount ) @@ -303,17 +329,80 @@ describe('FacadeRead + FacadeAct contracts', () => { expect(quantities[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) expect(quantities[2]).to.equal(issueAmount.div(4)) expect(quantities[3]).to.equal(issueAmount.div(4).mul(50).div(bn('1e10'))) - expect(isProrata).to.equal(false) + expect(available[0]).to.equal(issueAmount.div(4)) + expect(available[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) + expect(available[2]).to.equal(issueAmount.div(4)) + expect(available[3]).to.equal(issueAmount.div(4).mul(50).div(bn('1e10'))) + + // redeemCustom + const [toksCustom, quantitiesCustom] = await facade.callStatic.redeemCustom( + rToken.address, + issueAmount, + [await basketHandler.nonce()], + [fp('1')] + ) + expect(toksCustom.length).to.equal(4) + expect(toksCustom[0]).to.equal(token.address) + expect(toksCustom[1]).to.equal(usdc.address) + expect(toksCustom[2]).to.equal(aToken.address) + expect(toksCustom[3]).to.equal(cTokenVault.address) + expect(quantitiesCustom[0]).to.equal(issueAmount.div(4)) + expect(quantitiesCustom[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) + expect(quantitiesCustom[2]).to.equal(issueAmount.div(4)) + expect(quantitiesCustom[3]).to.equal(issueAmount.div(4).mul(50).div(bn('1e10'))) // Prorata case -- burn half await token.burn(await main.backingManager(), issueAmount.div(8)) - const [newToks, newQuantities, newIsProrata] = await facade.callStatic.redeem( + const [newToks, newQuantities, newAvailable] = await facade.callStatic.redeem( rToken.address, issueAmount ) expect(newToks[0]).to.equal(token.address) - expect(newQuantities[0]).to.equal(issueAmount.div(8)) - expect(newIsProrata).to.equal(true) + expect(newQuantities[0]).to.equal(issueAmount.div(4)) + expect(newAvailable[0]).to.equal(issueAmount.div(4).div(2)) + + // redeemCustom + const [newToksCustom, newQuantitiesCustom] = await facade.callStatic.redeemCustom( + rToken.address, + issueAmount, + [await basketHandler.nonce()], + [fp('1')] + ) + expect(newToksCustom.length).to.equal(4) + expect(newToksCustom[0]).to.equal(token.address) + expect(newToksCustom[1]).to.equal(usdc.address) + expect(newToksCustom[2]).to.equal(aToken.address) + expect(newToksCustom[3]).to.equal(cTokenVault.address) + expect(newQuantitiesCustom[0]).to.equal(issueAmount.div(4).div(2)) + expect(newQuantitiesCustom[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) + expect(newQuantitiesCustom[2]).to.equal(issueAmount.div(4)) + expect(newQuantitiesCustom[3]).to.equal(issueAmount.div(4).mul(50).div(bn('1e10'))) + + // refreshBasket() + await basketHandler.connect(owner).refreshBasket() + await expect(facade.callStatic.redeem(rToken.address, issueAmount)).not.to.be.reverted + const [prevBasketTokens, prevBasketQuantities] = await facade.callStatic.redeemCustom( + rToken.address, + issueAmount, + [(await basketHandler.nonce()) - 1], + [fp('1')] + ) + expect(prevBasketTokens.length).to.equal(4) + expect(prevBasketTokens[0]).to.equal(token.address) + expect(prevBasketTokens[1]).to.equal(usdc.address) + expect(prevBasketTokens[2]).to.equal(aToken.address) + expect(prevBasketTokens[3]).to.equal(cTokenVault.address) + expect(prevBasketQuantities[0]).to.equal(issueAmount.div(4).div(2)) + expect(prevBasketQuantities[1]).to.equal(issueAmount.div(4).div(bn('1e12'))) + expect(prevBasketQuantities[2]).to.equal(issueAmount.div(4)) + expect(prevBasketQuantities[3]).to.equal(issueAmount.div(4).mul(50).div(bn('1e10'))) + }) + + it('Should revert when returning redeemable quantities if frozen', async () => { + await main.connect(owner).freezeShort() + await expect(facade.callStatic.redeem(rToken.address, issueAmount)).to.be.revertedWith( + 'frozen' + ) }) it('Should return backingOverview correctly', async () => { @@ -518,10 +607,10 @@ describe('FacadeRead + FacadeAct contracts', () => { } // Run revenue auctions via multicall - const funcSig = ethers.utils.id('runRevenueAuctions(address,address[],address[],uint8)') + const funcSig = ethers.utils.id('runRevenueAuctions(address,address[],address[],uint8[])') const args = ethers.utils.defaultAbiCoder.encode( - ['address', 'address[]', 'address[]', 'uint8'], - [trader.address, [], erc20sToStart, TradeKind.DUTCH_AUCTION] + ['address', 'address[]', 'address[]', 'uint8[]'], + [trader.address, [], erc20sToStart, [TradeKind.DUTCH_AUCTION]] ) const data = funcSig.substring(0, 10) + args.slice(2) await expect(facadeAct.multicall([data])).to.emit(trader, 'TradeStarted') @@ -531,6 +620,8 @@ describe('FacadeRead + FacadeAct contracts', () => { await facadeAct.callStatic.revenueOverview(trader.address) expect(canStart).to.eql(Array(8).fill(false)) +<<<<<<< HEAD +======= // Nothing should be settleable expect((await facade.auctionsSettleable(trader.address)).length).to.equal(0) @@ -651,6 +742,7 @@ describe('FacadeRead + FacadeAct contracts', () => { await facadeAct.callStatic.revenueOverview(trader.address) expect(canStart).to.eql(Array(8).fill(false)) +>>>>>>> 3.0.0 // Nothing should be settleable expect((await facade.auctionsSettleable(trader.address)).length).to.equal(0) @@ -662,10 +754,13 @@ describe('FacadeRead + FacadeAct contracts', () => { expect(settleable.length).to.equal(1) expect(settleable[0]).to.equal(token.address) +<<<<<<< HEAD +======= // Upgrade to V1 await trader.connect(owner).upgradeTo(revTraderV1.address) await backingManager.connect(owner).upgradeTo(backingManagerV1.address) +>>>>>>> 3.0.0 // Another call to revenueOverview should settle and propose new auction ;[erc20s, canStart, surpluses, minTradeAmounts] = await facadeAct.callStatic.revenueOverview(trader.address) @@ -682,12 +777,18 @@ describe('FacadeRead + FacadeAct contracts', () => { } // Settle and start new auction +<<<<<<< HEAD + await facadeAct.runRevenueAuctions(trader.address, erc20sToStart, erc20sToStart, [ + TradeKind.DUTCH_AUCTION, + ]) +======= await facadeAct.runRevenueAuctions( trader.address, erc20sToStart, erc20sToStart, TradeKind.DUTCH_AUCTION ) +>>>>>>> 3.0.0 // Send additional revenues await token.connect(addr1).transfer(trader.address, tokenSurplus) @@ -706,43 +807,11 @@ describe('FacadeRead + FacadeAct contracts', () => { await ethers.getContractAt('BackingManagerP1', backingManager.address) ) - const revTraderInvalidVer: RevenueTraderInvalidVersion = ( - await RevenueTraderInvalidVerImplFactory.deploy() - ) - const bckMgrInvalidVer: BackingMgrInvalidVersion = ( await BackingMgrInvalidVerImplFactory.deploy() ) - const revTraderV2: RevenueTraderCompatibleV2 = ( - await RevenueTraderV2ImplFactory.deploy() - ) - - // Upgrade RevenueTrader to V0 - Use RSR as an example - await rsrTrader.connect(owner).upgradeTo(revTraderInvalidVer.address) - - const tokenSurplus = bn('0.5e18') - await token.connect(addr1).transfer(rsrTrader.address, tokenSurplus) - - await expect(facadeAct.callStatic.revenueOverview(rsrTrader.address)).to.be.revertedWith( - 'unrecognized version' - ) - - // Upgrade to a version where manageToken reverts in Traders - const revTraderReverts: RevenueTraderP1InvalidReverts = ( - await RevenueTraderRevertsImplFactory.deploy() - ) - await rsrTrader.connect(owner).upgradeTo(revTraderReverts.address) - - // revenue - const [erc20s, canStart, ,] = await facadeAct.callStatic.revenueOverview(rsrTrader.address) - expect(erc20s.length).to.equal(8) // should be full set of registered ERC20s - - // No auction can be started - expect(canStart).to.eql(Array(8).fill(false)) - - // Set revenue trader to a valid version but have an invalid Backing Manager - await rsrTrader.connect(owner).upgradeTo(revTraderV2.address) + await expect(facadeAct.callStatic.revenueOverview(rsrTrader.address)).not.to.be.reverted await backingManager.connect(owner).upgradeTo(bckMgrInvalidVer.address) // Reverts due to invalid version when forwarding revenue @@ -891,6 +960,33 @@ describe('FacadeRead + FacadeAct contracts', () => { ).to.be.revertedWith('unrecognized version') }) + itP1('Should handle invalid versions for nextRecollateralizationAuction', async () => { + // Use P1 specific versions + backingManager = ( + await ethers.getContractAt('BackingManagerP1', backingManager.address) + ) + + const backingManagerInvalidVer: BackingMgrInvalidVersion = ( + await BackingMgrInvalidVerImplFactory.deploy() + ) + + // Upgrade BackingManager to Invalid version + await backingManager.connect(owner).upgradeTo(backingManagerInvalidVer.address) + + // Setup prime basket + await basketHandler.connect(owner).setPrimeBasket([usdc.address], [fp('1')]) + + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(2, [usdc.address], [fp('1')], false) + + // Attempt to trigger recollateralization + await expect( + facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + ).to.be.revertedWith('unrecognized version') + }) + it('Should return basketBreakdown correctly for paused token', async () => { await main.connect(owner).pauseTrading() await expectValidBasketBreakdown(rToken) @@ -934,6 +1030,13 @@ describe('FacadeRead + FacadeAct contracts', () => { expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) }) + it('Should revert totalAssetValue when frozen - FacadeTest', async () => { + await main.connect(owner).freezeShort() + await expect(facadeTest.callStatic.totalAssetValue(rToken.address)).to.be.revertedWith( + 'frozen' + ) + }) + it('Should return RToken price correctly', async () => { const avgPrice = fp('1') const [lowPrice, highPrice] = await facade.price(rToken.address) @@ -1141,7 +1244,7 @@ describe('FacadeRead + FacadeAct contracts', () => { rsrTrader.address, [], [token.address], - TradeKind.DUTCH_AUCTION + [TradeKind.DUTCH_AUCTION] ) ) .to.emit(rsrTrader, 'TradeStarted') @@ -1159,7 +1262,7 @@ describe('FacadeRead + FacadeAct contracts', () => { rsrTrader.address, [token.address], [token.address], - TradeKind.DUTCH_AUCTION + [TradeKind.DUTCH_AUCTION] ), [ { @@ -1195,10 +1298,6 @@ describe('FacadeRead + FacadeAct contracts', () => { await BackingMgrV1ImplFactory.deploy() ) - // Upgrade components to V2 - await backingManager.connect(owner).upgradeTo(backingManagerV2.address) - await rTokenTrader.connect(owner).upgradeTo(revTraderV2.address) - const auctionLength = await broker.dutchAuctionLength() const tokenSurplus = bn('0.5e18') await token.connect(addr1).transfer(rTokenTrader.address, tokenSurplus) @@ -1209,7 +1308,7 @@ describe('FacadeRead + FacadeAct contracts', () => { rTokenTrader.address, [], [token.address], - TradeKind.DUTCH_AUCTION + [TradeKind.DUTCH_AUCTION] ) ) .to.emit(rTokenTrader, 'TradeStarted') @@ -1221,17 +1320,48 @@ describe('FacadeRead + FacadeAct contracts', () => { // Advance time till auction ended await advanceTime(auctionLength + 13) + // Upgrade components to V2 + await backingManager.connect(owner).upgradeTo(backingManagerV2.address) + await rTokenTrader.connect(owner).upgradeTo(revTraderV2.address) + + // Settle and start new auction - Will retry + await expectEvents( + facadeAct.runRevenueAuctions( + rTokenTrader.address, + [token.address], + [token.address], + [TradeKind.DUTCH_AUCTION] + ), + [ + { + contract: rTokenTrader, + name: 'TradeSettled', + args: [anyValue, token.address, rToken.address, anyValue, anyValue], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + args: [anyValue, token.address, rToken.address, anyValue, anyValue], + emitted: true, + }, + ] + ) + // Upgrade to V1 await backingManager.connect(owner).upgradeTo(backingManagerV1.address) await rTokenTrader.connect(owner).upgradeTo(revTraderV1.address) - // Settle and start new auction - Will retry + // Advance time till auction ended + await advanceTime(auctionLength + 13) + + // Settle and start new auction - Will retry again await expectEvents( facadeAct.runRevenueAuctions( rTokenTrader.address, [token.address], [token.address], - TradeKind.DUTCH_AUCTION + [TradeKind.DUTCH_AUCTION] ), [ { @@ -1270,7 +1400,7 @@ describe('FacadeRead + FacadeAct contracts', () => { rsrTrader.address, [], [token.address], - TradeKind.DUTCH_AUCTION + [TradeKind.DUTCH_AUCTION] ) ).to.be.revertedWith('unrecognized version') @@ -1282,7 +1412,7 @@ describe('FacadeRead + FacadeAct contracts', () => { rsrTrader.address, [], [token.address], - TradeKind.DUTCH_AUCTION + [TradeKind.DUTCH_AUCTION] ) ).to.be.revertedWith('unrecognized version') }) diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index f74e01ba29..b385d1c82b 100644 --- a/test/Furnace.test.ts +++ b/test/Furnace.test.ts @@ -411,7 +411,7 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { const expAmt = decayFn(hndAmt, 1) // 1 period // Melt - await expect(furnace.setRatio(bn('1e17'))) + await expect(furnace.setRatio(bn('1e13'))) .to.emit(rToken, 'Melted') .withArgs(hndAmt.sub(expAmt)) @@ -458,7 +458,7 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { } it('Should not revert at extremes', async () => { - const ratios = [fp('1'), fp('0'), fp('0.000001069671574938')] + const ratios = [bn('1e14'), fp('0'), fp('0.000001069671574938')] const bals = [fp('1e18'), fp('0'), bn('1e9')] diff --git a/test/Main.test.ts b/test/Main.test.ts index 841d2bf791..b3edf5cefe 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1148,10 +1148,11 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(await assetRegistry.isRegistered(other.address)).to.equal(false) }) - it('Should revert with gas error if cannot reserve 900k gas', async () => { + it('Should revert with gas error if cannot reserve 1M gas', async () => { expect(await assetRegistry.isRegistered(collateral0.address)) + await expect( - assetRegistry.unregister(collateral0.address, { gasLimit: bn('9e5') }) + assetRegistry.unregister(collateral0.address, { gasLimit: bn('1e6') }) ).to.be.revertedWith('not enough gas to unregister safely') }) @@ -1233,15 +1234,16 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { delayUntilDefault: await collateral0.delayUntilDefault(), }) expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + await gasGuzzlingColl.setRevertRefPerTok(true) - await assetRegistry.swapRegistered(replacementColl.address, { gasLimit: bn('1e6') }) + await assetRegistry.swapRegistered(replacementColl.address, { gasLimit: bn('1.1e6') }) expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) await gasGuzzlingColl.setRevertRefPerTok(false) await basketHandler.refreshBasket() expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - await assetRegistry.swapRegistered(gasGuzzlingColl.address, { gasLimit: bn('1e6') }) + await assetRegistry.swapRegistered(gasGuzzlingColl.address, { gasLimit: bn('1.1e6') }) await gasGuzzlingColl.setRevertRefPerTok(true) - await assetRegistry.unregister(gasGuzzlingColl.address, { gasLimit: bn('1e6') }) + await assetRegistry.unregister(gasGuzzlingColl.address, { gasLimit: bn('1.1e6') }) expect(await assetRegistry.isRegistered(gasGuzzlingColl.address)).to.equal(false) expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) await basketHandler.refreshBasket() @@ -1674,8 +1676,6 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { let eurToken: ERC20Mock beforeEach(async () => { - await upgrades.silenceWarnings() - if (IMPLEMENTATION == Implementation.P0) { const BasketHandlerFactory = await ethers.getContractFactory('BasketHandlerP0') freshBasketHandler = ((await BasketHandlerFactory.deploy()) as unknown) @@ -1999,7 +1999,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const amount = fp('10000') await expect( basketHandler.quoteCustomRedemption(basketNonces, portions, amount) - ).to.be.revertedWith('portions does not mirror basketNonces') + ).to.be.revertedWith('bad portions len') }) it('Should correctly quote the current basket, same as quote()', async () => { @@ -2285,7 +2285,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expectDelta(balsBefore, quote.quantities, balsAfter) }) - it('Should correctly quote a historical redemption with unregistered asset', async () => { + it('Should correctly quote historical redemption with almost all assets unregistered', async () => { // default usdc & refresh basket to use backup collateral await usdcChainlink.updateAnswer(bn('0.8e8')) // default token1 await daiChainlink.updateAnswer(bn('0.8e8')) // default token0, token2, token3 @@ -2294,8 +2294,14 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(false) - // Unregister asset in previous basket - await assetRegistry.connect(owner).unregister(collateral1.address) + // Unregister everything except token0 + const erc20s = await assetRegistry.erc20s() + for (const erc20 of erc20s) { + if (erc20 != token0.address) { + await assetRegistry.connect(owner).unregister(await assetRegistry.toAsset(erc20)) + } + } + /* Test Quote */ @@ -2304,35 +2310,24 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const amount = fp('10000') const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, amount) - expect(quote.erc20s.length).equal(4) - expect(quote.quantities.length).equal(4) + expect(quote.erc20s.length).equal(1) + expect(quote.quantities.length).equal(1) - const expectedTokens = [token0, token1, token2, token3] - const expectedAddresses = expectedTokens.map((t) => t.address) + const expectedAddresses = [token0.address] const expectedQuantities = [ fp('0.25') .mul(issueAmount) .div(await collateral0.refPerTok()) .div(bn(`1e${18 - (await token0.decimals())}`)), - bn('0') - .mul(issueAmount) - .div(await collateral1.refPerTok()) - .div(bn(`1e${18 - (await token1.decimals())}`)), - fp('0.25') - .mul(issueAmount) - .div(await collateral2.refPerTok()) - .div(bn(`1e${18 - (await token2.decimals())}`)), - fp('0.25') - .mul(issueAmount) - .div(await collateral3.refPerTok()) - .div(bn(`1e${18 - (await token3.decimals())}`)), ] + expectEqualArrays(quote.erc20s, expectedAddresses) expectEqualArrays(quote.quantities, expectedQuantities) /* Test Custom Redemption */ + const expectedTokens = [token0, token1, token2, token3] const balsBefore = await getBalances(addr1.address, expectedTokens) await backupToken1.mint(backingManager.address, issueAmount) @@ -2345,13 +2340,14 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { amount, basketNonces, portions, - quote.erc20s, - quote.quantities + expectedAddresses, + expectedQuantities ) ).to.not.be.reverted const balsAfter = await getBalances(addr1.address, expectedTokens) - expectDelta(balsBefore, quote.quantities, balsAfter) + const expectedDelta = [expectedQuantities[0], bn('0'), bn('0'), bn('0')] + expectDelta(balsBefore, expectedDelta, balsAfter) }) it('Should correctly quote a historical redemption with an non-collateral asset', async () => { @@ -2386,20 +2382,16 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const amount = fp('10000') const quote = await basketHandler.quoteCustomRedemption(basketNonces, portions, amount) - expect(quote.erc20s.length).equal(4) - expect(quote.quantities.length).equal(4) + expect(quote.erc20s.length).equal(3) + expect(quote.quantities.length).equal(3) - const expectedTokens = [token0, token1, token2, token3] + const expectedTokens = [token0, token2, token3] const expectedAddresses = expectedTokens.map((t) => t.address) const expectedQuantities = [ fp('0.25') .mul(issueAmount) .div(await collateral0.refPerTok()) .div(bn(`1e${18 - (await token0.decimals())}`)), - bn('0') - .mul(issueAmount) - .div(await collateral1.refPerTok()) - .div(bn(`1e${18 - (await token1.decimals())}`)), fp('0.25') .mul(issueAmount) .div(await collateral2.refPerTok()) @@ -2637,9 +2629,9 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await main.connect(other).poke() }) - it('Should not allow to poke when frozen', async () => { + it('Should allow to poke when frozen', async () => { await main.connect(owner).freezeForever() - await expect(main.connect(other).poke()).to.be.revertedWith('frozen') + await main.connect(other).poke() }) it('Should not allow to refresh basket if not OWNER when unfrozen and unpaused', async () => { @@ -2907,13 +2899,23 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { const [low, high] = await collateral0.price() await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) // oracle error - const [lowPrice, highPrice] = await basketHandler.price() - const [lotLowPrice, lotHighPrice] = await basketHandler.lotPrice() + // lotPrice() should begin at 100% + let [lowPrice, highPrice] = await basketHandler.price() + let [lotLowPrice, lotHighPrice] = await basketHandler.lotPrice() + expect(lowPrice).to.equal(0) + expect(highPrice).to.equal(MAX_UINT192) + expect(lotLowPrice).to.be.eq(low) + expect(lotHighPrice).to.be.eq(high) + + // Advance time past 100% period -- lotPrice() should begin to fall + await advanceTime(await collateral0.oracleTimeout()) + ;[lowPrice, highPrice] = await basketHandler.price() + ;[lotLowPrice, lotHighPrice] = await basketHandler.lotPrice() expect(lowPrice).to.equal(0) expect(highPrice).to.equal(MAX_UINT192) - expect(lotLowPrice).to.be.closeTo(low, low.div(bn('1e5'))) // small decay + expect(lotLowPrice).to.be.closeTo(low, low.div(bn('1e5'))) // small decay expected expect(lotLowPrice).to.be.lt(low) - expect(lotHighPrice).to.be.closeTo(high, high.div(bn('1e5'))) // small decay + expect(lotHighPrice).to.be.closeTo(high, high.div(bn('1e5'))) // small decay expected expect(lotHighPrice).to.be.lt(high) }) diff --git a/test/RToken.test.ts b/test/RToken.test.ts index d4436ec7a1..c13c235db7 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -42,7 +42,6 @@ import { ORACLE_TIMEOUT, VERSION, } from './fixtures' -import { expectEqualArrays } from './utils/matchers' import { useEnv } from '#/utils/env' import { mintCollaterals } from './utils/tokens' import { expectEvents } from '#/common/events' @@ -888,6 +887,44 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await rToken.balanceOf(rToken.address)).to.equal(0) expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) }) + + it('Should update issuance throttle correctly with redemptions - edge case', async function () { + // set fixed redemption amount + const redemptionThrottleParams = JSON.parse(JSON.stringify(config.redemptionThrottle)) + redemptionThrottleParams.amtRate = fp('1e6') + redemptionThrottleParams.pctRate = bn(0) + await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + + // Provide approvals + await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, initialBal))) + + // Issuance throttle is fully charged + expect(await rToken.issuanceAvailable()).to.equal(config.issuanceThrottle.amtRate) + expect(await rToken.redemptionAvailable()).to.equal(bn(0)) + + // Set automine to false for multiple transactions in one block + await hre.network.provider.send('evm_setAutomine', [false]) + + // Issuance #1 - Full available - Will succeed + const fullIssuanceAmount: BigNumber = await rToken.issuanceAvailable() + await rToken.connect(addr1).issue(fullIssuanceAmount) + + // Redemption #1 - Full amount + await rToken.connect(addr1).redeem(fullIssuanceAmount) + + // Issuance #2 - Less than max - Will succeed + await rToken.connect(addr1).issue(fullIssuanceAmount.div(2)) + + // Mine block + await hre.network.provider.send('evm_mine', []) + + // Issuance and redemption throttle still available for remainder + expect(await rToken.issuanceAvailable()).to.equal(fullIssuanceAmount.div(2)) + expect(await rToken.redemptionAvailable()).to.equal(fullIssuanceAmount.div(2)) + + // Set automine to true again + await hre.network.provider.send('evm_setAutomine', [true]) + }) }) describe('Redeem', function () { @@ -1485,20 +1522,6 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { redeemAmount ) - // Check simulation result - const [actualERC20s, actualQuantities] = await rToken - .connect(addr1) - .callStatic.redeemCustom( - addr1.address, - redeemAmount, - basketNonces, - portions, - quote.erc20s, - quote.quantities - ) - expectEqualArrays(actualERC20s, quote.erc20s) - expectEqualArrays(actualQuantities, quote.quantities) - await rToken .connect(addr1) .redeemCustom( @@ -1788,12 +1811,17 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { expect(await token2.balanceOf(addr1.address)).to.equal(initialBal) }) - it('Should not revert when redeeming unregistered collateral #fast', async function () { - // Unregister collateral2 + it('Should not revert when redeeming mostly unregistered collateral #fast', async function () { + // Unregister everything except token0 + const erc20s = await assetRegistry.erc20s() + for (const erc20 of erc20s) { + if (erc20 != token0.address) { + await assetRegistry.connect(owner).unregister(await assetRegistry.toAsset(erc20)) + } + } const basketNonces = [1] const portions = [fp('1')] - await assetRegistry.connect(owner).unregister(collateral2.address) const quote = await basketHandler.quoteCustomRedemption( basketNonces, portions, diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 21225ecbcb..9f707e1d1a 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -51,7 +51,7 @@ import { import snapshotGasCost from './utils/snapshotGasCost' import { expectTrade, getTrade, dutchBuyAmount } from './utils/trades' import { withinQuad } from './utils/matchers' -import { expectRTokenPrice, setOraclePrice } from './utils/oracles' +import { expectRTokenPrice, expectUnpriced, setOraclePrice } from './utils/oracles' import { useEnv } from '#/utils/env' import { mintCollaterals } from './utils/tokens' @@ -1023,6 +1023,45 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ) }) + it('Should not recollateralize when switching basket if all assets are UNPRICED', async () => { + // Set price to use lot price + await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) + + // Setup prime basket + await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) + + // Switch Basket + await expect(basketHandler.connect(owner).refreshBasket()) + .to.emit(basketHandler, 'BasketSet') + .withArgs(3, [token1.address], [fp('1')], false) + + // Advance time post warmup period - temporary IFFY->SOUND + await advanceTime(Number(config.warmupPeriod) + 1) + + // Set to sell price = 0 + await advanceTime(Number(ORACLE_TIMEOUT.add(PRICE_TIMEOUT))) + + // Check state remains SOUND + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) + expect(await token1.balanceOf(backingManager.address)).to.equal(0) + + // RToken unpriced + await expectUnpriced(rTokenAsset.address) + + // Attempt to recollateralize (no assets to sell) + await expect(facadeTest.runAuctionsForAllTraders(rToken.address)).to.not.emit( + backingManager, + 'TradeStarted' + ) + + // Nothing changes until situation is resolved + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.fullyCollateralized()).to.equal(false) + await expectUnpriced(rTokenAsset.address) + }) + context('Should successfully recollateralize after governance basket switch', () => { afterEach(async () => { // Should be fully capitalized again @@ -1184,10 +1223,15 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Trigger recollateralization const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) + const minBuyAmt: BigNumber = toBNDecimals( + await toMinBuyAmt(sellAmt, fp('1'), fp('1')), + 6 + ).add(1) + // since within oracleTimeout lotPrice() should still be at 100% of original price await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) .to.emit(backingManager, 'TradeStarted') - .withArgs(anyValue, token0.address, token1.address, sellAmt, bn(0)) + .withArgs(anyValue, token0.address, token1.address, sellAmt, minBuyAmt) const auctionTimestamp: number = await getLatestBlockTimestamp() diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 4e4314224b..cb4a99a5b6 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -380,14 +380,14 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should not trade if paused', async () => { await main.connect(owner).pauseTrading() await expect( - rsrTrader.manageToken(ZERO_ADDRESS, TradeKind.BATCH_AUCTION) + rsrTrader.manageTokens([ZERO_ADDRESS], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('frozen or trading paused') }) it('Should not trade if frozen', async () => { await main.connect(owner).freezeShort() await expect( - rTokenTrader.manageToken(ZERO_ADDRESS, TradeKind.BATCH_AUCTION) + rTokenTrader.manageTokens([ZERO_ADDRESS], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('frozen or trading paused') }) @@ -445,7 +445,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const rtokenPrice = await basketHandler.price() const realRtokenPrice = rtokenPrice.low.add(rtokenPrice.high).div(2) const minBuyAmt = await toMinBuyAmt(issueAmount, fp('0.7'), realRtokenPrice) - await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)) + await expect(rTokenTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])) .to.emit(rTokenTrader, 'TradeStarted') .withArgs(anyValue, token0.address, rToken.address, issueAmount, withinQuad(minBuyAmt)) }) @@ -563,24 +563,160 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rsr.balanceOf(rTokenTrader.address)).to.equal(0) }) - it('Should not launch revenue auction if UNPRICED', async () => { + it('Should launch revenue auction at lotPrice if UNPRICED', async () => { + // After oracleTimeout the lotPrice should be the original price still await advanceTime(ORACLE_TIMEOUT.toString()) await rsr.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await rTokenTrader.callStatic.manageTokens([rsr.address], [TradeKind.BATCH_AUCTION]) + + // After oracleTimeout the lotPrice should be the original price still + await advanceTime(PRICE_TIMEOUT.toString()) + await rsr.connect(addr1).transfer(rTokenTrader.address, issueAmount) await expect( - rTokenTrader.manageToken(rsr.address, TradeKind.BATCH_AUCTION) + rTokenTrader.manageTokens([rsr.address], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('buy asset price unknown') }) + it('Should not launch revenue auction if 0 erc20s len', async () => { + await expect(rTokenTrader.manageTokens([], [])).to.be.revertedWith('empty erc20s list') + }) + + it('Should not launch revenue auction with uneven length lists', async () => { + await expect(rTokenTrader.manageTokens([rsr.address], [])).to.be.revertedWith( + 'length mismatch' + ) + await expect( + rTokenTrader.manageTokens( + [rsr.address], + [TradeKind.BATCH_AUCTION, TradeKind.DUTCH_AUCTION] + ) + ).to.be.revertedWith('length mismatch') + await expect( + rTokenTrader.manageTokens([rsr.address], [TradeKind.BATCH_AUCTION]) + ).to.not.be.revertedWith('length mismatch') + }) + + it('Should distribute tokenToBuy - distributeTokenToBuy()', async () => { + // Use distributeTokenToBuy() + const stRSRBal = await rsr.balanceOf(stRSR.address) + await rsr.connect(owner).mint(rsrTrader.address, issueAmount) + await rsrTrader.distributeTokenToBuy() + const expectedAmount = stRSRBal.add(issueAmount) + expect(await rsr.balanceOf(stRSR.address)).to.be.closeTo(expectedAmount, 100) + }) + + it('Should distribute tokenToBuy - manageTokens()', async () => { + // Use manageTokens() + const stRSRBal = await rsr.balanceOf(stRSR.address) + await rsr.connect(owner).mint(rsrTrader.address, issueAmount) + await rsrTrader.manageTokens([rsr.address], [TradeKind.BATCH_AUCTION]) + const expectedAmount = stRSRBal.add(issueAmount) + expect(await rsr.balanceOf(stRSR.address)).to.be.closeTo(expectedAmount, 100) + }) + + it('Should not distribute tokenToBuy if frozen or trading paused', async () => { + // Use distributeTokenToBuy() + const stRSRBal = await rsr.balanceOf(stRSR.address) + await rsr.connect(owner).mint(rsrTrader.address, issueAmount) + + // Trading pause + await main.connect(owner).pauseTrading() + + // Attempt to distribute + await expect(rsrTrader.distributeTokenToBuy()).to.be.revertedWith( + 'frozen or trading paused' + ) + + // Revert pause and freeze + await main.connect(owner).unpauseTrading() + await main.connect(owner).freezeShort() + + // Attempt to distribute again + await expect(rsrTrader.distributeTokenToBuy()).to.be.revertedWith( + 'frozen or trading paused' + ) + + // Unfreeze + await main.connect(owner).unfreeze() + + // Can distribute now + await rsrTrader.distributeTokenToBuy() + const expectedAmount = stRSRBal.add(issueAmount) + expect(await rsr.balanceOf(stRSR.address)).to.be.closeTo(expectedAmount, 100) + }) + + it('Should launch multiple auctions -- has tokenToBuy', async () => { + // Mint AAVE, token0, and RSR to the RSRTrader + await aaveToken.connect(owner).mint(rsrTrader.address, issueAmount) + await token0.connect(owner).mint(rsrTrader.address, issueAmount) + await rsr.connect(owner).mint(rsrTrader.address, issueAmount) + + // Start auctions + await expectEvents( + rsrTrader.manageTokens( + [rsr.address, token0.address, aaveToken.address], + [TradeKind.BATCH_AUCTION, TradeKind.BATCH_AUCTION, TradeKind.BATCH_AUCTION] + ), + [ + { + contract: rsr, + name: 'Transfer', + args: [rsrTrader.address, stRSR.address, anyValue], + emitted: true, + }, + { + contract: rsrTrader, + name: 'TradeStarted', + args: [anyValue, token0.address, rsr.address, issueAmount, anyValue], + emitted: true, + }, + { + contract: rsrTrader, + name: 'TradeStarted', + args: [anyValue, aaveToken.address, rsr.address, issueAmount, anyValue], + emitted: true, + }, + ] + ) + }) + + it('Should launch multiple auctions -- involvesRToken', async () => { + // Mint AAVE, token0, and RSR to the RSRTrader + await rToken.connect(addr1).transfer(rsrTrader.address, issueAmount) + await rsr.connect(owner).mint(rsrTrader.address, issueAmount) + + // Start auctions + await expectEvents( + rsrTrader.manageTokens( + [rsr.address, rToken.address], + [TradeKind.BATCH_AUCTION, TradeKind.BATCH_AUCTION] + ), + [ + { + contract: rsr, + name: 'Transfer', + args: [rsrTrader.address, stRSR.address, anyValue], + emitted: true, + }, + { + contract: rsrTrader, + name: 'TradeStarted', + args: [anyValue, rToken.address, rsr.address, issueAmount, anyValue], + emitted: true, + }, + ] + ) + }) + it('Should launch revenue auction if DISABLED with nonzero minBuyAmount', async () => { await setOraclePrice(collateral0.address, bn('0.5e8')) await collateral0.refresh() await advanceTime((await collateral0.delayUntilDefault()).toString()) expect(await collateral0.status()).to.equal(CollateralStatus.DISABLED) await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( - rTokenTrader, - 'TradeStarted' - ) + await expect( + rTokenTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION]) + ).to.emit(rTokenTrader, 'TradeStarted') // Trade should have extremely nonzero worst-case price const trade = await getTrade(rTokenTrader, token0.address) @@ -748,16 +884,15 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Disable batch auctions, should not start auction await broker.connect(owner).setBatchAuctionLength(bn(0)) await expect( - p1RevenueTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) + p1RevenueTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('batch auctions not enabled') // Enable batch auctions (normal flow) await broker.connect(owner).setBatchAuctionLength(config.batchAuctionLength) - await expect(p1RevenueTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( - rTokenTrader, - 'TradeStarted' - ) + await expect( + p1RevenueTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION]) + ).to.emit(rTokenTrader, 'TradeStarted') }) it('Should be able to start a dust auction DUTCH_AUCTION, if enabled', async () => { @@ -775,16 +910,15 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Disable dutch auctions, should not start auction await broker.connect(owner).setDutchAuctionLength(bn(0)) await expect( - p1RevenueTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + p1RevenueTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) ).to.be.revertedWith('dutch auctions not enabled') // Enable batch auctions (normal flow) await broker.connect(owner).setDutchAuctionLength(config.dutchAuctionLength) - await expect(p1RevenueTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION)).to.emit( - rTokenTrader, - 'TradeStarted' - ) + await expect( + p1RevenueTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) + ).to.emit(rTokenTrader, 'TradeStarted') }) it('Should only be able to start a dust auction BATCH_AUCTION (and not DUTCH_AUCTION) if oracle has failed', async () => { @@ -799,21 +933,20 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await setOraclePrice(collateral0.address, bn(0)) await collateral0.refresh() await expect( - p1RevenueTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + p1RevenueTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) ).to.revertedWith('bad sell pricing') - await expect(p1RevenueTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( - rTokenTrader, - 'TradeStarted' - ) + await expect( + p1RevenueTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION]) + ).to.emit(rTokenTrader, 'TradeStarted') }) it('Should not launch an auction for 1 qTok', async () => { await token0.connect(addr1).transfer(rTokenTrader.address, 1) await expect( - rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) ).to.be.revertedWith('sell amount too low') await expect( - rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) + rTokenTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('sell amount too low') }) @@ -917,10 +1050,12 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }) it('Should handle GNOSIS_MAX_TOKENS cap in BATCH_AUCTION', async () => { - // Set Max trade volume very high for both assets in trade + // Halve price to trigger maxTradeSize() overflow const chainlinkFeed = ( - await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) + await (await ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('0.5e8')) ) + + // Set Max trade volume very high for both assets in trade const newSellAsset: Asset = ( await AssetFactory.deploy( PRICE_TIMEOUT, @@ -955,7 +1090,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const p1RevenueTrader = await ethers.getContractAt('RevenueTraderP1', rsrTrader.address) await expect( - p1RevenueTrader.manageToken(aaveToken.address, TradeKind.BATCH_AUCTION) + p1RevenueTrader.manageTokens([aaveToken.address], [TradeKind.BATCH_AUCTION]) ).to.emit(rsrTrader, 'TradeStarted') // Check trade is using the GNOSIS limits @@ -1713,11 +1848,12 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) }) - it('Should allow anyone to call distribute', async () => { + it('Should only allow RevenueTraders to call distribute()', async () => { const distAmount: BigNumber = bn('100e18') - // Transfer some RSR to BackingManager - await rsr.connect(addr1).transfer(backingManager.address, distAmount) + // Transfer some RSR to RevenueTraders + await rsr.connect(addr1).transfer(rTokenTrader.address, distAmount) + await rsr.connect(addr1).transfer(rsrTrader.address, distAmount) // Set f = 1 await expect( @@ -1736,24 +1872,36 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { .to.emit(distributor, 'DistributionSet') .withArgs(STRSR_DEST, bn(0), bn(1)) - // Check funds in Backing Manager and destinations - expect(await rsr.balanceOf(backingManager.address)).to.equal(distAmount) + // Check funds in RevenueTraders and destinations + expect(await rsr.balanceOf(rTokenTrader.address)).to.equal(distAmount) + expect(await rsr.balanceOf(rsrTrader.address)).to.equal(distAmount) expect(await rsr.balanceOf(stRSR.address)).to.equal(0) expect(await rToken.balanceOf(furnace.address)).to.equal(0) - // Distribute the RSR + // Try and fail to distribute the RSR from random account and BackingManager + await expect( + distributor.connect(owner).distribute(rsr.address, distAmount) + ).to.be.revertedWith('RevenueTraders only') await whileImpersonating(backingManager.address, async (bmSigner) => { await rsr.connect(bmSigner).approve(distributor.address, distAmount) - await expect(distributor.connect(bmSigner).distribute(rsr.address, distAmount)) - .to.emit(distributor, 'RevenueDistributed') - .withArgs(rsr.address, backingManager.address, distAmount) + await expect( + distributor.connect(bmSigner).distribute(rsr.address, distAmount) + ).to.be.revertedWith('RevenueTraders only') }) - // Check all funds distributed to StRSR - expect(await rsr.balanceOf(backingManager.address)).to.equal(0) - expect(await rsr.balanceOf(stRSR.address)).to.equal(distAmount) - expect(await rToken.balanceOf(furnace.address)).to.equal(0) + // Should succeed for RevenueTraders + await whileImpersonating(rTokenTrader.address, async (bmSigner) => { + await rsr.connect(bmSigner).approve(distributor.address, distAmount) + await distributor.connect(bmSigner).distribute(rsr.address, distAmount) + }) + await whileImpersonating(rsrTrader.address, async (bmSigner) => { + await rsr.connect(bmSigner).approve(distributor.address, distAmount) + await distributor.connect(bmSigner).distribute(rsr.address, distAmount) + }) + + // RSR should be in staking pool + expect(await rsr.balanceOf(stRSR.address)).to.equal(distAmount.mul(2)) }) it('Should revert if no distribution exists for a specific token', async () => { @@ -1778,9 +1926,11 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { .to.emit(distributor, 'DistributionSet') .withArgs(STRSR_DEST, bn(0), bn(0)) - await expect(distributor.distribute(rsr.address, bn(100))).to.be.revertedWith( - 'nothing to distribute' - ) + await whileImpersonating(rTokenTrader.address, async (bmSigner) => { + await expect( + distributor.connect(bmSigner).distribute(rsr.address, bn(100)) + ).to.be.revertedWith('nothing to distribute') + }) // Check funds, nothing changed expect(await rsr.balanceOf(backingManager.address)).to.equal(0) @@ -1833,7 +1983,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Should revert await expect( - rsrTrader.manageToken(aaveToken.address, TradeKind.BATCH_AUCTION) + rsrTrader.manageTokens([aaveToken.address], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('buy asset price unknown') // Funds still in Trader @@ -2017,10 +2167,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Attempt to run auctions await backingManager.forwardRevenue([aaveToken.address]) await expect( - rsrTrader.manageToken(aaveToken.address, TradeKind.BATCH_AUCTION) + rsrTrader.manageTokens([aaveToken.address], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('broker disabled') await expect( - rTokenTrader.manageToken(aaveToken.address, TradeKind.BATCH_AUCTION) + rTokenTrader.manageTokens([aaveToken.address], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('broker disabled') // Check funds - remain in traders @@ -2060,7 +2210,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(furnace.address)).to.equal(0) // Attempt to distribute AAVE token - await whileImpersonating(basketHandler.address, async (signer) => { + await whileImpersonating(rTokenTrader.address, async (signer) => { await expect( distributor.connect(signer).distribute(aaveToken.address, rewardAmountAAVE) ).to.be.revertedWith('RSR or RToken') @@ -2365,36 +2515,80 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should not trade when paused', async () => { await main.connect(owner).pauseTrading() await expect( - rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) ).to.be.revertedWith('frozen or trading paused') }) it('Should not trade when frozen', async () => { await main.connect(owner).freezeLong() await expect( - rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) ).to.be.revertedWith('frozen or trading paused') }) it('Should trade if issuance paused', async () => { await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) await main.connect(owner).pauseIssuance() - await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) }) it('Should only run 1 trade per ERC20 at a time', async () => { await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) await expect( - rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) ).to.be.revertedWith('trade open') await expect( - rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION) + rTokenTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('trade open') // Other ERC20 should be able to open trade await token1.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await rTokenTrader.manageToken(token1.address, TradeKind.DUTCH_AUCTION) + await rTokenTrader.manageTokens([token1.address], [TradeKind.DUTCH_AUCTION]) + }) + + it('Should not return bid amount before auction starts', async () => { + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) + await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) + + const trade = await ethers.getContractAt( + 'DutchTrade', + await rTokenTrader.trades(token0.address) + ) + + // Cannot get bid amount yet + await expect( + trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + ).to.be.revertedWith('auction not started') + + // Advance to start time + const start = await trade.startTime() + await advanceToTimestamp(start) + + // Now we can get bid amount + const actual = await trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + expect(actual).to.be.gt(bn(0)) + }) + + it('Should allow one bidder', async () => { + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount.div(2)) + await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) + + const trade = await ethers.getContractAt( + 'DutchTrade', + await rTokenTrader.trades(token0.address) + ) + + // Advance to auction on-going + await advanceToTimestamp((await trade.endTime()) - 1000) + + // Bid + await rToken.connect(addr1).approve(trade.address, initialBal) + await trade.connect(addr1).bid() + expect(await trade.bidder()).to.equal(addr1.address) + + // Cannot bid once is settled + await expect(trade.connect(addr1).bid()).to.be.revertedWith('bid already received') }) it('Should not return bid amount before auction starts', async () => { @@ -2444,7 +2638,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should quote piecewise-falling price correctly throughout entirety of auction', async () => { issueAmount = issueAmount.div(10000) await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) const trade = await ethers.getContractAt( 'DutchTrade', await rTokenTrader.trades(token0.address) @@ -2476,7 +2670,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should handle no bid case correctly', async () => { await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) const trade = await ethers.getContractAt( 'DutchTrade', await rTokenTrader.trades(token0.address) @@ -2501,7 +2695,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should bid at exactly endTime() and not launch another auction', async () => { await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) const trade = await ethers.getContractAt( 'DutchTrade', await rTokenTrader.trades(token0.address) @@ -2529,6 +2723,18 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(rTokenTrader.address)).to.be.closeTo(0, 100) expect(await rToken.balanceOf(furnace.address)).to.equal(expected) }) + + it('Should return lot() in {qSellTok} and sellAmount() in {sellTok}', async () => { + const amt = bn('1e6') + await token1.connect(addr1).transfer(rTokenTrader.address, amt) + await rTokenTrader.manageTokens([token1.address], [TradeKind.DUTCH_AUCTION]) + const trade = await ethers.getContractAt( + 'DutchTrade', + await rTokenTrader.trades(token1.address) + ) + expect(await trade.lot()).to.equal(amt) + expect(await trade.sellAmount()).to.equal(amt.mul(bn('1e12'))) + }) }) }) @@ -3462,8 +3668,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Manage Funds await backingManager.forwardRevenue([compToken.address]) - await snapshotGasCost(rsrTrader.manageToken(compToken.address, TradeKind.BATCH_AUCTION)) - await snapshotGasCost(rTokenTrader.manageToken(compToken.address, TradeKind.BATCH_AUCTION)) + await snapshotGasCost(rsrTrader.manageTokens([compToken.address], [TradeKind.BATCH_AUCTION])) + await snapshotGasCost( + rTokenTrader.manageTokens([compToken.address], [TradeKind.BATCH_AUCTION]) + ) // Advance time till auctions ended await advanceTime(config.batchAuctionLength.add(100).toString()) @@ -3493,7 +3701,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await snapshotGasCost(rTokenTrader.settleTrade(compToken.address)) // Manage Funds - await snapshotGasCost(rsrTrader.manageToken(compToken.address, TradeKind.BATCH_AUCTION)) + await snapshotGasCost(rsrTrader.manageTokens([compToken.address], [TradeKind.BATCH_AUCTION])) // Run final auction until all funds are converted // Advance time till auction ended @@ -3511,5 +3719,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Settle trades await snapshotGasCost(rsrTrader.settleTrade(compToken.address)) }) + + it('Selling RToken', async () => { + await rToken.connect(addr1).transfer(rsrTrader.address, fp('1')) + await snapshotGasCost(rsrTrader.manageTokens([rToken.address], [TradeKind.DUTCH_AUCTION])) + }) }) }) diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 9f7503df5e..752ec10a22 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -335,7 +335,7 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { await rsr.connect(addr2).transfer(stRSR.address, bn('10e18')) await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 1200) - await stRSR.setRewardRatio(bn('1e17')) + await stRSR.setRewardRatio(bn('1e13')) await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 1200) await stRSR.connect(addr1).unstake(stakeAmt) @@ -346,7 +346,7 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { expect(endingBal.sub(startBal)).gt(0) }) - it('Should not payout rewards when updating the reward ratio, if frozen', async () => { + it('Should payout rewards when updating the reward ratio, even if frozen', async () => { const startBal = await rsr.balanceOf(addr1.address) const stakeAmt = bn('100e18') await rsr.connect(addr1).approve(stRSR.address, stakeAmt) @@ -359,17 +359,17 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { // Freeze Main await main.connect(owner).freezeShort() - // Set reward ratio - no rewards payout - await expectEvents(stRSR.setRewardRatio(bn('1e17')), [ + // Set reward ratio - rewards payout + await expectEvents(stRSR.setRewardRatio(bn('1e13')), [ { contract: stRSR, name: 'ExchangeRateSet', - emitted: false, + emitted: true, }, { contract: stRSR, name: 'RewardsPaid', - emitted: false, + emitted: true, }, ]) @@ -381,8 +381,9 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 1209600) await stRSR.connect(addr1).withdraw(addr1.address, 1) + // Rewards paid const endingBal = await rsr.balanceOf(addr1.address) - expect(endingBal).to.equal(startBal) + expect(endingBal.sub(startBal)).gt(0) }) }) @@ -684,6 +685,23 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { expect(await rsr.balanceOf(addr1.address)).to.equal(initialBal.sub(amount)) // RSR wasn't returned }) + it('Should not allow to cancel unstake if fozen', async () => { + const amount: BigNumber = bn('1000e18') + + // Stake + await rsr.connect(addr1).approve(stRSR.address, amount) + await stRSR.connect(addr1).stake(amount) + + // Unstake + await stRSR.connect(addr1).unstake(amount) + + // Freeze Main + await main.connect(owner).freezeShort() + + // Attempt to cancel unstake + await expect(stRSR.connect(addr1).cancelUnstake(1)).to.be.revertedWith('frozen') + }) + describe('Withdrawal Leak', () => { const withdrawalLeak = fp('0.1') // 10% const stake = bn('1000e18') @@ -1125,11 +1143,7 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { await rsr.connect(addr2).approve(stRSR.address, amount3) await stRSR.connect(addr2).stake(amount3) - // Get current balances for users - const prevAddr1Balance = await rsr.balanceOf(addr1.address) - const prevAddr2Balance = await rsr.balanceOf(addr2.address) - - // Create withdrawal for user 2 + // Create 1st withdrawal for user 2 await stRSR.connect(addr2).unstake(amount2) // Move time forward to half of first period @@ -1137,24 +1151,40 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay / 2 ) - // Create additional withdrawal for user 2 + // Send reward RSR -- bn('3e18') + await rsr.connect(addr1).transfer(stRSR.address, amount3) + await stRSR.connect(owner).setRewardRatio(bn('1e14')) // handout max ratio + + // Create 2nd withdrawal for user 2 -- should unstake at 1:1 rate + expect(await stRSR.exchangeRate()).to.equal(fp('1')) await stRSR.connect(addr2).unstake(amount3) + expect(await stRSR.exchangeRate()).to.equal(fp('1')) // Check withdrawals - Nothing available yet expect(await stRSR.endIdForWithdraw(addr1.address)).to.equal(0) expect(await stRSR.endIdForWithdraw(addr2.address)).to.equal(0) - // Cancel withdrawal + // Move time forward 3/4 of way to first period complete + await advanceToTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay / 4) + + // Cancel 1st withdrawal await stRSR.connect(addr2).cancelUnstake(1) + // Calculate new exchange rate ~1.91 -- regression test + const decayFn = makeDecayFn(await stRSR.rewardRatio()) + const numRounds = stkWithdrawalDelay / 4 / 12 + const rewardHandout = amount3.sub(decayFn(amount3, numRounds)) + const newExchangeRate = amount3.add(rewardHandout).mul(fp('1')).div(amount3).add(1) + expect(await stRSR.exchangeRate()).to.be.closeTo(newExchangeRate, bn(200)) + // Move time forward to first period complete - await advanceToTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay / 2) + await advanceToTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay / 4) // Check withdrawals - We can withdraw the first stakes for each expect(await stRSR.endIdForWithdraw(addr1.address)).to.equal(1) expect(await stRSR.endIdForWithdraw(addr2.address)).to.equal(1) - // Create additional withdrawal for user 2 + // Create 3rd withdrawal for user 2 await stRSR.connect(addr2).unstake(amount3) // Move time forward to end of second period @@ -1177,26 +1207,15 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { 'index out-of-bounds' ) - // Withdraw - await stRSR - .connect(addr1) - .withdraw(addr1.address, await stRSR.endIdForWithdraw(addr1.address)) - await stRSR - .connect(addr2) - .withdraw(addr2.address, await stRSR.endIdForWithdraw(addr2.address)) - - // Withdrawals completed - expect(await stRSR.totalSupply()).to.equal(amount2) - expect(await rsr.balanceOf(stRSR.address)).to.equal(await stRSR.totalSupply()) - expect(await rsr.balanceOf(addr1.address)).to.equal(prevAddr1Balance.add(amount1)) + // Withdraw everything + await stRSR.connect(addr1).withdraw(addr1.address, 1) + await stRSR.connect(addr2).withdraw(addr2.address, 3) expect(await stRSR.balanceOf(addr1.address)).to.equal(0) - expect(await rsr.balanceOf(addr2.address)).to.equal( - prevAddr2Balance.add(amount3).add(amount3) - ) - expect(await stRSR.balanceOf(addr2.address)).to.equal(amount2) + expect(await stRSR.totalSupply()).to.be.gt(amount1) + expect(await stRSR.totalSupply()).to.be.lt(amount1.add(amount1.div(20))) // 5% - /// Exchange rate remains steady - expect(await stRSR.exchangeRate()).to.equal(fp('1')) + /// Exchange rate should increase + expect(await stRSR.exchangeRate()).to.be.gt(newExchangeRate) }) it('Should handle changes in stakingWithdrawalDelay correctly', async function () { @@ -1345,32 +1364,35 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { expect(await stRSR.exchangeRate()).to.be.gt(initialRate) }) - it('Rewards should not be handed out when paused but staking should still work', async () => { - await main.connect(owner).pauseTrading() - await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp()))) - + it('Rewards should be handed, even if frozen', async () => { // Stake await rsr.connect(addr1).approve(stRSR.address, stake) await stRSR.connect(addr1).stake(stake) + await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp()))) + expect(await stRSR.exchangeRate()).to.equal(initialRate) + + // Freeze main + await main.connect(owner).freezeShort() + + // Payout rewards + await stRSR.payoutRewards() + + // Should get new exchange rate expect(await stRSR.balanceOf(addr1.address)).to.equal(stake) + expect(await rsr.balanceOf(stRSR.address)).to.equal(stake.add(amountAdded)) expect(await rsr.balanceOf(addr1.address)).to.equal(initialBal.sub(stake)) - expect(await stRSR.exchangeRate()).to.equal(initialRate) + expect(await stRSR.exchangeRate()).to.be.gt(initialRate) }) - it('Rewards should not be handed out when frozen', async () => { + it('Rewards should not be handed out when paused but staking should still work', async () => { + await main.connect(owner).pauseTrading() + await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp()))) + // Stake await rsr.connect(addr1).approve(stRSR.address, stake) await stRSR.connect(addr1).stake(stake) - await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp()))) - - // Freeze main - await main.connect(owner).freezeShort() - - // Attempt to payout rewards - await expect(stRSR.payoutRewards()).to.be.revertedWith('frozen') - expect(await stRSR.balanceOf(addr1.address)).to.equal(stake) expect(await rsr.balanceOf(addr1.address)).to.equal(initialBal.sub(stake)) expect(await stRSR.exchangeRate()).to.equal(initialRate) @@ -3237,7 +3259,7 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { // Do accretion if (rsrAccreted.gt(0)) { await rsr.connect(owner).mint(stRSR.address, rsrAccreted) - await stRSR.connect(owner).setRewardRatio(fp('1')) // this pays out rewards + await stRSR.connect(owner).setRewardRatio(bn('1e14')) // this pays out rewards await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp()))) await expect(stRSR.payoutRewards()) // now the mint has been fully paid out @@ -3301,7 +3323,7 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { // max: 1 year const unstakingDelays = [bn(MAX_UNSTAKING_DELAY), bn('0'), bn('604800')] - const rewardRatios = [fp('1'), fp('0'), fp('0.000001069671574938')] + const rewardRatios = [bn('1e14'), fp('0'), fp('0.000001069671574938')] let dimensions = [ rsrStakes, diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index e71935ad25..b48be609f9 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1336127`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1305108`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1468729`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1445319`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `649733`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `649717`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1691559`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1675046`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1635487`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1624723`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184816`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1725283`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1714111`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `212916`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index 7e156a57b1..3408dca024 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -2,24 +2,26 @@ exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 1`] = `165168`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `165132`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `165110`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `165132`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `165110`; exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `208818`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229482`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229460`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212382`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212360`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `754579`; +exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `916913`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1146627`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `748182`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `317989`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1141155`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `271345`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `319722`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `737479`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `274788`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `248849`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `731082`; + +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `250582`; diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 96596a6721..808a21eca9 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -22,7 +22,6 @@ import { QUEUE_START, MAX_UINT48, MAX_UINT192, - MAX_UINT256, ONE_ADDRESS, PAUSER, } from '../../common/constants' @@ -44,6 +43,7 @@ import { TestIBackingManager, TestIBasketHandler, TestIBroker, + TestIRevenueTrader, TestIRToken, TestIStRSR, } from '../../typechain' @@ -67,6 +67,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function let rToken: TestIRToken let broker: TestIBroker let backingManager: TestIBackingManager + let rsrTrader: TestIRevenueTrader let basketHandler: TestIBasketHandler let facadeTest: FacadeTest let assetRegistry: IAssetRegistry @@ -97,6 +98,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function easyAuction, facadeTest, backingManager, + rsrTrader, basketHandler, rTokenAsset, } = await loadFixture(defaultFixture)) @@ -527,7 +529,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function }) }) - context('token0 -> token1', function () { + context('token0 -> RSR; boundary cases', function () { let issueAmount: BigNumber // Set up a basket of just token0 @@ -543,74 +545,36 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function token1.address, ]) await basketHandler.connect(owner).refreshBasket() - - // Issue - await token0.connect(owner).mint(addr1.address, issueAmount) - await token1.connect(owner).mint(addr1.address, issueAmount) - await token0.connect(addr1).approve(rToken.address, issueAmount) - await rToken.connect(addr1).issue(issueAmount) - - // Seed excess stake - await rsr.connect(owner).mint(addr1.address, issueAmount.mul(1e9)) - await rsr.connect(addr1).approve(stRSR.address, issueAmount.mul(1e9)) - await stRSR.connect(addr1).stake(issueAmount.mul(1e9)) - - // Check initial state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(true) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(issueAmount) - expect(await token0.balanceOf(backingManager.address)).to.equal(issueAmount) - expect(await rToken.totalSupply()).to.equal(issueAmount) - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + await token0.mint(rsrTrader.address, issueAmount) }) it('should be able to scoop entire auction cheaply when minBuyAmount = 0', async () => { - // Set collateral0 to unpriced - await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) // depeg - await collateral0.refresh() - await advanceTime((await collateral0.delayUntilDefault()).toString()) - await basketHandler.refreshBasket() - expect(await collateral0.status()).to.equal(CollateralStatus.DISABLED) - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - - // Advance warmup period - await advanceTime(Number(config.warmupPeriod) + 1) + await setOraclePrice(collateral0.address, bn('0')) // make collateral0 worthless - // Should launch auction for token1 - await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( - backingManager, + // force a revenue dust auction + await expect(rsrTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])).to.emit( + rsrTrader, 'TradeStarted' ) const auctionTimestamp: number = await getLatestBlockTimestamp() - const auctionId = await getAuctionId(backingManager, token0.address) + const auctionId = await getAuctionId(rsrTrader, token0.address) // Check auction opened even at minBuyAmount = 0 - await expectTrade(backingManager, { + await expectTrade(rsrTrader, { sell: token0.address, - buy: token1.address, + buy: rsr.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: auctionId, }) - const trade = await getTrade(backingManager, token0.address) + const trade = await getTrade(rsrTrader, token0.address) expect(await trade.status()).to.equal(1) // TradeStatus.OPEN - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check Gnosis - expect(await token0.balanceOf(easyAuction.address)).to.equal(issueAmount) - await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( - 'already rebalancing' - ) - // Auction should not be able to be settled await expect(easyAuction.settleAuction(auctionId)).to.be.reverted - await token1.connect(addr1).approve(easyAuction.address, issueAmount) + await rsr.mint(addr1.address, issueAmount) + await rsr.connect(addr1).approve(easyAuction.address, issueAmount) // Bid with a too-small order and fail. const lowBidAmt = 2 @@ -627,7 +591,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function ).to.be.revertedWith('order too small') // Bid with a nontheless pretty small order, and succeed. - const bidAmt = await trade.DEFAULT_MIN_BID() + const bidAmt = (await trade.DEFAULT_MIN_BID()).add(1) await easyAuction .connect(addr1) .placeSellOrders( @@ -642,11 +606,11 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction - await expectEvents(backingManager.settleTrade(token0.address), [ + await expectEvents(rsrTrader.settleTrade(token0.address), [ { - contract: backingManager, + contract: rsrTrader, name: 'TradeSettled', - args: [anyValue, token0.address, token1.address, issueAmount, bidAmt], + args: [anyValue, token0.address, rsr.address, issueAmount, bidAmt], emitted: true, }, ]) @@ -670,50 +634,38 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function const actualSellAmount = issueAmount.mul(feeDenominator).div(feeDenominator.add(feeNumerator)) const feeAmt = issueAmount.sub(actualSellAmount) - // Default collateral0 await setOraclePrice(collateral0.address, bn('0.5e8')) // depeg - await collateral0.refresh() - await advanceTime((await collateral0.delayUntilDefault()).toString()) - await basketHandler.refreshBasket() - - // Advance warmup period - await advanceTime(Number(config.warmupPeriod) + 1) - // Should launch auction for token1 - await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.emit( - backingManager, + // force a revenue dust auction + await expect(rsrTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])).to.emit( + rsrTrader, 'TradeStarted' ) const auctionTimestamp: number = await getLatestBlockTimestamp() - const auctionId = await getAuctionId(backingManager, token0.address) + const auctionId = await getAuctionId(rsrTrader, token0.address) // Check auction opened even at minBuyAmount = 0 - await expectTrade(backingManager, { + await expectTrade(rsrTrader, { sell: token0.address, - buy: token1.address, + buy: rsr.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: auctionId, }) - const trade = await getTrade(backingManager, token0.address) + const trade = await getTrade(rsrTrader, token0.address) expect(await trade.status()).to.equal(1) // TradeStatus.OPEN - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await rToken.totalSupply()).to.equal(issueAmount) - // Check Gnosis expect(await token0.balanceOf(easyAuction.address)).to.be.closeTo(issueAmount, 1) - await expect(backingManager.rebalance(TradeKind.BATCH_AUCTION)).to.be.revertedWith( - 'already rebalancing' - ) + await expect( + rsrTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION]) + ).to.be.revertedWith('trade open') // Auction should not be able to be settled await expect(easyAuction.settleAuction(auctionId)).to.be.reverted - await token1.connect(addr1).approve(easyAuction.address, issueAmount) + await rsr.mint(addr1.address, issueAmount) + await rsr.connect(addr1).approve(easyAuction.address, issueAmount) // Bid order const bidAmt = issueAmount @@ -731,11 +683,11 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await advanceTime(config.batchAuctionLength.add(100).toString()) // End current auction - await expectEvents(backingManager.settleTrade(token0.address), [ + await expectEvents(rsrTrader.settleTrade(token0.address), [ { - contract: backingManager, + contract: rsrTrader, name: 'TradeSettled', - args: [anyValue, token0.address, token1.address, issueAmount.sub(1), actualSellAmount], // Account for rounding + args: [anyValue, token0.address, rsr.address, issueAmount.sub(1), actualSellAmount], // Account for rounding emitted: true, }, ]) @@ -830,20 +782,28 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function // First simulate opening the trade to get where it will be deployed await sellTok.connect(addr1).approve(broker.address, auctionSellAmt) - const tradeAddr = await broker.connect(addr1).callStatic.openTrade(TradeKind.BATCH_AUCTION, { - sell: sellColl.address, - buy: buyColl.address, - sellAmount: auctionSellAmt, - minBuyAmount: auctionBuyAmt, - }) - + const prices = { sellLow: fp('1'), sellHigh: fp('1'), buyLow: fp('1'), buyHigh: fp('1') } + const tradeAddr = await broker.connect(addr1).callStatic.openTrade( + TradeKind.BATCH_AUCTION, + { + sell: sellColl.address, + buy: buyColl.address, + sellAmount: auctionSellAmt, + minBuyAmount: auctionBuyAmt, + }, + prices + ) // Start auction! - await broker.connect(addr1).openTrade(TradeKind.BATCH_AUCTION, { - sell: sellColl.address, - buy: buyColl.address, - sellAmount: auctionSellAmt, - minBuyAmount: auctionBuyAmt, - }) + await broker.connect(addr1).openTrade( + TradeKind.BATCH_AUCTION, + { + sell: sellColl.address, + buy: buyColl.address, + sellAmount: auctionSellAmt, + minBuyAmount: auctionBuyAmt, + }, + prices + ) // Get auctionId const trade = await ethers.getContractAt('GnosisTrade', tradeAddr) diff --git a/test/integration/fork-block-numbers.ts b/test/integration/fork-block-numbers.ts index f7b06dafc8..cf5b9d0336 100644 --- a/test/integration/fork-block-numbers.ts +++ b/test/integration/fork-block-numbers.ts @@ -4,6 +4,7 @@ const forkBlockNumber = { 'adai-plugin': 14216729, // Ethereum 'mainnet-deployment': 15690042, // Ethereum 'flux-finance': 16836855, // Ethereum + 'mainnet-2.0': 17522362, // Ethereum default: 16934828, // Ethereum } diff --git a/test/integration/mainnet-test/FacadeActVersion.test.ts b/test/integration/mainnet-test/FacadeActVersion.test.ts new file mode 100644 index 0000000000..8c39a6eebd --- /dev/null +++ b/test/integration/mainnet-test/FacadeActVersion.test.ts @@ -0,0 +1,156 @@ +import { expect } from 'chai' +import hre, { ethers } from 'hardhat' +import { evmRevert, evmSnapshot } from '../utils' +import { bn } from '../../../common/numbers' +import { IMPLEMENTATION } from '../../fixtures' +import { getChainId } from '../../../common/blockchain-utils' +import { networkConfig } from '../../../common/configuration' +import forkBlockNumber from '../fork-block-numbers' +import { FacadeAct, RevenueTraderP1 } from '../../../typechain' +import { useEnv } from '#/utils/env' + +const describeFork = useEnv('FORK') ? describe : describe.skip + +const REVENUE_TRADER_ADDR = '0xE04C26F68E0657d402FA95377aa7a2838D6cBA6f' // V2 +const FACADE_ACT_ADDR = '0xeaCaF85eA2df99e56053FD0250330C148D582547' // V3 +const SELL_TOKEN_ADDR = '0x60C384e226b120d93f3e0F4C502957b2B9C32B15' // aUSDC + +describeFork( + `FacadeAct - Settle Auctions - Mainnet Check - Mainnet Forking P${IMPLEMENTATION}`, + function () { + let facadeAct: FacadeAct + let newFacadeAct: FacadeAct + let revenueTrader: RevenueTraderP1 + let chainId: number + + let snap: string + + // Setup test environment + const setup = async (blockNumber: number) => { + // Use Mainnet fork + await hre.network.provider.request({ + method: 'hardhat_reset', + params: [ + { + forking: { + jsonRpcUrl: useEnv('MAINNET_RPC_URL'), + blockNumber: blockNumber, + }, + }, + ], + }) + } + + describe('FacadeAct', () => { + before(async () => { + await setup(forkBlockNumber['mainnet-2.0']) + + chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + snap = await evmSnapshot() + }) + + beforeEach(async () => { + await evmRevert(snap) + snap = await evmSnapshot() + + // Get contracts + facadeAct = await ethers.getContractAt('FacadeAct', FACADE_ACT_ADDR) + revenueTrader = ( + await ethers.getContractAt('RevenueTraderP1', REVENUE_TRADER_ADDR) + ) + }) + + after(async () => { + await evmRevert(snap) + }) + + it('Should settle trade successfully', async () => { + expect(await revenueTrader.tradesOpen()).to.equal(1) + await expect(revenueTrader.settleTrade(SELL_TOKEN_ADDR)).to.not.be.reverted + expect(await revenueTrader.tradesOpen()).to.equal(0) + }) + + it('Should fail with deployed FacadeAct', async () => { + expect(await revenueTrader.tradesOpen()).to.equal(1) + await expect( + facadeAct.runRevenueAuctions(revenueTrader.address, [SELL_TOKEN_ADDR], [], [1]) + ).to.be.reverted + expect(await revenueTrader.tradesOpen()).to.equal(1) + }) + + it('Should work with fixed FacadeAct', async () => { + expect(await revenueTrader.tradesOpen()).to.equal(1) + + const FacadeActFactory = await ethers.getContractFactory('FacadeAct') + newFacadeAct = await FacadeActFactory.deploy() + + await newFacadeAct.runRevenueAuctions(revenueTrader.address, [SELL_TOKEN_ADDR], [], [1]) + + expect(await revenueTrader.tradesOpen()).to.equal(0) + }) + + it('Fixed FacadeAct should return right revenueOverview', async () => { + const FacadeActFactory = await ethers.getContractFactory('FacadeAct') + newFacadeAct = await FacadeActFactory.deploy() + + const expectedSurpluses = [ + bn('13498155707558299290000'), + bn('9076'), + bn('0'), + bn('9791033088306000000'), + bn('0'), + bn('3899620000'), + bn('0'), + bn('30109289810000'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + bn('6413550000'), + ] + const expectedBmRewards = [ + bn('0'), + bn('0'), + bn('0'), + bn('9999'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + bn('0'), + ] + const [, , surpluses, , bmRewards, revTraderRewards] = + await newFacadeAct.callStatic.revenueOverview(revenueTrader.address) + + for (let i = 0; i < surpluses.length; i++) { + if (expectedSurpluses[i].gt(0)) expect(surpluses[i]).gte(expectedSurpluses[i]) + if (expectedBmRewards[i].gt(0)) expect(bmRewards[i]).gte(expectedBmRewards[i]) + expect(revTraderRewards[i]).to.equal(0) + } + }) + + it('Fixed FacadeAct should run revenue auctions', async () => { + const FacadeActFactory = await ethers.getContractFactory('FacadeAct') + newFacadeAct = await FacadeActFactory.deploy() + + expect(await revenueTrader.tradesOpen()).to.equal(1) + const main = await ethers.getContractAt('IMain', await revenueTrader.main()) + await expect( + newFacadeAct.runRevenueAuctions(revenueTrader.address, [], [await main.rToken()], [0]) + ).to.emit(revenueTrader, 'TradeStarted') + + expect(await revenueTrader.tradesOpen()).to.equal(2) + }) + }) + } +) diff --git a/test/integration/mainnet-test/StaticATokens.test.ts b/test/integration/mainnet-test/StaticATokens.test.ts index 3a363fea7b..01208c2093 100644 --- a/test/integration/mainnet-test/StaticATokens.test.ts +++ b/test/integration/mainnet-test/StaticATokens.test.ts @@ -222,7 +222,7 @@ describeFork(`Static ATokens - Mainnet Check - Mainnet Forking P${IMPLEMENTATION }) // Wrap aDAI - Underlying = false - expect(await aDai.balanceOf(addr1.address)).to.equal(initialBal) + expect(await aDai.balanceOf(addr1.address)).to.be.closeTo(initialBal, 1) expect(await stataDai.balanceOf(addr1.address)).to.equal(0) // Wrap aDAI into a staticaDAI @@ -331,7 +331,7 @@ describeFork(`Static ATokens - Mainnet Check - Mainnet Forking P${IMPLEMENTATION }) // Wrap aUSDT - Underlying = false - expect(await aUsdt.balanceOf(addr1.address)).to.equal(initialBalUSDT) + expect(await aUsdt.balanceOf(addr1.address)).to.be.closeTo(initialBalUSDT, 1) expect(await stataUsdt.balanceOf(addr1.address)).to.equal(0) // Wrap aUSDT into a staticaUSDT diff --git a/test/libraries/Fixed.test.ts b/test/libraries/Fixed.test.ts index 2b8ca66ebd..3f313e2c0f 100644 --- a/test/libraries/Fixed.test.ts +++ b/test/libraries/Fixed.test.ts @@ -598,7 +598,7 @@ describe('In FixLib,', () => { } }) }) - describe('div + divRnd', () => { + describe('div + divRnd + safeDiv', () => { it('correctly divides inside its range', async () => { // prettier-ignore const table: BigNumber[][] = [ @@ -613,6 +613,7 @@ describe('In FixLib,', () => { for (const [a, b, c] of table) { expect(await caller.div(a, b), `div(${a}, ${b})`).to.equal(c) + expect(await caller.safeDiv_(a, b, FLOOR), `safeDiv_(${a}, ${b}, FLOOR)`).to.equal(c) } }) it('correctly divides at the extremes of its range', async () => { @@ -625,6 +626,7 @@ describe('In FixLib,', () => { for (const [a, b, c] of table) { expect(await caller.div(a, b), `div((${a}, ${b})`).to.equal(c) + expect(await caller.safeDiv_(a, b, FLOOR), `safeDiv_((${a}, ${b}, FLOOR)`).to.equal(c) } }) it('correctly rounds', async () => { @@ -642,6 +644,9 @@ describe('In FixLib,', () => { expect(await caller.divRnd(a, b, FLOOR), `div((${a}, ${b}, FLOOR)`).to.equal(floor) expect(await caller.divRnd(a, b, ROUND), `div((${a}, ${b}, ROUND)`).to.equal(round) expect(await caller.divRnd(a, b, CEIL), `div((${a}, ${b}, CEIL)`).to.equal(ceil) + expect(await caller.safeDiv_(a, b, FLOOR), `safeDiv_((${a}, ${b}, FLOOR)`).to.equal(floor) + expect(await caller.safeDiv_(a, b, ROUND), `safeDiv_((${a}, ${b}, ROUND)`).to.equal(round) + expect(await caller.safeDiv_(a, b, CEIL), `safeDiv_((${a}, ${b}, CEIL)`).to.equal(ceil) } }) it('fails outside its range', async () => { @@ -654,6 +659,11 @@ describe('In FixLib,', () => { for (const [a, b] of table) { await expect(caller.div(a, b), `div((${a}, ${b})`).to.be.reverted + await expect(caller.safeDiv_(a, b, FLOOR), `safeDiv_((${a}, ${b}, FLOOR)`).not.to.be + .reverted + await expect(caller.safeDiv_(a, b, ROUND), `safeDiv_((${a}, ${b}, ROUND)`).not.to.be + .reverted + await expect(caller.safeDiv_(a, b, CEIL), `safeDiv_((${a}, ${b}, CEIL)`).not.to.be.reverted } }) it('fails to divide by zero', async () => { @@ -663,6 +673,11 @@ describe('In FixLib,', () => { for (const x of table) { await expect(caller.div(x, bn(0)), `div(${x}, 0`).to.be.reverted + await expect(caller.safeDiv_(x, bn(0), FLOOR), `safeDiv_(${x}, 0, FLOOR)`).to.not.be + .reverted + await expect(caller.safeDiv_(x, bn(0), ROUND), `safeDiv_(${x}, 0, ROUND)`).to.not.be + .reverted + await expect(caller.safeDiv_(x, bn(0), CEIL), `safeDiv_(${x}, 0, CEIL)`).to.not.be.reverted } }) }) @@ -1118,6 +1133,28 @@ describe('In FixLib,', () => { }) }) + describe('safeDiv_', () => { + it('rounds up to FIX_MAX', async () => { + expect(await caller.safeDiv_(MAX_UINT192, fp(1).sub(1), FLOOR)).to.equal(MAX_UINT192) + expect(await caller.safeDiv_(MAX_UINT192, fp(1).sub(1), ROUND)).to.equal(MAX_UINT192) + expect(await caller.safeDiv_(MAX_UINT192, fp(1).sub(1), CEIL)).to.equal(MAX_UINT192) + expect(await caller.safeDiv_(MAX_UINT192, 0, FLOOR)).to.equal(MAX_UINT192) + expect(await caller.safeDiv_(MAX_UINT192, 0, ROUND)).to.equal(MAX_UINT192) + expect(await caller.safeDiv_(MAX_UINT192, 0, CEIL)).to.equal(MAX_UINT192) + }) + + it('rounds down to 0', async () => { + expect(await caller.safeDiv_(0, 0, FLOOR)).to.equal(0) + expect(await caller.safeDiv_(0, 0, ROUND)).to.equal(0) + expect(await caller.safeDiv_(0, 0, CEIL)).to.equal(0) + expect(await caller.safeDiv_(MAX_UINT192.div(fp(1)).sub(1), MAX_UINT192, FLOOR)).to.equal(0) + expect(await caller.safeDiv_(MAX_UINT192.div(fp(1)).sub(1), MAX_UINT192, ROUND)).to.equal(1) + expect(await caller.safeDiv_(MAX_UINT192.div(fp(2)).sub(1), MAX_UINT192, ROUND)).to.equal(0) + expect(await caller.safeDiv_(MAX_UINT192.div(fp(1)).sub(1), MAX_UINT192, CEIL)).to.equal(1) + expect(await caller.safeDiv_(MAX_UINT192.div(fp(2)).sub(1), MAX_UINT192, CEIL)).to.equal(1) + }) + }) + describe('mulDiv256 + mulDiv256Rnd', () => { it('mulDiv256(0,0,1,*) = 0', async () => { expect(await caller.mulDiv256Rnd_(bn(0), bn(0), bn(1), FLOOR)).to.equal(bn(0)) diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index 3706a33177..2a6f946314 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -559,6 +559,8 @@ describe('Assets contracts #fast', () => { }) it('Should handle lot price correctly', async () => { + await rsrAsset.refresh() + // Check lot prices - use RSR as example const currBlockTimestamp: number = await getLatestBlockTimestamp() await expectPrice(rsrAsset.address, fp('1'), ORACLE_ERROR, true) @@ -584,26 +586,34 @@ describe('Assets contracts #fast', () => { expect(await rsrAsset.savedHighPrice()).to.equal(prevHighPrice) expect(await rsrAsset.lastSave()).to.equal(currBlockTimestamp) - // Lot price decreases a bit + // At first lot price doesn't decrease const [lotLowPrice2, lotHighPrice2] = await rsrAsset.lotPrice() - expect(lotLowPrice2).to.be.lt(lotLowPrice1) - expect(lotHighPrice2).to.be.lt(lotHighPrice1) + expect(lotLowPrice2).to.eq(lotLowPrice1) + expect(lotHighPrice2).to.eq(lotHighPrice1) + + // Advance past oracleTimeout + await advanceTime(await rsrAsset.oracleTimeout()) - // Advance blocks, lot price keeps decreasing - await advanceBlocks(100) + // Now lot price decreases const [lotLowPrice3, lotHighPrice3] = await rsrAsset.lotPrice() expect(lotLowPrice3).to.be.lt(lotLowPrice2) expect(lotHighPrice3).to.be.lt(lotHighPrice2) - // Advance blocks beyond PRICE_TIMEOUT - await advanceBlocks(PRICE_TIMEOUT) - - // Lot price returns 0 once time elapses + // Advance block, lot price keeps decreasing + await advanceBlocks(1) const [lotLowPrice4, lotHighPrice4] = await rsrAsset.lotPrice() expect(lotLowPrice4).to.be.lt(lotLowPrice3) expect(lotHighPrice4).to.be.lt(lotHighPrice3) - expect(lotLowPrice4).to.be.equal(bn(0)) - expect(lotHighPrice4).to.be.equal(bn(0)) + + // Advance blocks beyond PRICE_TIMEOUT + await advanceTime(PRICE_TIMEOUT.toNumber()) + + // Lot price returns 0 once time elapses + const [lotLowPrice5, lotHighPrice5] = await rsrAsset.lotPrice() + expect(lotLowPrice5).to.be.lt(lotLowPrice4) + expect(lotHighPrice5).to.be.lt(lotHighPrice4) + expect(lotLowPrice5).to.be.equal(bn(0)) + expect(lotHighPrice5).to.be.equal(bn(0)) }) }) diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index e5a0d90e24..55052779fc 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -354,6 +354,8 @@ export default function fn( expect(p[0]).to.equal(lotP[0]) expect(p[1]).to.equal(lotP[1]) + await advanceTime(await collateral.oracleTimeout()) + // Should be roughly half, after half of priceTimeout const priceTimeout = await collateral.priceTimeout() await advanceTime(priceTimeout / 2) diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index eeff7bdf43..05a8ef9642 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -78,7 +78,7 @@ export default function fn( it('supports up to 2 price feeds per token', async () => { const nonzeroError = fp('0.01') // 1% - const nTokens = defaultOpts.nTokens || 0 + const nTokens = Number(defaultOpts.nTokens) || 0 const feeds: string[][] = [] for (let i = 0; i < nTokens; i++) { diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts index a7993bb369..bef9af157a 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableMetapoolSuite.test.ts @@ -57,7 +57,7 @@ export const defaultCrvStableCollateralOpts: CurveMetapoolCollateralOpts = { defaultThreshold: THREE_POOL_DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, revenueHiding: bn('0'), // TODO - nTokens: bn('3'), + nTokens: 3, curvePool: THREE_POOL, lpToken: THREE_POOL_TOKEN, poolType: CurvePoolType.Plain, diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts index 86eed375ae..ec1a4c1582 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts @@ -43,13 +43,13 @@ export const defaultCrvStableCollateralOpts: CurveMetapoolCollateralOpts = { targetName: ethers.utils.formatBytes32String('USD'), priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: USDC_ORACLE_TIMEOUT, // should be greatest of all timeouts oracleError: bn('1'), // unused but cannot be zero maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, revenueHiding: bn('0'), // TODO - nTokens: bn('2'), + nTokens: 2, curvePool: FRAX_BP, lpToken: FRAX_BP_TOKEN, poolType: CurvePoolType.Plain, // for fraxBP, not the top-level pool diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts index 7cac9495b4..f098b9ef58 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableTestSuite.test.ts @@ -49,13 +49,13 @@ export const defaultCrvStableCollateralOpts: CurveCollateralOpts = { targetName: ethers.utils.formatBytes32String('USD'), priceTimeout: PRICE_TIMEOUT, chainlinkFeed: DAI_USD_FEED, // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts oracleError: bn('1'), // unused but cannot be zero maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, revenueHiding: bn('0'), - nTokens: bn('3'), + nTokens: 3, curvePool: THREE_POOL, lpToken: THREE_POOL_TOKEN, poolType: CurvePoolType.Plain, diff --git a/test/plugins/individual-collateral/curve/crv/CrvVolatileTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvVolatileTestSuite.test.ts index 70a7905ca1..3230147ce7 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvVolatileTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvVolatileTestSuite.test.ts @@ -48,13 +48,13 @@ export const defaultCrvVolatileCollateralOpts: CurveCollateralOpts = { targetName: ethers.utils.formatBytes32String('TRICRYPTO'), priceTimeout: PRICE_TIMEOUT, chainlinkFeed: USDT_USD_FEED, // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: USDT_ORACLE_TIMEOUT, // max of oracleTimeouts oracleError: bn('1'), // unused but cannot be zero maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, revenueHiding: bn('0'), - nTokens: bn('3'), + nTokens: 3, curvePool: TRI_CRYPTO, lpToken: TRI_CRYPTO_TOKEN, poolType: CurvePoolType.Plain, diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts index b92ba7c1b8..9a947dba48 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableMetapoolSuite.test.ts @@ -58,7 +58,7 @@ export const defaultCvxStableCollateralOpts: CurveMetapoolCollateralOpts = { defaultThreshold: THREE_POOL_DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, revenueHiding: bn('0'), // TODO - nTokens: bn('3'), + nTokens: 3, curvePool: THREE_POOL, lpToken: THREE_POOL_TOKEN, poolType: CurvePoolType.Plain, diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts index fbb8b04f72..fca510042b 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts @@ -44,13 +44,13 @@ export const defaultCvxStableCollateralOpts: CurveMetapoolCollateralOpts = { targetName: ethers.utils.formatBytes32String('USD'), priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts oracleError: bn('1'), // unused but cannot be zero maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, revenueHiding: bn('0'), // TODO - nTokens: bn('2'), + nTokens: 2, curvePool: FRAX_BP, lpToken: FRAX_BP_TOKEN, poolType: CurvePoolType.Plain, // for fraxBP, not the top-level pool diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts index eb9fc0eb40..ba3e7b7180 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts @@ -50,13 +50,13 @@ export const defaultCvxStableCollateralOpts: CurveCollateralOpts = { targetName: ethers.utils.formatBytes32String('USD'), priceTimeout: PRICE_TIMEOUT, chainlinkFeed: DAI_USD_FEED, // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts oracleError: bn('1'), // unused but cannot be zero maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, revenueHiding: bn('0'), - nTokens: bn('3'), + nTokens: 3, curvePool: THREE_POOL, lpToken: THREE_POOL_TOKEN, poolType: CurvePoolType.Plain, diff --git a/test/plugins/individual-collateral/curve/cvx/CvxVolatileTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxVolatileTestSuite.test.ts index 181a7fe59b..014b053b86 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxVolatileTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxVolatileTestSuite.test.ts @@ -49,13 +49,13 @@ export const defaultCvxVolatileCollateralOpts: CurveCollateralOpts = { targetName: ethers.utils.formatBytes32String('TRICRYPTO'), priceTimeout: PRICE_TIMEOUT, chainlinkFeed: USDT_USD_FEED, // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: USDT_ORACLE_TIMEOUT, // max of oracleTimeouts oracleError: bn('1'), // unused but cannot be zero maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, revenueHiding: bn('0'), - nTokens: bn('3'), + nTokens: 3, curvePool: TRI_CRYPTO, lpToken: TRI_CRYPTO_TOKEN, poolType: CurvePoolType.Plain, diff --git a/test/plugins/individual-collateral/curve/pluginTestTypes.ts b/test/plugins/individual-collateral/curve/pluginTestTypes.ts index 230e6cd146..8714a502fd 100644 --- a/test/plugins/individual-collateral/curve/pluginTestTypes.ts +++ b/test/plugins/individual-collateral/curve/pluginTestTypes.ts @@ -29,7 +29,7 @@ export interface CurveCollateralFixtureContext extends CurveBase { // The basic constructor arguments for a Curve collateral plugin -- extension export interface CurveCollateralOpts extends CollateralOpts { - nTokens?: BigNumberish + nTokens?: number curvePool?: string poolType?: CurvePoolType feeds?: string[][] diff --git a/test/scenario/BadERC20.test.ts b/test/scenario/BadERC20.test.ts index 1b1dfde490..6874a021ca 100644 --- a/test/scenario/BadERC20.test.ts +++ b/test/scenario/BadERC20.test.ts @@ -382,10 +382,10 @@ describe(`Bad ERC20 - P${IMPLEMENTATION}`, () => { it('should be able to process any uncensored assets already accumulated at RevenueTraders', async () => { await rToken.connect(addr1).transfer(rTokenTrader.address, issueAmt.div(2)) await rToken.connect(addr1).transfer(rsrTrader.address, issueAmt.div(2)) - await expect(rTokenTrader.manageToken(rToken.address, TradeKind.BATCH_AUCTION)) + await expect(rTokenTrader.manageTokens([rToken.address], [TradeKind.BATCH_AUCTION])) .to.emit(rToken, 'Transfer') .withArgs(rTokenTrader.address, furnace.address, issueAmt.div(2)) - await expect(rsrTrader.manageToken(rToken.address, TradeKind.BATCH_AUCTION)) + await expect(rsrTrader.manageTokens([rToken.address], [TradeKind.BATCH_AUCTION])) .to.emit(rsrTrader, 'TradeStarted') .withArgs( anyValue, diff --git a/test/scenario/ComplexBasket.test.ts b/test/scenario/ComplexBasket.test.ts index c00f465cb2..f55d5a3652 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -884,7 +884,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { sell: cUSDTokenVault.address, buy: rToken.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('1'), + externalId: bn('3'), }) trade = await getTrade(rTokenTrader, cUSDTokenVault.address) @@ -936,7 +936,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { sellAmount: auctionSellAmt2, buyAmount: auctionbuyAmt2, }) - await gnosis.placeBid(1, { + await gnosis.placeBid(3, { bidder: addr1.address, sellAmount: auctionSellAmtRToken2, buyAmount: auctionbuyAmtRToken2, @@ -1048,7 +1048,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { sell: cWBTCVault.address, buy: rToken.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('7'), + externalId: bn('8'), }) trade = await getTrade(rTokenTrader, cWBTCVault.address) @@ -1079,7 +1079,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { sellAmount: auctionSellAmt5, buyAmount: auctionbuyAmt5, }) - await gnosis.placeBid(7, { + await gnosis.placeBid(8, { bidder: addr1.address, sellAmount: auctionSellAmtRToken5, buyAmount: auctionbuyAmtRToken5, diff --git a/test/scenario/EURT.test.ts b/test/scenario/EURT.test.ts index bc49ffcfb6..07f19a8a1d 100644 --- a/test/scenario/EURT.test.ts +++ b/test/scenario/EURT.test.ts @@ -164,9 +164,9 @@ describe(`EUR fiatcoins (eg EURT) - P${IMPLEMENTATION}`, () => { expect(await eurt.balanceOf(rTokenTrader.address)).to.equal(0) expect(await eurt.balanceOf(rsrTrader.address)).to.equal(0) await expect( - rTokenTrader.manageToken(eurt.address, TradeKind.BATCH_AUCTION) + rTokenTrader.manageTokens([eurt.address], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('0 balance') - await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + await expect(rTokenTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])).to.emit( rTokenTrader, 'TradeStarted' ) @@ -176,10 +176,10 @@ describe(`EUR fiatcoins (eg EURT) - P${IMPLEMENTATION}`, () => { expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(rToken.address) - await expect(rsrTrader.manageToken(eurt.address, TradeKind.BATCH_AUCTION)).to.be.revertedWith( - '0 balance' - ) - await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + await expect( + rsrTrader.manageTokens([eurt.address], [TradeKind.BATCH_AUCTION]) + ).to.be.revertedWith('0 balance') + await expect(rsrTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])).to.emit( rsrTrader, 'TradeStarted' ) diff --git a/test/scenario/RevenueHiding.test.ts b/test/scenario/RevenueHiding.test.ts index 6412328602..8b1cfa00fb 100644 --- a/test/scenario/RevenueHiding.test.ts +++ b/test/scenario/RevenueHiding.test.ts @@ -252,11 +252,11 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME // Double exchange rate and launch auctions await cDAI.setExchangeRate(fp('2')) // double rate await backingManager.forwardRevenue([cDAI.address]) // transfers tokens to Traders - await expect(rTokenTrader.manageToken(cDAI.address, TradeKind.BATCH_AUCTION)).to.emit( + await expect(rTokenTrader.manageTokens([cDAI.address], [TradeKind.BATCH_AUCTION])).to.emit( rTokenTrader, 'TradeStarted' ) - await expect(rsrTrader.manageToken(cDAI.address, TradeKind.BATCH_AUCTION)).to.emit( + await expect(rsrTrader.manageTokens([cDAI.address], [TradeKind.BATCH_AUCTION])).to.emit( rsrTrader, 'TradeStarted' ) diff --git a/test/scenario/WBTC.test.ts b/test/scenario/WBTC.test.ts index 64f1766949..6a1ed679b2 100644 --- a/test/scenario/WBTC.test.ts +++ b/test/scenario/WBTC.test.ts @@ -178,9 +178,9 @@ describe(`Non-fiat collateral (eg WBTC) - P${IMPLEMENTATION}`, () => { expect(await wbtc.balanceOf(rTokenTrader.address)).to.equal(0) expect(await wbtc.balanceOf(rsrTrader.address)).to.equal(0) await expect( - rTokenTrader.manageToken(wbtc.address, TradeKind.BATCH_AUCTION) + rTokenTrader.manageTokens([wbtc.address], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('0 balance') - await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + await expect(rTokenTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])).to.emit( rTokenTrader, 'TradeStarted' ) @@ -190,10 +190,10 @@ describe(`Non-fiat collateral (eg WBTC) - P${IMPLEMENTATION}`, () => { expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(rToken.address) - await expect(rsrTrader.manageToken(wbtc.address, TradeKind.BATCH_AUCTION)).to.be.revertedWith( - '0 balance' - ) - await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + await expect( + rsrTrader.manageTokens([wbtc.address], [TradeKind.BATCH_AUCTION]) + ).to.be.revertedWith('0 balance') + await expect(rsrTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])).to.emit( rsrTrader, 'TradeStarted' ) diff --git a/test/scenario/WETH.test.ts b/test/scenario/WETH.test.ts index fcd897c4c3..219c6c4186 100644 --- a/test/scenario/WETH.test.ts +++ b/test/scenario/WETH.test.ts @@ -174,9 +174,9 @@ describe(`Self-referential collateral (eg ETH via WETH) - P${IMPLEMENTATION}`, ( expect(await weth.balanceOf(rTokenTrader.address)).to.equal(0) expect(await weth.balanceOf(rsrTrader.address)).to.equal(0) await expect( - rTokenTrader.manageToken(weth.address, TradeKind.BATCH_AUCTION) + rTokenTrader.manageTokens([weth.address], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('0 balance') - await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + await expect(rTokenTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])).to.emit( rTokenTrader, 'TradeStarted' ) @@ -186,10 +186,10 @@ describe(`Self-referential collateral (eg ETH via WETH) - P${IMPLEMENTATION}`, ( expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(rToken.address) - await expect(rsrTrader.manageToken(weth.address, TradeKind.BATCH_AUCTION)).to.be.revertedWith( - '0 balance' - ) - await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + await expect( + rsrTrader.manageTokens([weth.address], [TradeKind.BATCH_AUCTION]) + ).to.be.revertedWith('0 balance') + await expect(rsrTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])).to.emit( rsrTrader, 'TradeStarted' ) diff --git a/test/scenario/cETH.test.ts b/test/scenario/cETH.test.ts index 870f11dcc0..5c8b5cd2b5 100644 --- a/test/scenario/cETH.test.ts +++ b/test/scenario/cETH.test.ts @@ -211,9 +211,9 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, expect(await cETH.balanceOf(rTokenTrader.address)).to.equal(0) expect(await cETH.balanceOf(rsrTrader.address)).to.equal(0) await expect( - rTokenTrader.manageToken(cETH.address, TradeKind.BATCH_AUCTION) + rTokenTrader.manageTokens([cETH.address], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('0 balance') - await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + await expect(rTokenTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])).to.emit( rTokenTrader, 'TradeStarted' ) @@ -223,10 +223,10 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(rToken.address) - await expect(rsrTrader.manageToken(cETH.address, TradeKind.BATCH_AUCTION)).to.be.revertedWith( - '0 balance' - ) - await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + await expect( + rsrTrader.manageTokens([cETH.address], [TradeKind.BATCH_AUCTION]) + ).to.be.revertedWith('0 balance') + await expect(rsrTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])).to.emit( rsrTrader, 'TradeStarted' ) @@ -289,7 +289,7 @@ describe(`CToken of self-referential collateral (eg cETH) - P${IMPLEMENTATION}`, await backingManager.forwardRevenue([cETH.address]) // RTokenTrader should be selling cETH and buying RToken - await expect(rTokenTrader.manageToken(cETH.address, TradeKind.BATCH_AUCTION)).to.emit( + await expect(rTokenTrader.manageTokens([cETH.address], [TradeKind.BATCH_AUCTION])).to.emit( rTokenTrader, 'TradeStarted' ) diff --git a/test/scenario/cWBTC.test.ts b/test/scenario/cWBTC.test.ts index 35d916c2f6..f145299065 100644 --- a/test/scenario/cWBTC.test.ts +++ b/test/scenario/cWBTC.test.ts @@ -219,9 +219,9 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => expect(await cWBTC.balanceOf(rTokenTrader.address)).to.equal(0) expect(await cWBTC.balanceOf(rsrTrader.address)).to.equal(0) await expect( - rTokenTrader.manageToken(cWBTC.address, TradeKind.BATCH_AUCTION) + rTokenTrader.manageTokens([cWBTC.address], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('0 balance') - await expect(rTokenTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + await expect(rTokenTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])).to.emit( rTokenTrader, 'TradeStarted' ) @@ -232,9 +232,9 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => expect(await trade.buy()).to.equal(rToken.address) await expect( - rsrTrader.manageToken(cWBTC.address, TradeKind.BATCH_AUCTION) + rsrTrader.manageTokens([cWBTC.address], [TradeKind.BATCH_AUCTION]) ).to.be.revertedWith('0 balance') - await expect(rsrTrader.manageToken(token0.address, TradeKind.BATCH_AUCTION)).to.emit( + await expect(rsrTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])).to.emit( rsrTrader, 'TradeStarted' ) @@ -312,7 +312,7 @@ describe(`CToken of non-fiat collateral (eg cWBTC) - P${IMPLEMENTATION}`, () => await backingManager.forwardRevenue([cWBTC.address]) // RTokenTrader should be selling cWBTC and buying RToken - await expect(rTokenTrader.manageToken(cWBTC.address, TradeKind.BATCH_AUCTION)).to.emit( + await expect(rTokenTrader.manageTokens([cWBTC.address], [TradeKind.BATCH_AUCTION])).to.emit( rTokenTrader, 'TradeStarted' ) From a82b672a88daf189c7e72cdcbbd885f497cbac40 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 3 Jul 2023 17:51:59 -0400 Subject: [PATCH 334/462] fix compiling --- contracts/mixins/NetworkConfigLib.sol | 26 ------------------------ contracts/p0/BasketHandler.sol | 26 ++++++------------------ contracts/p0/Furnace.sol | 1 - contracts/p0/StRSR.sol | 1 - contracts/p0/mixins/TradingLib.sol | 4 ++-- contracts/p1/BackingManager.sol | 1 - contracts/p1/BasketHandler.sol | 22 ++++++-------------- contracts/p1/Broker.sol | 1 - contracts/p1/Furnace.sol | 1 - contracts/p1/StRSR.sol | 1 - contracts/p1/mixins/TradeLib.sol | 4 ++-- contracts/plugins/trading/DutchTrade.sol | 1 - yarn.lock | 24 ++++++++++++++++++++++ 13 files changed, 40 insertions(+), 73 deletions(-) delete mode 100644 contracts/mixins/NetworkConfigLib.sol diff --git a/contracts/mixins/NetworkConfigLib.sol b/contracts/mixins/NetworkConfigLib.sol deleted file mode 100644 index b347bec484..0000000000 --- a/contracts/mixins/NetworkConfigLib.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.19; - -/** - * @title NetworkConfigLib - * @notice Provides network-specific configuration parameters - */ -library NetworkConfigLib { - error InvalidNetwork(); - - // Returns the blocktime based on the current network (e.g. 12s for Ethereum PoS) - // See docs/system-design.md for discussion of handling longer or shorter times - function blocktime() internal view returns (uint48) { - uint256 chainId = block.chainid; - // untestable: - // most of the branches will be shown as uncovered, because we only run coverage - // on local Ethereum PoS network (31337). Manual testing was performed. - if (chainId == 1 || chainId == 5 || chainId == 31337) { - return 12; // Ethereum PoS, Goerli, HH (tests) - } else if (chainId == 8453 || chainId == 84531) { - return 2; // Base, Base Goerli - } else { - revert InvalidNetwork(); - } - } -} diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 20464dc0d8..b0562e1d34 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -514,26 +514,12 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { for (uint256 i = 0; i < len; ++i) { erc20s[i] = address(erc20sAll[i]); - try main.assetRegistry().toAsset(IERC20(erc20s[i])) returns (IAsset asset) { - if (!asset.isCollateral()) continue; // skip token if no longer registered - quantities[i] = FIX_MAX; - - // prevent div-by-zero - uint192 refPerTok = ICollateral(address(asset)).refPerTok(); - if (refPerTok == 0) continue; - - // {tok} = {BU} * {ref/BU} / {ref/tok} - - quantities[i] = amount.safeMulDiv(refAmtsAll[i], refPerTok, FLOOR).shiftl_toUint( - int8(asset.erc20Decimals()), - FLOOR - ); - // marginally more penalizing than its sibling calculation that uses _quantity() - // because does not intermediately CEIL as part of the division - } catch (bytes memory errData) { - // see: docs/solidity-style.md#Catching-Empty-Data - if (errData.length == 0) revert(); // solhint-disable-line reason-string - } + // {tok} = {BU} * {ref/BU} / {ref/tok} + quantities[i] = amount + .safeMulDiv(refAmtsAll[i], collsAll[i].refPerTok(), FLOOR) + .shiftl_toUint(int8(collsAll[i].erc20Decimals()), FLOOR); + // marginally more penalizing than its sibling calculation that uses _quantity() + // because does not intermediately CEIL as part of the division } } diff --git a/contracts/p0/Furnace.sol b/contracts/p0/Furnace.sol index 16b0d4377c..869a3eff76 100644 --- a/contracts/p0/Furnace.sol +++ b/contracts/p0/Furnace.sol @@ -5,7 +5,6 @@ import "../libraries/Fixed.sol"; import "../libraries/NetworkConfigLib.sol"; import "../interfaces/IFurnace.sol"; import "./mixins/Component.sol"; -import "../mixins/NetworkConfigLib.sol"; /** * @title FurnaceP0 diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index 926861473e..aca576a471 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -17,7 +17,6 @@ import "../libraries/Fixed.sol"; import "../libraries/NetworkConfigLib.sol"; import "../libraries/Permit.sol"; import "./mixins/Component.sol"; -import "../mixins/NetworkConfigLib.sol"; /* * @title StRSRP0 diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index f1ed5e5bc8..a71df6c027 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -66,8 +66,8 @@ library TradingLibP0 { // Calculate equivalent buyAmount within [0, FIX_MAX] // {buyTok} = {sellTok} * {1} * {UoA/sellTok} / {UoA/buyTok} uint192 b = s.mul(FIX_ONE.minus(maxTradeSlippage)).safeMulDiv( - trade.sellPrice, - trade.buyPrice, + trade.prices.sellLow, + trade.prices.buyHigh, CEIL ); diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index de60a1d0a5..b16d4a1677 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -11,7 +11,6 @@ import "../libraries/Fixed.sol"; import "../libraries/NetworkConfigLib.sol"; import "./mixins/Trading.sol"; import "./mixins/RecollateralizationLib.sol"; -import "../mixins/NetworkConfigLib.sol"; /** * @title BackingManager diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 5d8655df70..3bb3fb4df9 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -459,22 +459,12 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { for (uint256 i = 0; i < len; ++i) { erc20s[i] = address(erc20sAll[i]); - try assetRegistry.toAsset(IERC20(erc20s[i])) returns (IAsset asset) { - if (!asset.isCollateral()) continue; // skip token if no longer registered - - // {tok} = {BU} * {ref/BU} / {ref/tok} - quantities[i] = amount - .safeMulDiv(refAmtsAll[i], ICollateral(address(asset)).refPerTok(), FLOOR) - .shiftl_toUint(int8(asset.erc20Decimals()), FLOOR); - - // marginally more penalizing than its sibling calculation that uses _quantity() - // because does not intermediately CEIL as part of the division - } catch (bytes memory errData) { - // untested: - // OOG pattern tested in other contracts, cost to test here is high - // see: docs/solidity-style.md#Catching-Empty-Data - if (errData.length == 0) revert(); // solhint-disable-line reason-string - } + // {tok} = {BU} * {ref/BU} / {ref/tok} + quantities[i] = amount + .safeMulDiv(refAmtsAll[i], collsAll[i].refPerTok(), FLOOR) + .shiftl_toUint(int8(collsAll[i].erc20Decimals()), FLOOR); + // marginally more penalizing than its sibling calculation that uses _quantity() + // because does not intermediately CEIL as part of the division } } diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index abd9ec9ec9..f248cb3fcd 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -10,7 +10,6 @@ import "../interfaces/ITrade.sol"; import "../libraries/Fixed.sol"; import "../libraries/NetworkConfigLib.sol"; import "./mixins/Component.sol"; -import "../mixins/NetworkConfigLib.sol"; import "../plugins/trading/DutchTrade.sol"; import "../plugins/trading/GnosisTrade.sol"; diff --git a/contracts/p1/Furnace.sol b/contracts/p1/Furnace.sol index c670f11235..3259e48fd2 100644 --- a/contracts/p1/Furnace.sol +++ b/contracts/p1/Furnace.sol @@ -5,7 +5,6 @@ import "../libraries/Fixed.sol"; import "../libraries/NetworkConfigLib.sol"; import "../interfaces/IFurnace.sol"; import "./mixins/Component.sol"; -import "../mixins/NetworkConfigLib.sol"; /** * @title FurnaceP1 diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index 8cde9097a2..f6babc8eb6 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -14,7 +14,6 @@ import "../libraries/Fixed.sol"; import "../libraries/NetworkConfigLib.sol"; import "../libraries/Permit.sol"; import "./mixins/Component.sol"; -import "../mixins/NetworkConfigLib.sol"; /* * @title StRSRP1 diff --git a/contracts/p1/mixins/TradeLib.sol b/contracts/p1/mixins/TradeLib.sol index 769cd569ce..f0921dd511 100644 --- a/contracts/p1/mixins/TradeLib.sol +++ b/contracts/p1/mixins/TradeLib.sol @@ -68,8 +68,8 @@ library TradeLib { // Calculate equivalent buyAmount within [0, FIX_MAX] // {buyTok} = {sellTok} * {1} * {UoA/sellTok} / {UoA/buyTok} uint192 b = s.mul(FIX_ONE.minus(maxTradeSlippage)).safeMulDiv( - trade.sellPrice, - trade.buyPrice, + trade.prices.sellLow, + trade.prices.buyHigh, CEIL ); diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 057262f7c5..86160bcedc 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -8,7 +8,6 @@ import "../../libraries/NetworkConfigLib.sol"; import "../../interfaces/IAsset.sol"; import "../../interfaces/IBroker.sol"; import "../../interfaces/ITrade.sol"; -import "../../mixins/NetworkConfigLib.sol"; uint192 constant FORTY_PERCENT = 4e17; // {1} 0.4 uint192 constant SIXTY_PERCENT = 6e17; // {1} 0.6 diff --git a/yarn.lock b/yarn.lock index c693a311c8..5e74124a72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6731,6 +6731,16 @@ fsevents@~2.1.1: languageName: node linkType: hard +"isomorphic-unfetch@npm:^3.0.0": + version: 3.1.0 + resolution: "isomorphic-unfetch@npm:3.1.0" + dependencies: + node-fetch: ^2.6.1 + unfetch: ^4.2.0 + checksum: 82b92fe4ec2823a81ab0fc0d11bd94d710e6f9a940d56b3cba31896d4345ec9ffc7949f4ff31ebcae84f6b95f7ebf3474c4c7452b834eb4078ea3f2c37e459c5 + languageName: node + linkType: hard + "isstream@npm:~0.1.2": version: 0.1.2 resolution: "isstream@npm:0.1.2" @@ -7899,6 +7909,20 @@ fsevents@~2.1.1: languageName: node linkType: hard +"node-fetch@npm:^2.6.0": + version: 2.6.12 + resolution: "node-fetch@npm:2.6.12" + dependencies: + whatwg-url: ^5.0.0 + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 3bc1655203d47ee8e313c0d96664b9673a3d4dd8002740318e9d27d14ef306693a4b2ef8d6525775056fd912a19e23f3ac0d7111ad8925877b7567b29a625592 + languageName: node + linkType: hard + "node-fetch@npm:^2.6.1": version: 2.6.11 resolution: "node-fetch@npm:2.6.11" From 3d847b0a4aca7206e21999b6ca0322a5ead98205 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 3 Jul 2023 18:01:41 -0400 Subject: [PATCH 335/462] lint clean --- test/Broker.test.ts | 4 +- test/Facade.test.ts | 157 -------------------------------------------- 2 files changed, 2 insertions(+), 159 deletions(-) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 57e3f29087..be7299035c 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -41,7 +41,6 @@ import { PRICE_TIMEOUT, } from './fixtures' import snapshotGasCost from './utils/snapshotGasCost' -import { setOraclePrice } from './utils/oracles' import { advanceTime, advanceToTimestamp, getLatestBlockTimestamp } from './utils/time' import { ITradeRequest } from './utils/trades' import { useEnv } from '#/utils/env' @@ -1127,7 +1126,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { newCollateral0.address, collateral1.address, amount, - config.dutchAuctionLength + config.dutchAuctionLength, + prices ) ).to.not.be.reverted diff --git a/test/Facade.test.ts b/test/Facade.test.ts index 947f554576..1ebc9145e4 100644 --- a/test/Facade.test.ts +++ b/test/Facade.test.ts @@ -24,10 +24,6 @@ import { RecollateralizationLibP1, RevenueTraderCompatibleV1, RevenueTraderCompatibleV2, -<<<<<<< HEAD -======= - RevenueTraderP1InvalidReverts, ->>>>>>> 3.0.0 RevenueTraderInvalidVersion, RevenueTraderP1, StaticATokenMock, @@ -109,10 +105,6 @@ describe('FacadeRead + FacadeAct contracts', () => { let RevenueTraderV2ImplFactory: ContractFactory let RevenueTraderV1ImplFactory: ContractFactory let RevenueTraderInvalidVerImplFactory: ContractFactory -<<<<<<< HEAD -======= - let RevenueTraderRevertsImplFactory: ContractFactory ->>>>>>> 3.0.0 let BackingMgrV2ImplFactory: ContractFactory let BackingMgrV1ImplFactory: ContractFactory let BackingMgrInvalidVerImplFactory: ContractFactory @@ -164,13 +156,6 @@ describe('FacadeRead + FacadeAct contracts', () => { 'RevenueTraderInvalidVersion' ) -<<<<<<< HEAD -======= - RevenueTraderRevertsImplFactory = await ethers.getContractFactory( - 'RevenueTraderP1InvalidReverts' - ) - ->>>>>>> 3.0.0 const tradingLib: RecollateralizationLibP1 = ( await (await ethers.getContractFactory('RecollateralizationLibP1')).deploy() ) @@ -305,7 +290,6 @@ describe('FacadeRead + FacadeAct contracts', () => { expect(uoas[3]).to.equal(0) }) -<<<<<<< HEAD it('Should revert when returning issuable quantities if frozen', async () => { await main.connect(owner).freezeShort() await expect(facade.callStatic.issue(rToken.address, issueAmount)).to.be.revertedWith( @@ -313,8 +297,6 @@ describe('FacadeRead + FacadeAct contracts', () => { ) }) -======= ->>>>>>> 3.0.0 it('Should return redeemable quantities correctly', async () => { const [toks, quantities, available] = await facade.callStatic.redeem( rToken.address, @@ -620,129 +602,6 @@ describe('FacadeRead + FacadeAct contracts', () => { await facadeAct.callStatic.revenueOverview(trader.address) expect(canStart).to.eql(Array(8).fill(false)) -<<<<<<< HEAD -======= - // Nothing should be settleable - expect((await facade.auctionsSettleable(trader.address)).length).to.equal(0) - - // Advance time till auction ended - await advanceTime(auctionLength + 13) - - // Now should be settleable - const settleable = await facade.auctionsSettleable(trader.address) - expect(settleable.length).to.equal(1) - expect(settleable[0]).to.equal(token.address) - - // Another call to revenueOverview should settle and propose new auction - ;[erc20s, canStart, surpluses, minTradeAmounts] = - await facadeAct.callStatic.revenueOverview(trader.address) - - // Should repeat the same auctions - for (let i = 0; i < 8; i++) { - if (erc20s[i] == token.address) { - expect(canStart[i]).to.equal(true) - expect(surpluses[i]).to.equal(tokenSurplus) - } else { - expect(canStart[i]).to.equal(false) - expect(surpluses[i]).to.equal(0) - } - } - - // Settle and start new auction - await facadeAct.runRevenueAuctions( - trader.address, - erc20sToStart, - erc20sToStart, - TradeKind.DUTCH_AUCTION - ) - - // Send additional revenues - await token.connect(addr1).transfer(trader.address, tokenSurplus) - - // Call revenueOverview, cannot open new auctions - ;[erc20s, canStart, surpluses, minTradeAmounts] = - await facadeAct.callStatic.revenueOverview(trader.address) - expect(canStart).to.eql(Array(8).fill(false)) - } - }) - - itP1('Should handle other versions when running revenueOverview revenue', async () => { - // Use P1 specific versions - backingManager = ( - await ethers.getContractAt('BackingManagerP1', backingManager.address) - ) - rTokenTrader = ( - await ethers.getContractAt('RevenueTraderP1', rTokenTrader.address) - ) - rsrTrader = await ethers.getContractAt('RevenueTraderP1', rsrTrader.address) - - const revTraderV2: RevenueTraderCompatibleV2 = ( - await RevenueTraderV2ImplFactory.deploy() - ) - - const revTraderV1: RevenueTraderCompatibleV1 = ( - await RevenueTraderV1ImplFactory.deploy() - ) - - const backingManagerV2: BackingMgrCompatibleV2 = ( - await BackingMgrV2ImplFactory.deploy() - ) - - const backingManagerV1: BackingMgrCompatibleV1 = ( - await BackingMgrV1ImplFactory.deploy() - ) - - // Upgrade RevenueTraders and BackingManager to V2 - await rsrTrader.connect(owner).upgradeTo(revTraderV2.address) - await rTokenTrader.connect(owner).upgradeTo(revTraderV2.address) - await backingManager.connect(owner).upgradeTo(backingManagerV2.address) - - const traders = [rTokenTrader, rsrTrader] - for (let traderIndex = 0; traderIndex < traders.length; traderIndex++) { - const trader = traders[traderIndex] - - const minTradeVolume = await trader.minTradeVolume() - const auctionLength = await broker.dutchAuctionLength() - const tokenSurplus = bn('0.5e18') - await token.connect(addr1).transfer(trader.address, tokenSurplus) - - // revenue - let [erc20s, canStart, surpluses, minTradeAmounts] = - await facadeAct.callStatic.revenueOverview(trader.address) - expect(erc20s.length).to.equal(8) // should be full set of registered ERC20s - - const erc20sToStart = [] - for (let i = 0; i < 8; i++) { - if (erc20s[i] == token.address) { - erc20sToStart.push(erc20s[i]) - expect(canStart[i]).to.equal(true) - expect(surpluses[i]).to.equal(tokenSurplus) - } else { - expect(canStart[i]).to.equal(false) - expect(surpluses[i]).to.equal(0) - } - const asset = await ethers.getContractAt('IAsset', await assetRegistry.toAsset(erc20s[i])) - const [low] = await asset.price() - expect(minTradeAmounts[i]).to.equal( - minTradeVolume.mul(bn('10').pow(await asset.erc20Decimals())).div(low) - ) // 1% oracleError - } - - // Run revenue auctions via multicall - const funcSig = ethers.utils.id('runRevenueAuctions(address,address[],address[],uint8)') - const args = ethers.utils.defaultAbiCoder.encode( - ['address', 'address[]', 'address[]', 'uint8'], - [trader.address, [], erc20sToStart, TradeKind.DUTCH_AUCTION] - ) - const data = funcSig.substring(0, 10) + args.slice(2) - await expect(facadeAct.multicall([data])).to.emit(trader, 'TradeStarted') - - // Another call to revenueOverview should not propose any auction - ;[erc20s, canStart, surpluses, minTradeAmounts] = - await facadeAct.callStatic.revenueOverview(trader.address) - expect(canStart).to.eql(Array(8).fill(false)) - ->>>>>>> 3.0.0 // Nothing should be settleable expect((await facade.auctionsSettleable(trader.address)).length).to.equal(0) @@ -754,13 +613,6 @@ describe('FacadeRead + FacadeAct contracts', () => { expect(settleable.length).to.equal(1) expect(settleable[0]).to.equal(token.address) -<<<<<<< HEAD -======= - // Upgrade to V1 - await trader.connect(owner).upgradeTo(revTraderV1.address) - await backingManager.connect(owner).upgradeTo(backingManagerV1.address) - ->>>>>>> 3.0.0 // Another call to revenueOverview should settle and propose new auction ;[erc20s, canStart, surpluses, minTradeAmounts] = await facadeAct.callStatic.revenueOverview(trader.address) @@ -777,18 +629,9 @@ describe('FacadeRead + FacadeAct contracts', () => { } // Settle and start new auction -<<<<<<< HEAD await facadeAct.runRevenueAuctions(trader.address, erc20sToStart, erc20sToStart, [ TradeKind.DUTCH_AUCTION, ]) -======= - await facadeAct.runRevenueAuctions( - trader.address, - erc20sToStart, - erc20sToStart, - TradeKind.DUTCH_AUCTION - ) ->>>>>>> 3.0.0 // Send additional revenues await token.connect(addr1).transfer(trader.address, tokenSurplus) From a99856e36344cc85e181814a48ab932b8e28d084 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 3 Jul 2023 18:14:23 -0400 Subject: [PATCH 336/462] BasketHandlerP1 to under contract limit --- contracts/p0/BasketHandler.sol | 14 ++++++------ contracts/p1/BasketHandler.sol | 19 +++++++-------- test/Main.test.ts | 42 +++++++++++++++++----------------- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index b0562e1d34..ff88e9d319 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -238,7 +238,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { external governance { - require(erc20s.length > 0, "cannot empty basket"); + require(erc20s.length > 0, "empty basket"); require(erc20s.length == targetAmts.length, "must be same length"); requireValidCollArray(erc20s); @@ -747,10 +747,10 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { IERC20 zero = IERC20(address(0)); for (uint256 i = 0; i < erc20s.length; i++) { - require(erc20s[i] != main.rsr(), "RSR is not valid collateral"); - require(erc20s[i] != IERC20(address(main.rToken())), "RToken is not valid collateral"); - require(erc20s[i] != IERC20(address(main.stRSR())), "stRSR is not valid collateral"); - require(erc20s[i] != zero, "address zero is not valid collateral"); + require(erc20s[i] != main.rsr(), "invalid collateral"); + require(erc20s[i] != IERC20(address(main.rToken())), "invalid collateral"); + require(erc20s[i] != IERC20(address(main.stRSR())), "invalid collateral"); + require(erc20s[i] != zero, "invalid collateral"); } require(ArrayLib.allUnique(erc20s), "contains duplicates"); @@ -780,11 +780,11 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { for (uint256 i = 0; i < newERC20s.length; i++) { bytes32 targetName = main.assetRegistry().toColl(newERC20s[i]).targetName(); (bool contains, uint256 amt) = _targetAmts.tryGet(targetName); - require(contains && amt >= newTargetAmts[i], "new basket adds target weights"); + require(contains && amt >= newTargetAmts[i], "new target weights"); if (amt == newTargetAmts[i]) _targetAmts.remove(targetName); else _targetAmts.set(targetName, amt - newTargetAmts[i]); } - require(_targetAmts.length() == 0, "new basket missing target weights"); + require(_targetAmts.length() == 0, "missing target weights"); } /// Good collateral is registered, collateral, SOUND, has the expected targetName, diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 3bb3fb4df9..218b2ce5f2 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -176,7 +176,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { external governance { - require(erc20s.length > 0, "cannot empty basket"); + require(erc20s.length > 0, "empty basket"); require(erc20s.length == targetAmts.length, "must be same length"); requireValidCollArray(erc20s); @@ -562,22 +562,23 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { for (uint256 i = 0; i < len; ++i) { bytes32 targetName = assetRegistry.toColl(newERC20s[i]).targetName(); (bool contains, uint256 amt) = _targetAmts.tryGet(targetName); - require(contains && amt >= newTargetAmts[i], "new basket adds target weights"); + require(contains && amt >= newTargetAmts[i], "new target weights"); if (amt > newTargetAmts[i]) _targetAmts.set(targetName, amt - newTargetAmts[i]); else _targetAmts.remove(targetName); } - require(_targetAmts.length() == 0, "new basket missing target weights"); + require(_targetAmts.length() == 0, "missing target weights"); } /// Require that erc20s is a valid collateral array function requireValidCollArray(IERC20[] calldata erc20s) private view { - IERC20 zero = IERC20(address(0)); - for (uint256 i = 0; i < erc20s.length; i++) { - require(erc20s[i] != rsr, "RSR is not valid collateral"); - require(erc20s[i] != IERC20(address(rToken)), "RToken is not valid collateral"); - require(erc20s[i] != IERC20(address(stRSR)), "stRSR is not valid collateral"); - require(erc20s[i] != zero, "address zero is not valid collateral"); + require( + erc20s[i] != rsr && + erc20s[i] != IERC20(address(rToken)) && + erc20s[i] != IERC20(address(stRSR)) && + erc20s[i] != IERC20(address(0)), + "invalid collateral" + ); } require(ArrayLib.allUnique(erc20s), "contains duplicates"); diff --git a/test/Main.test.ts b/test/Main.test.ts index b3edf5cefe..81f697deb8 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1759,19 +1759,19 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { it('Should not allow to set prime Basket with 0 address tokens', async () => { await expect( freshBasketHandler.connect(owner).setPrimeBasket([ZERO_ADDRESS], [fp('1')]) - ).to.be.revertedWith('address zero is not valid collateral') + ).to.be.revertedWith('invalid collateral') await expect( basketHandler.connect(owner).setPrimeBasket([ZERO_ADDRESS], [fp('1')]) - ).to.be.revertedWith('address zero is not valid collateral') + ).to.be.revertedWith('invalid collateral') }) it('Should not allow to set prime Basket with stRSR', async () => { await expect( freshBasketHandler.connect(owner).setPrimeBasket([stRSR.address], [fp('1')]) - ).to.be.revertedWith('stRSR is not valid collateral') + ).to.be.revertedWith('invalid collateral') await expect( basketHandler.connect(owner).setPrimeBasket([stRSR.address], [fp('1')]) - ).to.be.revertedWith('stRSR is not valid collateral') + ).to.be.revertedWith('invalid collateral') }) it('Should not allow to bypass MAX_TARGET_AMT', async () => { @@ -1785,22 +1785,22 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { // not possible on freshBasketHandler await expect( basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1').add(1)]) - ).to.be.revertedWith('new basket adds target weights') + ).to.be.revertedWith('new target weights') }) it('Should not allow to decrease prime Basket weights', async () => { // not possible on freshBasketHandler await expect( basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1').sub(1)]) - ).to.be.revertedWith('new basket missing target weights') + ).to.be.revertedWith('missing target weights') }) it('Should not allow to set prime Basket with an empty basket', async () => { await expect(freshBasketHandler.connect(owner).setPrimeBasket([], [])).to.be.revertedWith( - 'cannot empty basket' + 'empty basket' ) await expect(basketHandler.connect(owner).setPrimeBasket([], [])).to.be.revertedWith( - 'cannot empty basket' + 'empty basket' ) }) @@ -1810,7 +1810,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { ).to.be.revertedWith('invalid target amount; must be nonzero') await expect( basketHandler.connect(owner).setPrimeBasket([token0.address], [0]) - ).to.be.revertedWith('new basket missing target weights') + ).to.be.revertedWith('missing target weights') }) it('Should be able to set exactly same basket', async () => { @@ -1853,7 +1853,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { [token0.address, token1.address, token2.address, token3.address, backupToken1.address], [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.25'), fp('0.01')] ) - ).to.be.revertedWith('new basket adds target weights') + ).to.be.revertedWith('new target weights') await expect( basketHandler @@ -1862,7 +1862,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { [token0.address, token1.address, token2.address, token3.address, eurToken.address], [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.25'), fp('0.01')] ) - ).to.be.revertedWith('new basket adds target weights') + ).to.be.revertedWith('new target weights') }) it('Should not allow to set prime Basket as subset of old basket', async () => { @@ -1873,7 +1873,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { [token0.address, token1.address, token2.address, token3.address], [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.24')] ) - ).to.be.revertedWith('new basket missing target weights') + ).to.be.revertedWith('missing target weights') await expect( basketHandler .connect(owner) @@ -1881,7 +1881,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { [token0.address, token1.address, token2.address], [fp('0.25'), fp('0.25'), fp('0.25')] ) - ).to.be.revertedWith('new basket missing target weights') + ).to.be.revertedWith('missing target weights') }) it('Should not allow to change target unit in old basket', async () => { @@ -1892,27 +1892,27 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { [token0.address, token1.address, token2.address, eurToken.address], [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.25')] ) - ).to.be.revertedWith('new basket adds target weights') + ).to.be.revertedWith('new target weights') }) it('Should not allow to set prime Basket with RSR/RToken', async () => { await expect( freshBasketHandler.connect(owner).setPrimeBasket([rsr.address], [fp('1')]) - ).to.be.revertedWith('RSR is not valid collateral') + ).to.be.revertedWith('invalid collateral') await expect( basketHandler.connect(owner).setPrimeBasket([rsr.address], [fp('1')]) - ).to.be.revertedWith('RSR is not valid collateral') + ).to.be.revertedWith('invalid collateral') await expect( freshBasketHandler .connect(owner) .setPrimeBasket([token0.address, rToken.address], [fp('0.5'), fp('0.5')]) - ).to.be.revertedWith('RToken is not valid collateral') + ).to.be.revertedWith('invalid collateral') await expect( basketHandler .connect(owner) .setPrimeBasket([token0.address, rToken.address], [fp('0.5'), fp('0.5')]) - ).to.be.revertedWith('RToken is not valid collateral') + ).to.be.revertedWith('invalid collateral') }) it('Should allow to set prime Basket if OWNER', async () => { @@ -1946,7 +1946,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { [token0.address, token1.address, token2.address, token3.address], [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.25')] ) - ).to.be.revertedWith('new basket adds target weights') + ).to.be.revertedWith('new target weights') }) describe('Custom Redemption', () => { @@ -2557,7 +2557,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { basketHandler .connect(owner) .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [rsr.address]) - ).to.be.revertedWith('RSR is not valid collateral') + ).to.be.revertedWith('invalid collateral') it('Should not allow to set backup Config with duplicate ERC20s', async () => { await expect( @@ -2574,7 +2574,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { basketHandler .connect(owner) .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [rToken.address]) - ).to.be.revertedWith('RToken is not valid collateral') + ).to.be.revertedWith('invalid collateral') }) it('Should allow to set backup Config if OWNER', async () => { From 23ffa89a8e8fda0775ccf5605d2ff807bb12496e Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 3 Jul 2023 18:16:08 -0400 Subject: [PATCH 337/462] nit --- contracts/p0/BasketHandler.sol | 2 +- contracts/p1/BasketHandler.sol | 2 +- test/Main.test.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index ff88e9d319..602ec43339 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -239,7 +239,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { governance { require(erc20s.length > 0, "empty basket"); - require(erc20s.length == targetAmts.length, "must be same length"); + require(erc20s.length == targetAmts.length, "len mismatch"); requireValidCollArray(erc20s); // If this isn't initial setup, require targets remain constant diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 218b2ce5f2..b684b8c3e2 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -177,7 +177,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { governance { require(erc20s.length > 0, "empty basket"); - require(erc20s.length == targetAmts.length, "must be same length"); + require(erc20s.length == targetAmts.length, "len mismatch"); requireValidCollArray(erc20s); // If this isn't initial setup, require targets remain constant diff --git a/test/Main.test.ts b/test/Main.test.ts index 81f697deb8..591fe6d095 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1728,10 +1728,10 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { it('Should not allow to set prime Basket with invalid length', async () => { await expect( freshBasketHandler.connect(owner).setPrimeBasket([token0.address], []) - ).to.be.revertedWith('must be same length') + ).to.be.revertedWith('len mismatch') await expect( basketHandler.connect(owner).setPrimeBasket([token0.address], []) - ).to.be.revertedWith('must be same length') + ).to.be.revertedWith('len mismatch') }) it('Should not allow to set prime Basket with non-collateral tokens', async () => { From 16b4c87c47eddf73243db2204e38b058bd805114 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 3 Jul 2023 20:23:05 -0400 Subject: [PATCH 338/462] Broker.test.ts --- test/Broker.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index be7299035c..4c47b54176 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -1132,10 +1132,10 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ).to.not.be.reverted // Check trade values - const [sellLow, sellHigh] = await newCollateral0.price() - const [buyLow, buyHigh] = await collateral1.price() - expect(await trade.middlePrice()).to.equal(divCeil(sellHigh.mul(fp('1')), buyLow)) - const withoutSlippage = sellLow.mul(fp('1')).div(buyHigh) + expect(await trade.middlePrice()).to.equal( + divCeil(prices.sellHigh.mul(fp('1')), prices.buyLow) + ) + const withoutSlippage = prices.sellLow.mul(fp('1')).div(prices.buyHigh) const withSlippage = withoutSlippage.sub( withoutSlippage.mul(config.maxTradeSlippage).div(fp('1')) ) From 33454b5ad047cb5432bf2d6de37ace41c2fabb4e Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 3 Jul 2023 20:23:10 -0400 Subject: [PATCH 339/462] Revenues.test.ts --- test/Revenues.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index cb4a99a5b6..16cfcb54e7 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2593,7 +2593,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should not return bid amount before auction starts', async () => { await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) const trade = await ethers.getContractAt( 'DutchTrade', @@ -2616,7 +2616,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should allow one bidder', async () => { await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount.div(2)) - await rTokenTrader.manageToken(token0.address, TradeKind.DUTCH_AUCTION) + await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) const trade = await ethers.getContractAt( 'DutchTrade', From fe85f8ac9e4f571259861a3d0cbc4042ef66e268 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 3 Jul 2023 20:24:31 -0400 Subject: [PATCH 340/462] curve collateralTests.ts --- test/plugins/individual-collateral/curve/collateralTests.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index 05a8ef9642..17cb3f6881 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -430,6 +430,8 @@ export default function fn( expect(p[0]).to.equal(lotP[0]) expect(p[1]).to.equal(lotP[1]) + await advanceTime(await ctx.collateral.oracleTimeout()) + // Should be roughly half, after half of priceTimeout const priceTimeout = await ctx.collateral.priceTimeout() await advanceTime(priceTimeout / 2) From e51b4662df53f77a1fb2313cb5ac4c7d99753f19 Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Wed, 5 Jul 2023 13:10:06 -0400 Subject: [PATCH 341/462] Fix 4byte 300 (#862) --- .github/workflows/4bytes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/4bytes.yml b/.github/workflows/4bytes.yml index aefd37d0d9..c1f7e3c9bb 100644 --- a/.github/workflows/4bytes.yml +++ b/.github/workflows/4bytes.yml @@ -11,7 +11,7 @@ on: jobs: deployment-scripts: if: github.event.pull_request.merged == true - name: 'Deployment Scripts' + name: '4Bytes Sync' runs-on: ubuntu-latest permissions: contents: write From aedbd14c4ed3e65db489f293a72becc3b7aabd7e Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 6 Jul 2023 09:01:09 -0300 Subject: [PATCH 342/462] prettier fixes 3.0.0 (#859) --- contracts/libraries/test/FixedCallerMock.sol | 7 +- scripts/4bytes-syncced.json | 2 +- scripts/4bytes.ts | 167 +++++++++--------- scripts/ci_backtest_plugin.ts | 32 ++-- .../phase1-common/2_deploy_implementations.ts | 84 ++++----- scripts/deployment/utils.ts | 10 +- .../verification/1_verify_implementations.ts | 8 +- scripts/verification/5_verify_facadeWrite.ts | 2 +- scripts/verification/6_verify_collateral.ts | 6 +- .../verify_convex_stable.ts | 2 +- .../upgrade-checker-utils/upgrades/2_1_0.ts | 2 +- 11 files changed, 165 insertions(+), 157 deletions(-) diff --git a/contracts/libraries/test/FixedCallerMock.sol b/contracts/libraries/test/FixedCallerMock.sol index 4c3cc1dd5f..a046fcffff 100644 --- a/contracts/libraries/test/FixedCallerMock.sol +++ b/contracts/libraries/test/FixedCallerMock.sol @@ -240,7 +240,12 @@ contract FixedCallerMock { ) public pure returns (uint192) { return FixLib.safeMul(a, b, rnd); } - function safeDiv_(uint192 a, uint192 b, RoundingMode rnd) public pure returns (uint192) { + + function safeDiv_( + uint192 a, + uint192 b, + RoundingMode rnd + ) public pure returns (uint192) { return FixLib.safeDiv(a, b, rnd); } diff --git a/scripts/4bytes-syncced.json b/scripts/4bytes-syncced.json index dfcff7fe35..ccb75c42d6 100644 --- a/scripts/4bytes-syncced.json +++ b/scripts/4bytes-syncced.json @@ -1123,4 +1123,4 @@ "NoToken(uint8)", "WrongIndex(uint8)" ] -} \ No newline at end of file +} diff --git a/scripts/4bytes.ts b/scripts/4bytes.ts index d8bead0fd0..e0bdc74421 100644 --- a/scripts/4bytes.ts +++ b/scripts/4bytes.ts @@ -1,98 +1,105 @@ import hre from 'hardhat' import fs from 'fs' -import fetch from "isomorphic-fetch" -import previousSync from "./4bytes-syncced.json" +import fetch from 'isomorphic-fetch' +import previousSync from './4bytes-syncced.json' /** * This script will sync any event and function we have with www.4byte.directory * The script saves all processed signatures with 4bytes-syncced.json as it succcesses * this way we avoid syncing the same signature twice. * */ -const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) async function main() { - const artifacts = await hre.artifacts.getAllFullyQualifiedNames(); - const artifactsWithAbi = (await Promise.all(artifacts.map(name => hre.artifacts.readArtifact(name)))).filter(artifact => artifact.abi.length !== 0); - const prevFunctions = new Set(previousSync.functions) - const prevEvents = new Set(previousSync.events) - const newErrorSignatures = new Set() - const newFunctionSignatures = new Set() - const newEventSignatures = new Set() - for (const { abi } of artifactsWithAbi) { - const abiInterface = new hre.ethers.utils.Interface(abi) - // Events and Errors seem to be the same thing for 4bytes - Object.keys(abiInterface.events).filter(e => !prevEvents.has(e)).forEach(e => newEventSignatures.add(e)) - Object.keys(abiInterface.errors).filter(e => !prevEvents.has(e)).forEach(e => newEventSignatures.add(e)) - - Object.keys(abiInterface.functions).filter(e => !prevFunctions.has(e)).forEach(e => newFunctionSignatures.add(e)) - } - const total = newErrorSignatures.size + newFunctionSignatures.size + newEventSignatures.size - if (total === 0) { - console.log("All up to date!") - return; - } + const artifacts = await hre.artifacts.getAllFullyQualifiedNames() + const artifactsWithAbi = ( + await Promise.all(artifacts.map((name) => hre.artifacts.readArtifact(name))) + ).filter((artifact) => artifact.abi.length !== 0) + const prevFunctions = new Set(previousSync.functions) + const prevEvents = new Set(previousSync.events) + const newErrorSignatures = new Set() + const newFunctionSignatures = new Set() + const newEventSignatures = new Set() + for (const { abi } of artifactsWithAbi) { + const abiInterface = new hre.ethers.utils.Interface(abi) + // Events and Errors seem to be the same thing for 4bytes + Object.keys(abiInterface.events) + .filter((e) => !prevEvents.has(e)) + .forEach((e) => newEventSignatures.add(e)) + Object.keys(abiInterface.errors) + .filter((e) => !prevEvents.has(e)) + .forEach((e) => newEventSignatures.add(e)) - console.log("Will sync " + total + " signatures with 4bytes...") + Object.keys(abiInterface.functions) + .filter((e) => !prevFunctions.has(e)) + .forEach((e) => newFunctionSignatures.add(e)) + } + const total = newErrorSignatures.size + newFunctionSignatures.size + newEventSignatures.size + if (total === 0) { + console.log('All up to date!') + return + } - const save = () => { - fs.writeFileSync("./scripts/4bytes-syncced.json", JSON.stringify(previousSync, null, 2)); - } - console.log("----- Synccing functions ----- ") - for (const sig of newFunctionSignatures) { - for (let i = 0; i < 3; i++) { - const resp = await fetch("https://www.4byte.directory/api/v1/signatures/", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - "text_signature": sig, - }) - }) - if (resp.status === 400 || resp.status === 201) { - console.log("function", sig, resp.status, await resp.text()) - previousSync.functions.push(sig); - save() - break - } - if (i === 2) { - console.log("Failed to sync function", sig, "after 3 attempts") - } else { - await sleep(1000) - } - } + console.log('Will sync ' + total + ' signatures with 4bytes...') - } - console.log("----- Synccing events ----- ") - for (const sig of newEventSignatures) { - for (let i = 0; i < 3; i++) { - const resp = await fetch("https://www.4byte.directory/api/v1/event-signatures/", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - "text_signature": sig, - }) - }) - if (resp.status === 400 || resp.status === 201) { - console.log("event", sig, resp.status, await resp.text()) - previousSync.events.push(sig); - save() - break - } + const save = () => { + fs.writeFileSync('./scripts/4bytes-syncced.json', JSON.stringify(previousSync, null, 2)) + } + console.log('----- Synccing functions ----- ') + for (const sig of newFunctionSignatures) { + for (let i = 0; i < 3; i++) { + const resp = await fetch('https://www.4byte.directory/api/v1/signatures/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + text_signature: sig, + }), + }) + if (resp.status === 400 || resp.status === 201) { + console.log('function', sig, resp.status, await resp.text()) + previousSync.functions.push(sig) + save() + break + } + if (i === 2) { + console.log('Failed to sync function', sig, 'after 3 attempts') + } else { + await sleep(1000) + } + } + } + console.log('----- Synccing events ----- ') + for (const sig of newEventSignatures) { + for (let i = 0; i < 3; i++) { + const resp = await fetch('https://www.4byte.directory/api/v1/event-signatures/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + text_signature: sig, + }), + }) + if (resp.status === 400 || resp.status === 201) { + console.log('event', sig, resp.status, await resp.text()) + previousSync.events.push(sig) + save() + break + } - if (i === 2) { - console.log("Failed to sync event", sig, "after 3 attempts") - } else { - await sleep(1000) - } - } - } - console.log("Done!") + if (i === 2) { + console.log('Failed to sync event', sig, 'after 3 attempts') + } else { + await sleep(1000) + } + } + } + console.log('Done!') } main().catch((error) => { - console.error(error) - process.exitCode = 1 + console.error(error) + process.exitCode = 1 }) diff --git a/scripts/ci_backtest_plugin.ts b/scripts/ci_backtest_plugin.ts index 568e487f2e..f99c5ca63e 100644 --- a/scripts/ci_backtest_plugin.ts +++ b/scripts/ci_backtest_plugin.ts @@ -3,7 +3,7 @@ import { ethers } from 'hardhat' import fs from 'fs' import { backTestPlugin } from './backtester/backtester' -const htmlReportTemplate = fs.readFileSync("./scripts/backtester/report-template.html", "utf8") +const htmlReportTemplate = fs.readFileSync('./scripts/backtester/report-template.html', 'utf8') export const main = async () => { const provider = new providers.JsonRpcProvider(process.env.MAINNET_RPC_URL) @@ -11,9 +11,9 @@ export const main = async () => { const currentBlock = await provider.getBlockNumber() const stride = parseInt(process.env.STRIDE ?? '300', 10) const numberOfSamples = parseInt(process.env.SAMPLES ?? '1000', 10) - const contractToTest = (await ethers.getContractFactory(process.env.CONTRACT_NAME!)).getDeployTransaction( - ...JSON.parse(process.env.CONSTRUCTOR_PARAMETERS!) - ) + const contractToTest = ( + await ethers.getContractFactory(process.env.CONTRACT_NAME!) + ).getDeployTransaction(...JSON.parse(process.env.CONSTRUCTOR_PARAMETERS!)) if (process.env.BACKTEST_RESULT_DIR != null) { console.log('Will save results to ', process.env.BACKTEST_RESULT_DIR) @@ -24,20 +24,16 @@ export const main = async () => { const start = currentBlock - stride * numberOfSamples const result = { - ...(await backTestPlugin( - [ - contractToTest.data! - ], - { + ...( + await backTestPlugin([contractToTest.data!], { start, stride, numberOfSamples, backtestServiceUrl: process.env.BACKTEST_SERVICE_URL!, - } - ))[0], - backtestName: process.env.CONTRACT_NAME! - }; - + }) + )[0], + backtestName: process.env.CONTRACT_NAME!, + } if (process.env.BACKTEST_RESULT_DIR != null) { console.log('Backtest done, saving results') @@ -47,11 +43,11 @@ export const main = async () => { JSON.stringify(result, null, 2) ) - const htmlReport = htmlReportTemplate.replace("const data = []", "const data = " + JSON.stringify([result], null, 2)) - fs.writeFileSync( - `${process.env.BACKTEST_RESULT_DIR}/report.html`, - htmlReport + const htmlReport = htmlReportTemplate.replace( + 'const data = []', + 'const data = ' + JSON.stringify([result], null, 2) ) + fs.writeFileSync(`${process.env.BACKTEST_RESULT_DIR}/report.html`, htmlReport) } else { console.log(JSON.stringify(result, null, 2)) } diff --git a/scripts/deployment/phase1-common/2_deploy_implementations.ts b/scripts/deployment/phase1-common/2_deploy_implementations.ts index eff5eac9f6..b8a5098d66 100644 --- a/scripts/deployment/phase1-common/2_deploy_implementations.ts +++ b/scripts/deployment/phase1-common/2_deploy_implementations.ts @@ -103,17 +103,17 @@ async function main() { const MainImplFactory = await ethers.getContractFactory('MainP1') let mainImplAddr = '' if (!upgrade) { - mainImplAddr = await upgrades.deployImplementation(MainImplFactory, { + mainImplAddr = (await upgrades.deployImplementation(MainImplFactory, { kind: 'uups', - }) as string + })) as string } else { - mainImplAddr = await upgrades.prepareUpgrade( + mainImplAddr = (await upgrades.prepareUpgrade( prevDeployments.implementations.main, MainImplFactory, { kind: 'uups', } - ) as string + )) as string } mainImpl = await ethers.getContractAt('MainP1', mainImplAddr) @@ -156,17 +156,17 @@ async function main() { const AssetRegImplFactory = await ethers.getContractFactory('AssetRegistryP1') let assetRegImplAddr = '' if (!upgrade) { - assetRegImplAddr = await upgrades.deployImplementation(AssetRegImplFactory, { + assetRegImplAddr = (await upgrades.deployImplementation(AssetRegImplFactory, { kind: 'uups', - }) as string + })) as string } else { - assetRegImplAddr = await upgrades.prepareUpgrade( + assetRegImplAddr = (await upgrades.prepareUpgrade( prevDeployments.implementations.components.assetRegistry, AssetRegImplFactory, { kind: 'uups', } - ) as string + )) as string } assetRegImpl = await ethers.getContractAt('AssetRegistryP1', assetRegImplAddr) @@ -188,19 +188,19 @@ async function main() { }) let backingMgrImplAddr = '' if (!upgrade) { - backingMgrImplAddr = await upgrades.deployImplementation(BackingMgrImplFactory, { + backingMgrImplAddr = (await upgrades.deployImplementation(BackingMgrImplFactory, { kind: 'uups', unsafeAllow: ['external-library-linking', 'delegatecall'], - }) as string + })) as string } else { - backingMgrImplAddr = await upgrades.prepareUpgrade( + backingMgrImplAddr = (await upgrades.prepareUpgrade( prevDeployments.implementations.components.backingManager, BackingMgrImplFactory, { kind: 'uups', unsafeAllow: ['external-library-linking', 'delegatecall'], } - ) as string + )) as string } backingMgrImpl = ( @@ -222,19 +222,19 @@ async function main() { }) let bskHndlrImplAddr = '' if (!upgrade) { - bskHndlrImplAddr = await upgrades.deployImplementation(BskHandlerImplFactory, { + bskHndlrImplAddr = (await upgrades.deployImplementation(BskHandlerImplFactory, { kind: 'uups', unsafeAllow: ['external-library-linking'], - }) as string + })) as string } else { - bskHndlrImplAddr = await upgrades.prepareUpgrade( + bskHndlrImplAddr = (await upgrades.prepareUpgrade( prevDeployments.implementations.components.basketHandler, BskHandlerImplFactory, { kind: 'uups', unsafeAllow: ['external-library-linking'], } - ) as string + )) as string } bskHndlrImpl = await ethers.getContractAt('BasketHandlerP1', bskHndlrImplAddr) @@ -252,17 +252,17 @@ async function main() { const BrokerImplFactory = await ethers.getContractFactory('BrokerP1') let brokerImplAddr = '' if (!upgrade) { - brokerImplAddr = await upgrades.deployImplementation(BrokerImplFactory, { + brokerImplAddr = (await upgrades.deployImplementation(BrokerImplFactory, { kind: 'uups', - }) as string + })) as string } else { - brokerImplAddr = await upgrades.prepareUpgrade( + brokerImplAddr = (await upgrades.prepareUpgrade( prevDeployments.implementations.components.broker, BrokerImplFactory, { kind: 'uups', } - ) as string + )) as string } brokerImpl = await ethers.getContractAt('BrokerP1', brokerImplAddr) @@ -280,17 +280,17 @@ async function main() { const DistribImplFactory = await ethers.getContractFactory('DistributorP1') let distribImplAddr = '' if (!upgrade) { - distribImplAddr = await upgrades.deployImplementation(DistribImplFactory, { + distribImplAddr = (await upgrades.deployImplementation(DistribImplFactory, { kind: 'uups', - }) as string + })) as string } else { - distribImplAddr = await upgrades.prepareUpgrade( + distribImplAddr = (await upgrades.prepareUpgrade( prevDeployments.implementations.components.distributor, DistribImplFactory, { kind: 'uups', } - ) as string + )) as string } distribImpl = await ethers.getContractAt('DistributorP1', distribImplAddr) @@ -308,17 +308,17 @@ async function main() { const FurnaceImplFactory = await ethers.getContractFactory('FurnaceP1') let furnaceImplAddr = '' if (!upgrade) { - furnaceImplAddr = await upgrades.deployImplementation(FurnaceImplFactory, { + furnaceImplAddr = (await upgrades.deployImplementation(FurnaceImplFactory, { kind: 'uups', - }) as string + })) as string } else { - furnaceImplAddr = await upgrades.prepareUpgrade( + furnaceImplAddr = (await upgrades.prepareUpgrade( prevDeployments.implementations.components.furnace, FurnaceImplFactory, { kind: 'uups', } - ) as string + )) as string } furnaceImpl = await ethers.getContractAt('FurnaceP1', furnaceImplAddr) @@ -338,21 +338,21 @@ async function main() { let rsrTraderImplAddr = '' let rTokenTraderImplAddr = '' if (!upgrade) { - rsrTraderImplAddr = await upgrades.deployImplementation(RevTraderImplFactory, { + rsrTraderImplAddr = (await upgrades.deployImplementation(RevTraderImplFactory, { kind: 'uups', unsafeAllow: ['delegatecall'], - }) as string + })) as string rTokenTraderImplAddr = rsrTraderImplAddr // Both equal in initial deployment } else { // RSR Trader - rsrTraderImplAddr = await upgrades.prepareUpgrade( + rsrTraderImplAddr = (await upgrades.prepareUpgrade( prevDeployments.implementations.components.rsrTrader, RevTraderImplFactory, { kind: 'uups', unsafeAllow: ['delegatecall'], } - ) as string + )) as string // If Traders have different implementations, upgrade separately if ( @@ -360,14 +360,14 @@ async function main() { prevDeployments.implementations.components.rTokenTrader ) { // RToken Trader - rTokenTraderImplAddr = await upgrades.prepareUpgrade( + rTokenTraderImplAddr = (await upgrades.prepareUpgrade( prevDeployments.implementations.components.rTokenTrader, RevTraderImplFactory, { kind: 'uups', unsafeAllow: ['delegatecall'], } - ) as string + )) as string } else { // Both use the same implementation rTokenTraderImplAddr = rsrTraderImplAddr @@ -400,17 +400,17 @@ async function main() { const RTokenImplFactory = await ethers.getContractFactory('RTokenP1') let rTokenImplAddr = '' if (!upgrade) { - rTokenImplAddr = await upgrades.deployImplementation(RTokenImplFactory, { + rTokenImplAddr = (await upgrades.deployImplementation(RTokenImplFactory, { kind: 'uups', - }) as string + })) as string } else { - rTokenImplAddr = await upgrades.prepareUpgrade( + rTokenImplAddr = (await upgrades.prepareUpgrade( prevDeployments.implementations.components.rToken, RTokenImplFactory, { kind: 'uups', } - ) as string + )) as string } rTokenImpl = await ethers.getContractAt('RTokenP1', rTokenImplAddr) @@ -429,17 +429,17 @@ async function main() { const StRSRImplFactory = await ethers.getContractFactory('StRSRP1Votes') let stRSRImplAddr = '' if (!upgrade) { - stRSRImplAddr = await upgrades.deployImplementation(StRSRImplFactory, { + stRSRImplAddr = (await upgrades.deployImplementation(StRSRImplFactory, { kind: 'uups', - }) as string + })) as string } else { - stRSRImplAddr = await upgrades.prepareUpgrade( + stRSRImplAddr = (await upgrades.prepareUpgrade( prevDeployments.implementations.components.stRSR, StRSRImplFactory, { kind: 'uups', } - ) as string + )) as string } stRSRImpl = await ethers.getContractAt('StRSRP1Votes', stRSRImplAddr) diff --git a/scripts/deployment/utils.ts b/scripts/deployment/utils.ts index 8b39f6a2e2..3b847da0b5 100644 --- a/scripts/deployment/utils.ts +++ b/scripts/deployment/utils.ts @@ -110,14 +110,14 @@ export async function verifyContract( await tenderly.verify({ name: contract, address: address!, - libraries - }); + libraries, + }) } else { // Sleep 0.5s to not overwhelm API await new Promise((r) => setTimeout(r, 500)) - + const ETHERSCAN_API_KEY = useEnv('ETHERSCAN_API_KEY') - + // Check to see if already verified const url = `${getEtherscanBaseURL( chainId, @@ -127,7 +127,7 @@ export async function verifyContract( if (status != 200 || data['status'] != '1') { throw new Error("Can't communicate with Etherscan API") } - + // Only run verification script if not verified if (data['result'][0]['SourceCode']?.length > 0) { console.log('Already verified. Continuing') diff --git a/scripts/verification/1_verify_implementations.ts b/scripts/verification/1_verify_implementations.ts index 8d2f853f91..c135a644c4 100644 --- a/scripts/verification/1_verify_implementations.ts +++ b/scripts/verification/1_verify_implementations.ts @@ -65,16 +65,16 @@ async function main() { desc: 'BackingManager', contract: 'contracts/p1/BackingManager.sol:BackingManagerP1', libraries: { - 'RecollateralizationLibP1': deployments.tradingLib, - } + RecollateralizationLibP1: deployments.tradingLib, + }, }, { name: 'basketHandler', desc: 'BasketHandler', contract: 'contracts/p1/BasketHandler.sol:BasketHandlerP1', libraries: { - 'BasketLibP1': deployments.basketLib, - } + BasketLibP1: deployments.basketLib, + }, }, { name: 'broker', diff --git a/scripts/verification/5_verify_facadeWrite.ts b/scripts/verification/5_verify_facadeWrite.ts index e6d569ac44..76411028d8 100644 --- a/scripts/verification/5_verify_facadeWrite.ts +++ b/scripts/verification/5_verify_facadeWrite.ts @@ -34,7 +34,7 @@ async function main() { deployments.facadeWrite, [deployments.deployer], 'contracts/facade/FacadeWrite.sol:FacadeWrite', - { FacadeWriteLib: deployments.facadeWriteLib} + { FacadeWriteLib: deployments.facadeWriteLib } ) } diff --git a/scripts/verification/6_verify_collateral.ts b/scripts/verification/6_verify_collateral.ts index 347c5fd2a3..a5377340a2 100644 --- a/scripts/verification/6_verify_collateral.ts +++ b/scripts/verification/6_verify_collateral.ts @@ -127,7 +127,7 @@ async function main() { defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h }, - revenueHiding.toString() + revenueHiding.toString(), ], 'contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol:CTokenFiatCollateral' ) @@ -154,7 +154,7 @@ async function main() { }, networkConfig[chainId].chainlinkFeeds.BTC, oracleTimeout(chainId, '3600').toString(), - revenueHiding.toString() + revenueHiding.toString(), ], 'contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol:CTokenNonFiatCollateral' ) @@ -175,7 +175,7 @@ async function main() { delayUntilDefault: '0', }, revenueHiding.toString(), - '18' + '18', ], 'contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol:CTokenSelfReferentialCollateral' ) diff --git a/scripts/verification/collateral-plugins/verify_convex_stable.ts b/scripts/verification/collateral-plugins/verify_convex_stable.ts index 4a1a4b7bba..b26f111b39 100644 --- a/scripts/verification/collateral-plugins/verify_convex_stable.ts +++ b/scripts/verification/collateral-plugins/verify_convex_stable.ts @@ -62,7 +62,7 @@ async function main() { await w3PoolCollateral.erc20(), [], 'contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol:ConvexStakingWrapper', - {CvxMining: coreDeployments.cvxMiningLib} + { CvxMining: coreDeployments.cvxMiningLib } ) /******** Verify CvxMining Lib **************************/ diff --git a/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts b/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts index b0e60f5665..c5398e5744 100644 --- a/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts +++ b/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts @@ -168,7 +168,7 @@ export default async ( await whileImpersonating(hre, whales[buyTokenAddress.toLowerCase()], async (whale) => { const sellToken = await hre.ethers.getContractAt('ERC20Mock', buyTokenAddress) let repeat = true - while(repeat) { + while (repeat) { try { await sellToken.connect(whale).approve(gnosis.address, 0) await sellToken.connect(whale).approve(gnosis.address, buyAmount) From ce892b2ab19db32b75bd184474ebfdf5b0ae4291 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:37:13 -0300 Subject: [PATCH 343/462] ci tests - non-determinism (#858) --- test/Furnace.test.ts | 200 ++++++++++-------- test/Revenues.test.ts | 11 +- test/ZZStRSR.test.ts | 4 +- .../mainnet-test/FacadeActVersion.test.ts | 3 + 4 files changed, 127 insertions(+), 91 deletions(-) diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index b385d1c82b..04d3922321 100644 --- a/test/Furnace.test.ts +++ b/test/Furnace.test.ts @@ -1,4 +1,4 @@ -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { loadFixture, setStorageAt } from '@nomicfoundation/hardhat-network-helpers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { BigNumber, ContractFactory } from 'ethers' @@ -63,11 +63,43 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { // Implementation-agnostic interface for deploying the Furnace const deployNewFurnace = async (): Promise => { - // Deploy fixture - ;({ furnace } = await loadFixture(defaultFixture)) + let FurnaceFactory: ContractFactory + let furnace: TestIFurnace + if (IMPLEMENTATION == Implementation.P0) { + FurnaceFactory = await ethers.getContractFactory('FurnaceP0') + furnace = await FurnaceFactory.deploy() + } else if (IMPLEMENTATION == Implementation.P1) { + FurnaceFactory = await ethers.getContractFactory('FurnaceP1') + furnace = await upgrades.deployProxy(FurnaceFactory, [], { + kind: 'uups', + }) + } else { + throw new Error('PROTO_IMPL must be set to either `0` or `1`') + } + return furnace } + const setFurnace = async (main: TestIMain, furnace: TestIFurnace) => { + if (IMPLEMENTATION == Implementation.P0) { + // Setup new furnace correctly in MainP0 - Slot 209 (0xD1) + await setStorageAt( + main.address, + '0xD1', + ethers.utils.hexlify(ethers.utils.zeroPad(furnace.address, 32)) + ) + } else if (IMPLEMENTATION == Implementation.P1) { + // Setup new furnace correctly in RTokenP1 - Slot 357 (0x165) + await setStorageAt( + await main.rToken(), + '0x165', + ethers.utils.hexlify(ethers.utils.zeroPad(furnace.address, 32)) + ) + } else { + throw new Error('PROTO_IMPL must be set to either `0` or `1`') + } + } + beforeEach(async () => { ;[owner, addr1, addr2] = await ethers.getSigners() @@ -102,19 +134,7 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { // Applies to all components - used here as an example it('Deployment does not accept invalid main address', async () => { - let FurnaceFactory: ContractFactory - let newFurnace: TestIFurnace - if (IMPLEMENTATION == Implementation.P0) { - FurnaceFactory = await ethers.getContractFactory('FurnaceP0') - newFurnace = await FurnaceFactory.deploy() - } else if (IMPLEMENTATION == Implementation.P1) { - FurnaceFactory = await ethers.getContractFactory('FurnaceP1') - newFurnace = await upgrades.deployProxy(FurnaceFactory, [], { - kind: 'uups', - }) - } else { - throw new Error('PROTO_IMPL must be set to either `0` or `1`') - } + const newFurnace: TestIFurnace = await deployNewFurnace() await expect(newFurnace.init(ZERO_ADDRESS, config.rewardRatio)).to.be.revertedWith( 'main is zero address' ) @@ -274,83 +294,14 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { .to.emit(rToken, 'Melted') .withArgs(hndAmt.sub(expAmt)) - // Another call to melt should have no impact + // Another call to melt right away before next period should have no impact + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 2) await expect(furnace.connect(addr1).melt()).to.not.emit(rToken, 'Melted') expect(await rToken.balanceOf(addr1.address)).to.equal(initialBal.sub(hndAmt)) expect(await rToken.balanceOf(furnace.address)).to.equal(expAmt) }) - it('Should accumulate negligible error - parallel furnaces', async () => { - // Maintain two furnaces in parallel, one burning every block and one burning annually - // We have to use two brand new instances here to ensure their timestamps are synced - const firstFurnace = await deployNewFurnace() - const secondFurnace = await deployNewFurnace() - - // Set automine to false for multiple transactions in one block - await hre.network.provider.send('evm_setAutomine', [false]) - - // Populate balances - const hndAmt: BigNumber = bn('1e18') - await rToken.connect(addr1).transfer(firstFurnace.address, hndAmt) - await rToken.connect(addr1).transfer(secondFurnace.address, hndAmt) - await firstFurnace.init(main.address, config.rewardRatio) - await secondFurnace.init(main.address, config.rewardRatio) - await advanceBlocks(1) - - // Set automine to true again - await hre.network.provider.send('evm_setAutomine', [true]) - - const oneDay = bn('86400') - for (let i = 0; i < Number(oneDay.div(ONE_PERIOD)); i++) { - // Advance a period - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) - await firstFurnace.melt() - // secondFurnace does not melt - } - - // SecondFurnace melts once - await secondFurnace.melt() - - const one = await rToken.balanceOf(firstFurnace.address) - const two = await rToken.balanceOf(secondFurnace.address) - const diff = one.sub(two).abs() // {qRTok} - const expectedDiff = bn(3555) // empirical exact diff - // At a rate of 3555 qRToken per day error, a year's worth of error would result in - // a difference only starting in the 12th decimal place: .000000000001 - // This seems more than acceptable - - expect(diff).to.be.lte(expectedDiff) - }) - - it('Should accumulate negligible error - a year all at once', async () => { - const hndAmt: BigNumber = bn('10e18') - - // Transfer - await rToken.connect(addr1).transfer(furnace.address, hndAmt) - - // Get past first noop melt - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) - await expect(furnace.connect(addr1).melt()).to.not.emit(rToken, 'Melted') - expect(await rToken.balanceOf(addr1.address)).to.equal(initialBal.sub(hndAmt)) - expect(await rToken.balanceOf(furnace.address)).to.equal(hndAmt) - - const periods = 2628000 // one year worth - - // Advance a year's worth of periods - await setNextBlockTimestamp( - Number(await getLatestBlockTimestamp()) + periods * Number(ONE_PERIOD) - ) - - // Precise JS calculation should be within 3 atto - const decayFn = makeDecayFn(await furnace.ratio()) - const expAmt = decayFn(hndAmt, periods) - const error = bn('3') - await expect(furnace.melt()).to.emit(rToken, 'Melted').withArgs(hndAmt.sub(expAmt).add(error)) - expect(await rToken.balanceOf(addr1.address)).to.equal(initialBal.sub(hndAmt)) - expect(await rToken.balanceOf(furnace.address)).to.equal(expAmt.sub(error)) - }) - it('Should allow melt - two periods, one at a time #fast', async () => { const hndAmt: BigNumber = bn('10e18') @@ -415,12 +366,85 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { .to.emit(rToken, 'Melted') .withArgs(hndAmt.sub(expAmt)) - // Another call to melt should have no impact + // Another call to melt right away before next period should have no impact + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 2) await expect(furnace.connect(addr1).melt()).to.not.emit(rToken, 'Melted') expect(await rToken.balanceOf(addr1.address)).to.equal(initialBal.sub(hndAmt)) expect(await rToken.balanceOf(furnace.address)).to.equal(expAmt) }) + + it('Should accumulate negligible error - a year all at once', async () => { + const hndAmt: BigNumber = bn('10e18') + + // Transfer + await rToken.connect(addr1).transfer(furnace.address, hndAmt) + + // Get past first noop melt + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) + await expect(furnace.connect(addr1).melt()).to.not.emit(rToken, 'Melted') + expect(await rToken.balanceOf(addr1.address)).to.equal(initialBal.sub(hndAmt)) + expect(await rToken.balanceOf(furnace.address)).to.equal(hndAmt) + + const periods = 2628000 // one year worth + + // Advance a year's worth of periods + await setNextBlockTimestamp( + Number(await getLatestBlockTimestamp()) + periods * Number(ONE_PERIOD) + ) + + // Precise JS calculation should be within 3 atto + const decayFn = makeDecayFn(await furnace.ratio()) + const expAmt = decayFn(hndAmt, periods) + const error = bn('3') + await expect(furnace.melt()).to.emit(rToken, 'Melted').withArgs(hndAmt.sub(expAmt).add(error)) + expect(await rToken.balanceOf(addr1.address)).to.equal(initialBal.sub(hndAmt)) + expect(await rToken.balanceOf(furnace.address)).to.equal(expAmt.sub(error)) + }) + + it('Should accumulate negligible error - parallel furnaces', async () => { + // Maintain two furnaces in parallel, one burning every block and one burning annually + // We have to use two brand new instances here to ensure their timestamps are synced + const firstFurnace = await deployNewFurnace() + const secondFurnace = await deployNewFurnace() + + // Set automine to false for multiple transactions in one block + await hre.network.provider.send('evm_setAutomine', [false]) + + // Populate balances + const hndAmt: BigNumber = bn('1e18') + await rToken.connect(addr1).transfer(firstFurnace.address, hndAmt) + await rToken.connect(addr1).transfer(secondFurnace.address, hndAmt) + await firstFurnace.init(main.address, config.rewardRatio) + await secondFurnace.init(main.address, config.rewardRatio) + await advanceBlocks(1) + + // Set automine to true again + await hre.network.provider.send('evm_setAutomine', [true]) + + const oneDay = bn('86400') + await setFurnace(main, firstFurnace) + for (let i = 0; i < Number(oneDay.div(ONE_PERIOD)); i++) { + // Advance a period + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) + await firstFurnace.melt() + // secondFurnace does not melt + } + + // SecondFurnace melts once + await setFurnace(main, secondFurnace) + await secondFurnace.melt() + + const one = await rToken.balanceOf(firstFurnace.address) + const two = await rToken.balanceOf(secondFurnace.address) + const diff = one.sub(two).abs() // {qRTok} + const expectedDiff = bn(3555) // empirical exact diff + // At a rate of 3555 qRToken per day error, a year's worth of error would result in + // a difference only starting in the 12th decimal place: .000000000001 + // This seems more than acceptable + + expect(diff).to.be.lte(expectedDiff) + }) }) describeExtreme('Extreme Bounds', () => { diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 16cfcb54e7..746815c077 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -44,7 +44,12 @@ import { } from '../typechain' import { whileImpersonating } from './utils/impersonation' import snapshotGasCost from './utils/snapshotGasCost' -import { advanceTime, advanceToTimestamp, getLatestBlockTimestamp } from './utils/time' +import { + advanceTime, + advanceToTimestamp, + getLatestBlockTimestamp, + setNextBlockTimestamp, +} from './utils/time' import { withinQuad } from './utils/matchers' import { Collateral, @@ -2705,7 +2710,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Snipe auction at 0s left await advanceToTimestamp((await trade.endTime()) - 1) await expect(trade.bidAmount(await trade.endTime())).to.not.be.reverted - await trade.connect(addr1).bid() // timestamp should be exactly endTime() + // Set timestamp to be exactly endTime() + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 1) + await trade.connect(addr1).bid() expect(await trade.canSettle()).to.equal(false) expect(await trade.status()).to.equal(2) // Status.CLOSED expect(await trade.bidder()).to.equal(addr1.address) diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 752ec10a22..30444cd379 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -1165,7 +1165,9 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { expect(await stRSR.endIdForWithdraw(addr2.address)).to.equal(0) // Move time forward 3/4 of way to first period complete - await advanceToTimestamp(Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay / 4) + await setNextBlockTimestamp( + Number(await getLatestBlockTimestamp()) + stkWithdrawalDelay / 4 + ) // Cancel 1st withdrawal await stRSR.connect(addr2).cancelUnstake(1) diff --git a/test/integration/mainnet-test/FacadeActVersion.test.ts b/test/integration/mainnet-test/FacadeActVersion.test.ts index 8c39a6eebd..0fadf15743 100644 --- a/test/integration/mainnet-test/FacadeActVersion.test.ts +++ b/test/integration/mainnet-test/FacadeActVersion.test.ts @@ -8,6 +8,8 @@ import { networkConfig } from '../../../common/configuration' import forkBlockNumber from '../fork-block-numbers' import { FacadeAct, RevenueTraderP1 } from '../../../typechain' import { useEnv } from '#/utils/env' +import { getLatestBlockTimestamp, setNextBlockTimestamp } from '../../utils/time' +import { ONE_PERIOD } from '#/common/constants' const describeFork = useEnv('FORK') ? describe : describe.skip @@ -95,6 +97,7 @@ describeFork( it('Fixed FacadeAct should return right revenueOverview', async () => { const FacadeActFactory = await ethers.getContractFactory('FacadeAct') + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) newFacadeAct = await FacadeActFactory.deploy() const expectedSurpluses = [ From eea677628b2c2742fefef0977d9b07148fed1fcf Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Fri, 7 Jul 2023 13:10:23 -0300 Subject: [PATCH 344/462] fix storage slots Distributor (#865) --- contracts/p1/Distributor.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index c46ed858b7..4373a9a9bc 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -200,5 +200,5 @@ contract DistributorP1 is ComponentP1, IDistributor { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[46] private __gap; + uint256[44] private __gap; } From e351b3243b5c4cba0a0caca65fe52a004644d65f Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Fri, 7 Jul 2023 13:17:05 -0300 Subject: [PATCH 345/462] additional furnace timestamp check (#866) --- test/Furnace.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index 04d3922321..de3a602612 100644 --- a/test/Furnace.test.ts +++ b/test/Furnace.test.ts @@ -262,7 +262,8 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { // Melt await expect(furnace.connect(addr1).melt()).to.not.emit(rToken, 'Melted') - // Another call to melt should also have no impact + // Another immediate call to melt should also have no impact + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 1) await expect(furnace.connect(addr1).melt()).to.not.emit(rToken, 'Melted') expect(await rToken.balanceOf(addr1.address)).to.equal(initialBal.sub(hndAmt)) @@ -295,7 +296,7 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { .withArgs(hndAmt.sub(expAmt)) // Another call to melt right away before next period should have no impact - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 2) + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 1) await expect(furnace.connect(addr1).melt()).to.not.emit(rToken, 'Melted') expect(await rToken.balanceOf(addr1.address)).to.equal(initialBal.sub(hndAmt)) @@ -367,7 +368,7 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { .withArgs(hndAmt.sub(expAmt)) // Another call to melt right away before next period should have no impact - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 2) + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 1) await expect(furnace.connect(addr1).melt()).to.not.emit(rToken, 'Melted') expect(await rToken.balanceOf(addr1.address)).to.equal(initialBal.sub(hndAmt)) From 6da3481f48742e9a70357ad3223f65ba53a2b3f9 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Tue, 11 Jul 2023 17:32:22 -0300 Subject: [PATCH 346/462] Base Testnet configuration (#867) --- .env.example | 3 + common/configuration.ts | 22 ++++++++ docs/deployment.md | 2 + hardhat.config.ts | 18 ++++++ .../addresses/84531-RTKN-tmp-deployments.json | 19 +++++++ .../84531-tmp-assets-collateral.json | 13 +++++ scripts/addresses/84531-tmp-deployments.json | 35 ++++++++++++ .../phase1-common/1_deploy_libraries.ts | 2 +- .../deployment/phase3-rtoken/rTokenConfig.ts | 39 +++++++++++++ scripts/deployment/utils.ts | 29 ++++++++-- .../mock/deploy-mock-easyauction.ts | 55 +++++++++++++++++++ tasks/index.ts | 1 + utils/env.ts | 1 + 13 files changed, 233 insertions(+), 6 deletions(-) create mode 100644 scripts/addresses/84531-RTKN-tmp-deployments.json create mode 100644 scripts/addresses/84531-tmp-assets-collateral.json create mode 100644 scripts/addresses/84531-tmp-deployments.json create mode 100644 tasks/deployment/mock/deploy-mock-easyauction.ts diff --git a/.env.example b/.env.example index 4e9a27b164..3ee518afba 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,9 @@ # Goerli Infura URL, used for Testnet deployments GOERLI_RPC_URL='https://goerli.infura.io/v3/your_infura_key' +# Base Goerli URL +BASE_GOERLI_RPC_URL="https://goerli.base.org" + # Mainnet URL, used for Mainnet forking (Alchemy) MAINNET_RPC_URL="https://eth-mainnet.alchemyapi.io/v2/your_mainnet_api_key" diff --git a/common/configuration.ts b/common/configuration.ts index 4084935193..b3b3370de8 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -331,6 +331,26 @@ export const networkConfig: { [key: string]: INetworkConfig } = { GNOSIS_EASY_AUCTION: '0x1fbab40c338e2e7243da945820ba680c92ef8281', // canonical COMPTROLLER: '0x627ea49279fd0de89186a58b8758ad02b6be2867', // canonical }, + '84531': { + name: 'base-goerli', + tokens: { + // mocks + DAI: '0xDA2eA2f60545555e268124E51EA27bc97DE78E9c', + USDC: '0x1265Ec05FD621d82F224814902c925a600307fb3', + USDT: '0x3D3226C68B1425FdaA273F2A6295D5C40462327C', + RSR: '0xc8058960a9d7E7d81143BDBA38d19e6824165932', + }, + chainlinkFeeds: { + DAI: '0x440bD1535a02243d72E0fEED45B137efcC98bF7e', + ETH: '0xcD2A119bD1F7DF95d706DE6F2057fDD45A0503E2', + BTC: '0xAC15714c08986DACC0379193e22382736796496f', + USDC: '0xb85765935B4d9Ab6f841c9a00690Da5F34368bc0', + USDT: '0xd5973EB46D6fE54E82C5337dD9536B35D080912C', + // mocks + RSR: '0xbEfB78358eAaaCAa083C2dff5D2Ed6e7e32b2d3A', + }, + GNOSIS_EASY_AUCTION: '0xcdf32E323e69090eCA17adDeF058A6A921c3e75A', // mock + }, } export const getNetworkConfig = (chainId: string) => { @@ -342,6 +362,8 @@ export const getNetworkConfig = (chainId: string) => { export const developmentChains = ['hardhat', 'localhost'] +export const baseL2Chains = ['base-goerli', 'base'] + // Common configuration interfaces export interface IConfig { dist: IRevenueShare diff --git a/docs/deployment.md b/docs/deployment.md index f2c85987b9..6fbf565027 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -18,6 +18,8 @@ ROPSTEN_RPC_URL="" # Goerli Infura URL, used for Testnet deployments GOERLI_RPC_URL="" +# Base Goerli URL +BASE_GOERLI_RPC_URL="" # Mainnet URL, used for Mainnet forking MAINNET_RPC_URL="" diff --git a/hardhat.config.ts b/hardhat.config.ts index 08cf7e3f7e..f45506c31d 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -22,6 +22,7 @@ tenderly.setup() const MAINNET_RPC_URL = useEnv(['MAINNET_RPC_URL', 'ALCHEMY_MAINNET_RPC_URL']) const TENDERLY_RPC_URL = useEnv('TENDERLY_RPC_URL') const GOERLI_RPC_URL = useEnv('GOERLI_RPC_URL') +const BASE_GOERLI_RPC_URL = useEnv('BASE_GOERLI_RPC_URL') const MNEMONIC = useEnv('MNEMONIC') ?? 'test test test test test test test test test test test junk' const TIMEOUT = useEnv('SLOW') ? 6_000_000 : 600_000 @@ -61,6 +62,13 @@ const config: HardhatUserConfig = { mnemonic: MNEMONIC, }, }, + 'base-goerli': { + chainId: 84531, + url: BASE_GOERLI_RPC_URL, + accounts: { + mnemonic: MNEMONIC, + }, + }, mainnet: { chainId: 1, url: MAINNET_RPC_URL, @@ -123,6 +131,16 @@ const config: HardhatUserConfig = { }, etherscan: { apiKey: useEnv('ETHERSCAN_API_KEY'), + customChains: [ + { + network: 'base-goerli', + chainId: 84531, + urls: { + apiURL: 'https://api-goerli.basescan.org/api', + browserURL: 'https://goerli.basescan.org', + }, + }, + ], }, tenderly: { // see https://github.com/Tenderly/hardhat-tenderly/tree/master/packages/tenderly-hardhat for details diff --git a/scripts/addresses/84531-RTKN-tmp-deployments.json b/scripts/addresses/84531-RTKN-tmp-deployments.json new file mode 100644 index 0000000000..e7ec7dc68f --- /dev/null +++ b/scripts/addresses/84531-RTKN-tmp-deployments.json @@ -0,0 +1,19 @@ +{ + "facadeWrite": "0x0903048fD4E948c60451B41A48B35E0bafc0967F", + "main": "0x1274F03639932140bBd48D8376a39ee86EbFEe66", + "components": { + "assetRegistry": "0x09909aD4e15167f18dc42f86F12Ba85137Fc51a3", + "backingManager": "0xd53F642B04ba005E9A27FC82961F7c1563BEF301", + "basketHandler": "0xE9E22548C92EF74c02A9dab73c33eBcEb53cA216", + "broker": "0x7466593929d61308C89ce651029B7019E644b398", + "distributor": "0xd3e333fb488e7DF8BA49D98399a7f42d7fAc7b2C", + "furnace": "0xfe702Ff577B0a9B3865a59af31D039fA92739d39", + "rsrTrader": "0xF0c203Be2ac6747C107D119FCb3d8BED28d9A2db", + "rTokenTrader": "0xc2C5542ceF5d6C8c79b538ff3c3DA976720F93bf", + "rToken": "0x41d5a65ba05bEB7C5Ce01FF3eFb2c52eF2D46469", + "stRSR": "0xB8f96Ec61B4f209F7562bC10375b374f8305De97" + }, + "rTokenAsset": "0xa9063D1153DA2160A298ea83CA388c827c623A5D", + "governance": "0x326A8309f9b5f1ee06e832cdc168eac7feBA2Bea", + "timelock": "0x9686C510f9b5d101c75f659D0Fd3De20c01649dE" +} diff --git a/scripts/addresses/84531-tmp-assets-collateral.json b/scripts/addresses/84531-tmp-assets-collateral.json new file mode 100644 index 0000000000..6e8bbc2523 --- /dev/null +++ b/scripts/addresses/84531-tmp-assets-collateral.json @@ -0,0 +1,13 @@ +{ + "assets": {}, + "collateral": { + "DAI": "0x89B2eF0dd1422F482617eE8B01E57ef5f778E612", + "USDC": "0x0908A3193D14064f5831cbAFc47703f001313Ff6", + "USDT": "0xB5e44CbbC77D23e4C973a27Db5AE59AcE4c46a87" + }, + "erc20s": { + "DAI": "0xDA2eA2f60545555e268124E51EA27bc97DE78E9c", + "USDC": "0x1265Ec05FD621d82F224814902c925a600307fb3", + "USDT": "0x3D3226C68B1425FdaA273F2A6295D5C40462327C" + } +} diff --git a/scripts/addresses/84531-tmp-deployments.json b/scripts/addresses/84531-tmp-deployments.json new file mode 100644 index 0000000000..71e44eaf32 --- /dev/null +++ b/scripts/addresses/84531-tmp-deployments.json @@ -0,0 +1,35 @@ +{ + "prerequisites": { + "RSR": "0xc8058960a9d7E7d81143BDBA38d19e6824165932", + "RSR_FEED": "0xbEfB78358eAaaCAa083C2dff5D2Ed6e7e32b2d3A", + "GNOSIS_EASY_AUCTION": "0xcdf32E323e69090eCA17adDeF058A6A921c3e75A" + }, + "tradingLib": "0x8d68d450a33ea275edE80Efc82D8cd208DAe4402", + "cvxMiningLib": "0xF64A5C1329Ad224B0E50C4640f4bBd677a5cb391", + "facadeRead": "0xe1aa15DA8b993c6312BAeD91E0b470AE405F91BF", + "facadeAct": "0x3d6D679c863858E89e35c925F937F5814ca687F3", + "facadeWriteLib": "0x29e9740275D26fdeDBb0ABA8129C74c15c393027", + "basketLib": "0x25Aa9878a97948f9908DB4325dc20e5635023Ee2", + "facadeWrite": "0x0903048fD4E948c60451B41A48B35E0bafc0967F", + "deployer": "0xf1B06c2305445E34CF0147466352249724c2EAC1", + "rsrAsset": "0x23b57479327f9BccE6A1F6Be65F3dAa3C9Db797B", + "implementations": { + "main": "0x4E01677488384B851EeAa09C8b8F6Dd0b16d7E9B", + "trading": { + "gnosisTrade": "0xDfCc89cf76aC93D113A21Da8fbfA63365b1E3DC7", + "dutchTrade": "0x9c387fc258061bd3E02c851F36aE227DB03a396C" + }, + "components": { + "assetRegistry": "0xD4e1D5b1311C992b2735710D46A10284Bcd7D39F", + "backingManager": "0x63e12c3b2DBCaeF1835Bb99Ac1Fdb0Ebe1bE69bE", + "basketHandler": "0x25E92785C1AC01B397224E0534f3D626868A1Cbf", + "broker": "0x12c3BB1B0da85fDaE0137aE8fDe901F7D0e106ba", + "distributor": "0xd31de64957b79435bfc702044590ac417e02c19B", + "furnace": "0x45D7dFE976cdF80962d863A66918346a457b87Bd", + "rsrTrader": "0xAeA6BD7b231C0eC7f35C2bdf47A76053D09dbD09", + "rTokenTrader": "0xAeA6BD7b231C0eC7f35C2bdf47A76053D09dbD09", + "rToken": "0xA42850A760151bb3ACF17E7f8643EB4d864bF7a6", + "stRSR": "0x53321f03A7cce52413515DFD0527e0163ec69A46" + } + } +} diff --git a/scripts/deployment/phase1-common/1_deploy_libraries.ts b/scripts/deployment/phase1-common/1_deploy_libraries.ts index 1ec3696a2b..764676c327 100644 --- a/scripts/deployment/phase1-common/1_deploy_libraries.ts +++ b/scripts/deployment/phase1-common/1_deploy_libraries.ts @@ -16,7 +16,7 @@ async function main() { const chainId = await getChainId(hre) console.log( - `Deploying TradingLib and CvxMining to network ${hre.network.name} (${chainId}) with burner account: ${burner.address}` + `Deploying TradingLib, BasketLib, and CvxMining to network ${hre.network.name} (${chainId}) with burner account: ${burner.address}` ) if (!networkConfig[chainId]) { diff --git a/scripts/deployment/phase3-rtoken/rTokenConfig.ts b/scripts/deployment/phase3-rtoken/rTokenConfig.ts index dfc14ca28a..0da6fc1e35 100644 --- a/scripts/deployment/phase3-rtoken/rTokenConfig.ts +++ b/scripts/deployment/phase3-rtoken/rTokenConfig.ts @@ -121,6 +121,45 @@ export const rTokenConfig: { [key: string]: IRToken } = { timelockDelay: bn(1), // 1s }, }, + '84531': { + RTKN: { + name: 'RToken', + symbol: 'RTKN', + mandate: 'mandate', + params: { + minTradeVolume: fp('1e4'), // $10k + rTokenMaxTradeVolume: fp('1e6'), // $1M + dist: { + rTokenDist: bn(40), // 2/5 RToken + rsrDist: bn(60), // 3/5 RSR + }, + rewardRatio: bn('1069671574938'), // approx. half life of 90 days + unstakingDelay: bn('1209600'), // 2 weeks + warmupPeriod: bn('60'), // (the delay _after_ SOUND was regained) + tradingDelay: bn('0'), // (the delay _after_ default has been confirmed) + withdrawalLeak: bn('5e16'), + batchAuctionLength: bn('900'), // 15 minutes + dutchAuctionLength: bn('1800'), // 30 minutes + backingBuffer: fp('0.0001'), // 0.01% + maxTradeSlippage: fp('0.01'), // 1% + shortFreeze: bn('259200'), // 3 days + longFreeze: bn('2592000'), // 30 days + issuanceThrottle: { + amtRate: fp('1e6'), // 1M RToken + pctRate: fp('0.05'), // 5% + }, + redemptionThrottle: { + amtRate: fp('1e6'), // 1M RToken + pctRate: fp('0.05'), // 5% + }, + }, + votingDelay: bn(5), // 5 blocks + votingPeriod: bn(100), // 100 blocks + proposalThresholdAsMicroPercent: bn(5e4), // 0.05% + quorumPercent: bn(10), // 10% + timelockDelay: bn(1), // 1s + }, + }, } export const getRTokenConfig = (chainId: string, name: string) => { diff --git a/scripts/deployment/utils.ts b/scripts/deployment/utils.ts index 3b847da0b5..a76d0f4e6a 100644 --- a/scripts/deployment/utils.ts +++ b/scripts/deployment/utils.ts @@ -4,7 +4,7 @@ import axios from 'axios' import { exec } from 'child_process' import { BigNumber, BigNumberish } from 'ethers' import { bn, fp } from '../../common/numbers' -import { IComponents } from '../../common/configuration' +import { IComponents, baseL2Chains } from '../../common/configuration' import { isValidContract } from '../../common/blockchain-utils' import { IDeployments } from './common' import { useEnv } from '#/utils/env' @@ -118,11 +118,21 @@ export async function verifyContract( const ETHERSCAN_API_KEY = useEnv('ETHERSCAN_API_KEY') + let url: string + if (baseL2Chains.includes(hre.network.name)) { + // Base L2 + url = `${getBasescanURL( + chainId + )}/?module=contract&action=getsourcecode&address=${address}&apikey=${ETHERSCAN_API_KEY}` + } else { + // Ethereum + url = `${getEtherscanBaseURL( + chainId, + true + )}/api/?module=contract&action=getsourcecode&address=${address}&apikey=${ETHERSCAN_API_KEY}` + } + // Check to see if already verified - const url = `${getEtherscanBaseURL( - chainId, - true - )}/api/?module=contract&action=getsourcecode&address=${address}&apikey=${ETHERSCAN_API_KEY}` const { data, status } = await axios.get(url, { headers: { Accept: 'application/json' } }) if (status != 200 || data['status'] != '1') { throw new Error("Can't communicate with Etherscan API") @@ -159,6 +169,15 @@ export const getEtherscanBaseURL = (chainId: number, api = false) => { return `https://${prefix}etherscan.io` } +export const getBasescanURL = (chainId: number) => { + // For Base, get URL from HH config + const chainConfig = hre.config.etherscan.customChains.find((chain) => chain.chainId == chainId) + if (!chainConfig || !chainConfig.urls) { + throw new Error(`Missing custom chain configuration for ${hre.network.name}`) + } + return `${chainConfig.urls.apiURL}` +} + export const getEmptyDeployment = (): IDeployments => { return { prerequisites: { diff --git a/tasks/deployment/mock/deploy-mock-easyauction.ts b/tasks/deployment/mock/deploy-mock-easyauction.ts new file mode 100644 index 0000000000..0936b02979 --- /dev/null +++ b/tasks/deployment/mock/deploy-mock-easyauction.ts @@ -0,0 +1,55 @@ +import { getChainId } from '../../../common/blockchain-utils' +import { task, types } from 'hardhat/config' +import { EasyAuction } from '../../../typechain' + +task('deploy-mock-easyauction', 'Deploys a mock Easy Auction contract') + .addOptionalParam('noOutput', 'Suppress output', false, types.boolean) + .setAction(async (params, hre) => { + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + if (!params.noOutput) { + console.log( + `Deploying EasyAuction to ${hre.network.name} (${chainId}) with burner account ${deployer.address}` + ) + } + + const easyAuction = ( + await (await hre.ethers.getContractFactory('EasyAuction')).connect(deployer).deploy() + ) + await easyAuction.deployed() + + if (!params.noOutput) { + console.log( + `Deployed EasyAuction to ${hre.network.name} (${chainId}): ${easyAuction.address}` + ) + } + + // // Uncomment to verify + // if (!params.noOutput) { + // console.log('sleeping 30s') + // } + + // // Sleep to ensure API is in sync with chain + // await new Promise((r) => setTimeout(r, 30000)) // 30s + + // if (!params.noOutput) { + // console.log('verifying') + // } + + // // /** ******************** Verify EasyAuction ****************************************/ + // console.time('Verifying EasyAuction') + // await hre.run('verify:verify', { + // address: easyAuction.address, + // constructorArguments: [], + // contract: 'contracts/plugins/mocks/EasyAuction.sol:EasyAuction', + // }) + // console.timeEnd('Verifying EasyAuction') + + // if (!params.noOutput) { + // console.log('verified') + // } + + return { feed: easyAuction.address } + }) diff --git a/tasks/index.ts b/tasks/index.ts index e542e85840..74d51bf9b8 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -14,6 +14,7 @@ import './deployment/mock/deploy-mock-oracle' import './deployment/mock/deploy-mock-usdc' import './deployment/mock/deploy-mock-aave' import './deployment/mock/deploy-mock-wbtc' +import './deployment/mock/deploy-mock-easyauction' import './deployment/create-deployer-registry' import './deployment/empty-wallet' import './deployment/cancel-tx' diff --git a/utils/env.ts b/utils/env.ts index 540a910714..9f07c74e1b 100644 --- a/utils/env.ts +++ b/utils/env.ts @@ -21,6 +21,7 @@ type IEnvVars = | 'SUBGRAPH_URL' | 'TENDERLY_RPC_URL' | 'SKIP_PROMPT' + | 'BASE_GOERLI_RPC_URL' export function useEnv(key: IEnvVars | IEnvVars[], _default = ''): string { if (typeof key === 'string') { From a1092d74c28b046cbd58a64243b8c53fed57e803 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Tue, 11 Jul 2023 17:32:40 -0300 Subject: [PATCH 347/462] Base Testnet configuration (#867) From 05961a1239fad87a9d2a632bab5f05f15c672708 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Tue, 18 Jul 2023 14:05:26 +0530 Subject: [PATCH 348/462] Stargate Asset Plugin (#863) Co-authored-by: brr Co-authored-by: Taylor Brent Co-authored-by: pmckelvy1 --- .github/workflows/4bytes.yml | 8 +- .openzeppelin/goerli-old.json | 22 +- audits/Code4rena Reserve Audit Report.md | 2314 +++++++++-------- common/configuration.ts | 12 + .../plugins/assets/erc20/RewardableERC20.sol | 83 +- contracts/plugins/assets/stargate/README.md | 61 + .../stargate/StargatePoolETHCollateral.sol | 49 + .../stargate/StargatePoolFiatCollateral.sol | 123 + .../stargate/StargateRewardableWrapper.sol | 62 + .../interfaces/IStargateLPStaking.sol | 48 + .../stargate/interfaces/IStargatePool.sol | 16 + .../stargate/interfaces/IStargateRouter.sol | 16 + .../stargate/mocks/StargateLPStakingMock.sol | 76 + .../stargate/mocks/StargatePoolMock.sol | 32 + docs/dev-env.md | 6 + docs/system-design.md | 2 +- scripts/addresses/5-RTKN-tmp-deployments.json | 2 +- .../addresses/5-tmp-assets-collateral.json | 2 +- .../deploy_stargate_eth_collateral.ts | 104 + .../deploy_stargate_usdc_collateral.ts | 104 + .../deploy_stargate_usdt_collateral.ts | 104 + .../stargate/StargateETHTestSuite.test.ts | 28 + .../StargateRewardableWrapper.test.ts | 387 +++ .../stargate/StargateUSDCTestSuite.test.ts | 270 ++ .../stargate/constants.ts | 23 + .../individual-collateral/stargate/helpers.ts | 93 + yarn.lock | 281 +- 27 files changed, 2942 insertions(+), 1386 deletions(-) create mode 100644 contracts/plugins/assets/stargate/README.md create mode 100644 contracts/plugins/assets/stargate/StargatePoolETHCollateral.sol create mode 100644 contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol create mode 100644 contracts/plugins/assets/stargate/StargateRewardableWrapper.sol create mode 100644 contracts/plugins/assets/stargate/interfaces/IStargateLPStaking.sol create mode 100644 contracts/plugins/assets/stargate/interfaces/IStargatePool.sol create mode 100644 contracts/plugins/assets/stargate/interfaces/IStargateRouter.sol create mode 100644 contracts/plugins/assets/stargate/mocks/StargateLPStakingMock.sol create mode 100644 contracts/plugins/assets/stargate/mocks/StargatePoolMock.sol create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_stargate_eth_collateral.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdt_collateral.ts create mode 100644 test/plugins/individual-collateral/stargate/StargateETHTestSuite.test.ts create mode 100644 test/plugins/individual-collateral/stargate/StargateRewardableWrapper.test.ts create mode 100644 test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts create mode 100644 test/plugins/individual-collateral/stargate/constants.ts create mode 100644 test/plugins/individual-collateral/stargate/helpers.ts diff --git a/.github/workflows/4bytes.yml b/.github/workflows/4bytes.yml index c1f7e3c9bb..72ce1fb622 100644 --- a/.github/workflows/4bytes.yml +++ b/.github/workflows/4bytes.yml @@ -1,4 +1,4 @@ -name: Tests +name: Sync 4byte Signatures on: push: @@ -9,9 +9,8 @@ on: - closed jobs: - deployment-scripts: - if: github.event.pull_request.merged == true - name: '4Bytes Sync' + sync-signatures: + name: '4byte Sync' runs-on: ubuntu-latest permissions: contents: write @@ -31,4 +30,3 @@ jobs: commit_message: 4bytes-syncced.json commit_options: '--no-verify --signoff' file_pattern: 'scripts/4bytes-syncced.json' - diff --git a/.openzeppelin/goerli-old.json b/.openzeppelin/goerli-old.json index ec087c2b12..c808117e46 100644 --- a/.openzeppelin/goerli-old.json +++ b/.openzeppelin/goerli-old.json @@ -858,10 +858,7 @@ }, "t_enum(TradeKind)20270": { "label": "enum TradeKind", - "members": [ - "DUTCH_AUCTION", - "BATCH_AUCTION" - ], + "members": ["DUTCH_AUCTION", "BATCH_AUCTION"], "numberOfBytes": "1" }, "t_mapping(t_contract(IERC20)11487,t_contract(ITrade)22280)": { @@ -1162,11 +1159,7 @@ }, "t_enum(CollateralStatus)9907": { "label": "enum CollateralStatus", - "members": [ - "SOUND", - "IFFY", - "DISABLED" - ], + "members": ["SOUND", "IFFY", "DISABLED"], "numberOfBytes": "1" }, "t_mapping(t_bytes32,t_bytes32)": { @@ -3959,10 +3952,7 @@ }, "t_enum(TradeKind)21047": { "label": "enum TradeKind", - "members": [ - "DUTCH_AUCTION", - "BATCH_AUCTION" - ], + "members": ["DUTCH_AUCTION", "BATCH_AUCTION"], "numberOfBytes": "1" }, "t_mapping(t_contract(IERC20)11635,t_contract(ITrade)23084)": { @@ -4255,11 +4245,7 @@ }, "t_enum(CollateralStatus)20505": { "label": "enum CollateralStatus", - "members": [ - "SOUND", - "IFFY", - "DISABLED" - ], + "members": ["SOUND", "IFFY", "DISABLED"], "numberOfBytes": "1" }, "t_mapping(t_bytes32,t_bytes32)": { diff --git a/audits/Code4rena Reserve Audit Report.md b/audits/Code4rena Reserve Audit Report.md index 637073b16d..e2e93fc597 100644 --- a/audits/Code4rena Reserve Audit Report.md +++ b/audits/Code4rena Reserve Audit Report.md @@ -1,9 +1,9 @@ --- -sponsor: "Reserve" -slug: "2023-01-reserve" -date: "2023-03-⭕" # the date this report is published to the C4 website -title: "Reserve contest" -findings: "https://github.com/code-423n4/2023-01-reserve-findings/issues" +sponsor: 'Reserve' +slug: '2023-01-reserve' +date: '2023-03-⭕' # the date this report is published to the C4 website +title: 'Reserve contest' +findings: 'https://github.com/code-423n4/2023-01-reserve-findings/issues' contest: 203 --- @@ -23,79 +23,79 @@ Following the C4 audit contest, 3 wardens (0xA5DF, HollaDieWaldfee, and [AkshayS 76 Wardens contributed reports to the Reserve contest: - 1. 0x52 - 2. 0xA5DF - 3. [0xAgro](https://twitter.com/0xAgro) - 4. [0xNazgul](https://twitter.com/0xNazgul) - 5. [0xSmartContract](https://twitter.com/0xSmartContract) - 6. [0xTaylor](https://twitter.com/0xTaylor_) - 7. [0xdeadbeef0x](https://twitter.com/0xdeadbeef____) - 8. 0xhacksmithh - 9. [AkshaySrivastav](https://twitter.com/akshaysrivastv) - 10. Awesome - 11. [Aymen0909](https://github.com/Aymen1001) - 12. BRONZEDISC - 13. Bauer - 14. Bnke0x0 - 15. Breeje - 16. Budaghyan - 17. CodingNameKiki - 18. [Cyfrin](https://www.cyfrin.io/) ([PatrickAlphaC](https://twitter.com/PatrickAlphaC) and [giovannidisiena](https://twitter.com/giovannidisiena) and [hansfriese](https://twitter.com/hansfriese)) - 19. [Franfran](https://franfran.dev/) - 20. [GalloDaSballo](https://twitter.com/gallodasballo) - 21. HollaDieWaldfee - 22. IceBear - 23. IllIllI - 24. JTJabba - 25. Madalad - 26. MyFDsYours - 27. NoamYakov - 28. RHaO-sec - 29. Rageur - 30. RaymondFam - 31. ReyAdmirado - 32. Rolezn - 33. [Ruhum](https://twitter.com/0xruhum) - 34. SAAJ - 35. SaharDevep - 36. [Sathish9098](https://www.linkedin.com/in/sathishkumar-p-26069915a) - 37. Soosh - 38. [Udsen](https://github.com/udsene) - 39. \_\_141345\_\_ - 40. amshirif - 41. arialblack14 - 42. brgltd - 43. btk - 44. [c3phas](https://twitter.com/c3ph_) - 45. [carlitox477](https://twitter.com/carlitox477) - 46. chaduke - 47. chrisdior4 - 48. cryptonue - 49. [csanuragjain](https://twitter.com/csanuragjain) - 50. delfin454000 - 51. descharre - 52. fs0c - 53. hihen - 54. immeas - 55. [joestakey](https://twitter.com/JoeStakey) - 56. [ladboy233](https://twitter.com/Xc1008Cu) - 57. lukris02 - 58. luxartvinsec - 59. [nadin](https://twitter.com/nadin20678790) - 60. [oyc\_109](https://twitter.com/andyfeili) - 61. [pavankv](https://twitter.com/@PavanKumarKv2) - 62. peanuts - 63. pedr02b2 - 64. rotcivegaf - 65. rvierdiiev - 66. [saneryee](https://medium.com/@saneryee-studio) - 67. severity (medium-or-low and critical-or-high) - 68. shark - 69. tnevler - 70. unforgiven - 71. [ustas](https://lenster.xyz/u/ustas) - 72. wait - 73. yongskiws +1. 0x52 +2. 0xA5DF +3. [0xAgro](https://twitter.com/0xAgro) +4. [0xNazgul](https://twitter.com/0xNazgul) +5. [0xSmartContract](https://twitter.com/0xSmartContract) +6. [0xTaylor](https://twitter.com/0xTaylor_) +7. [0xdeadbeef0x](https://twitter.com/0xdeadbeef____) +8. 0xhacksmithh +9. [AkshaySrivastav](https://twitter.com/akshaysrivastv) +10. Awesome +11. [Aymen0909](https://github.com/Aymen1001) +12. BRONZEDISC +13. Bauer +14. Bnke0x0 +15. Breeje +16. Budaghyan +17. CodingNameKiki +18. [Cyfrin](https://www.cyfrin.io/) ([PatrickAlphaC](https://twitter.com/PatrickAlphaC) and [giovannidisiena](https://twitter.com/giovannidisiena) and [hansfriese](https://twitter.com/hansfriese)) +19. [Franfran](https://franfran.dev/) +20. [GalloDaSballo](https://twitter.com/gallodasballo) +21. HollaDieWaldfee +22. IceBear +23. IllIllI +24. JTJabba +25. Madalad +26. MyFDsYours +27. NoamYakov +28. RHaO-sec +29. Rageur +30. RaymondFam +31. ReyAdmirado +32. Rolezn +33. [Ruhum](https://twitter.com/0xruhum) +34. SAAJ +35. SaharDevep +36. [Sathish9098](https://www.linkedin.com/in/sathishkumar-p-26069915a) +37. Soosh +38. [Udsen](https://github.com/udsene) +39. \_\_141345\_\_ +40. amshirif +41. arialblack14 +42. brgltd +43. btk +44. [c3phas](https://twitter.com/c3ph_) +45. [carlitox477](https://twitter.com/carlitox477) +46. chaduke +47. chrisdior4 +48. cryptonue +49. [csanuragjain](https://twitter.com/csanuragjain) +50. delfin454000 +51. descharre +52. fs0c +53. hihen +54. immeas +55. [joestakey](https://twitter.com/JoeStakey) +56. [ladboy233](https://twitter.com/Xc1008Cu) +57. lukris02 +58. luxartvinsec +59. [nadin](https://twitter.com/nadin20678790) +60. [oyc_109](https://twitter.com/andyfeili) +61. [pavankv](https://twitter.com/@PavanKumarKv2) +62. peanuts +63. pedr02b2 +64. rotcivegaf +65. rvierdiiev +66. [saneryee](https://medium.com/@saneryee-studio) +67. severity (medium-or-low and critical-or-high) +68. shark +69. tnevler +70. unforgiven +71. [ustas](https://lenster.xyz/u/ustas) +72. wait +73. yongskiws This contest was judged by [0xean](https://github.com/0xean). @@ -127,8 +127,10 @@ High-level considerations for vulnerabilities span the following key areas when For more information regarding the severity criteria referenced throughout the submission review process, please refer to the documentation provided on [the C4 website](https://code4rena.com), specifically our section on [Severity Categorization](https://docs.code4rena.com/awarding/judging-criteria/severity-categorization). # High Risk Findings (2) + ## [[H-01] Adversary can abuse a quirk of compound redemption to manipulate the underlying exchange rate and maliciously disable cToken collaterals](https://github.com/code-423n4/2023-01-reserve-findings/issues/310) -*Submitted by [0x52](https://github.com/code-423n4/2023-01-reserve-findings/issues/310)* + +_Submitted by [0x52](https://github.com/code-423n4/2023-01-reserve-findings/issues/310)_ Adversary can maliciously disable cToken collateral to cause loss to rToken during restructuring. @@ -166,7 +168,7 @@ CTokenNonFiatCollateral and CTokenFiatCollateral both use the default refresh be redeemAmount = redeemAmountIn; } -The exchange rate can be manipulated by a tiny amount during the redeem process. The focus above is the scenario where the user requests a specific amount of underlying. When calculating the number of cTokens to redeem for a specific amount of underlying it rounds IN FAVOR of the user. This allows the user to redeem more underlying than the exchange rate would otherwise imply. Because the user can redeem *slightly* more than intended they can create a scenario in which the exchange rate actually drops after they redeem. This is because compound calculates the exchange rate dynamically using the current supply of cTokens and the assets under management. +The exchange rate can be manipulated by a tiny amount during the redeem process. The focus above is the scenario where the user requests a specific amount of underlying. When calculating the number of cTokens to redeem for a specific amount of underlying it rounds IN FAVOR of the user. This allows the user to redeem more underlying than the exchange rate would otherwise imply. Because the user can redeem _slightly_ more than intended they can create a scenario in which the exchange rate actually drops after they redeem. This is because compound calculates the exchange rate dynamically using the current supply of cTokens and the assets under management. [CToken.sol](https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/CToken.sol#L293-L312) @@ -202,7 +204,7 @@ Example: cTokens always start at a whole token ratio of 50:1 so let's assume this ratio to begin with. Let's use values similar to the current supply of cETH which is \~15M cETH and \~300k ETH. We'll start by calculating the current ratio: - exchangeRate = 300_000 * 1e18 * 1e18 / 15_000_000 * 1e8 = 2e26 + exchangeRate = 300_000 * 1e18 * 1e18 / 15_000_000 * 1e8 = 2e26 Now to exploit the ratio we request to redeem 99e8 redeemAmount which we can use to calculate the amount of tokens we need to burn: @@ -214,7 +216,7 @@ After truncation the amount burned is only 1. Now we can recalculate our ratio: The ratio has now been slightly decreased. In CTokenFiatCollateral the exchange rate is truncated to 18 dp so: - (referencePrice < prevReferencePrice) -> (19999999999999993 < 2e18) == true + (referencePrice < prevReferencePrice) -> (19999999999999993 < 2e18) == true This results in that the collateral is now disabled. This forces the vault to liquidate their holdings to convert to a backup asset. This will almost certainly incur losses to the protocol that were maliciously inflicted. @@ -232,36 +234,38 @@ Since the issue is with the underlying compound contracts, nothing can make the } **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/310#issuecomment-1399632835):** - > I do see in the cToken code base that the warden is correct with regard to the round down mechanism when redeeming cTokens using a redeemAmountIn. -> + +> I do see in the cToken code base that the warden is correct with regard to the round down mechanism when redeeming cTokens using a redeemAmountIn. +> > The question I think comes down to is this dust amount enough to counteract the interest that would be accrued to the cToken which is added during the refresh call in `CTokenFiatCollateral` -> -> Will leave open for sponsor review. +> +> Will leave open for sponsor review. **[tmattimore (Reserve) confirmed](https://github.com/code-423n4/2023-01-reserve-findings/issues/310#issuecomment-1404050632)** **[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/310#issuecomment-1404051723):** - > Issue confirmed. -> -> Many defi protocols may have similar issues. We may choose to mitigate by building in revenue hiding to something like 1 part in 1 million to all collateral plugins. -**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** - > This PR adds universal revenue hiding to all appreciating collateral: [reserve-protocol/protocol#620](https://github.com/reserve-protocol/protocol/pull/620) +> Issue confirmed. +> +> Many defi protocols may have similar issues. We may choose to mitigate by building in revenue hiding to something like 1 part in 1 million to all collateral plugins. -**Status:** Mitigation confirmed with comments. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/35), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/23), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/25). +**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** +> This PR adds universal revenue hiding to all appreciating collateral: [reserve-protocol/protocol#620](https://github.com/reserve-protocol/protocol/pull/620) +**Status:** Mitigation confirmed with comments. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/35), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/23), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/25). -*** +--- ## [[H-02] Basket range formula is inefficient, leading the protocol to unnecessary haircut](https://github.com/code-423n4/2023-01-reserve-findings/issues/235) -*Submitted by [0xA5DF](https://github.com/code-423n4/2023-01-reserve-findings/issues/235), also found by [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/317)* + +_Submitted by [0xA5DF](https://github.com/code-423n4/2023-01-reserve-findings/issues/235), also found by [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/317)_ The `BackingManager.manageTokens()` function checks if there's any deficit in collateral, in case there is, if there's a surplus from another collateral token it trades it to cover the deficit, otherwise it goes for a 'haircut' and cuts the amount of basket 'needed' (i.e. the number of baskets RToken claims to hold). In order to determine how much deficit/surplus there is the protocol calculates the 'basket range', where the top range is the optimistic estimation of the number of baskets the token would hold after trading and the bottom range is a pessimistic estimation. -The estimation is done by dividing the total collateral value by the price of 1 basket unit (for optimistic estimation the max value is divided by min price of basket-unit and vice versa). +The estimation is done by dividing the total collateral value by the price of 1 basket unit (for optimistic estimation the max value is divided by min price of basket-unit and vice versa). The problem is that this estimation is inefficient, for cases where just a little bit of collateral is missing the range 'band' (range.top - range.bottom) would be about 4% (when oracle error deviation is ±1%) instead of less than 1%. @@ -284,7 +288,7 @@ index 62223442..03d3c3f4 100644 +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -123,7 +123,7 @@ contract RTokenAsset is IAsset { // ==== Private ==== - + function basketRange() - private + public @@ -318,7 +322,7 @@ index 3c53fa30..386c0673 100644 + + // show the basket range is 95.9 to 99.9 + console.log({range}); - + + let needed = await rToken.basketsNeeded(); + + // show that prices are more or less the same @@ -329,10 +333,10 @@ index 3c53fa30..386c0673 100644 + + // show how many baskets are left after the haircut + needed = await rToken.basketsNeeded(); -+ ++ + console.log({prices, needed}); + return; -+ ++ + }) + return; it('Should select backup config correctly - Single backup token', async () => { @@ -394,9 +398,9 @@ Output (comments are added by me): Change the formula so that we first calculate the 'base' (i.e. the min amount of baskets the RToken can satisfy without trading): base = basketsHeldBy(backingManager) // in the PoC's case it'd be 95 - (diffLowValue, diffHighValue) = (0,0) + (diffLowValue, diffHighValue) = (0,0) for each collateral token: - diff = collateralBalance - basketHandler.quantity(base) + diff = collateralBalance - basketHandler.quantity(base) (diffLowValue, diffHighValue) = diff * (priceLow, priceHigh) addBasketsLow = diffLowValue / basketPriceHigh addBasketHigh = diffHighValue / basketPriceLow @@ -404,93 +408,99 @@ Change the formula so that we first calculate the 'base' (i.e. the min amount of range.bottom = base + addBasketLow **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/235#issuecomment-1400384608):** - > Would like sponsor to comment on this issue and will determine severity from there. + +> Would like sponsor to comment on this issue and will determine severity from there. **[tmattimore (Reserve) acknowledged](https://github.com/code-423n4/2023-01-reserve-findings/issues/235)** **[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/235#issuecomment-1404082371):** - > Agree this behaves the way described. We're aware of this problem and have been looking at fixes that are similar to the one suggested. + +> Agree this behaves the way described. We're aware of this problem and have been looking at fixes that are similar to the one suggested. **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/235#issuecomment-1409489804):** - > Thank you @tbrent - I think High seems correct here as this does directly lead to a loss of value for users. + +> Thank you @tbrent - I think High seems correct here as this does directly lead to a loss of value for users. **[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/235#issuecomment-1412311915):** - > @0xean - Seems right. -**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** - > This PR simplifies and improves the basket range formula. The new logic should provide much tighter basket range estimates and result in smaller haircuts. - > [reserve-protocol/protocol#585](https://github.com/reserve-protocol/protocol/pull/585) +> @0xean - Seems right. -**Status:** Not fully mitigated. Full details in [report from 0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/49), and also included in Mitigation Review section below. +**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** +> This PR simplifies and improves the basket range formula. The new logic should provide much tighter basket range estimates and result in smaller haircuts. +> [reserve-protocol/protocol#585](https://github.com/reserve-protocol/protocol/pull/585) +**Status:** Not fully mitigated. Full details in [report from 0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/49), and also included in Mitigation Review section below. -*** +--- - # Medium Risk Findings (25) + ## [[M-01] Battery discharge mechanism doesn't work correctly for first redemption](https://github.com/code-423n4/2023-01-reserve-findings/issues/452) -*Submitted by [AkshaySrivastav](https://github.com/code-423n4/2023-01-reserve-findings/issues/452)* + +_Submitted by [AkshaySrivastav](https://github.com/code-423n4/2023-01-reserve-findings/issues/452)_ The `RTokenP1` contract implements a throttling mechanism using the `RedemptionBatteryLib` library. The library models a "battery" which "recharges" linearly block by block, over roughly 1 hour. RToken.sol ```solidity - function redeem(uint256 amount) external notFrozen { - // ... +function redeem(uint256 amount) external notFrozen { + // ... - uint256 supply = totalSupply(); + uint256 supply = totalSupply(); - // ... - battery.discharge(supply, amount); // reverts on over-redemption + // ... + battery.discharge(supply, amount); // reverts on over-redemption + + // ... +} - // ... - } ``` RedemptionBatteryLib.sol ```solidity - function discharge( - Battery storage battery, - uint256 supply, - uint256 amount - ) internal { - if (battery.redemptionRateFloor == 0 && battery.scalingRedemptionRate == 0) return; - - // {qRTok} - uint256 charge = currentCharge(battery, supply); - - // A nice error message so people aren't confused why redemption failed - require(amount <= charge, "redemption battery insufficient"); - - // Update battery - battery.lastBlock = uint48(block.number); - battery.lastCharge = charge - amount; - } +function discharge( + Battery storage battery, + uint256 supply, + uint256 amount +) internal { + if (battery.redemptionRateFloor == 0 && battery.scalingRedemptionRate == 0) return; + + // {qRTok} + uint256 charge = currentCharge(battery, supply); + + // A nice error message so people aren't confused why redemption failed + require(amount <= charge, 'redemption battery insufficient'); + + // Update battery + battery.lastBlock = uint48(block.number); + battery.lastCharge = charge - amount; +} - /// @param supply {qRTok} Total RToken supply before the burn step - /// @return charge {qRTok} The current total charge as an amount of RToken - function currentCharge(Battery storage battery, uint256 supply) - internal - view - returns (uint256 charge) - { - // {qRTok/hour} = {qRTok} * D18{1/hour} / D18 - uint256 amtPerHour = (supply * battery.scalingRedemptionRate) / FIX_ONE_256; +/// @param supply {qRTok} Total RToken supply before the burn step +/// @return charge {qRTok} The current total charge as an amount of RToken +function currentCharge(Battery storage battery, uint256 supply) + internal + view + returns (uint256 charge) +{ + // {qRTok/hour} = {qRTok} * D18{1/hour} / D18 + uint256 amtPerHour = (supply * battery.scalingRedemptionRate) / FIX_ONE_256; - if (battery.redemptionRateFloor > amtPerHour) amtPerHour = battery.redemptionRateFloor; + if (battery.redemptionRateFloor > amtPerHour) amtPerHour = battery.redemptionRateFloor; - // {blocks} - uint48 blocks = uint48(block.number) - battery.lastBlock; + // {blocks} + uint48 blocks = uint48(block.number) - battery.lastBlock; - // {qRTok} = {qRTok} + {qRTok/hour} * {blocks} / {blocks/hour} - charge = battery.lastCharge + (amtPerHour * blocks) / BLOCKS_PER_HOUR; + // {qRTok} = {qRTok} + {qRTok/hour} * {blocks} / {blocks/hour} + charge = battery.lastCharge + (amtPerHour * blocks) / BLOCKS_PER_HOUR; + + uint256 maxCharge = amtPerHour > supply ? supply : amtPerHour; + if (charge > maxCharge) charge = maxCharge; +} - uint256 maxCharge = amtPerHour > supply ? supply : amtPerHour; - if (charge > maxCharge) charge = maxCharge; - } ``` The linear redemption limit is calculated in the `currentCharge` function. This function calculates the delta blocks by `uint48 blocks = uint48(block.number) - battery.lastBlock;`. @@ -510,34 +520,38 @@ It should be noted that the issue only exists for the first ever redemption as d The following test case was added to `test/RToken.test.ts` file and was ran using command `PROTO_IMPL=1 npx hardhat test ./test/RToken.test.ts`. ```typescript - describe.only('Battery lastBlock bug', () => { - it('redemption battery does not work on first redemption', async () => { - // real chain scenario - await advanceBlocks(1_000_000) - await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, ethers.constants.MaxUint256))) - - expect(await rToken.totalSupply()).to.eq(0) - await rToken.connect(owner).setRedemptionRateFloor(fp('1e4')) - await rToken.connect(owner).setScalingRedemptionRate(fp('0')) - - // first issue - const issueAmount = fp('10000') - await rToken.connect(addr1)['issue(uint256)'](issueAmount) - expect(await rToken.balanceOf(addr1.address)).to.eq(issueAmount) - expect(await rToken.totalSupply()).to.eq(issueAmount) - - // first redemption - expect(await rToken.redemptionLimit()).to.eq(await rToken.totalSupply()) // for first redemption the currentCharge value is capped by rToken.totalSupply() - await rToken.connect(addr1).redeem(issueAmount) - expect(await rToken.totalSupply()).to.eq(0) - - // second redemption - await rToken.connect(addr1)['issue(uint256)'](issueAmount) - expect(await rToken.balanceOf(addr1.address)).to.eq(issueAmount) - // from second redemtion onwards the battery discharge mechanism takes place correctly - await expect(rToken.connect(addr1).redeem(issueAmount)).to.be.revertedWith('redemption battery insufficient') - }) +describe.only('Battery lastBlock bug', () => { + it('redemption battery does not work on first redemption', async () => { + // real chain scenario + await advanceBlocks(1_000_000) + await Promise.all( + tokens.map((t) => t.connect(addr1).approve(rToken.address, ethers.constants.MaxUint256)) + ) + + expect(await rToken.totalSupply()).to.eq(0) + await rToken.connect(owner).setRedemptionRateFloor(fp('1e4')) + await rToken.connect(owner).setScalingRedemptionRate(fp('0')) + + // first issue + const issueAmount = fp('10000') + await rToken.connect(addr1)['issue(uint256)'](issueAmount) + expect(await rToken.balanceOf(addr1.address)).to.eq(issueAmount) + expect(await rToken.totalSupply()).to.eq(issueAmount) + + // first redemption + expect(await rToken.redemptionLimit()).to.eq(await rToken.totalSupply()) // for first redemption the currentCharge value is capped by rToken.totalSupply() + await rToken.connect(addr1).redeem(issueAmount) + expect(await rToken.totalSupply()).to.eq(0) + + // second redemption + await rToken.connect(addr1)['issue(uint256)'](issueAmount) + expect(await rToken.balanceOf(addr1.address)).to.eq(issueAmount) + // from second redemtion onwards the battery discharge mechanism takes place correctly + await expect(rToken.connect(addr1).redeem(issueAmount)).to.be.revertedWith( + 'redemption battery insufficient' + ) }) +}) ``` ### Tools Used @@ -549,31 +563,36 @@ Hardhat The `battery.lastBlock` value must be initialized in the `init` function of `RTokenP1` ```solidity - function init( - // ... - ) external initializer { - // ... - battery.lastBlock = uint48(block.number); - } +function init() + external + // ... + initializer +{ + // ... + battery.lastBlock = uint48(block.number); +} + ``` **[0xean (judge) decreased severity to Medium and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/452#issuecomment-1399277278):** - > The first redemption is not constrained by the battery properly from what I can tell in the code base. I don't see sufficient evidence that this would lead to a direct loss of user funds however. I will leave open for sponsor review, but think either Medium severity or below is appropriate without a better statement of impact from the warden. + +> The first redemption is not constrained by the battery properly from what I can tell in the code base. I don't see sufficient evidence that this would lead to a direct loss of user funds however. I will leave open for sponsor review, but think either Medium severity or below is appropriate without a better statement of impact from the warden. **[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/452#issuecomment-1405393242):** - > This can't lead to loss of user funds, but I think it is indeed Medium severity -**[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/452#issuecomment-1421598482):** - > Fixed here: https://github.com/reserve-protocol/protocol/pull/584 +> This can't lead to loss of user funds, but I think it is indeed Medium severity -**Status:** Mitigation confirmed. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/50), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/16), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/26). +**[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/452#issuecomment-1421598482):** +> Fixed here: https://github.com/reserve-protocol/protocol/pull/584 +**Status:** Mitigation confirmed. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/50), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/16), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/26). -*** +--- ## [[M-02] Attacker can make stakeRate to be 1 in the StRSR contract and users depositing tokens can lose funds because of the big rounding error](https://github.com/code-423n4/2023-01-reserve-findings/issues/439) -*Submitted by [unforgiven](https://github.com/code-423n4/2023-01-reserve-findings/issues/439)* + +_Submitted by [unforgiven](https://github.com/code-423n4/2023-01-reserve-findings/issues/439)_

@@ -654,7 +673,7 @@ This is `_payoutReward()` code: emit ExchangeRateSet(initRate, exchangeRate()); } -As you can see it sets the value of the `stakeRate` to `(totalStakes * FIX_ONE_256 + (stakeRSR - 1)) / stakeRSR`. +As you can see it sets the value of the `stakeRate` to `(totalStakes * FIX_ONE_256 + (stakeRSR - 1)) / stakeRSR`. So to exploit this attacker needs to perform these steps: @@ -678,16 +697,16 @@ Prevent early manipulation of the PPS. **[tbrent (Reserve) confirmed](https://github.com/code-423n4/2023-01-reserve-findings/issues/439)** **[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/439#issuecomment-1421600557):** - > Addressed in https://github.com/reserve-protocol/protocol/pull/617 - -**Status:** Mitigation confirmed with comments. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/51), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/20), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/27). +> Addressed in https://github.com/reserve-protocol/protocol/pull/617 +**Status:** Mitigation confirmed with comments. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/51), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/20), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/27). -*** +--- ## [[M-03] Baited by redemption during undercollateralization (no issuance, just transfer)](https://github.com/code-423n4/2023-01-reserve-findings/issues/416) -*Submitted by [Cyfrin](https://github.com/code-423n4/2023-01-reserve-findings/issues/416)* + +_Submitted by [Cyfrin](https://github.com/code-423n4/2023-01-reserve-findings/issues/416)_ This is similar to the "high" vulnerability I submitted, but also shows a similar exploit can be done if a user isn't a whale, and isn't issuing anything. @@ -695,21 +714,21 @@ A user can send a redeem TX and an evil actor can make it so they get almost not ### Proof of Concept -* UserA is looking to redeem their rToken for tokenA (the max the battery will allow, let's say 100k) +- UserA is looking to redeem their rToken for tokenA (the max the battery will allow, let's say 100k) -* A basket refresh is about to be triggered +- A basket refresh is about to be triggered -* Evil user wants the protocol to steal UserA's funds +- Evil user wants the protocol to steal UserA's funds -* UserA sends redeem TX to the mempool, but Evil user move transactions around before it hits +- UserA sends redeem TX to the mempool, but Evil user move transactions around before it hits -* Evil user calls refreshbasket in same block as original collateral (tokenA) is disabled, kicking in backupconfig (tokenB) +- Evil user calls refreshbasket in same block as original collateral (tokenA) is disabled, kicking in backupconfig (tokenB) -* Protocol is now undercollateralized but collateral is sound (tokenB is good) +- Protocol is now undercollateralized but collateral is sound (tokenB is good) -* Evil sends 1tokenB to backingManager to UserA's redeem has something to redeem +- Evil sends 1tokenB to backingManager to UserA's redeem has something to redeem -* UserA's redemption tx lands, and redeems 100k rTokens for a fraction of tokenB! +- UserA's redemption tx lands, and redeems 100k rTokens for a fraction of tokenB! UserA redeems and has nothing to show for it! @@ -728,26 +747,28 @@ Disallow redemptions/issuance during undercollateralization See warden's [original submission](https://github.com/code-423n4/2023-01-reserve-findings/issues/416) for full details. **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/416#issuecomment-1402220739):** - > Not sure this is distinct enough from the other attack vector to stand alone, leaving open for sponsor comment before duping. + +> Not sure this is distinct enough from the other attack vector to stand alone, leaving open for sponsor comment before duping. **[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/416#issuecomment-1405419594):** - > Duplicate of [`#399`](https://github.com/code-423n4/2023-01-reserve-findings/issues/399) + +> Duplicate of [`#399`](https://github.com/code-423n4/2023-01-reserve-findings/issues/399) **[tbrent (Reserve) confirmed](https://github.com/code-423n4/2023-01-reserve-findings/issues/416#issuecomment-1419296557)** -***Please note: the following comment and re-assessment took place after judging and awarding were finalized. As such, this report will leave this finding in its originally assessed risk category as it simply reflects a snapshot in time.*** +**_Please note: the following comment and re-assessment took place after judging and awarding were finalized. As such, this report will leave this finding in its originally assessed risk category as it simply reflects a snapshot in time._** **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/416#issuecomment-1428777177):** - > After re-reviewing, I do believe this should have been included in the [M-04](https://github.com/code-423n4/2023-01-reserve-findings/issues/399) batch of issues as well. As it is past the QA period, no changes will be made to awards, but I wanted to comment as such for the benefit of the sponsor. -*Note: see mitigation status under M-04 below.* +> After re-reviewing, I do believe this should have been included in the [M-04](https://github.com/code-423n4/2023-01-reserve-findings/issues/399) batch of issues as well. As it is past the QA period, no changes will be made to awards, but I wanted to comment as such for the benefit of the sponsor. +_Note: see mitigation status under M-04 below._ - -*** +--- ## [[M-04] Redemptions during undercollateralization can be hot-swapped to steal all funds](https://github.com/code-423n4/2023-01-reserve-findings/issues/399) -*Submitted by [Cyfrin](https://github.com/code-423n4/2023-01-reserve-findings/issues/399), also found by [Cyfrin](https://github.com/code-423n4/2023-01-reserve-findings/issues/411)* + +_Submitted by [Cyfrin](https://github.com/code-423n4/2023-01-reserve-findings/issues/399), also found by [Cyfrin](https://github.com/code-423n4/2023-01-reserve-findings/issues/411)_ During recollateralization/a switch basket/when the protocol collateral isn't sound, a user can have almost their entire redemption transaction hot swapped for nothing. @@ -755,23 +776,23 @@ For example, trying to redeem 1M collateral for 1M rTokens could have the user e Example: -* User A issues 1M rToken for 1M tokenA +- User A issues 1M rToken for 1M tokenA -* Evil user sees tokenA is about to become disabled, and that User A sent a normally innocuous redeem tx for too much underlying collateral in the mempool +- Evil user sees tokenA is about to become disabled, and that User A sent a normally innocuous redeem tx for too much underlying collateral in the mempool -* Evil user orders transactions so they and RSR/Rtoken holders can steal user A's funds +- Evil user orders transactions so they and RSR/Rtoken holders can steal user A's funds -* They first buy a ton of tokenA and send it to the backing Manager +- They first buy a ton of tokenA and send it to the backing Manager -* They call `manageTokens` which flash issues a ton of new Rtoken due to the inflated tokenA balance, increasing the totalSupply +- They call `manageTokens` which flash issues a ton of new Rtoken due to the inflated tokenA balance, increasing the totalSupply -* The increase in total supply allows the normal redemption cap to be drastically lifted +- The increase in total supply allows the normal redemption cap to be drastically lifted -* They then let the disabling of tokenA process, and calls refreshBasket where a backup token (tokenB) kicks in +- They then let the disabling of tokenA process, and calls refreshBasket where a backup token (tokenB) kicks in -* We are now undercollateralized, and evil user sends tokenB dust to the backingmanager +- We are now undercollateralized, and evil user sends tokenB dust to the backingmanager -* FINALLY: the original redemption TX is ordered, and due to the inflated RToken supply, the battery discharge amount is also inflated, allowing the redemption to go through. Due to the new collateral in place, they redeem ALL their Rtoken (1M) for dust of tokenB!! The protocol has essentially honeypotted them!! +- FINALLY: the original redemption TX is ordered, and due to the inflated RToken supply, the battery discharge amount is also inflated, allowing the redemption to go through. Due to the new collateral in place, they redeem ALL their Rtoken (1M) for dust of tokenB!! The protocol has essentially honeypotted them!! ### Proof of Concept @@ -854,7 +875,7 @@ And we calculate how much they get back like so. We see how much `$` we currentl if (prorata < amounts[i]) amounts[i] = prorata; } -And just like that, a seemingly innocuous redemption transaction was a trap the whole time. The next step would be to go through the rest of the process to see how much our evil user profited (from running the auctions), as they need to be a whale to inflate the RToken supply. However, we've seen attacks like this, and one could consider it a [highly profitable trading strategy](https://twitter.com/avi_eisen/status/1581326197241180160?s=20\&t=8WpEg76bW_Kk8YaJ5orP5Q). If they buy up majority shares in the RToken, or, they coordinate with most of the StRSR token holders they could advertise and honey pot people to do redemptions whenever a switchBasket is coming. Spread FUD like "you need to redeem otherwise you'll lose money!" and it's the redeeming that actually steals their money. +And just like that, a seemingly innocuous redemption transaction was a trap the whole time. The next step would be to go through the rest of the process to see how much our evil user profited (from running the auctions), as they need to be a whale to inflate the RToken supply. However, we've seen attacks like this, and one could consider it a [highly profitable trading strategy](https://twitter.com/avi_eisen/status/1581326197241180160?s=20&t=8WpEg76bW_Kk8YaJ5orP5Q). If they buy up majority shares in the RToken, or, they coordinate with most of the StRSR token holders they could advertise and honey pot people to do redemptions whenever a switchBasket is coming. Spread FUD like "you need to redeem otherwise you'll lose money!" and it's the redeeming that actually steals their money. ### Tools Used @@ -869,25 +890,26 @@ Disallow issuance/redemptions while the protocol is undercollateralized. See warden's [original submission](https://github.com/code-423n4/2023-01-reserve-findings/issues/399) for full details. **[0xean (judge) decreased severity to Medium and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/399#issuecomment-1399349370):** - > Certainly a creative attack vector, will leave open for sponsor review. I am unclear on a few nuances of the attack here, but ultimately would like the sponsor to comment. -> -> Downgrading to Medium for the moment due to a very particular sequence of events being required for this to be executed. + +> Certainly a creative attack vector, will leave open for sponsor review. I am unclear on a few nuances of the attack here, but ultimately would like the sponsor to comment. +> +> Downgrading to Medium for the moment due to a very particular sequence of events being required for this to be executed. **[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/399#issuecomment-1405418612):** - > The bug is simpler than the description. If the basket is DISABLED, then all that needs to happen is for a redeem tx to be in the mempool. An MEV searcher can order a `refreshBasket()` call earlier in the block, causing the redemption to be partial. This acts as a net transfer between the RToken redeemer and RSR stakers, who will eventually collect the money. -**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** - > This PR allows an RToken redeemer to specify when they require full redemptions vs accept partial (prorata) redemptions.
-> [reserve-protocol/protocol#615](https://github.com/reserve-protocol/protocol/pull/615) +> The bug is simpler than the description. If the basket is DISABLED, then all that needs to happen is for a redeem tx to be in the mempool. An MEV searcher can order a `refreshBasket()` call earlier in the block, causing the redemption to be partial. This acts as a net transfer between the RToken redeemer and RSR stakers, who will eventually collect the money. -**Status:** Not fully mitigated. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/54), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/14), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/69). Also included in Mitigation Review section below. +**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** +> This PR allows an RToken redeemer to specify when they require full redemptions vs accept partial (prorata) redemptions.
> [reserve-protocol/protocol#615](https://github.com/reserve-protocol/protocol/pull/615) +**Status:** Not fully mitigated. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/54), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/14), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/69). Also included in Mitigation Review section below. -*** +--- ## [[M-05] Early user can call `issue()` and then `melt()` to increase `basketsNeeded` to supply ratio to its maximum value and then `melt()` won't work and contract features like `issue()` won't work](https://github.com/code-423n4/2023-01-reserve-findings/issues/384) -*Submitted by [unforgiven](https://github.com/code-423n4/2023-01-reserve-findings/issues/384)* + +_Submitted by [unforgiven](https://github.com/code-423n4/2023-01-reserve-findings/issues/384)_

@@ -922,7 +944,7 @@ As you can see it allows anyone to burn their RToken balance. This is `requireVa require(uint192(low) >= 1e9 && uint192(high) <= 1e27, "BU rate out of range"); } -As you can see it checks and makes sure that the BU to RToken exchange rate to be in \[1e-9, 1e9]. so Attacker can perform this steps: +As you can see it checks and makes sure that the BU to RToken exchange rate to be in \[1e-9, 1e9]. so Attacker can perform this steps: 1. add `1e18` RToken as first issuer by calling `issue()` 2. call `melt()` and burn `1e18 - 1` of his RTokens. @@ -941,26 +963,28 @@ VIM Don't allow everyone to melt their tokens or don't allow melting if totalSupply() become very small. **[tmattimore (Reserve) disagreed with severity and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/384#issuecomment-1403998889):** - > Understand that `issue()` plus `melt()` can brick an RTokens issuance, but redeem should still work. -> + +> Understand that `issue()` plus `melt()` can brick an RTokens issuance, but redeem should still work. +> > So, the RToken would no longer function as expected but no RToken holder funds would be lost. And in fact, RToken holders now have more funds. -> +> > Believe this is severity 2 but we should mitigate so that an annoying person / entity cannot DDOS every RToken on deployment w/ small amounts of capital. RToken holders can always continue to redeem though. **[0xean (judge) decreased severity to Medium and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/384#issuecomment-1409460672):** - > Agreed on downgrading due to no direct loss of significant funds and this mostly being a griefing type attack. -**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** - > This PR prevents melting RToken until the RToken supply is at least 1e18: [reserve-protocol/protocol#619](https://github.com/reserve-protocol/protocol/pull/619) +> Agreed on downgrading due to no direct loss of significant funds and this mostly being a griefing type attack. -**Status:** Not fully mitigated. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/13) and 0xA5DF ([here](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/70) and [here](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/55)). Also included in Mitigation Review section below. +**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** +> This PR prevents melting RToken until the RToken supply is at least 1e18: [reserve-protocol/protocol#619](https://github.com/reserve-protocol/protocol/pull/619) +**Status:** Not fully mitigated. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/13) and 0xA5DF ([here](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/70) and [here](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/55)). Also included in Mitigation Review section below. -*** +--- ## [[M-06] Too few rewards paid over periods in Furnace and StRSR](https://github.com/code-423n4/2023-01-reserve-findings/issues/377) -*Submitted by [Franfran](https://github.com/code-423n4/2023-01-reserve-findings/issues/377)* + +_Submitted by [Franfran](https://github.com/code-423n4/2023-01-reserve-findings/issues/377)_

@@ -1035,30 +1059,33 @@ uint256 payout = (payoutRatio * rsrRewardsAtLastPayout) / rewardRatio; ``` **[tbrent (Reserve) disputed and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/377#issuecomment-1405453764):** - > I think there is a mistake in the math here, possibly arising from the fact that `rsrRewards()` doesn't correspond to how much rewards _has_ been handed out, but how much is _available_ to be handed out. -> -> I don't understand why the warden is computing the sum of `u_i`. If `u_0` is the value of `rsrRewards()` at time 0, and `u_1` is the value of `rsrRewards()` at time 1, why is the sum of `u_i` for all i interesting? This is double-counting balances, since only some of `u_i` is handed out each time. -> -> As the number of payouts approach infinity, the total amount handed out approaches `u_0`. + +> I think there is a mistake in the math here, possibly arising from the fact that `rsrRewards()` doesn't correspond to how much rewards _has_ been handed out, but how much is _available_ to be handed out. +> +> I don't understand why the warden is computing the sum of `u_i`. If `u_0` is the value of `rsrRewards()` at time 0, and `u_1` is the value of `rsrRewards()` at time 1, why is the sum of `u_i` for all i interesting? This is double-counting balances, since only some of `u_i` is handed out each time. +> +> As the number of payouts approach infinity, the total amount handed out approaches `u_0`. **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/377#issuecomment-1410642492):** - > Would be good to get the warden to comment here during QA - will see if we can have that occur to clear up the difference in understanding. -***Please note: the following comment and re-assessment took place after judging and awarding were finalized. As such, this report will leave this finding in its originally assessed risk category as it simply reflects a snapshot in time.*** +> Would be good to get the warden to comment here during QA - will see if we can have that occur to clear up the difference in understanding. + +**_Please note: the following comment and re-assessment took place after judging and awarding were finalized. As such, this report will leave this finding in its originally assessed risk category as it simply reflects a snapshot in time._** **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/377#issuecomment-1428771851):** - > I want to apologize that I missed the fact that no response was given during QA and currently believe this issue to be invalid. -**[Franfran (warden) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/377#issuecomment-1429273607):** - > Hey friends, sorry for not hopping into the discussion earlier!
-> My reasoning was that if the staker's rewards doesn't compound over time, then there is no reason for them to stay in the pool and not harvest the rewards, which is a costly process if they would have to harvest each cycle. +> I want to apologize that I missed the fact that no response was given during QA and currently believe this issue to be invalid. +**[Franfran (warden) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/377#issuecomment-1429273607):** +> Hey friends, sorry for not hopping into the discussion earlier!
+> My reasoning was that if the staker's rewards doesn't compound over time, then there is no reason for them to stay in the pool and not harvest the rewards, which is a costly process if they would have to harvest each cycle. -*** +--- ## [[M-07] Attacker can steal RToken holders' funds by performing reentrancy attack during `redeem()` function token transfers](https://github.com/code-423n4/2023-01-reserve-findings/issues/347) -*Submitted by [unforgiven](https://github.com/code-423n4/2023-01-reserve-findings/issues/347), also found by [unforgiven](https://github.com/code-423n4/2023-01-reserve-findings/issues/318), [unforgiven](https://github.com/code-423n4/2023-01-reserve-findings/issues/304), [ustas](https://github.com/code-423n4/2023-01-reserve-findings/issues/297), and [hihen](https://github.com/code-423n4/2023-01-reserve-findings/issues/95)* + +_Submitted by [unforgiven](https://github.com/code-423n4/2023-01-reserve-findings/issues/347), also found by [unforgiven](https://github.com/code-423n4/2023-01-reserve-findings/issues/318), [unforgiven](https://github.com/code-423n4/2023-01-reserve-findings/issues/304), [ustas](https://github.com/code-423n4/2023-01-reserve-findings/issues/297), and [hihen](https://github.com/code-423n4/2023-01-reserve-findings/issues/95)_
@@ -1113,7 +1140,7 @@ This is `redeem()` code: if (allZero) revert("Empty redemption"); } -As you can see code calculates withdrawal amount of each basket erc20 tokens by calling `basketHandler.quote()` and then bounds each withdrawal by the prorata share of token balance, in case protocol is under-collateralized. and then code updates `basketsNeeded` and in the end transfers the tokens. if one of those tokens were ERC777 then that token would call receiver hook function in token transfer. there may be other 3rd party protocol tokens that calls registered hook functions during the token transfer. as reserve protocol is permission less and tries to work with all tokens so the external call in the token transfer can call hook functions. attacker can use this hook and perform reentrancy attack. +As you can see code calculates withdrawal amount of each basket erc20 tokens by calling `basketHandler.quote()` and then bounds each withdrawal by the prorata share of token balance, in case protocol is under-collateralized. and then code updates `basketsNeeded` and in the end transfers the tokens. if one of those tokens were ERC777 then that token would call receiver hook function in token transfer. there may be other 3rd party protocol tokens that calls registered hook functions during the token transfer. as reserve protocol is permission less and tries to work with all tokens so the external call in the token transfer can call hook functions. attacker can use this hook and perform reentrancy attack. This is `fullyCollateralized()` code in BasketHandler: @@ -1132,7 +1159,7 @@ The root cause of the issue is that during tokens transfers in `redeem()` not al 3. attacker would register a hook for his address in `SOME_ERC777` token to get called during transfers. 4. attacker would call `redeem()` to redeem 15K RToken and code would updated `basketsNeeded` to 135K and code would bounds withdrawal by prorata shares of balance of the BackingManager because protocol is under-collateralized and code would calculated withdrawal amouns as 15K `SOME_ERC777` tokens and 10K `USDT` tokens (instead of 15K `USDT` tokens) for withdraws. 5. then contract would transfer 15K `SOME_ERC777` tokens first to attacker address and attacker contract would get called during the hook function and now `basketsNeeded` is 135K and total RTokens is 135K and BackingManager balance is 185K `SOME_ERC777` and 100K `USDT` (`USDT` is not yet transferred). then attacker contract can call `redeem()` again for the remaining 15K RTokens. -6. because protocol is under-collateralized code would calculated withdrawal amouns as 15K `SOME_ERC777` and 11.1K `USDT` (USDT_balance * rtokenAmount / totalSupply = 100K * 15K / 135K) and it would burn 15K RToken form caller and the new value of totalSupply of RTokens would be 120K and `basketsNeeded` would be 120K too. then code would transfers 15K `SOME_ERC777` and 11.1K `USDT` for attacker address. +6. because protocol is under-collateralized code would calculated withdrawal amouns as 15K `SOME_ERC777` and 11.1K `USDT` (USDT_balance \* rtokenAmount / totalSupply = 100K \* 15K / 135K) and it would burn 15K RToken form caller and the new value of totalSupply of RTokens would be 120K and `basketsNeeded` would be 120K too. then code would transfers 15K `SOME_ERC777` and 11.1K `USDT` for attacker address. 7. attacker's hook function would return and `redeem()` would transfer 10K `USDT` to attacker in the rest of the execution. attacker would receive 30K `SOME_ERC777` and 21.1K `USDT` tokens for 15K redeemed RToken but attacker should have get (`100 * 30K / 150K = 20K`) 20K `USDT` tokens because of the bound each withdrawal by the prorata share, in case we're currently under-collateralized. 8. so attacker would be able to bypass the bounding check and withdraw more funds and stole other users funds. the attack is more effective if withdrawal battery charge is higher but in general case attacker can perform two withdraw each with about `charge/2` amount of RToken in each block and stole other users funds when protocol is under collaterlized. @@ -1158,28 +1185,32 @@ Prevent reading reentrancy attack by central reentrancy guard or by one main pro Or create contract state (similar to basket nonce) which changes after each interaction and check for contracts states change during the call. (start and end of the call) **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/347#issuecomment-1399382867):** - > Would like to get some sponsor comments on this once prior to final review. + +> Would like to get some sponsor comments on this once prior to final review. **[tmattimore (Reserve) confirmed and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/347#issuecomment-1404024005):** - > We think it's real. -> + +> We think it's real. +> > Other potential mitigation:
+> > - governance level norm of excluding erc777 as collateral. Can't fully enforce though, so not a full mitigation. -> +> > Will discuss more and decide on mitigation path with team. **[0xean (judge) decreased severity to Medium and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/347#issuecomment-1409453091):** - > Thanks @tmattimore - I am going to downgrade to Medium due to the external requirements needed for it to become a reality. If I may ask, what is the hesitancy to simply introduce standard reentrancy modifiers? It's not critical to the audit in any way, just more of my own curiosity. -**[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/347#issuecomment-1426447206):** - > @0xean - we would need a global mutex in order to prevent the attack noted here, which means lots of gas-inefficient external calls. The classic OZ modifier wouldn't be enough. +> Thanks @tmattimore - I am going to downgrade to Medium due to the external requirements needed for it to become a reality. If I may ask, what is the hesitancy to simply introduce standard reentrancy modifiers? It's not critical to the audit in any way, just more of my own curiosity. +**[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/347#issuecomment-1426447206):** +> @0xean - we would need a global mutex in order to prevent the attack noted here, which means lots of gas-inefficient external calls. The classic OZ modifier wouldn't be enough. -*** +--- ## [[M-08] `Asset.lotPrice()` doesn't use the most recent price in case of oracle timeout](https://github.com/code-423n4/2023-01-reserve-findings/issues/326) -*Submitted by [0xA5DF](https://github.com/code-423n4/2023-01-reserve-findings/issues/326)* + +_Submitted by [0xA5DF](https://github.com/code-423n4/2023-01-reserve-findings/issues/326)_ `Asset.lotPrice()` has a fallback mechanism in case that `tryPrice()` fails - it uses the last saved price and multiplies its value by `lotMultiplier` (a variable that decreases as the time since the last saved price increase) and returns the results. @@ -1191,12 +1222,12 @@ This can cause the backing manager to misestimate the value of the asset, trade In the PoC below: -* Oracle price is set at day 0 -* The asset is refreshed (e.g. somebody issued/vested/redeemed) -* After 5 days the oracle gets an update -* 25 hours later the `lotPrice()` is calculated based on the oracle price from day 0 even though a price from day 5 is available from the oracle -* Oracle gets another update -* 25 hours later the `lotPrice()` goes down to zero since it considers the price from day 0 (which is more than a week ago) to be the last saved price, even though a price from a day ago is available from the oracle +- Oracle price is set at day 0 +- The asset is refreshed (e.g. somebody issued/vested/redeemed) +- After 5 days the oracle gets an update +- 25 hours later the `lotPrice()` is calculated based on the oracle price from day 0 even though a price from day 5 is available from the oracle +- Oracle gets another update +- 25 hours later the `lotPrice()` goes down to zero since it considers the price from day 0 (which is more than a week ago) to be the last saved price, even though a price from a day ago is available from the oracle ```diff diff --git a/test/fixtures.ts b/test/fixtures.ts @@ -1204,14 +1235,14 @@ index 5299a5f6..75ca8010 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -69,7 +69,7 @@ export const SLOW = !!useEnv('SLOW') - + export const PRICE_TIMEOUT = bn('604800') // 1 week - + -export const ORACLE_TIMEOUT = bn('281474976710655').div(2) // type(uint48).max / 2 +export const ORACLE_TIMEOUT = bn('86400') // one day - + export const ORACLE_ERROR = fp('0.01') // 1% oracle error - + diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index d49c53f3..7f2f721e 100644 --- a/test/plugins/Asset.test.ts @@ -1219,7 +1250,7 @@ index d49c53f3..7f2f721e 100644 @@ -233,6 +233,45 @@ describe('Assets contracts #fast', () => { ) }) - + + it('PoC lot price doesn\'t use most recent price', async () => { + // Update values in Oracles to 0 + @@ -1267,7 +1298,7 @@ index d49c53f3..7f2f721e 100644 }) }) + return; - + describe('Constructor validation', () => { it('Should not allow price timeout to be zero', async () => { @@ -1303,16 +1334,16 @@ Allow specifying a timeout to `tryPrice()`, in case that `tryPrice()` fails due If the call succeeds the second time then use it as the most recent price for fallback calculations. **[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/326#issuecomment-1405530751):** - > Nice find! When `StalePrice()`is thrown in `OracleLib.sol`, it should revert with the latest price, and this latest price should be used in the asset plugin. - -**[tbrent (Reserve) acknowledged](https://github.com/code-423n4/2023-01-reserve-findings/issues/326)** +> Nice find! When `StalePrice()`is thrown in `OracleLib.sol`, it should revert with the latest price, and this latest price should be used in the asset plugin. +**[tbrent (Reserve) acknowledged](https://github.com/code-423n4/2023-01-reserve-findings/issues/326)** -*** +--- ## [[M-09] Withdrawals will stuck](https://github.com/code-423n4/2023-01-reserve-findings/issues/325) -*Submitted by [csanuragjain](https://github.com/code-423n4/2023-01-reserve-findings/issues/325)* + +_Submitted by [csanuragjain](https://github.com/code-423n4/2023-01-reserve-findings/issues/325)_ If a new era gets started for stakeRSR and draftRSR still point to old era then user will be at risk of losing their future holdings. @@ -1402,28 +1433,30 @@ If a new era gets started for stakeRSR and draftRSR still point to old era then Era should be same for staking and draft. So if User is unstaking at era 1 then withdrawal draft should always be era 1 and not some previous era. **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/325#issuecomment-1402728967):** - > I believe the sequence of events here to be off with when beginDraftEra would be called. -> -> Will leave open for sponsor confirmation on the beginDraftEra call being triggered earlier in the process due to the value of `draftRSR == 0`. + +> I believe the sequence of events here to be off with when beginDraftEra would be called. +> +> Will leave open for sponsor confirmation on the beginDraftEra call being triggered earlier in the process due to the value of `draftRSR == 0`. **[tbrent (Reserve) disputed and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/325#issuecomment-1405551460):** - > In the example described, I'm pretty sure point 4 is wrong: draftRSR would be 0 and both the eras would be changed at the same time. -> + +> In the example described, I'm pretty sure point 4 is wrong: draftRSR would be 0 and both the eras would be changed at the same time. +> > That said, I don't think it's a problem to have different eras for stakeRSR and draftRSR. It's subtle, but it could be that due to rounding one of these overflows `MAX_STAKE_RATE`/`MAX_DRAFT_RATE`, but not the other. This is fine. This means enough devaluation has happened to one of the polities (current stakers; current withdrawers) that they have been wiped out. It's not a contradiction for the other polity to still be entitled to a small amount of RSR. -> -> It also might be the warden is misunderstanding the intended design here: if you initiate StRSR unstaking, then a sufficient RSR seizure event _should_ result in the inability to withdraw anything after. +> +> It also might be the warden is misunderstanding the intended design here: if you initiate StRSR unstaking, then a sufficient RSR seizure event _should_ result in the inability to withdraw anything after. -***Please note: the following comment and re-assessment took place after judging and awarding were finalized. As such, this report will leave this finding in its originally assessed risk category as it simply reflects a snapshot in time.*** +**_Please note: the following comment and re-assessment took place after judging and awarding were finalized. As such, this report will leave this finding in its originally assessed risk category as it simply reflects a snapshot in time._** **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/325#issuecomment-1428774156):** - > I wanted to comment and apologize that this issue slipped through the QA process and I didn't give it a second pass to close it out as invalid. While C4 will not change grades or awards retroactively, it is worth noting for the final report that I do not believe this issue to be valid. - +> I wanted to comment and apologize that this issue slipped through the QA process and I didn't give it a second pass to close it out as invalid. While C4 will not change grades or awards retroactively, it is worth noting for the final report that I do not believe this issue to be valid. -*** +--- ## [[M-10] Unsafe downcasting in `issue(...)` can be exploited to cause permanent DoS](https://github.com/code-423n4/2023-01-reserve-findings/issues/320) -*Submitted by [Soosh](https://github.com/code-423n4/2023-01-reserve-findings/issues/320)* + +_Submitted by [Soosh](https://github.com/code-423n4/2023-01-reserve-findings/issues/320)_ **Important note!** @@ -1489,7 +1522,7 @@ it('Audit: DoS by downcasting', async function () { const tx = await rToken.connect(addr1)['issue(uint256)'](issueAmount) const receipt = await tx.wait() console.log(receipt.events[0].args) - + await token0.connect(addr2).approve(rToken.address, initialBal) const tx2 = await rToken.connect(addr2)['issue(uint256)'](initialBal) const receipt2 = await tx2.wait() @@ -1544,7 +1577,7 @@ Expect to see (only important parts shown): ### Impact Permanent DoS would be High risk considering RToken is an asset-backed **currency**.
-*A currency that is unable to issue new currency does not work as a currency* +_A currency that is unable to issue new currency does not work as a currency_ Also, I believe existing collateral cannot be redeemed due to the extreme values also used in `redeem(...)` function. No PoC written due to time constriant for this case... but above should be enough impact. @@ -1555,110 +1588,123 @@ Many other downcasting issues for this project. But using a safe casting library Use some safe casting library. OpenZeppelin's library does not have safe casting for `uint192` type. May have to find another or write your own. **[0xean (judge) decreased severity to Medium and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/320#issuecomment-1399385997):** - > Will leave open for sponsor review, I think Medium severity is correct if the finding turns out to be fully valid. If no more issuance can occur, redemption is still possible. Warden would have needed to demonstrate a loss of funds for this to qualify as H severity. + +> Will leave open for sponsor review, I think Medium severity is correct if the finding turns out to be fully valid. If no more issuance can occur, redemption is still possible. Warden would have needed to demonstrate a loss of funds for this to qualify as H severity. **[tbrent (Reserve) disagreed with severity and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/320#issuecomment-1405572151):** - > We have supported ranges of value. See `docs/solidity-style.md`. -> + +> We have supported ranges of value. See `docs/solidity-style.md`. +> > The only mistake here is that `issue()` has somewhat lacking in-line documentation:
+> > > Downcast is safe because an actual quantity of qBUs fits in uint192 -> +> > The comment in `redeem()` is a bit better:
-> > // downcast is safe: amount < totalSupply and basketsNeeded_ < 1e57 < 2^190 (just barely) -> -> We'll probably improve the comment in `issue()` to match `redeem()`. This should be a QA-level issue. +> +> > // downcast is safe: amount < totalSupply and basketsNeeded\_ < 1e57 < 2^190 (just barely) +> +> We'll probably improve the comment in `issue()` to match `redeem()`. This should be a QA-level issue. **[0xean (judge) decreased severity to Low/Non-Critical](https://github.com/code-423n4/2023-01-reserve-findings/issues/320#issuecomment-1399385997)** **[Soosh (warden) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/320#issuecomment-1420413631):** - > I don't see how documentation prevents this issue. -> + +> I don't see how documentation prevents this issue. +> > The issue exists because downcasting values above 2^192 does not revert. Maybe the sponsor misunderstood the issue thinking that it would require the attacker to deposit 2^192 of the collateral in order for the attack to succeed which is an extremely unlikely scenario. -> +> > Updated the PoC to clearly show that the attacker can permanently disable the `issue(...)` function for the protocol, without owning any amount of the basket token. - addr1 is the attacker with 0 basket tokens, addr2 represents all future users who will not be able to issue new RTokens. +> > ```js > it('Audit: DoS by downcasting', async function () { -> const issueAmount: BigNumber = BigNumber.from(2n ** 192n) -> -> await token0.burn(addr1.address, bn('6.3e57')) -> await token0.burn(addr2.address, bn('6.3e57')) -> // await token0.mint(addr1.address, bn('10e18')) -> await token0.mint(addr2.address, bn('10e18')) -> expect(await token0.balanceOf(addr1.address)).to.eq(0) -> expect(await token0.balanceOf(addr2.address)).to.eq(bn('10e18')) -> -> // Set basket -> await basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1')]) -> await basketHandler.connect(owner).refreshBasket() -> -> // Attacker issues 2 ** 192, or a multiple of 2 ** 192 RTokens -> // This will cause allVestAt to be very high, permanent DoS -> const tx = await rToken.connect(addr1)['issue(uint256)'](issueAmount) -> const receipt = await tx.wait() -> console.log(receipt.events[0].args) -> -> await token0.connect(addr2).approve(rToken.address, bn('10e18')) -> const tx2 = await rToken.connect(addr2)['issue(uint256)'](bn('10e18')) -> const receipt2 = await tx2.wait() -> console.log(receipt2.events[0].args) -> -> // one eternity later... -> await advanceTime('123456789123456789') -> // and still not ready -> await expect(rToken.connect(addr2).vest(addr2.address, 1)) -> .to.be.revertedWith("issuance not ready") -> }) +> const issueAmount: BigNumber = BigNumber.from(2n ** 192n) +> +> await token0.burn(addr1.address, bn('6.3e57')) +> await token0.burn(addr2.address, bn('6.3e57')) +> // await token0.mint(addr1.address, bn('10e18')) +> await token0.mint(addr2.address, bn('10e18')) +> expect(await token0.balanceOf(addr1.address)).to.eq(0) +> expect(await token0.balanceOf(addr2.address)).to.eq(bn('10e18')) +> +> // Set basket +> await basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1')]) +> await basketHandler.connect(owner).refreshBasket() +> +> // Attacker issues 2 ** 192, or a multiple of 2 ** 192 RTokens +> // This will cause allVestAt to be very high, permanent DoS +> const tx = await rToken.connect(addr1)['issue(uint256)'](issueAmount) +> const receipt = await tx.wait() +> console.log(receipt.events[0].args) +> +> await token0.connect(addr2).approve(rToken.address, bn('10e18')) +> const tx2 = await rToken.connect(addr2)['issue(uint256)'](bn('10e18')) +> const receipt2 = await tx2.wait() +> console.log(receipt2.events[0].args) +> +> // one eternity later... +> await advanceTime('123456789123456789') +> // and still not ready +> await expect(rToken.connect(addr2).vest(addr2.address, 1)).to.be.revertedWith( +> 'issuance not ready' +> ) +> }) > ``` -> +> > Additionally, I still believe this issue should be considered High risk as: -> +> > 1. Disabling of critical function of the protocol > 2. Attack is very simple to exploit, with no cost to the attacker - Low complexity with High likelihood > 3. Permanent disabling of RToken issuance means that the **RToken can no longer be used** so all funds must be moved out, this will entail: +> > - Redeeming all existing RTokens, which will take a reasonable amount of time depending on redemption battery parameters > - Unstaking all stRSR which will take a reasonable amount of time depending on unstaking delay -> 4. Gas costs for all the above redeeming and unstaking will be in the thousands for a RToken with reasonable market cap. +> +> 4. Gas costs for all the above redeeming and unstaking will be in the thousands for a RToken with reasonable market cap. > 5. RToken is a stable currency which means that it would be used in DeFi protocols. In the case of Lending/Borrowing, it would take even longer for RToken to be redeemed. There may also be loss of funds as a long wait time to redeem RTokens means that the RToken will trade at a discount in secondary markets - this can cause RToken-collateralized loans to be underwater. -> +> > There is no **direct** loss of funds but I'd argue the impact is vast due to RToken being used as a currency. **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/320#issuecomment-1420866210):** - > Thanks for the response. -> + +> Thanks for the response. +> > > There is no direct loss of funds but I'd argue the impact is vast due to RToken being used as a currency. -> +> > If there is no direct loss of funds, how can this issue be High per the C4 criteria, not your own opinion? -> -> I will ask @tbrent to take another look at your POC and do the same as well. +> +> I will ask @tbrent to take another look at your POC and do the same as well. **[Soosh (warden) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/320#issuecomment-1420954105):** - > I agree with Medium if following C4 criteria in the docs exactly word for word. -> + +> I agree with Medium if following C4 criteria in the docs exactly word for word. +> > It is just that there are many High findings in previous contests where High findings did not need to cause direct loss of funds, but break an important functionality in the protocol. -> +> > To be clear, this issue does lead to loss of funds. It is just that it may not be considered **direct**. -> +> > It is indeed my opinion that the finding should be High, but the points listed below are all facts. I will respect your decision regardless. Thanks! **[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/320#issuecomment-1421058666):** - > Apologies, I misunderstood the issue the first time I read through it...indeed this can be used to mint large amounts of RToken to yourself while putting down very little in collateral, while pushing `allVestAt` extremely far into the future. -> -> Since `issuanceRate` cannot be disabled, and cannot be above 100%, there is no way for the absurdly high RToken mint to finish vesting. In the event of the attack, RToken issuance would be bricked but redemption would remain enabled, and since no RToken is minted until vesting the redemptions would still function. I think this is a Medium. + +> Apologies, I misunderstood the issue the first time I read through it...indeed this can be used to mint large amounts of RToken to yourself while putting down very little in collateral, while pushing `allVestAt` extremely far into the future. +> +> Since `issuanceRate` cannot be disabled, and cannot be above 100%, there is no way for the absurdly high RToken mint to finish vesting. In the event of the attack, RToken issuance would be bricked but redemption would remain enabled, and since no RToken is minted until vesting the redemptions would still function. I think this is a Medium. **[0xean (judge) increased severity to Medium and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/320#issuecomment-1421336734):** - > Thanks for all the conversation, marking as Medium. -**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** - > This PR makes all dangerous uint192 downcasts truncation-safe: [reserve-protocol/protocol#628](https://github.com/reserve-protocol/protocol/pull/628) +> Thanks for all the conversation, marking as Medium. -**Status:** Mitigation confirmed. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/19), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/56), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/33). +**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** +> This PR makes all dangerous uint192 downcasts truncation-safe: [reserve-protocol/protocol#628](https://github.com/reserve-protocol/protocol/pull/628) +**Status:** Mitigation confirmed. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/19), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/56), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/33). -*** +--- ## [[M-11] Should Accrue Before Change, Loss of Rewards in case of change of settings](https://github.com/code-423n4/2023-01-reserve-findings/issues/287) -*Submitted by [GalloDaSballo](https://github.com/code-423n4/2023-01-reserve-findings/issues/287), also found by [chaduke](https://github.com/code-423n4/2023-01-reserve-findings/issues/380), [\_\_141345\_\_](https://github.com/code-423n4/2023-01-reserve-findings/issues/333), and [\_\_141345\_\_](https://github.com/code-423n4/2023-01-reserve-findings/issues/330)* + +_Submitted by [GalloDaSballo](https://github.com/code-423n4/2023-01-reserve-findings/issues/287), also found by [chaduke](https://github.com/code-423n4/2023-01-reserve-findings/issues/380), [\_\_141345\_\_](https://github.com/code-423n4/2023-01-reserve-findings/issues/333), and [\_\_141345\_\_](https://github.com/code-423n4/2023-01-reserve-findings/issues/330)_ In `StRSR.sol`, `_payoutRewards` is used to accrue the value of rewards based on the time that has passed since `payoutLastPaid` @@ -1668,7 +1714,7 @@ There is a specific instance, in which `_payoutRewards` should also be called, w If you imagine the ratio at which rewards are paid out as a line, then you can see that by changing `rewardRatio` and `period` you're changing it's slope. -You should then agree, that while governance can *rightfully* change those settings, it should `_payoutRewards` first, to ensure that the slope of rewards changes only for rewards to be distributed after the setting has changed. +You should then agree, that while governance can _rightfully_ change those settings, it should `_payoutRewards` first, to ensure that the slope of rewards changes only for rewards to be distributed after the setting has changed. ### Mitigation @@ -1676,16 +1722,16 @@ Functions that change the slope or period size should accrue rewards up to that This is to avoid: -* Incorrect reward distribution -* Change (positive or negative) of rewards from the past +- Incorrect reward distribution +- Change (positive or negative) of rewards from the past Without accrual, the change will apply retroactively from `payoutLastPaid` Which could: -* Change the period length prematurely -* Start a new period inadvertently -* Cause a gain or loss of yield to stakers +- Change the period length prematurely +- Start a new period inadvertently +- Cause a gain or loss of yield to stakers Instead of starting a new period @@ -1693,35 +1739,37 @@ Instead of starting a new period ```solidity function setRewardPeriod(uint48 val) public governance { - require(val > 0 && val <= MAX_REWARD_PERIOD, "invalid rewardPeriod"); - _payoutRewards(); // @audit Payout rewards for fairness - emit RewardPeriodSet(rewardPeriod, val); - rewardPeriod = val; - require(rewardPeriod * 2 <= unstakingDelay, "unstakingDelay/rewardPeriod incompatible"); + require(val > 0 && val <= MAX_REWARD_PERIOD, 'invalid rewardPeriod'); + _payoutRewards(); // @audit Payout rewards for fairness + emit RewardPeriodSet(rewardPeriod, val); + rewardPeriod = val; + require(rewardPeriod * 2 <= unstakingDelay, 'unstakingDelay/rewardPeriod incompatible'); } function setRewardRatio(uint192 val) public governance { - require(val <= MAX_REWARD_RATIO, "invalid rewardRatio"); - _payoutRewards(); // @audit Payout rewards for fairness - emit RewardRatioSet(rewardRatio, val); - rewardRatio = val; + require(val <= MAX_REWARD_RATIO, 'invalid rewardRatio'); + _payoutRewards(); // @audit Payout rewards for fairness + emit RewardRatioSet(rewardRatio, val); + rewardRatio = val; } + ``` **[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/287#issuecomment-1404391327):** - > Nice finding, agree. -**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** - > This PR adds a `Furnace.melt()`/`StRSR.payoutRewards()` step when governance changes the `rewardRatio`: [reserve-protocol/protocol#622](https://github.com/reserve-protocol/protocol/pull/622) +> Nice finding, agree. -**Status:** Mitigation confirmed with comments. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/18), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/57), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/34). +**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** +> This PR adds a `Furnace.melt()`/`StRSR.payoutRewards()` step when governance changes the `rewardRatio`: [reserve-protocol/protocol#622](https://github.com/reserve-protocol/protocol/pull/622) +**Status:** Mitigation confirmed with comments. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/18), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/57), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/34). -*** +--- ## [[M-12] BackingManager: rsr is distributed across all rsr revenue destinations which is a loss for rsr stakers](https://github.com/code-423n4/2023-01-reserve-findings/issues/276) -*Submitted by [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/276)* + +_Submitted by [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/276)_ The `BackingManager.handoutExcessAssets` function sends all `rsr` that the `BackingManager` holds to the `rsrTrader` (). @@ -1739,9 +1787,9 @@ Stakers should only pay for recollateralization of the `RToken`, not however sen Assume the following situation: -* A seizure of `rsr` from the `StRSR` contract occurred because the `RToken` was under-collateralized. +- A seizure of `rsr` from the `StRSR` contract occurred because the `RToken` was under-collateralized. -* A trade occurred which restored collateralization. However not all `rsr` was sold by the trade and was returned to the `BackingManager`. +- A trade occurred which restored collateralization. However not all `rsr` was sold by the trade and was returned to the `BackingManager`. Now `BackingManager.manageTokens` is called which due to the full collateralization calls `BackingManager.handoutExcessAssets` (). @@ -1788,19 +1836,20 @@ However there is no easy way to differentiate where the `rsr` came from. Therefore I think it is reasonable to send all `rsr` to `StRSR` and make it clear to developers and users that `rsr` rewards cannot be paid out to `rToken` holders. **[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/276#issuecomment-1404405069):** - > Yep, this one is a great find. -**[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/276#issuecomment-1421609180):** - > Fixed here: https://github.com/reserve-protocol/protocol/pull/584 +> Yep, this one is a great find. -**Status:** Mitigation confirmed. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/58), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/11), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/36). +**[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/276#issuecomment-1421609180):** +> Fixed here: https://github.com/reserve-protocol/protocol/pull/584 +**Status:** Mitigation confirmed. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/58), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/11), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/36). -*** +--- ## [[M-13] Attacker can prevent vesting for a very long time](https://github.com/code-423n4/2023-01-reserve-findings/issues/267) -*Submitted by [immeas](https://github.com/code-423n4/2023-01-reserve-findings/issues/267), also found by [wait](https://github.com/code-423n4/2023-01-reserve-findings/issues/367), [unforgiven](https://github.com/code-423n4/2023-01-reserve-findings/issues/364), [JTJabba](https://github.com/code-423n4/2023-01-reserve-findings/issues/135), [rvierdiiev](https://github.com/code-423n4/2023-01-reserve-findings/issues/116), [hihen](https://github.com/code-423n4/2023-01-reserve-findings/issues/73), and [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/24)* + +_Submitted by [immeas](https://github.com/code-423n4/2023-01-reserve-findings/issues/267), also found by [wait](https://github.com/code-423n4/2023-01-reserve-findings/issues/367), [unforgiven](https://github.com/code-423n4/2023-01-reserve-findings/issues/364), [JTJabba](https://github.com/code-423n4/2023-01-reserve-findings/issues/135), [rvierdiiev](https://github.com/code-423n4/2023-01-reserve-findings/issues/116), [hihen](https://github.com/code-423n4/2023-01-reserve-findings/issues/73), and [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/24)_ When a user wants to issue RTokens there is a limit of how many can be issued in the same block. This is determined in the `whenFinished` function. @@ -1868,60 +1917,60 @@ A malicious user can delay issuances a very long time costing only gas. PoC test in `RToken.test.ts`: ```javascript - // based on 'Should allow the recipient to rollback minting' - it('large issuance and cancel griefs later issuances', async function () { - const issueAmount: BigNumber = bn('5000000e18') // flashloan or rich - - // Provide approvals - const [, depositTokenAmounts] = await facade.callStatic.issue(rToken.address, issueAmount) - await Promise.all( - tokens.map((t, i) => t.connect(addr1).approve(rToken.address, depositTokenAmounts[i])) - ) - - await Promise.all( - tokens.map((t, i) => t.connect(addr2).approve(rToken.address, ethers.constants.MaxInt256)) - ) - - // Get initial balances - const initialRecipientBals = await Promise.all(tokens.map((t) => t.balanceOf(addr2.address))) - - // Issue a lot of rTokens - await rToken.connect(addr1)['issue(address,uint256)'](addr2.address, issueAmount) - - // Cancel - await expect(rToken.connect(addr2).cancel(1, true)) - .to.emit(rToken, 'IssuancesCanceled') - .withArgs(addr2.address, 0, 1, issueAmount) - - // repeat to make allVestAt very large - for(let j = 0; j<100 ; j++) { - await rToken.connect(addr2)['issue(address,uint256)'](addr2.address, issueAmount) - - await expect(rToken.connect(addr2).cancel(1, true)) - .to.emit(rToken, 'IssuancesCanceled') - .withArgs(addr2.address, 0, 1, issueAmount) - } - - // Check balances returned to the recipient, addr2 - await Promise.all( - tokens.map(async (t, i) => { - const expectedBalance = initialRecipientBals[i].add(depositTokenAmounts[i]) - expect(await t.balanceOf(addr2.address)).to.equal(expectedBalance) - }) - ) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) - - const instantIssue: BigNumber = MIN_ISSUANCE_PER_BLOCK.sub(1) - await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, initialBal))) - - // what should have been immediate issuance will be queued - await rToken.connect(addr1)['issue(uint256)'](instantIssue) - - expect(await rToken.balanceOf(addr1.address)).to.equal(0) - - const issuances = await facade.callStatic.pendingIssuances(rToken.address, addr1.address) - expect(issuances.length).to.eql(1) +// based on 'Should allow the recipient to rollback minting' +it('large issuance and cancel griefs later issuances', async function () { + const issueAmount: BigNumber = bn('5000000e18') // flashloan or rich + + // Provide approvals + const [, depositTokenAmounts] = await facade.callStatic.issue(rToken.address, issueAmount) + await Promise.all( + tokens.map((t, i) => t.connect(addr1).approve(rToken.address, depositTokenAmounts[i])) + ) + + await Promise.all( + tokens.map((t, i) => t.connect(addr2).approve(rToken.address, ethers.constants.MaxInt256)) + ) + + // Get initial balances + const initialRecipientBals = await Promise.all(tokens.map((t) => t.balanceOf(addr2.address))) + + // Issue a lot of rTokens + await rToken.connect(addr1)['issue(address,uint256)'](addr2.address, issueAmount) + + // Cancel + await expect(rToken.connect(addr2).cancel(1, true)) + .to.emit(rToken, 'IssuancesCanceled') + .withArgs(addr2.address, 0, 1, issueAmount) + + // repeat to make allVestAt very large + for (let j = 0; j < 100; j++) { + await rToken.connect(addr2)['issue(address,uint256)'](addr2.address, issueAmount) + + await expect(rToken.connect(addr2).cancel(1, true)) + .to.emit(rToken, 'IssuancesCanceled') + .withArgs(addr2.address, 0, 1, issueAmount) + } + + // Check balances returned to the recipient, addr2 + await Promise.all( + tokens.map(async (t, i) => { + const expectedBalance = initialRecipientBals[i].add(depositTokenAmounts[i]) + expect(await t.balanceOf(addr2.address)).to.equal(expectedBalance) }) + ) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal(0) + + const instantIssue: BigNumber = MIN_ISSUANCE_PER_BLOCK.sub(1) + await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, initialBal))) + + // what should have been immediate issuance will be queued + await rToken.connect(addr1)['issue(uint256)'](instantIssue) + + expect(await rToken.balanceOf(addr1.address)).to.equal(0) + + const issuances = await facade.callStatic.pendingIssuances(rToken.address, addr1.address) + expect(issuances.length).to.eql(1) +}) ``` ### Tools Used @@ -1935,17 +1984,16 @@ Manual auditing and hardhat **[tbrent (Reserve) confirmed via duplicate issue `#364`](https://github.com/code-423n4/2023-01-reserve-findings/issues/364#issuecomment-1405483304)** **[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** - > This PR removes the non-atomic issuance mechanism and adds an issuance throttle. The redemption battery is rebranded to a redemption throttle.
-> [reserve-protocol/protocol#571](https://github.com/reserve-protocol/protocol/pull/571) - -**Status:** Mitigation confirmed with comments. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/10), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/59), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/37). +> This PR removes the non-atomic issuance mechanism and adds an issuance throttle. The redemption battery is rebranded to a redemption throttle.
> [reserve-protocol/protocol#571](https://github.com/reserve-protocol/protocol/pull/571) +**Status:** Mitigation confirmed with comments. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/10), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/59), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/37). -*** +--- ## [[M-14] Unsafe cast of `uint8` datatype to `int8`](https://github.com/code-423n4/2023-01-reserve-findings/issues/265) -*Submitted by [0xTaylor](https://github.com/code-423n4/2023-01-reserve-findings/issues/265)* + +_Submitted by [0xTaylor](https://github.com/code-423n4/2023-01-reserve-findings/issues/265)_
@@ -1974,19 +2022,20 @@ Chisel Validate that the decimals value is within an acceptable upper-bound before attempting to cast it to a signed integer. **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/265#issuecomment-1401152907):** - > The warden does make a reasonable point re: the cast being done here and others have pointed out concerns over assuming that an ERC20 _must_ have a decimals field. -> + +> The warden does make a reasonable point re: the cast being done here and others have pointed out concerns over assuming that an ERC20 _must_ have a decimals field. +> > Will leave open for sponsor review, but I think this would qualify as Medium. **[tbrent (Reserve) acknowledged and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/265#issuecomment-1404412161):** - > Agreed, seems Medium to me. - +> Agreed, seems Medium to me. -*** +--- ## [[M-15] The `Furnace#melt()` is vulnerable to sandwich attacks](https://github.com/code-423n4/2023-01-reserve-findings/issues/258) -*Submitted by [wait](https://github.com/code-423n4/2023-01-reserve-findings/issues/258)* + +_Submitted by [wait](https://github.com/code-423n4/2023-01-reserve-findings/issues/258)_ Malicious users can get more of the RToken appreciation benefit brought by [`Furnace.sol#melt()`](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Furnace.sol#L70), and long-term RToken holders will get less benefit. @@ -2011,8 +2060,8 @@ This assumption is reasonable because if there are price differentials, they can The attack can be profitable for: -* `Furnace#melt()` will increase the price of RToken in issue/redeem (according to basket rate). -* Step 2 buys RTokens at a lower price, and then step 3 sells RTokens at a higher price(`melt()` is called first in `redeem()`). +- `Furnace#melt()` will increase the price of RToken in issue/redeem (according to basket rate). +- Step 2 buys RTokens at a lower price, and then step 3 sells RTokens at a higher price(`melt()` is called first in `redeem()`). **A2. Get a higher yield by holding RToken for a short period of time** @@ -2033,21 +2082,22 @@ Alternatively, always use a very small rewardPeriod and rewardRatio, and lower t **[0xean (judge) decreased severity to Medium](https://github.com/code-423n4/2023-01-reserve-findings/issues/258)** **[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/258#issuecomment-1404414135):** - > Agreed with the warden. And agree this is a Medium severity issue. -> + +> Agreed with the warden. And agree this is a Medium severity issue. +> > (aside: we are already planning to fix the period at 12s to mitigate this issue) **[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/258#issuecomment-1421613758):** - > Addressed here: https://github.com/reserve-protocol/protocol/pull/571 - -**Status:** Mitigation confirmed with comments. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/9), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/60), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/39). +> Addressed here: https://github.com/reserve-protocol/protocol/pull/571 +**Status:** Mitigation confirmed with comments. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/9), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/60), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/39). -*** +--- ## [[M-16] RToken permanently insolvent/unusable if a single collateral in the basket behaves unexpectedly](https://github.com/code-423n4/2023-01-reserve-findings/issues/254) -*Submitted by [0xdeadbeef0x](https://github.com/code-423n4/2023-01-reserve-findings/issues/254), also found by [\_\_141345\_\_](https://github.com/code-423n4/2023-01-reserve-findings/issues/345), [severity](https://github.com/code-423n4/2023-01-reserve-findings/issues/183), [severity](https://github.com/code-423n4/2023-01-reserve-findings/issues/133), [severity](https://github.com/code-423n4/2023-01-reserve-findings/issues/132), and [severity](https://github.com/code-423n4/2023-01-reserve-findings/issues/129)* + +_Submitted by [0xdeadbeef0x](https://github.com/code-423n4/2023-01-reserve-findings/issues/254), also found by [\_\_141345\_\_](https://github.com/code-423n4/2023-01-reserve-findings/issues/345), [severity](https://github.com/code-423n4/2023-01-reserve-findings/issues/183), [severity](https://github.com/code-423n4/2023-01-reserve-findings/issues/133), [severity](https://github.com/code-423n4/2023-01-reserve-findings/issues/132), and [severity](https://github.com/code-423n4/2023-01-reserve-findings/issues/129)_

@@ -2071,9 +2121,10 @@ This includes (and not limited to): The impacts become permanent as the unregistering of bad collateral assets is also dependent on collateral token behavior. Emphasis of funds lost:
-* A basket holds 2 collateral assets \[cAssetA, cAssetB] where cAssetA holds 1% of the RToken collateral and cAssetB holds 99%. -* cAssetA gets hacked and self-destructed. This means it will revert on any interaction with it. -* Even though 99% of funds still exists in cAssetB. They will be permanently locked and RToken will be unusable. + +- A basket holds 2 collateral assets \[cAssetA, cAssetB] where cAssetA holds 1% of the RToken collateral and cAssetB holds 99%. +- cAssetA gets hacked and self-destructed. This means it will revert on any interaction with it. +- Even though 99% of funds still exists in cAssetB. They will be permanently locked and RToken will be unusable. ### Proof of Concept @@ -2129,7 +2180,7 @@ As can seen above, `basketHandler.quantity(asset.erc20());` is called as part of } } -The asset is still registered so the `try` call will succeed and ` coll.refPerTok(); ` will be called. +The asset is still registered so the `try` call will succeed and `coll.refPerTok();` will be called. `refPerTok` function in `CTokenFiatCollateral` (which is used as an asset of `cUSDP`):
@@ -2174,25 +2225,25 @@ In our case, `CTokenFiatCollateral.refresh()` will revert therefore the call to `AssetRegistry.refresh()` is called in critical functions that will revert: 1. `_manageTokens` - used manage backing policy, handout excess assets and perform recollateralization -() + () 2. `refreshBucket` - used to switch the basket configuration -() + () 3. `issue` - used to issue RTokens to depositors -() + () 4. `vest` - used to vest issuance of an account -() + () 5. `redeem` - used to redeem collateral assets for RTokens -() + () 6. `poke` - in main, used as a refresher -() + () 7. `withdraw` in RSR, stakers will not be able to unstake -() + () ### Tools Used @@ -2205,23 +2256,24 @@ For plugins to function as intended there has to be a dependency on protocol spe In a case that the collateral token is corrupted, the governance should be able to replace to corrupted token. The unregistering flow should never be depended on the token functionality. **[0xean (judge) decreased severity to Medium and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/254#issuecomment-1400504728):** - > Downgrading to Medium and leaving open to sponsor review. There are externalities here that do no qualify the issue as High. + +> Downgrading to Medium and leaving open to sponsor review. There are externalities here that do no qualify the issue as High. **[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/254#issuecomment-1404418850):** - > Nice find! -**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** - > This PR makes the AssetRegistry more resilient to bad collateral during asset unregistration, and disables staking when frozen.
-> [reserve-protocol/protocol#623](https://github.com/reserve-protocol/protocol/pull/623) +> Nice find! -**Status:** Not fully mitigated. Full details in [report from AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73), and also included in Mitigation Review section below. +**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** +> This PR makes the AssetRegistry more resilient to bad collateral during asset unregistration, and disables staking when frozen.
> [reserve-protocol/protocol#623](https://github.com/reserve-protocol/protocol/pull/623) +**Status:** Not fully mitigated. Full details in [report from AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73), and also included in Mitigation Review section below. -*** +--- ## [[M-17] `refresh()` will revert on Oracle deprecation, effectively disabling part of the protocol](https://github.com/code-423n4/2023-01-reserve-findings/issues/234) -*Submitted by [0xA5DF](https://github.com/code-423n4/2023-01-reserve-findings/issues/234)* + +_Submitted by [0xA5DF](https://github.com/code-423n4/2023-01-reserve-findings/issues/234)_

@@ -2237,15 +2289,15 @@ However, an error with empty data isn't thrown only in case of out of gas, in th Any function that requires refreshing the assets will fail to execute (till the asset is replaced in the asset registry, passing the proposal via governance would usually take 7 days), that includes: -* Issuance -* Vesting -* Redemption -* Auctions (`manageTokens()`) -* `StRSR.withdraw()` +- Issuance +- Vesting +- Redemption +- Auctions (`manageTokens()`) +- `StRSR.withdraw()` ### Proof of Concept -The [docs](https://github.com/reserve-protocol/protocol/blob/d224c14c398d2727d39d133aa7511e1e6b161833/docs/recollateralization.md#:\~:text=If%20an%20asset%27s%20oracle%20goes%20offline%20forever%2C%20its%20lotPrice\(\)%20will%20eventually%20reach%20%5B0%2C%200%5D%20and%20the%20protocol%20will%20completely%20stop%20trading%20this%20asset.) imply in case of deprecation the protocol is expected continue to operate: +The [docs]() imply in case of deprecation the protocol is expected continue to operate: > If an asset's oracle goes offline forever, its lotPrice() will eventually reach \[0, 0] and the protocol will completely stop trading this asset. @@ -2274,14 +2326,10 @@ import { TestIRToken, USDCMock, } from '../../typechain' -import { - Collateral, - defaultFixture, -} from '../fixtures' +import { Collateral, defaultFixture } from '../fixtures' const createFixtureLoader = waffle.createFixtureLoader - describe('Assets contracts #fast', () => { // Tokens let rsr: ERC20Mock @@ -2373,31 +2421,29 @@ describe('Assets contracts #fast', () => { describe('Deployment', () => { it('Deployment should setup assets correctly', async () => { - - console.log(network.config.chainId); - // let validOracle = '0x443C5116CdF663Eb387e72C688D276e702135C87'; - let deprecatedOracle = '0x2E5B04aDC0A3b7dB5Fd34AE817c7D0993315A8a6'; - let priceTimeout_ = await aaveAsset.priceTimeout(), - chainlinkFeed_ = deprecatedOracle, - oracleError_ = await aaveAsset.oracleError(), - erc20_ = await aaveAsset.erc20(), - maxTradeVolume_ = await aaveAsset.maxTradeVolume(), - oracleTimeout_ = await aaveAsset.oracleTimeout(); - - aaveAsset = await AssetFactory.deploy(priceTimeout_, - chainlinkFeed_ , - oracleError_ , - erc20_ , - maxTradeVolume_ , - oracleTimeout_ ) as Asset; - - await aaveAsset.refresh(); - + console.log(network.config.chainId) + // let validOracle = '0x443C5116CdF663Eb387e72C688D276e702135C87'; + let deprecatedOracle = '0x2E5B04aDC0A3b7dB5Fd34AE817c7D0993315A8a6' + let priceTimeout_ = await aaveAsset.priceTimeout(), + chainlinkFeed_ = deprecatedOracle, + oracleError_ = await aaveAsset.oracleError(), + erc20_ = await aaveAsset.erc20(), + maxTradeVolume_ = await aaveAsset.maxTradeVolume(), + oracleTimeout_ = await aaveAsset.oracleTimeout() + + aaveAsset = (await AssetFactory.deploy( + priceTimeout_, + chainlinkFeed_, + oracleError_, + erc20_, + maxTradeVolume_, + oracleTimeout_ + )) as Asset + + await aaveAsset.refresh() }) }) - }) - ``` Modification of `hardhat.config.ts` to set it to the Polygon network: @@ -2410,7 +2456,7 @@ index f1886d25..53565799 100644 @@ -24,18 +24,19 @@ const TIMEOUT = useEnv('SLOW') ? 3_000_000 : 300_000 const src_dir = `./contracts/${useEnv('PROTO')}` const settings = useEnv('NO_OPT') ? {} : { optimizer: { enabled: true, runs: 200 } } - + +let recentBlockNumber = 38231040; +let jan6Block = 37731612; // causes 'missing trie node' error + @@ -2452,8 +2498,8 @@ Output: Notes: -* Chainlink list deprecating oracles only till deprecation, afterwards they're removed from the website. For this reason I wasn't able to trace deprecated oracles on the mainnet -* I was trying to prove this worked before deprecation, however, I kept getting the 'missing trie node' error when forking the older block. This isn't essential for the PoC so I decided to give up on it for now (writing this PoC was hard enough on its own). +- Chainlink list deprecating oracles only till deprecation, afterwards they're removed from the website. For this reason I wasn't able to trace deprecated oracles on the mainnet +- I was trying to prove this worked before deprecation, however, I kept getting the 'missing trie node' error when forking the older block. This isn't essential for the PoC so I decided to give up on it for now (writing this PoC was hard enough on its own). ### Recommended Mitigation Steps @@ -2462,14 +2508,14 @@ At `OracleLib.price()` catch the error and check if the error data is empty and Another approach might be to check in the `refresh()` function that the `tryPrice()` function didn't revert due to out of gas error by checking the gas before and after (in case of out of gas error only \~1/64 of the gas-before would be left). The advantage of this approach is that it would catch also other errors that might revert with empty data. **[tbrent (Reserve) acknowledged and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/234#issuecomment-1404422733):** - > I did not know this! Nice find. +> I did not know this! Nice find. - -*** +--- ## [[M-18] If name is changed then the domain separator would be wrong](https://github.com/code-423n4/2023-01-reserve-findings/issues/211) -*Submitted by [fs0c](https://github.com/code-423n4/2023-01-reserve-findings/issues/211)* + +_Submitted by [fs0c](https://github.com/code-423n4/2023-01-reserve-findings/issues/211)_
@@ -2480,8 +2526,9 @@ Now, governance can change this `name` anytime using the following function: ```solidity function setName(string calldata name_) external governance { - name = name_; - } + name = name_; +} + ``` After that call the domain separator would still be calculated using the old name, which shouldn’t be the case. @@ -2497,16 +2544,16 @@ While changing the name in setName function, update the domain separator. **[tbrent (Reserve) confirmed](https://github.com/code-423n4/2023-01-reserve-findings/issues/211)** **[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** - > This PR removes the ability to change StRSR token's name and symbol: [reserve-protocol/protocol#614](https://github.com/reserve-protocol/protocol/pull/614) - -**Status:** Mitigation confirmed. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/7), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/62), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/42). +> This PR removes the ability to change StRSR token's name and symbol: [reserve-protocol/protocol#614](https://github.com/reserve-protocol/protocol/pull/614) +**Status:** Mitigation confirmed. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/7), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/62), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/42). -*** +--- ## [[M-19] In case that `unstakingDelay` is decreased, users who have previously unstaked would have to wait more than `unstakingDelay` for new unstakes](https://github.com/code-423n4/2023-01-reserve-findings/issues/210) -*Submitted by [0xA5DF](https://github.com/code-423n4/2023-01-reserve-findings/issues/210), also found by [Soosh](https://github.com/code-423n4/2023-01-reserve-findings/issues/151)* + +_Submitted by [0xA5DF](https://github.com/code-423n4/2023-01-reserve-findings/issues/210), also found by [Soosh](https://github.com/code-423n4/2023-01-reserve-findings/issues/151)_ Users who wish to unstake their RSR from StRSR have to first unstake and then wait `unstakingDelay` till they can actually withdraw their stake. @@ -2518,12 +2565,12 @@ The issue is that when the `unstakingDelay` is decreased - users that have pendi The following PoC shows an example similar to above: -* Unstaking delay was 6 months -* Bob unstaked (create a draft) 1 wei of RSR -* Unstaking delay was changed to 2 weeks -* Both Bob and Alice unstake their remaining stake -* Alice can withdraw her stake after 2 weeks -* Bob has to wait 6 months in order to withdraw both that 1 wei and the remaining of the stake +- Unstaking delay was 6 months +- Bob unstaked (create a draft) 1 wei of RSR +- Unstaking delay was changed to 2 weeks +- Both Bob and Alice unstake their remaining stake +- Alice can withdraw her stake after 2 weeks +- Bob has to wait 6 months in order to withdraw both that 1 wei and the remaining of the stake ```diff diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts @@ -2533,27 +2580,27 @@ index f507cd50..3312686a 100644 @@ -599,6 +599,8 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { let amount2: BigNumber let amount3: BigNumber - + + let sixMonths = bn(60*60*24*30*6); + beforeEach(async () => { stkWithdrawalDelay = bn(await stRSR.unstakingDelay()).toNumber() - + @@ -608,18 +610,56 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { amount3 = bn('3e18') - + // Approve transfers - await rsr.connect(addr1).approve(stRSR.address, amount1) + await rsr.connect(addr1).approve(stRSR.address, amount1.add(1)) await rsr.connect(addr2).approve(stRSR.address, amount2.add(amount3)) - -+ + ++ // Stake - await stRSR.connect(addr1).stake(amount1) + await stRSR.connect(addr1).stake(amount1.add(1)) await stRSR.connect(addr2).stake(amount2) await stRSR.connect(addr2).stake(amount3) - + - // Unstake - Create withdrawal - await stRSR.connect(addr1).unstake(amount1) + // here @@ -2595,7 +2642,7 @@ index f507cd50..3312686a 100644 + + }) - + + return; // don't run further test it('Should revert withdraw if Main is paused', async () => { // Get current balance for user @@ -2605,7 +2652,7 @@ index f507cd50..3312686a 100644 }) }) + return; // don't run further test - + describe('Add RSR / Rewards', () => { const initialRate = fp('1') ``` @@ -2614,21 +2661,21 @@ index f507cd50..3312686a 100644 Allow users to use current delay even if it was previously higher. I think this should apply not only to new drafts but also for drafts that were created before the change. -Alternatively, the protocol can set a rule that even if the staking delay was lowered stakers have to wait at least the old delay since the change till they can withdraw. But in this case the rule should apply to everybody regardless if they have pending drafts or not. +Alternatively, the protocol can set a rule that even if the staking delay was lowered stakers have to wait at least the old delay since the change till they can withdraw. But in this case the rule should apply to everybody regardless if they have pending drafts or not. **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/210#issuecomment-1401184805):** - > Ultimately this feels like a design tradeoff. I do agree that the UX would be better if the most recent value was used, but it can cut both ways if the delay is increased. + +> Ultimately this feels like a design tradeoff. I do agree that the UX would be better if the most recent value was used, but it can cut both ways if the delay is increased. **[0xean (judge) decreased severity to Medium](https://github.com/code-423n4/2023-01-reserve-findings/issues/210)** **[pmckelvy1 (Reserve) acknowledged via duplicate issue `#151`](https://github.com/code-423n4/2023-01-reserve-findings/issues/151#issuecomment-1404435948)** - - -*** +--- ## [[M-20] Shortfall might be calculated incorrectly if a price value for one collateral isn't fetched correctly](https://github.com/code-423n4/2023-01-reserve-findings/issues/200) -*Submitted by [severity](https://github.com/code-423n4/2023-01-reserve-findings/issues/200)* + +_Submitted by [severity](https://github.com/code-423n4/2023-01-reserve-findings/issues/200)_ Function `price()` of an asset doesn't revert. It [returns values](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/plugins/assets/Asset.sol#L117) `(0, FIX_MAX)` for `low, high` values of price in case there's a problem with fetching it. Code that calls `price()` is able to validate returned values to detect that returned price is incorrect. @@ -2643,38 +2690,39 @@ Inside function `collateralShortfall()` of `RecollateralizationLibP1` [collatera Check that price is correctly fetched for a collateral. **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/200#issuecomment-1401173756):** - > Mitigation here is a little challenging to understand considering checking a price is hard on chain and hence the concern. -> -> I think this issue is a bit too general, but would like further comments. + +> Mitigation here is a little challenging to understand considering checking a price is hard on chain and hence the concern. +> +> I think this issue is a bit too general, but would like further comments. **[tbrent (Reserve) acknowledged and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/200#issuecomment-1404438772):** - > I think this issue is real, but it happens in a super-corner-case that I doubt the warden is thinking about. -> + +> I think this issue is real, but it happens in a super-corner-case that I doubt the warden is thinking about. +> > Some related statements: +> > - `prepareRecollateralizationTrade` checks that all collateral in the basket is SOUND before calling `collateralShortfall` > - From `docs/collateral.md`: "Should return `(0, FIX_MAX)` if pricing data is unavailable or stale." -> +> > `collateralShortfall` should never reach a collateral with `FIX_MAX` high price in the normal flow of things. -> +> > But, it is possible for one RToken system instance to have an `RTokenAsset` registered for a 2nd RToken. In this case, it could be that RToken 2 contains a collateral plugin that is now connected to a broken oracle, but RToken 2 may not have recognized this yet. When RToken 1 calls `RTokenAsset.price()`, it could end up reverting because of overflow in this line from `collateralShortfall`: -> +> > > shortfall = shortfall.plus(needed.minus(held).mul(priceHigh, CEIL)); -> -> -> So I think it's a real issue, and I would even leave it as Medium severity. +> +> So I think it's a real issue, and I would even leave it as Medium severity. **[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** - > This PR simplifies and improves the basket range formula. The new logic should provide much tighter basket range estimates and result in smaller haircuts.
-> [reserve-protocol/protocol#585](https://github.com/reserve-protocol/protocol/pull/585) - -**Status:** Mitigation confirmed with comments. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/21), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/63), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/77). +> This PR simplifies and improves the basket range formula. The new logic should provide much tighter basket range estimates and result in smaller haircuts.
> [reserve-protocol/protocol#585](https://github.com/reserve-protocol/protocol/pull/585) +**Status:** Mitigation confirmed with comments. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/21), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/63), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/77). -*** +--- ## [[M-21] Loss of staking yield for stakers when another user stakes in pause/frozen state](https://github.com/code-423n4/2023-01-reserve-findings/issues/148) -*Submitted by [Soosh](https://github.com/code-423n4/2023-01-reserve-findings/issues/148), also found by [\_\_141345\_\_](https://github.com/code-423n4/2023-01-reserve-findings/issues/328)* + +_Submitted by [Soosh](https://github.com/code-423n4/2023-01-reserve-findings/issues/148), also found by [\_\_141345\_\_](https://github.com/code-423n4/2023-01-reserve-findings/issues/328)_ It is possible for a user to steal the yield from other stakers by staking when the system is paused or frozen. @@ -2683,7 +2731,7 @@ This is because staking is allowed while paused/frozen, but `_payoutRewards()` i ```sol function stake(uint256 rsrAmount) external { require(rsrAmount > 0, "Cannot stake zero"); - + if (!main.pausedOrFrozen()) _payoutRewards(); ... } @@ -2694,30 +2742,30 @@ function stake(uint256 rsrAmount) external { A test case can be included in `ZZStRSR.test.ts` under 'Add RSR / Rewards': ```js - it('Audit: Loss of staking yield for stakers when another user stakes in pause/frozen state', async () => { - await rsr.connect(addr1).approve(stRSR.address, stake) - await stRSR.connect(addr1).stake(stake) +it('Audit: Loss of staking yield for stakers when another user stakes in pause/frozen state', async () => { + await rsr.connect(addr1).approve(stRSR.address, stake) + await stRSR.connect(addr1).stake(stake) - await advanceTime(Number(config.rewardPeriod) * 5) - await main.connect(owner).pause() + await advanceTime(Number(config.rewardPeriod) * 5) + await main.connect(owner).pause() - await rsr.connect(addr2).approve(stRSR.address, stake) - await stRSR.connect(addr2).stake(stake) + await rsr.connect(addr2).approve(stRSR.address, stake) + await stRSR.connect(addr2).stake(stake) - await main.connect(owner).unpause() + await main.connect(owner).unpause() - await stRSR.connect(addr1).unstake(stake) - await stRSR.connect(addr2).unstake(stake) - await advanceTime(Number(config.unstakingDelay) + 1) + await stRSR.connect(addr1).unstake(stake) + await stRSR.connect(addr2).unstake(stake) + await advanceTime(Number(config.unstakingDelay) + 1) - await stRSR.connect(addr1).withdraw(addr1.address, 1) - await stRSR.connect(addr2).withdraw(addr2.address, 1) - const addr1RSR = await rsr.balanceOf(addr1.address) - const addr2RSR = await rsr.balanceOf(addr2.address) - console.log(`addr1 RSR = ${addr1RSR}`) - console.log(`addr2 RSR = ${addr2RSR}`) - expect(Number(addr1RSR)).to.be.approximately(Number(addr2RSR), 10) - }) + await stRSR.connect(addr1).withdraw(addr1.address, 1) + await stRSR.connect(addr2).withdraw(addr2.address, 1) + const addr1RSR = await rsr.balanceOf(addr1.address) + const addr2RSR = await rsr.balanceOf(addr2.address) + console.log(`addr1 RSR = ${addr1RSR}`) + console.log(`addr2 RSR = ${addr2RSR}`) + expect(Number(addr1RSR)).to.be.approximately(Number(addr2RSR), 10) +}) ``` Note that `await advanceTime(Number(config.rewardPeriod) * 5)` can be before or after the pause, same result will occur. @@ -2769,17 +2817,18 @@ If the above logic is required, then I would suggest that `poke()` in `Main.sol` **[pmckelvy1 (Reserve) confirmed](https://github.com/code-423n4/2023-01-reserve-findings/issues/148#issuecomment-1406612686)** **[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** - > This PR makes the AssetRegistry more resilient to bad collateral during asset unregistration, and disables staking when frozen.
+ +> This PR makes the AssetRegistry more resilient to bad collateral during asset unregistration, and disables staking when frozen.
+> > [reserve-protocol/protocol#623](https://github.com/reserve-protocol/protocol/pull/623) **Status:** Mitigation confirmed. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/6), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/71), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/44). - - -*** +--- ## [[M-22] RecollateralizationLib: Dust loss for an asset should be capped at it's low value](https://github.com/code-423n4/2023-01-reserve-findings/issues/106) -*Submitted by [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/106)* + +_Submitted by [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/106)_ The `RecollateralizationLib.basketRange` function () internally calls the `RecollateralizationLib.totalAssetValue` function (). @@ -2869,48 +2918,50 @@ The fix first saves the `assetLow` value which should be saved to memory because --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -261,7 +261,8 @@ library RecollateralizationLibP1 { - + // Intentionally include value of IFFY/DISABLED collateral when low is nonzero // {UoA} = {UoA} + {UoA/tok} * {tok} - assetsLow += low.mul(bal, FLOOR); + uint192 assetLow = low.mul(bal,FLOOR); + assetsLow += assetLow; // += is same as Fix.plus - + // assetsHigh += high.mul(bal, CEIL), where assetsHigh is [0, FIX_MAX] @@ -272,7 +273,7 @@ library RecollateralizationLibP1 { // += is same as Fix.plus - + // Accumulate potential losses to dust - potentialDustLoss = potentialDustLoss.plus(rules.minTradeVolume); + potentialDustLoss = potentialDustLoss.plus(fixMin(rules.minTradeVolume, assetLow)); } - + // Account for all the places dust could get stuck **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/106#issuecomment-1401992675):** - > It would have been beneficial for the warden to use more realistic values for these trades with the full integer values to show how much of an actual impact this has when we are talking about tokens with 6 or more decimals. Will leave open for sponsor comment. + +> It would have been beneficial for the warden to use more realistic values for these trades with the full integer values to show how much of an actual impact this has when we are talking about tokens with 6 or more decimals. Will leave open for sponsor comment. **[pmckelvy1 (Reserve) disputed](https://github.com/code-423n4/2023-01-reserve-findings/issues/106#issuecomment-1406903842)** **[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/106#issuecomment-1406905121):** - > The balance of the asset before trading has nothing to do with how much value can potentially be lost when we try to _trade into_ that asset. + +> The balance of the asset before trading has nothing to do with how much value can potentially be lost when we try to _trade into_ that asset. **[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/106#issuecomment-1407107703):** - > On further thought, this is not really a good response. We have access to the UoA from the asset, and we could use that to potentially limit the contribution to `potentialDustLoss`. -**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** - > This PR simplifies and improves the basket range formula. The new logic should provide much tighter basket range estimates and result in smaller haircuts.
-> [reserve-protocol/protocol#585](https://github.com/reserve-protocol/protocol/pull/585) +> On further thought, this is not really a good response. We have access to the UoA from the asset, and we could use that to potentially limit the contribution to `potentialDustLoss`. -**Status:** Mitigation confirmed. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/5) and [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/64). +**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** +> This PR simplifies and improves the basket range formula. The new logic should provide much tighter basket range estimates and result in smaller haircuts.
> [reserve-protocol/protocol#585](https://github.com/reserve-protocol/protocol/pull/585) +**Status:** Mitigation confirmed. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/5) and [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/64). -*** +--- ## [[M-23] StRSR: seizeRSR function fails to update rsrRewardsAtLastPayout variable](https://github.com/code-423n4/2023-01-reserve-findings/issues/64) -*Submitted by [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/64)* + +_Submitted by [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/64)_

@@ -2987,16 +3038,16 @@ When `StRSR.seizeRSR` is called, the `rsrRewardsAtLastPayout` variable should be **[pmckelvy1 (Reserve) confirmed](https://github.com/code-423n4/2023-01-reserve-findings/issues/64#issuecomment-1406912384)** **[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/64#issuecomment-1421619875):** - > Addressed here: https://github.com/reserve-protocol/protocol/pull/584 - -**Status:** Mitigation confirmed with comments. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/65), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/4), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/45). +> Addressed here: https://github.com/reserve-protocol/protocol/pull/584 +**Status:** Mitigation confirmed with comments. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/65), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/4), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/45). -*** +--- ## [[M-24] BasketHandler: Users might not be able to redeem their rToken when protocol is paused due to refreshBasket function](https://github.com/code-423n4/2023-01-reserve-findings/issues/39) -*Submitted by [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/39)* + +_Submitted by [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/39)_

@@ -3055,7 +3106,7 @@ So the above `require` statement can be changed like this: --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -185,7 +185,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { - + require( main.hasRole(OWNER, _msgSender()) || - (status() == CollateralStatus.DISABLED && !main.pausedOrFrozen()), @@ -3071,24 +3122,26 @@ In other words only disallow it when the protocol is `frozen`. This however needs further consideration by the sponsor as it might negatively affect other aspects of the protocol that are beyond the scope of this report. **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/39#issuecomment-1401973403):** - > I believe this to be a design choice. Will leave open to sponsor review and most likely downgrade to QA. + +> I believe this to be a design choice. Will leave open to sponsor review and most likely downgrade to QA. **[pmckelvy1 (Reserve) acknowledged](https://github.com/code-423n4/2023-01-reserve-findings/issues/39#issuecomment-1406917946)** **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/39#issuecomment-1409516991):** - > The warden identified a state that was inconsistent with sponsors expectations since they acknowledged the issue, I believe this should be Medium as it does affect the availability of the protocol. -**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** - > This PR enables redemption while the basket is DISABLED: [reserve-protocol/protocol#575](https://github.com/reserve-protocol/protocol/pull/575) +> The warden identified a state that was inconsistent with sponsors expectations since they acknowledged the issue, I believe this should be Medium as it does affect the availability of the protocol. -**Status:** Mitigation confirmed. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/66), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/3), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/46). +**[tbrent (Reserve) mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#mitigations-to-be-reviewed):** +> This PR enables redemption while the basket is DISABLED: [reserve-protocol/protocol#575](https://github.com/reserve-protocol/protocol/pull/575) +**Status:** Mitigation confirmed. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/66), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/3), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/46). -*** +--- ## [[M-25] BackingManager: rTokens might not be redeemable when protocol is paused due to missing token allowance](https://github.com/code-423n4/2023-01-reserve-findings/issues/16) -*Submitted by [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/16), also found by [unforgiven](https://github.com/code-423n4/2023-01-reserve-findings/issues/424)* + +_Submitted by [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/16), also found by [unforgiven](https://github.com/code-423n4/2023-01-reserve-findings/issues/424)_
@@ -3146,91 +3199,97 @@ The `BackingManager.grantRTokenAllowance` function should use the `notFrozen` mo IERC20Upgradeable(address(erc20)).safeApprove(address(main.rToken()), 0); **[0xean (judge) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/16#issuecomment-1402019974):** - > I think the warden does identify a possible state of the system that could be problematic, albeit highly unlikely to be realized. Leaving open for sponsor review. + +> I think the warden does identify a possible state of the system that could be problematic, albeit highly unlikely to be realized. Leaving open for sponsor review. **[pmckelvy1 (Reserve) confirmed](https://github.com/code-423n4/2023-01-reserve-findings/issues/16#issuecomment-1404274024)** **[tbrent (Reserve) commented](https://github.com/code-423n4/2023-01-reserve-findings/issues/16#issuecomment-1421621092):** - > Addressed here: https://github.com/reserve-protocol/protocol/pull/584 - -**Status:** Mitigation confirmed. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/2), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/67), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/47). +> Addressed here: https://github.com/reserve-protocol/protocol/pull/584 +**Status:** Mitigation confirmed. Full details in reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/2), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/67), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/47). -*** +--- # Low Risk and Non-Critical Issues For this contest, 41 reports were submitted by wardens detailing low risk and non-critical issues. The [report highlighted below](https://github.com/code-423n4/2023-01-reserve-findings/issues/420) by **CodingNameKiki** received the top score from the judge. -*The following wardens also submitted reports: [brgltd](https://github.com/code-423n4/2023-01-reserve-findings/issues/490), [joestakey](https://github.com/code-423n4/2023-01-reserve-findings/issues/489), [Udsen](https://github.com/code-423n4/2023-01-reserve-findings/issues/477), [0xSmartContract](https://github.com/code-423n4/2023-01-reserve-findings/issues/471), [0xAgro](https://github.com/code-423n4/2023-01-reserve-findings/issues/466), [yongskiws](https://github.com/code-423n4/2023-01-reserve-findings/issues/462), [Aymen0909](https://github.com/code-423n4/2023-01-reserve-findings/issues/458), [Breeje](https://github.com/code-423n4/2023-01-reserve-findings/issues/456), [peanuts](https://github.com/code-423n4/2023-01-reserve-findings/issues/450), [0xNazgul](https://github.com/code-423n4/2023-01-reserve-findings/issues/443), [Cyfrin](https://github.com/code-423n4/2023-01-reserve-findings/issues/437), [lukris02](https://github.com/code-423n4/2023-01-reserve-findings/issues/429), [delfin454000](https://github.com/code-423n4/2023-01-reserve-findings/issues/417), [luxartvinsec](https://github.com/code-423n4/2023-01-reserve-findings/issues/400), [IllIllI](https://github.com/code-423n4/2023-01-reserve-findings/issues/392), [descharre](https://github.com/code-423n4/2023-01-reserve-findings/issues/359), [0xA5DF](https://github.com/code-423n4/2023-01-reserve-findings/issues/356), [GalloDaSballo](https://github.com/code-423n4/2023-01-reserve-findings/issues/350), [cryptonue](https://github.com/code-423n4/2023-01-reserve-findings/issues/342), [\_\_141345\_\_](https://github.com/code-423n4/2023-01-reserve-findings/issues/323), [hihen](https://github.com/code-423n4/2023-01-reserve-findings/issues/315), [pedr02b2](https://github.com/code-423n4/2023-01-reserve-findings/issues/306), [carlitox477](https://github.com/code-423n4/2023-01-reserve-findings/issues/286), [SaharDevep](https://github.com/code-423n4/2023-01-reserve-findings/issues/274), [shark](https://github.com/code-423n4/2023-01-reserve-findings/issues/257), [BRONZEDISC](https://github.com/code-423n4/2023-01-reserve-findings/issues/255), [chrisdior4](https://github.com/code-423n4/2023-01-reserve-findings/issues/226), [tnevler](https://github.com/code-423n4/2023-01-reserve-findings/issues/213), [ladboy233](https://github.com/code-423n4/2023-01-reserve-findings/issues/203), [rotcivegaf](https://github.com/code-423n4/2023-01-reserve-findings/issues/179), [MyFDsYours](https://github.com/code-423n4/2023-01-reserve-findings/issues/176), [IceBear](https://github.com/code-423n4/2023-01-reserve-findings/issues/165), [Bnke0x0](https://github.com/code-423n4/2023-01-reserve-findings/issues/118), [Soosh](https://github.com/code-423n4/2023-01-reserve-findings/issues/109), [btk](https://github.com/code-423n4/2023-01-reserve-findings/issues/102), [chaduke](https://github.com/code-423n4/2023-01-reserve-findings/issues/101), [RaymondFam](https://github.com/code-423n4/2023-01-reserve-findings/issues/90), [Ruhum](https://github.com/code-423n4/2023-01-reserve-findings/issues/81), [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/80), and [Sathish9098](https://github.com/code-423n4/2023-01-reserve-findings/issues/66).* +_The following wardens also submitted reports: [brgltd](https://github.com/code-423n4/2023-01-reserve-findings/issues/490), [joestakey](https://github.com/code-423n4/2023-01-reserve-findings/issues/489), [Udsen](https://github.com/code-423n4/2023-01-reserve-findings/issues/477), [0xSmartContract](https://github.com/code-423n4/2023-01-reserve-findings/issues/471), [0xAgro](https://github.com/code-423n4/2023-01-reserve-findings/issues/466), [yongskiws](https://github.com/code-423n4/2023-01-reserve-findings/issues/462), [Aymen0909](https://github.com/code-423n4/2023-01-reserve-findings/issues/458), [Breeje](https://github.com/code-423n4/2023-01-reserve-findings/issues/456), [peanuts](https://github.com/code-423n4/2023-01-reserve-findings/issues/450), [0xNazgul](https://github.com/code-423n4/2023-01-reserve-findings/issues/443), [Cyfrin](https://github.com/code-423n4/2023-01-reserve-findings/issues/437), [lukris02](https://github.com/code-423n4/2023-01-reserve-findings/issues/429), [delfin454000](https://github.com/code-423n4/2023-01-reserve-findings/issues/417), [luxartvinsec](https://github.com/code-423n4/2023-01-reserve-findings/issues/400), [IllIllI](https://github.com/code-423n4/2023-01-reserve-findings/issues/392), [descharre](https://github.com/code-423n4/2023-01-reserve-findings/issues/359), [0xA5DF](https://github.com/code-423n4/2023-01-reserve-findings/issues/356), [GalloDaSballo](https://github.com/code-423n4/2023-01-reserve-findings/issues/350), [cryptonue](https://github.com/code-423n4/2023-01-reserve-findings/issues/342), [\_\_141345\_\_](https://github.com/code-423n4/2023-01-reserve-findings/issues/323), [hihen](https://github.com/code-423n4/2023-01-reserve-findings/issues/315), [pedr02b2](https://github.com/code-423n4/2023-01-reserve-findings/issues/306), [carlitox477](https://github.com/code-423n4/2023-01-reserve-findings/issues/286), [SaharDevep](https://github.com/code-423n4/2023-01-reserve-findings/issues/274), [shark](https://github.com/code-423n4/2023-01-reserve-findings/issues/257), [BRONZEDISC](https://github.com/code-423n4/2023-01-reserve-findings/issues/255), [chrisdior4](https://github.com/code-423n4/2023-01-reserve-findings/issues/226), [tnevler](https://github.com/code-423n4/2023-01-reserve-findings/issues/213), [ladboy233](https://github.com/code-423n4/2023-01-reserve-findings/issues/203), [rotcivegaf](https://github.com/code-423n4/2023-01-reserve-findings/issues/179), [MyFDsYours](https://github.com/code-423n4/2023-01-reserve-findings/issues/176), [IceBear](https://github.com/code-423n4/2023-01-reserve-findings/issues/165), [Bnke0x0](https://github.com/code-423n4/2023-01-reserve-findings/issues/118), [Soosh](https://github.com/code-423n4/2023-01-reserve-findings/issues/109), [btk](https://github.com/code-423n4/2023-01-reserve-findings/issues/102), [chaduke](https://github.com/code-423n4/2023-01-reserve-findings/issues/101), [RaymondFam](https://github.com/code-423n4/2023-01-reserve-findings/issues/90), [Ruhum](https://github.com/code-423n4/2023-01-reserve-findings/issues/81), [HollaDieWaldfee](https://github.com/code-423n4/2023-01-reserve-findings/issues/80), and [Sathish9098](https://github.com/code-423n4/2023-01-reserve-findings/issues/66)._ ## Issues Overview -| Letter | Name | Description | -|:--:|:-------:|:-------:| -| L | Low risk | Potential risk | -| NC | Non-critical | Non risky findings | -| R | Refactor | Changing the code | -| O | Ordinary | Often found issues | -| Total Found Issues | 26 | -|:--:|:--:| +| Letter | Name | Description | +| :----: | :----------: | :----------------: | +| L | Low risk | Potential risk | +| NC | Non-critical | Non risky findings | +| R | Refactor | Changing the code | +| O | Ordinary | Often found issues | + +| Total Found Issues | 26 | +| :----------------: | :-: | ### Low Risk Issues -| Count | Explanation | Instances | -|:--:|:-------|:--:| -| [L-01] | Melt function should be only callable by the Furnance contract | 1 | -| [L-02] | Stake function shouldn't be accessible, when the status is paused or frozen | 1 | -| Total Low Risk Issues | 2 | -|:--:|:--:| +| Count | Explanation | Instances | +| :----: | :-------------------------------------------------------------------------- | :-------: | +| [L-01] | Melt function should be only callable by the Furnance contract | 1 | +| [L-02] | Stake function shouldn't be accessible, when the status is paused or frozen | 1 | + +| Total Low Risk Issues | 2 | +| :-------------------: | :-: | ### Non-Critical Issues -| Count | Explanation | Instances | -|:--:|:-------|:--:| -| [N-01] | Create your own import names instead of using the regular ones | 17 | -| [N-02] | Max value can't be applied in the setters | 9 | -| [N-03] | Using while for unbounded loops isn't recommended | 3 | -| [N-04] | Inconsistent visibility on the bool "disabled" | 2 | -| [N-05] | Modifier exists, but not used when needed | 6 | -| [N-06] | Unused constructor | 2 | -| [N-07] | Unnecessary check in both the \_mint and \_burn function | 2 | - -| Total Non-Critical Issues | 7 | -|:--:|:--:| + +| Count | Explanation | Instances | +| :----: | :------------------------------------------------------------- | :-------: | +| [N-01] | Create your own import names instead of using the regular ones | 17 | +| [N-02] | Max value can't be applied in the setters | 9 | +| [N-03] | Using while for unbounded loops isn't recommended | 3 | +| [N-04] | Inconsistent visibility on the bool "disabled" | 2 | +| [N-05] | Modifier exists, but not used when needed | 6 | +| [N-06] | Unused constructor | 2 | +| [N-07] | Unnecessary check in both the \_mint and \_burn function | 2 | + +| Total Non-Critical Issues | 7 | +| :-----------------------: | :-: | ### Refactor Issues -| Count | Explanation | Instances | -|:--:|:-------|:--:| -| [R-01] | Numeric values having to do with time should use time units for readability | 5 | -| [R-02] | Use require instead of assert | 9 | -| [R-03] | Unnecessary overflow check can be rafactored in a better way | 1 | -| [R-04] | If statement should check first, if the status is disabled | 1 | -| [R-05] | Some number values can be refactored with `_` | 2 | -| [R-06] | Revert should be used on some functions instead of return | 9 | -| [R-07] | Modifier can be applied on the function instead of creating require statement | 2 | -| [R-08] | Shorthand way to write if / else statement | 1 | -| [R-09] | Function should be deleted, if a modifier already exists doing its job | 1 | -| [R-10] | The right value should be used instead of downcasting from uint256 to uint192 | 2 | - -| Total Refactor Issues | 10 | -|:--:|:--:| + +| Count | Explanation | Instances | +| :----: | :---------------------------------------------------------------------------- | :-------: | +| [R-01] | Numeric values having to do with time should use time units for readability | 5 | +| [R-02] | Use require instead of assert | 9 | +| [R-03] | Unnecessary overflow check can be rafactored in a better way | 1 | +| [R-04] | If statement should check first, if the status is disabled | 1 | +| [R-05] | Some number values can be refactored with `_` | 2 | +| [R-06] | Revert should be used on some functions instead of return | 9 | +| [R-07] | Modifier can be applied on the function instead of creating require statement | 2 | +| [R-08] | Shorthand way to write if / else statement | 1 | +| [R-09] | Function should be deleted, if a modifier already exists doing its job | 1 | +| [R-10] | The right value should be used instead of downcasting from uint256 to uint192 | 2 | + +| Total Refactor Issues | 10 | +| :-------------------: | :-: | ### Ordinary Issues -| Count | Explanation | Instances | -|:--:|:-------|:--:| -| [O-01] | Code contains empty blocks | 3 | -| [O-02] | Use a more recent pragma version | 17 | -| [O-03] | Function Naming suggestions | 6 | -| [O-04] | Events is missing indexed fields | 2 | -| [O-05] | Proper use of get as a function name prefix | 12 | -| [O-06] | Commented out code | 3 | -| [O-07] | Value should be unchecked | 1 | - -| Total Ordinary Issues | 7 | -|:--:|:--:| + +| Count | Explanation | Instances | +| :----: | :------------------------------------------ | :-------: | +| [O-01] | Code contains empty blocks | 3 | +| [O-02] | Use a more recent pragma version | 17 | +| [O-03] | Function Naming suggestions | 6 | +| [O-04] | Events is missing indexed fields | 2 | +| [O-05] | Proper use of get as a function name prefix | 12 | +| [O-06] | Commented out code | 3 | +| [O-07] | Value should be unchecked | 1 | + +| Total Ordinary Issues | 7 | +| :-------------------: | :-: | ## [L-01] Melt function should be only callable by the Furnance contract + The function `melt` in RToken.sol is supposed to be called only by Furnace.sol, but as how it is right now the function can be called by anyone. This is problematic considering that this function burns tokens, if a user calls it by mistake. His tokens will be lost and he won't be able to get them back. ```solidity @@ -3255,6 +3314,7 @@ Consider applying a require statement in the function `melt` that the msg.sender ``` ## [L-02] Stake function shouldn't be accessible, when the status is paused or frozen + The function `stake` in StRSR.sol is used by users to stake a RSR amount on the corresponding RToken to earn yield and over-collateralize the system. If the contract is in paused or frozen status, some of the main functions `payoutRewards`, `unstake`, `withdraw` and `seizeRSR` can't be used. The `stake` function will keep operating but will skip to payoutRewards, this is problematic considering if the status is paused or frozen and a user stakes without knowing that. He won't be able to unstake or call any of the core functions, the only option he has is to wait for the status to be unpaused or unfrozen. Consider if a contract is in paused or frozen status to turn off all of the core functions including staking as well. @@ -3291,9 +3351,11 @@ contracts/p1/StRSR.sol ``` ## [N-01] Create your own import names instead of using the regular ones + For better readability, you should name the imports instead of using the regular ones. Example: + ```solidity 6: {IStRSRVotes} import "../interfaces/IStRSRVotes.sol"; ``` @@ -3301,6 +3363,7 @@ Example: Instances - All of the contracts. ## [N-02] Max value can't be applied in the setters + The function `setTradingDelay` is used by the governance to change the tradingDelay. However in the require statement applying the maximum delay is not allowed. @@ -3336,6 +3399,7 @@ contracts/p1/StRSR.sol ``` ## [N-03] Using while for unbounded loops isn't recommended + Don't write loops that are unbounded as this can hit the gas limit, causing your transaction to fail. For the reason above, while and do while loops are rarely used. @@ -3355,9 +3419,11 @@ contracts/p1/StRSRVotes.sol ``` ## [N-04] Inconsistent visibility on the bool "disabled" + In some contracts the visibility of the bool `disabled` is set as private, while on others it is set as public. Instances: + ```solidity contracts/p1/BasketHandler.sol @@ -3369,6 +3435,7 @@ contracts/p1/Broker.sol ``` ## [N-05] Modifier exists, but not used when needed + In the RToken contract, a lot of private calls are made to `requireNotPausedOrFrozen()` checking if it's paused or frozen. While there is already modifier used for this purpose in the contract. @@ -3408,6 +3475,7 @@ contracts/p1/RToken.sol ``` ## [N-06] Unused constructor + The constructor does nothing. ```solidity @@ -3421,6 +3489,7 @@ contracts/p1/mixins/Component.sol ``` ## [N-07] Unnecessary check in both the \_mint and \_burn function + The function `_mint` and burn in StRSR.sol is called only by someone calling the stake and unstake functions.
A check is made in the functions to ensure the account to mint and burn the amounts isn't address(0).
However this isn't possible as both the stake and unstake function input the address of the msg.sender.
@@ -3482,6 +3551,7 @@ contracts/p1/StRSR.sol ``` ## [R-01] Numeric values having to do with time should use time units for readability + Suffixes like seconds, minutes, hours, days and weeks after literal numbers can be used to specify units of time where seconds are the base unit and units are considered naively in the following way: `1 == 1 seconds`
@@ -3507,14 +3577,16 @@ contracts/p1/StRSR.sol 37: uint48 public constant MAX_UNSTAKING_DELAY = 31536000; // {s} 1 year 38: uint48 public constant MAX_REWARD_PERIOD = 31536000; // {s} 1 year -``` +``` + +## [R-02] Use require instead of assert -## [R-02] Use require instead of assert The Solidity assert() function is meant to assert invariants. Properly functioning code should never reach a failing assert statement. Instances: + ```solidity contracts/p1/mixins/RecollateralizationLib.sol @@ -3550,6 +3622,7 @@ Recommended: Consider whether the condition checked in the assert() is actually If not, replace the assert() statement with a require() statement. ## [R-03] Unnecessary overflow check can be rafactored in a better way + In the function `quantityMulPrice` an unchecked code is made, where the local variable `rawDelta` is calculated and after that an if statement is created, where is check if `rawDelta` overflows. This check won't be needed if we just move the variable above the unchecked block, so it will revert if this ever happens. ```solidity @@ -3571,10 +3644,11 @@ The instance above can be refactored to: // rawDelta is moved above the unchecked block and reverts if overflows 364: uint256 rawDelta = uint256(p) * qty; // {D36} = {D18} * {D18} -365: unchecked { +365: unchecked { ``` ## [R-04] If statement should check first, if the status is disabled + The if statement in the function `basketsHeldBy` check first if basket's length equals zero and then checks if the basket is invalid and disabled. Consider first checking if the staus is disabled and then if the length equals zero. ```solidity @@ -3592,6 +3666,7 @@ Refactor the instance above to: ``` ## [R-05] Some number values can be refactored with `_` + Consider using underscores for number values to improve readability. ```solidity @@ -3609,6 +3684,7 @@ The above instance can be refactored to: ``` ## [R-06] Revert should be used on some functions instead of return + Some instances just return without doing anything, consider applying revert statement instead with a descriptive string why it does that. ```solidity @@ -3638,6 +3714,7 @@ contracts/p1/StRSR.sol ``` ## [R-07] Modifier can be applied on the function instead of creating require statement + If functions are only allowed to be called by a certain individual, modifier should be used instead of checking with require statement, if the individual is the msg.sender calling the function. ```solidity @@ -3670,6 +3747,7 @@ contracts/p1/RToken.sol ``` ## [R-08] Shorthand way to write if / else statement + The normal if / else statement can be refactored in a shorthand way to write it: 1. Increases readability @@ -3711,6 +3789,7 @@ The above instance can be refactored to: ``` ## [R-09] Function should be deleted, if a modifier already exists doing its job + The function `requireNotPausedOrFrozen` is created only to hold the modifier notPausedOrFrozen.
And for this purpose in some functions requireNotPausedOrFrozen is called in order to check if its paused or frozen.
This function isn't necessary as the modifier notPausedOrFrozen can just be applied on the functions. @@ -3720,6 +3799,7 @@ contracts/p1/RToken.sol 838: function requireNotPausedOrFrozen() private notPausedOrFrozen {} ``` + ```solidity 520: function claimRewards() external { 521: requireNotPausedOrFrozen(); @@ -3736,6 +3816,7 @@ Consider removing `requireNotPausedOrFrozen();` and apply the modifier to the fu ``` ## [R-10] The right value should be used instead of downcasting from uint256 to uint192 + In the function `requireValidBUExchangeRate` local variables are used to calculate the outcome of low and high.
After that a require statement is made to ensure the BU rate is in range. The problem is that for the local variables uint256 is used and later in the require statement the value are downcasted to uint192. @@ -3766,6 +3847,7 @@ Consider changing the local variables to use uint192 in the first place, instead ``` ## [O-01] Code contains empty blocks + There are some empty blocks, which are unused. The code should do something or at least have a description why it is structured that way. ```solidity @@ -3775,6 +3857,7 @@ contracts/p1/Main.sol ``` Other instances: + ```solidity contracts/p1/RToken.sol @@ -3786,6 +3869,7 @@ contracts/p1/mixins/Component.sol ``` ## [O-02] Use a more recent pragma version + Old version of solidity is used, consider using the new one `0.8.17`. You can see what new versions offer regarding bug fixed [here](https://github.com/ethereum/solidity/blob/develop/Changelog.md) @@ -3793,11 +3877,13 @@ You can see what new versions offer regarding bug fixed [here](https://github.co Instances - All of the contracts. ## [O-03] Function Naming suggestions + Proper use of `_` as a function name prefix and a common pattern is to prefix internal and private function names with `_`. This pattern is correctly applied in the Party contracts, however there are some inconsistencies in the libraries. Instances: + ```solidity contracts/p1/BackingManager.sol @@ -3813,11 +3899,13 @@ contracts/p1/BasketHandler.sol ``` ## [O-04] Events is missing indexed fields + Index event fields make the field more quickly accessible to off-chain. Each event should use three indexed fields if there are three or more fields. Instances in: + ```solidity contracts/interfaces/IDistributor.sol @@ -3829,9 +3917,11 @@ contracts/interfaces/IRToken.sol ``` ## [O-05] Proper use of get as a function name prefix + Clear function names can increase readability. Follow a standard convertion function names such as using get for getter (view/pure) functions. Instances: + ```solidity contracts/p1/BasketHandler.sol @@ -3859,6 +3949,7 @@ contracts/p1/StRSR.sol ``` ## [O-06] Commented out code + Commented code in the protocol. Instances:
@@ -3866,8 +3957,8 @@ Instances:
[L457-L510](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/BasketHandler.sol#L457-L510)
[L339-L372](https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L339-L372)
- ## [O-07] Value should be unchecked + The function `_mint` is used to mint tokens to user's accounts. The storage variable `totalStakes` is an uint256 and there is a check before that preventing it from going overflow. @@ -3904,59 +3995,58 @@ Consider unchecking totalStakes as how it is done in the `_burn` function as wel 703: } ``` - - -*** +--- # Gas Optimizations For this contest, 35 reports were submitted by wardens detailing gas optimizations. The [report highlighted below](https://github.com/code-423n4/2023-01-reserve-findings/issues/391) by **IllIllI** received the top score from the judge. -*The following wardens also submitted reports: [Awesome](https://github.com/code-423n4/2023-01-reserve-findings/issues/493), [SAAJ](https://github.com/code-423n4/2023-01-reserve-findings/issues/485), [NoamYakov](https://github.com/code-423n4/2023-01-reserve-findings/issues/484), [0xA5DF](https://github.com/code-423n4/2023-01-reserve-findings/issues/476), [c3phas](https://github.com/code-423n4/2023-01-reserve-findings/issues/475), [0xSmartContract](https://github.com/code-423n4/2023-01-reserve-findings/issues/473), [Budaghyan](https://github.com/code-423n4/2023-01-reserve-findings/issues/472), [nadin](https://github.com/code-423n4/2023-01-reserve-findings/issues/453), [Aymen0909](https://github.com/code-423n4/2023-01-reserve-findings/issues/438), [delfin454000](https://github.com/code-423n4/2023-01-reserve-findings/issues/434), [Breeje](https://github.com/code-423n4/2023-01-reserve-findings/issues/433), [Cyfrin](https://github.com/code-423n4/2023-01-reserve-findings/issues/431), [ReyAdmirado](https://github.com/code-423n4/2023-01-reserve-findings/issues/423), [RHaO-sec](https://github.com/code-423n4/2023-01-reserve-findings/issues/410), [descharre](https://github.com/code-423n4/2023-01-reserve-findings/issues/355), [pavankv](https://github.com/code-423n4/2023-01-reserve-findings/issues/348), [AkshaySrivastav](https://github.com/code-423n4/2023-01-reserve-findings/issues/339), [\_\_141345\_\_](https://github.com/code-423n4/2023-01-reserve-findings/issues/322), [carlitox477](https://github.com/code-423n4/2023-01-reserve-findings/issues/294), [Rageur](https://github.com/code-423n4/2023-01-reserve-findings/issues/288), [SaharDevep](https://github.com/code-423n4/2023-01-reserve-findings/issues/273), [shark](https://github.com/code-423n4/2023-01-reserve-findings/issues/266), [Bauer](https://github.com/code-423n4/2023-01-reserve-findings/issues/263), [amshirif](https://github.com/code-423n4/2023-01-reserve-findings/issues/231), [Madalad](https://github.com/code-423n4/2023-01-reserve-findings/issues/180), [saneryee](https://github.com/code-423n4/2023-01-reserve-findings/issues/152), [RaymondFam](https://github.com/code-423n4/2023-01-reserve-findings/issues/87), [Rolezn](https://github.com/code-423n4/2023-01-reserve-findings/issues/78), [chaduke](https://github.com/code-423n4/2023-01-reserve-findings/issues/74), [Sathish9098](https://github.com/code-423n4/2023-01-reserve-findings/issues/65), [Bnke0x0](https://github.com/code-423n4/2023-01-reserve-findings/issues/38), [oyc\_109](https://github.com/code-423n4/2023-01-reserve-findings/issues/12), [arialblack14](https://github.com/code-423n4/2023-01-reserve-findings/issues/8), and [0xhacksmithh](https://github.com/code-423n4/2023-01-reserve-findings/issues/2).* +_The following wardens also submitted reports: [Awesome](https://github.com/code-423n4/2023-01-reserve-findings/issues/493), [SAAJ](https://github.com/code-423n4/2023-01-reserve-findings/issues/485), [NoamYakov](https://github.com/code-423n4/2023-01-reserve-findings/issues/484), [0xA5DF](https://github.com/code-423n4/2023-01-reserve-findings/issues/476), [c3phas](https://github.com/code-423n4/2023-01-reserve-findings/issues/475), [0xSmartContract](https://github.com/code-423n4/2023-01-reserve-findings/issues/473), [Budaghyan](https://github.com/code-423n4/2023-01-reserve-findings/issues/472), [nadin](https://github.com/code-423n4/2023-01-reserve-findings/issues/453), [Aymen0909](https://github.com/code-423n4/2023-01-reserve-findings/issues/438), [delfin454000](https://github.com/code-423n4/2023-01-reserve-findings/issues/434), [Breeje](https://github.com/code-423n4/2023-01-reserve-findings/issues/433), [Cyfrin](https://github.com/code-423n4/2023-01-reserve-findings/issues/431), [ReyAdmirado](https://github.com/code-423n4/2023-01-reserve-findings/issues/423), [RHaO-sec](https://github.com/code-423n4/2023-01-reserve-findings/issues/410), [descharre](https://github.com/code-423n4/2023-01-reserve-findings/issues/355), [pavankv](https://github.com/code-423n4/2023-01-reserve-findings/issues/348), [AkshaySrivastav](https://github.com/code-423n4/2023-01-reserve-findings/issues/339), [\_\_141345\_\_](https://github.com/code-423n4/2023-01-reserve-findings/issues/322), [carlitox477](https://github.com/code-423n4/2023-01-reserve-findings/issues/294), [Rageur](https://github.com/code-423n4/2023-01-reserve-findings/issues/288), [SaharDevep](https://github.com/code-423n4/2023-01-reserve-findings/issues/273), [shark](https://github.com/code-423n4/2023-01-reserve-findings/issues/266), [Bauer](https://github.com/code-423n4/2023-01-reserve-findings/issues/263), [amshirif](https://github.com/code-423n4/2023-01-reserve-findings/issues/231), [Madalad](https://github.com/code-423n4/2023-01-reserve-findings/issues/180), [saneryee](https://github.com/code-423n4/2023-01-reserve-findings/issues/152), [RaymondFam](https://github.com/code-423n4/2023-01-reserve-findings/issues/87), [Rolezn](https://github.com/code-423n4/2023-01-reserve-findings/issues/78), [chaduke](https://github.com/code-423n4/2023-01-reserve-findings/issues/74), [Sathish9098](https://github.com/code-423n4/2023-01-reserve-findings/issues/65), [Bnke0x0](https://github.com/code-423n4/2023-01-reserve-findings/issues/38), [oyc_109](https://github.com/code-423n4/2023-01-reserve-findings/issues/12), [arialblack14](https://github.com/code-423n4/2023-01-reserve-findings/issues/8), and [0xhacksmithh](https://github.com/code-423n4/2023-01-reserve-findings/issues/2)._ ## Summary -| |Issue|Instances|Total Gas Saved| -|-|:-|:-:|:-:| -| [G‑01] | Don't apply the same value to state variables | 1 | - | -| [G‑02] | Multiple `address`/ID mappings can be combined into a single `mapping` of an `address`/ID to a `struct`, where appropriate | 4 | - | -| [G‑03] | State variables only set in the constructor should be declared `immutable` | 6 | 12582 | -| [G‑04] | State variables can be packed into fewer storage slots | 1 | - | -| [G‑05] | Structs can be packed into fewer storage slots | 1 | - | -| [G‑06] | Using `calldata` instead of `memory` for read-only arguments in `external` functions saves gas | 8 | 960 | -| [G‑07] | Using `storage` instead of `memory` for structs/arrays saves gas | 1 | 4200 | -| [G‑08] | Avoid contract existence checks by using low level calls | 67 | 6700 | -| [G‑09] | State variables should be cached in stack variables rather than re-reading them from storage | 60 | 5820 | -| [G‑10] | Multiple accesses of a mapping/array should use a local variable cache | 1 | 42 | -| [G‑11] | The result of function calls should be cached rather than re-calling the function | 12 | - | -| [G‑12] | ` += ` costs more gas than ` = + ` for state variables | 10 | 1130 | -| [G‑13] | `internal` functions only called once can be inlined to save gas | 2 | 40 | -| [G‑14] | Add `unchecked {}` for subtractions where the operands cannot underflow because of a previous `require()` or `if`-statement | 6 | 510 | -| [G‑15] | `++i`/`i++` should be `unchecked{++i}`/`unchecked{i++}` when it is not possible for them to overflow, as is the case when used in `for`- and `while`-loops | 51 | 3060 | -| [G‑16] | `require()`/`revert()` strings longer than 32 bytes cost extra gas | 2 | - | -| [G‑17] | Optimize names to save gas | 49 | 1078 | -| [G‑18] | Use a more recent version of solidity | 7 | - | -| [G‑19] | Use a more recent version of solidity | 1 | - | -| [G‑20] | `>=` costs less gas than `>` | 3 | 9 | -| [G‑21] | `++i` costs less gas than `i++`, especially when it's used in `for`-loops (`--i`/`i--` too) | 1 | 5 | -| [G‑22] | Splitting `require()` statements that use `&&` saves gas | 15 | 45 | -| [G‑23] | Usage of `uints`/`ints` smaller than 32 bytes (256 bits) incurs overhead | 68 | - | -| [G‑24] | Using `private` rather than `public` for constants, saves gas | 11 | - | -| [G‑25] | `require()` or `revert()` statements that check input arguments should be at the top of the function | 2 | - | -| [G‑26] | Empty blocks should be removed or emit something | 3 | - | -| [G‑27] | Use custom errors rather than `revert()`/`require()` strings to save gas | 25 | - | -| [G‑28] | Functions guaranteed to revert when called by normal users can be marked `payable` | 2 | 42 | -| [G‑29] | Don't use `_msgSender()` if not supporting EIP-2771 | 35 | 560 | -| [G‑30] | `public` functions not called by the contract should be declared `external` instead | 2 | - | +| | Issue | Instances | Total Gas Saved | +| ------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------: | :-------------: | +| [G‑01] | Don't apply the same value to state variables | 1 | - | +| [G‑02] | Multiple `address`/ID mappings can be combined into a single `mapping` of an `address`/ID to a `struct`, where appropriate | 4 | - | +| [G‑03] | State variables only set in the constructor should be declared `immutable` | 6 | 12582 | +| [G‑04] | State variables can be packed into fewer storage slots | 1 | - | +| [G‑05] | Structs can be packed into fewer storage slots | 1 | - | +| [G‑06] | Using `calldata` instead of `memory` for read-only arguments in `external` functions saves gas | 8 | 960 | +| [G‑07] | Using `storage` instead of `memory` for structs/arrays saves gas | 1 | 4200 | +| [G‑08] | Avoid contract existence checks by using low level calls | 67 | 6700 | +| [G‑09] | State variables should be cached in stack variables rather than re-reading them from storage | 60 | 5820 | +| [G‑10] | Multiple accesses of a mapping/array should use a local variable cache | 1 | 42 | +| [G‑11] | The result of function calls should be cached rather than re-calling the function | 12 | - | +| [G‑12] | ` += ` costs more gas than ` = + ` for state variables | 10 | 1130 | +| [G‑13] | `internal` functions only called once can be inlined to save gas | 2 | 40 | +| [G‑14] | Add `unchecked {}` for subtractions where the operands cannot underflow because of a previous `require()` or `if`-statement | 6 | 510 | +| [G‑15] | `++i`/`i++` should be `unchecked{++i}`/`unchecked{i++}` when it is not possible for them to overflow, as is the case when used in `for`- and `while`-loops | 51 | 3060 | +| [G‑16] | `require()`/`revert()` strings longer than 32 bytes cost extra gas | 2 | - | +| [G‑17] | Optimize names to save gas | 49 | 1078 | +| [G‑18] | Use a more recent version of solidity | 7 | - | +| [G‑19] | Use a more recent version of solidity | 1 | - | +| [G‑20] | `>=` costs less gas than `>` | 3 | 9 | +| [G‑21] | `++i` costs less gas than `i++`, especially when it's used in `for`-loops (`--i`/`i--` too) | 1 | 5 | +| [G‑22] | Splitting `require()` statements that use `&&` saves gas | 15 | 45 | +| [G‑23] | Usage of `uints`/`ints` smaller than 32 bytes (256 bits) incurs overhead | 68 | - | +| [G‑24] | Using `private` rather than `public` for constants, saves gas | 11 | - | +| [G‑25] | `require()` or `revert()` statements that check input arguments should be at the top of the function | 2 | - | +| [G‑26] | Empty blocks should be removed or emit something | 3 | - | +| [G‑27] | Use custom errors rather than `revert()`/`require()` strings to save gas | 25 | - | +| [G‑28] | Functions guaranteed to revert when called by normal users can be marked `payable` | 2 | 42 | +| [G‑29] | Don't use `_msgSender()` if not supporting EIP-2771 | 35 | 560 | +| [G‑30] | `public` functions not called by the contract should be declared `external` instead | 2 | - | Total: 457 instances over 30 issues with **36783 gas** saved Gas totals use lower bounds of ranges and count two iterations of each `for`-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions. The table above as well as its gas numbers do not include any of the excluded findings. ## [G‑01] Don't apply the same value to state variables + If `_whenDefault` is already `NEVER`, it'll save 2100 gas to not set it to that value again -*There is 1 instance of this issue:* +_There is 1 instance of this issue:_ ```solidity File: /contracts/plugins/assets/FiatCollateral.sol @@ -3964,24 +4054,28 @@ File: /contracts/plugins/assets/FiatCollateral.sol 189: if (sum >= NEVER) _whenDefault = NEVER; ``` + https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/plugins/assets/FiatCollateral.sol#L189 ## [G‑02] Multiple `address`/ID mappings can be combined into a single `mapping` of an `address`/ID to a `struct`, where appropriate + Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (**20000 gas**) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save **~42 gas per access** due to [not having to recalculate the key's keccak256 hash](https://gist.github.com/IllIllI000/ec23a57daa30a8f8ca8b9681c8ccefb0) (Gkeccak256 - 30 gas) and that calculation's associated stack operations. -*There are 4 instances of this issue. (For in-depth details on this and all further gas optimizations with multiple instances, please see the warden's [full report](https://github.com/code-423n4/2023-01-reserve-findings/issues/391).)* +_There are 4 instances of this issue. (For in-depth details on this and all further gas optimizations with multiple instances, please see the warden's [full report](https://github.com/code-423n4/2023-01-reserve-findings/issues/391).)_ ## [G‑03] State variables only set in the constructor should be declared `immutable` -Avoids a Gsset (**20000 gas**) in the constructor, and replaces the first access in each transaction (Gcoldsload - **2100 gas**) and each access thereafter (Gwarmacces - **100 gas**) with a `PUSH32` (**3 gas**). + +Avoids a Gsset (**20000 gas**) in the constructor, and replaces the first access in each transaction (Gcoldsload - **2100 gas**) and each access thereafter (Gwarmacces - **100 gas**) with a `PUSH32` (**3 gas**). While `string`s are not value types, and therefore cannot be `immutable`/`constant` if not hard-coded outside of the constructor, the same behavior can be achieved by making the current contract `abstract` with `virtual` functions for the `string` accessors, and having a child contract override the functions with the hard-coded implementation-specific values. -*There are 6 instances of this issue.* +_There are 6 instances of this issue._ ## [G‑04] State variables can be packed into fewer storage slots + If variables occupying the same slot are both written the same function or by the constructor, avoids a separate Gsset (**20000 gas**). Reads of the variables can also be cheaper. -*There is 1 instance of this issue:* +_There is 1 instance of this issue:_ ```solidity File: contracts/p1/StRSR.sol @@ -3991,12 +4085,14 @@ File: contracts/p1/StRSR.sol 42: string public name; // mutable ``` + https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/StRSR.sol#L42 ## [G‑05] Structs can be packed into fewer storage slots + Each slot saved can avoid an extra Gsset (**20000 gas**) for the first setting of the struct. Subsequent reads as well as writes have smaller gas savings. -*There is 1 instance of this issue:* +_There is 1 instance of this issue:_ ```solidity File: contracts/interfaces/IGnosis.sol @@ -4021,21 +4117,24 @@ File: contracts/interfaces/IGnosis.sol 21: } ``` + https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/interfaces/IGnosis.sol#L6-L21 ## [G‑06] Using `calldata` instead of `memory` for read-only arguments in `external` functions saves gas -When a function with a `memory` array is called externally, the `abi.decode()` step has to use a for-loop to copy each index of the `calldata` to the `memory` index. **Each iteration of this for-loop costs at least 60 gas** (i.e. `60 * .length`). Using `calldata` directly, obliviates the need for such a loop in the contract code and runtime execution. Note that even if an interface defines a function as having `memory` arguments, it's still valid for implementation contracs to use `calldata` arguments instead. + +When a function with a `memory` array is called externally, the `abi.decode()` step has to use a for-loop to copy each index of the `calldata` to the `memory` index. **Each iteration of this for-loop costs at least 60 gas** (i.e. `60 * .length`). Using `calldata` directly, obliviates the need for such a loop in the contract code and runtime execution. Note that even if an interface defines a function as having `memory` arguments, it's still valid for implementation contracs to use `calldata` arguments instead. If the array is passed to an `internal` function which passes the array to another internal function where the array is modified and therefore `memory` is used in the `external` call, it's still more gass-efficient to use `calldata` when the `external` function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one. Note that I've also flagged instances where the function is `public` but can be marked as `external` since it's not called by the contract, and cases where a constructor is involved. -*There are 8 instances of this issue.* +_There are 8 instances of this issue._ ## [G‑07] Using `storage` instead of `memory` for structs/arrays saves gas -When fetching data from a storage location, assigning the data to a `memory` variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (**2100 gas**) for *each* field of the struct/array. If the fields are read from the new memory variable, they incur an additional `MLOAD` rather than a cheap stack read. Instead of declearing the variable with the `memory` keyword, declaring the variable with the `storage` keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a `memory` variable, is if the full struct/array is being returned by the function, is being passed to a function that requires `memory`, or if the array/struct is being read from another `memory` array/struct. -*There is 1 instance of this issue:* +When fetching data from a storage location, assigning the data to a `memory` variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (**2100 gas**) for _each_ field of the struct/array. If the fields are read from the new memory variable, they incur an additional `MLOAD` rather than a cheap stack read. Instead of declearing the variable with the `memory` keyword, declaring the variable with the `storage` keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a `memory` variable, is if the full struct/array is being returned by the function, is being passed to a function that requires `memory`, or if the array/struct is being read from another `memory` array/struct. + +_There is 1 instance of this issue:_ ```solidity File: contracts/p1/Distributor.sol @@ -4043,22 +4142,26 @@ File: contracts/p1/Distributor.sol 134: Transfer memory t = transfers[i]; ``` + https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Distributor.sol#L134 ## [G‑08] Avoid contract existence checks by using low level calls + Prior to 0.8.10 the compiler inserted extra code, including `EXTCODESIZE` (**100 gas**), to check for contract existence for external function calls. In more recent solidity versions, the compiler will not insert these checks if the external call has a return value. Similar behavior can be achieved in earlier versions by using low-level calls, since low level calls never check for contract existence. -*There are 67 instances of this issue.* +_There are 67 instances of this issue._ ## [G‑09] State variables should be cached in stack variables rather than re-reading them from storage + The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces each Gwarmaccess (**100 gas**) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses. -*There are 60 instances of this issue.* +_There are 60 instances of this issue._ ## [G‑10] Multiple accesses of a mapping/array should use a local variable cache + The instances below point to the second+ access of a value inside a mapping/array, within a function. Caching a mapping's value in a local `storage` or `calldata` variable when the value is accessed [multiple times](https://gist.github.com/IllIllI000/ec23a57daa30a8f8ca8b9681c8ccefb0), saves **~42 gas per access** due to not having to recalculate the key's keccak256 hash (Gkeccak256 - **30 gas**) and that calculation's associated stack operations. Caching an array's struct avoids recalculating the array offsets into memory/calldata. -*There is 1 instance of this issue:* +_There is 1 instance of this issue:_ ```solidity File: contracts/p1/RToken.sol @@ -4067,59 +4170,69 @@ File: contracts/p1/RToken.sol 635: return (issueQueues[account].left, issueQueues[account].right); ``` + https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/RToken.sol#L635 ## [G‑11] The result of function calls should be cached rather than re-calling the function + The instances below point to the second+ call of the function within a single function. -*There are 12 instances of this issue.* +_There are 12 instances of this issue._ ## [G‑12] ` += ` costs more gas than ` = + ` for state variables + Using the addition operator instead of plus-equals saves **[113 gas](https://gist.github.com/IllIllI000/cbbfb267425b898e5be734d4008d4fe8)**. -*There are 10 instances of this issue.* +_There are 10 instances of this issue._ ## [G‑13] `internal` functions only called once can be inlined to save gas + Not inlining costs **20 to 40 gas** because of two extra `JUMP` instructions and additional stack operations needed for function calls. -*There are 2 instances of this issue.* +_There are 2 instances of this issue._ ## [G‑14] Add `unchecked {}` for subtractions where the operands cannot underflow because of a previous `require()` or `if`-statement + `require(a <= b); x = b - a` => `require(a <= b); unchecked { x = b - a }` -*There are 6 instances of this issue.* +_There are 6 instances of this issue._ ## [G‑15] `++i`/`i++` should be `unchecked{++i}`/`unchecked{i++}` when it is not possible for them to overflow, as is the case when used in `for`- and `while`-loops + The `unchecked` keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves **30-40 gas [per loop](https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc#the-increment-in-for-loop-post-condition-can-be-made-unchecked)**. -*There are 51 instances of this issue.* +_There are 51 instances of this issue._ ## [G‑16] `require()`/`revert()` strings longer than 32 bytes cost extra gas + Each extra memory word of bytes past the original 32 [incurs an MSTORE](https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc#consider-having-short-revert-strings) which costs **3 gas**. -*There are 2 instances of this issue.* +_There are 2 instances of this issue._ ## [G‑17] Optimize names to save gas + `public`/`external` function names and `public` member variable names can be optimized to save gas. See [this](https://gist.github.com/IllIllI000/a5d8b486a8259f9f77891a919febd1a9) link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save **128 gas** each during deployment, and renaming functions to have lower method IDs will save **22 gas** per call, [per sorted position shifted](https://medium.com/joyso/solidity-how-does-function-name-affect-gas-consumption-in-smart-contract-47d270d8ac92). -*There are 49 instances of this issue.* +_There are 49 instances of this issue._ ## [G‑18] Use a more recent version of solidity + - Use a solidity version of at least 0.8.0 to get overflow protection without `SafeMath` - Use a solidity version of at least 0.8.2 to get simple compiler automatic inlining - Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads - Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than `revert()/require()` strings - Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value -*There are 7 instances of this issue.* +_There are 7 instances of this issue._ ## [G‑19] Use a more recent version of solidity + - Use a solidity version of at least 0.8.2 to get simple compiler automatic inlining - Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads - Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than `revert()/require()` strings - Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value -*There is 1 instance of this issue:* +_There is 1 instance of this issue:_ ```solidity File: contracts/plugins/aave/ReentrancyGuard.sol @@ -4127,17 +4240,20 @@ File: contracts/plugins/aave/ReentrancyGuard.sol 3: pragma solidity >=0.6.0 <0.8.0; ``` + https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/plugins/aave/ReentrancyGuard.sol#L3 ## [G‑20] `>=` costs less gas than `>` + The compiler uses opcodes `GT` and `ISZERO` for solidity code that uses `>`, but only requires `LT` for `>=`, [which saves **3 gas**](https://gist.github.com/IllIllI000/3dc79d25acccfa16dee4e83ffdc6ffde). -*There are 3 instances of this issue.* +_There are 3 instances of this issue._ ## [G‑21] `++i` costs less gas than `i++`, especially when it's used in `for`-loops (`--i`/`i--` too) + Saves **5 gas per loop**. -*There is 1 instance of this issue:* +_There is 1 instance of this issue:_ ```solidity File: contracts/p1/mixins/Trading.sol @@ -4145,92 +4261,107 @@ File: contracts/p1/mixins/Trading.sol 72: tradesOpen--; ``` + https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/mixins/Trading.sol#L72 ## [G‑22] Splitting `require()` statements that use `&&` saves gas + See [this issue](https://github.com/code-423n4/2022-01-xdefi-findings/issues/128) which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper by **3 gas**. -*There are 15 instances of this issue.* +_There are 15 instances of this issue._ ## [G‑23] Usage of `uints`/`ints` smaller than 32 bytes (256 bits) incurs overhead + > When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size. https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Each operation involving a `uint8` costs an extra [**22-28 gas**](https://gist.github.com/IllIllI000/9388d20c70f9a4632eb3ca7836f54977) (depending on whether the other operand is also a variable of type `uint8`) as compared to ones involving `uint256`, due to the compiler having to clear the higher bits of the memory word before operating on the `uint8`, as well as the associated stack operations of doing so. Use a larger size then downcast where needed. -*There are 68 instances of this issue.* +_There are 68 instances of this issue._ ## [G‑24] Using `private` rather than `public` for constants, saves gas + If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that [returns a tuple](https://github.com/code-423n4/2022-08-frax/blob/90f55a9ce4e25bceed3a74290b854341d8de6afa/src/contracts/FraxlendPair.sol#L156-L178) of the values of all currently-public constants. Saves **3406-3606 gas** in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table. -*There are 11 instances of this issue.* +_There are 11 instances of this issue._ ## [G‑25] `require()` or `revert()` statements that check input arguments should be at the top of the function -Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (**2100 gas***) in a function that may ultimately revert in the unhappy case. -*There are 2 instances of this issue.* +Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (**2100 gas\***) in a function that may ultimately revert in the unhappy case. + +_There are 2 instances of this issue._ + +## [G‑26] Empty blocks should be removed or emit something -## [G‑26] Empty blocks should be removed or emit something The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting. If the contract is meant to be extended, the contract should be `abstract` and the function signatures be added without any default implementation. If the block is an empty `if`-statement block to avoid doing subsequent checks in the else-if/else conditions, the else-if/else conditions should be nested under the negation of the if-statement, because they involve different classes of checks, which may lead to the introduction of errors when the code is later modified (`if(x){}else if(y){...}else{...}` => `if(!x){if(y){...}else{...}}`). Empty `receive()`/`fallback() payable` functions that are not used, can be removed to save deployment gas. -*There are 3 instances of this issue.* +_There are 3 instances of this issue._ ## [G‑27] Use custom errors rather than `revert()`/`require()` strings to save gas + Custom errors are available from solidity version 0.8.4. Custom errors save [**~50 gas**](https://gist.github.com/IllIllI000/ad1bd0d29a0101b25e57c293b4b0c746) each time they're hit by [avoiding having to allocate and store the revert string](https://blog.soliditylang.org/2021/04/21/custom-errors/#errors-in-depth). Not defining the strings also save deployment gas. -*There are 25 instances of this issue.* +_There are 25 instances of this issue._ ## [G‑28] Functions guaranteed to revert when called by normal users can be marked `payable` -If a function modifier such as `onlyOwner` is used, the function will revert if a normal user tries to pay the function. Marking the function as `payable` will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are + +If a function modifier such as `onlyOwner` is used, the function will revert if a normal user tries to pay the function. Marking the function as `payable` will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are `CALLVALUE`(2),`DUP1`(3),`ISZERO`(3),`PUSH2`(3),`JUMPI`(10),`PUSH1`(3),`DUP1`(3),`REVERT`(0),`JUMPDEST`(1),`POP`(2), which costs an average of about **21 gas per call** to the function, in addition to the extra deployment cost. -*There are 2 instances of this issue.* +_There are 2 instances of this issue._ ## [G‑29] Don't use `_msgSender()` if not supporting EIP-2771 + Use `msg.sender` if the code does not implement [EIP-2771 trusted forwarder](https://eips.ethereum.org/EIPS/eip-2771) support. -*There are 35 instances of this issue.* +_There are 35 instances of this issue._ ## [G‑30] `public` functions not called by the contract should be declared `external` instead + Contracts [are allowed](https://docs.soliditylang.org/en/latest/contracts.html#function-overriding) to override their parents' functions and change the visibility from `external` to `public` and [prior to solidity version 0.6.9](https://ethereum.stackexchange.com/a/107939) can save gas by doing so. -*There are 2 instances of this issue.* +_There are 2 instances of this issue._ ## Excluded findings + These findings are excluded from awards calculations because there are publicly-available automated tools that find them. The valid ones appear here for completeness. -| |Issue|Instances|Total Gas Saved| -|-|:-|:-:|:-:| -| [G‑31] | `.length` should not be looked up in every loop of a `for`-loop | 12 | 36 | -| [G‑32] | `require()`/`revert()` strings longer than 32 bytes cost extra gas | 18 | - | -| [G‑33] | Using `bool`s for storage incurs overhead | 4 | 68400 | -| [G‑34] | Using `> 0` costs more gas than `!= 0` when used on a `uint` in a `require()` statement | 11 | 66 | -| [G‑35] | `++i` costs less gas than `i++`, especially when it's used in `for`-loops (`--i`/`i--` too) | 11 | 55 | -| [G‑36] | Using `private` rather than `public` for constants, saves gas | 33 | - | -| [G‑37] | Division by two should use bit shifting | 8 | 160 | -| [G‑38] | Use custom errors rather than `revert()`/`require()` strings to save gas | 151 | - | -| [G‑39] | Functions guaranteed to revert when called by normal users can be marked `payable` | 12 | 252 | +| | Issue | Instances | Total Gas Saved | +| ------------- | :------------------------------------------------------------------------------------------ | :-------: | :-------------: | +| [G‑31] | `.length` should not be looked up in every loop of a `for`-loop | 12 | 36 | +| [G‑32] | `require()`/`revert()` strings longer than 32 bytes cost extra gas | 18 | - | +| [G‑33] | Using `bool`s for storage incurs overhead | 4 | 68400 | +| [G‑34] | Using `> 0` costs more gas than `!= 0` when used on a `uint` in a `require()` statement | 11 | 66 | +| [G‑35] | `++i` costs less gas than `i++`, especially when it's used in `for`-loops (`--i`/`i--` too) | 11 | 55 | +| [G‑36] | Using `private` rather than `public` for constants, saves gas | 33 | - | +| [G‑37] | Division by two should use bit shifting | 8 | 160 | +| [G‑38] | Use custom errors rather than `revert()`/`require()` strings to save gas | 151 | - | +| [G‑39] | Functions guaranteed to revert when called by normal users can be marked `payable` | 12 | 252 | Total: 260 instances over 9 issues with **68969 gas** saved Gas totals use lower bounds of ranges and count two iterations of each `for`-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions. ## [G‑31] `.length` should not be looked up in every loop of a `for`-loop + The overheads outlined below are _PER LOOP_, excluding the first loop -* storage arrays incur a Gwarmaccess (**100 gas**) -* memory arrays use `MLOAD` (**3 gas**) -* calldata arrays use `CALLDATALOAD` (**3 gas**) + +- storage arrays incur a Gwarmaccess (**100 gas**) +- memory arrays use `MLOAD` (**3 gas**) +- calldata arrays use `CALLDATALOAD` (**3 gas**) Caching the length changes each of these to a `DUP` (**3 gas**), and gets rid of the extra `DUP` needed to store the stack offset. -*There are 12 instances of this issue.* +_There are 12 instances of this issue._ ## [G‑32] `require()`/`revert()` strings longer than 32 bytes cost extra gas + Each extra memory word of bytes past the original 32 [incurs an MSTORE](https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc#consider-having-short-revert-strings) which costs **3 gas**. -*There are 18 instances of this issue.* +_There are 18 instances of this issue._ ## [G‑33] Using `bool`s for storage incurs overhead + ```solidity // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the @@ -4238,46 +4369,51 @@ Each extra memory word of bytes past the original 32 [incurs an MSTORE](https:// // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. ``` + https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27 Use `uint256(1)` and `uint256(2)` for true/false to avoid a Gwarmaccess (**[100 gas](https://gist.github.com/IllIllI000/1b70014db712f8572a72378321250058)**) for the extra SLOAD, and to avoid Gsset (**20000 gas**) when changing from `false` to `true`, after having been `true` in the past. -*There are 4 instances of this issue.* +_There are 4 instances of this issue._ ## [G‑34] Using `> 0` costs more gas than `!= 0` when used on a `uint` in a `require()` statement + This change saves **[6 gas](https://aws1.discourse-cdn.com/business6/uploads/zeppelin/original/2X/3/363a367d6d68851f27d2679d10706cd16d788b96.png)** per instance. The optimization works until solidity version [0.8.13](https://gist.github.com/IllIllI000/bf2c3120f24a69e489f12b3213c06c94) where there is a regression in gas costs. -*There are 11 instances of this issue.* +_There are 11 instances of this issue._ ## [G‑35] `++i` costs less gas than `i++`, especially when it's used in `for`-loops (`--i`/`i--` too) + Saves **5 gas per loop** -*There are 11 instances of this issue.* +_There are 11 instances of this issue._ ## [G‑36] Using `private` rather than `public` for constants, saves gas + If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that [returns a tuple](https://github.com/code-423n4/2022-08-frax/blob/90f55a9ce4e25bceed3a74290b854341d8de6afa/src/contracts/FraxlendPair.sol#L156-L178) of the values of all currently-public constants. Saves **3406-3606 gas** in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table. -*There are 33 instances of this issue.* +_There are 33 instances of this issue._ ## [G‑37] Division by two should use bit shifting + ` / 2` is the same as ` >> 1`. While the compiler uses the `SHR` opcode to accomplish both, the version that uses division incurs an overhead of [**20 gas**](https://gist.github.com/IllIllI000/ec0e4e6c4f52a6bca158f137a3afd4ff) due to `JUMP`s to and from a compiler utility function that introduces checks which can be avoided by using `unchecked {}` around the division by two. -*There are 8 instances of this issue.* +_There are 8 instances of this issue._ ## [G‑38] Use custom errors rather than `revert()`/`require()` strings to save gas + Custom errors are available from solidity version 0.8.4. Custom errors save [**~50 gas**](https://gist.github.com/IllIllI000/ad1bd0d29a0101b25e57c293b4b0c746) each time they're hit by [avoiding having to allocate and store the revert string](https://blog.soliditylang.org/2021/04/21/custom-errors/#errors-in-depth). Not defining the strings also save deployment gas. -*There are 151 instances of this issue.* +_There are 151 instances of this issue._ ## [G‑39] Functions guaranteed to revert when called by normal users can be marked `payable` -If a function modifier such as `onlyOwner` is used, the function will revert if a normal user tries to pay the function. Marking the function as `payable` will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are -`CALLVALUE`(2),`DUP1`(3),`ISZERO`(3),`PUSH2`(3),`JUMPI`(10),`PUSH1`(3),`DUP1`(3),`REVERT`(0),`JUMPDEST`(1),`POP`(2), which costs an average of about **21 gas per call** to the function, in addition to the extra deployment cost. - -*There are 12 instances of this issue.* +If a function modifier such as `onlyOwner` is used, the function will revert if a normal user tries to pay the function. Marking the function as `payable` will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are +`CALLVALUE`(2),`DUP1`(3),`ISZERO`(3),`PUSH2`(3),`JUMPI`(10),`PUSH1`(3),`DUP1`(3),`REVERT`(0),`JUMPDEST`(1),`POP`(2), which costs an average of about **21 gas per call** to the function, in addition to the extra deployment cost. +_There are 12 instances of this issue._ -*** +--- # Mitigation Review @@ -4290,123 +4426,124 @@ Following the C4 audit contest, 3 wardens (0xA5DF, HollaDieWaldfee, and [AkshayS **[Summary from the Sponsor](https://github.com/code-423n4/2023-02-reserve-mitigation-contest#overview-of-changes):** > The sponsors have made many, many changes, in response to the thoughtful feedback from the Wardens. In most cases changes were straightforward and of limited scope, but in at least two cases there were significant reductions or simplifications of large portions of the code. These areas are expanded upon below in their own sections. The 3rd section will cover everything else: -> +> > ### 1. Removal of non-atomic RToken issuance (M-13, M-15) -> +> > [PR #571: remove non-atomic issuance](https://github.com/reserve-protocol/protocol/pull/571) -> -> This audit, as in previous audits ([ToB](https://github.com/code-423n4/2023-01-reserve/blob/main/audits/Trail%20of%20Bits%20-%20Aug%2011%202022.pdf); [Solidified](https://github.com/code-423n4/2023-01-reserve/blob/main/audits/Solidified%20-%20Oct%2016%202022.pdf)) problems were found with the RToken issuance queue, a fussy cumulative data structure that exists to support constant-time `cancel()` and `vest()` operations for non-atomic issuance. This audit too, another issue was discovered with **M-13**. This prompted us to look for alternatives that achieve a similar purpose to the issuance queue, leading to removal of non-atomic issuance entirely and creation of the issuance throttle. The issuance throttle is at a low-level mechanistically similar to the redemption battery from before, except it is a _net hourly issuance_ measure. This addresses the problem of ingesting large amounts of bad collateral too quickly in a different way and with less frictions for users, both in terms of time and gas fees. -> -> As wardens will see, large portions of the `RToken` contract code have been removed. This also freed up contract bytecode real estate that allowed us to take libraries internal that were previously external. -> +> +> This audit, as in previous audits ([ToB](https://github.com/code-423n4/2023-01-reserve/blob/main/audits/Trail%20of%20Bits%20-%20Aug%2011%202022.pdf); [Solidified](https://github.com/code-423n4/2023-01-reserve/blob/main/audits/Solidified%20-%20Oct%2016%202022.pdf)) problems were found with the RToken issuance queue, a fussy cumulative data structure that exists to support constant-time `cancel()` and `vest()` operations for non-atomic issuance. This audit too, another issue was discovered with **M-13**. This prompted us to look for alternatives that achieve a similar purpose to the issuance queue, leading to removal of non-atomic issuance entirely and creation of the issuance throttle. The issuance throttle is at a low-level mechanistically similar to the redemption battery from before, except it is a _net hourly issuance_ measure. This addresses the problem of ingesting large amounts of bad collateral too quickly in a different way and with less frictions for users, both in terms of time and gas fees. +> +> As wardens will see, large portions of the `RToken` contract code have been removed. This also freed up contract bytecode real estate that allowed us to take libraries internal that were previously external. +> > **Context: Original purpose of issuance queue** -> -> The original purpose of the issuance queue was to prevent MEV searchers and other unspeakables from depositing large amounts of collateral right before the basket becomes IFFY and issuance is turned off. The overall IFFY -> DISABLED basket flow can be frontrun, and even though the depositer does not know yet whether a collateral token will default, acquiring a position in the queue acts like a valuable option that pays off if it does and has only opportunity cost otherwise. From the protocol's perspective, this kind of issuance just introduces bad debt. -> -> The new issunce throttle is purely atomic and serves the same purpose of limiting the loss due to bad debt directly prior to a collateral default. -> +> +> The original purpose of the issuance queue was to prevent MEV searchers and other unspeakables from depositing large amounts of collateral right before the basket becomes IFFY and issuance is turned off. The overall IFFY -> DISABLED basket flow can be frontrun, and even though the depositer does not know yet whether a collateral token will default, acquiring a position in the queue acts like a valuable option that pays off if it does and has only opportunity cost otherwise. From the protocol's perspective, this kind of issuance just introduces bad debt. +> +> The new issunce throttle is purely atomic and serves the same purpose of limiting the loss due to bad debt directly prior to a collateral default. +> > ### 2. Tightening of the basket range formula (H-02, M-20, M-22) -> +> > [PR #585: Narrow bu band](https://github.com/reserve-protocol/protocol/pull/585) -> +> > H-02 is the other highly consequential change, from a sheer quantity of SLOC point of view. Indeed, the calculation of the top and bottom of the basket range was highly inefficient and would generally result in larger haircuts than desirable. Below are two datapoints from tests that show the severity of a haircut after default in the absence of RSR overcollateralization: -> -> - **37.5%** loss + market-even trades +> +> - **37.5%** loss + market-even trades > - Before: **39.7%** haircut > - After: **37.52%** haircut > - **15%** loss + worst-case below market trades > - Before: **17.87%** haircut > - After: **16.38%** haircut -> -> The previous code was more complicated, more costly, and provided worse outcomes. In short this was because it didn't distinguish between capital that needed to be traded vs capital that did not. While the protocol cannot know ahead of time exactly how many BUs it will have after recollateralization, it can use the number of basket units currently held as a bedrock that it knows it will not need to trade, and thus do not differentially contribute to `basket.top` and `basket.bottom`. -> +> +> The previous code was more complicated, more costly, and provided worse outcomes. In short this was because it didn't distinguish between capital that needed to be traded vs capital that did not. While the protocol cannot know ahead of time exactly how many BUs it will have after recollateralization, it can use the number of basket units currently held as a bedrock that it knows it will not need to trade, and thus do not differentially contribute to `basket.top` and `basket.bottom`. +> > **Related issues** -> -> In addition to H-02 this PR also addressed M-20 and M-22, which are related to the calculation of the dust loss and potential overflow during the shortfall calculation. The calculation of the dust loss is now capped appropriately and the shortfall calculation has been eliminated. -> +> +> In addition to H-02 this PR also addressed M-20 and M-22, which are related to the calculation of the dust loss and potential overflow during the shortfall calculation. The calculation of the dust loss is now capped appropriately and the shortfall calculation has been eliminated. +> > ### 3. Everything else -> +> > The mitigations for the remaining issues were more narrow in scope. Most do not require further context or description. But there are 2 smaller clusters of changes worth calling out: -> +> > **Universal Revenue Hiding** -> +> > [PR #620: Universal revenue hiding](https://github.com/reserve-protocol/protocol/pull/620) -> -> As a warden pointed out in H-01, there are subtleties that can cause the compound v2 cToken rate to decrease, albeit by extremely little. Since we have dealt with Compund V2 for so long, and only just discovered this detail, we reason there are probably more like it. -> -> To this end we've implemented universal revenue hiding at the collateral plugin level, for all appreciating collateral. The idea is that even a small amount of revenue hiding such as 1-part-in-1-million may end up protecting the collateral plugin from unexpected default while being basically undetectable to humans. -> -> We mention this change because it can potentially impact other areas of the protocol, such as what prices trades are opened at, or how the basket range is calculated during recollateralization. A warden looking to examine this should focus their attention on `contracts/assets/AppreciatingFiatCollateral.sol`. -> +> +> As a warden pointed out in H-01, there are subtleties that can cause the compound v2 cToken rate to decrease, albeit by extremely little. Since we have dealt with Compund V2 for so long, and only just discovered this detail, we reason there are probably more like it. +> +> To this end we've implemented universal revenue hiding at the collateral plugin level, for all appreciating collateral. The idea is that even a small amount of revenue hiding such as 1-part-in-1-million may end up protecting the collateral plugin from unexpected default while being basically undetectable to humans. +> +> We mention this change because it can potentially impact other areas of the protocol, such as what prices trades are opened at, or how the basket range is calculated during recollateralization. A warden looking to examine this should focus their attention on `contracts/assets/AppreciatingFiatCollateral.sol`. +> > **Redemption while DISABLED** -> +> > [PR #575: support redemption while disabled](https://github.com/reserve-protocol/protocol/pull/575) -> +> > The final change area to bring attention to is the enabling of RToken redemption while the basket is DISABLED. The motivation for this change is not neatly captured in a single contest issue, though it was something discussed with wardens via DM, and which seems tangentially related to issues like M-03. -> -> Previous behavior: Cannot redeem while DISABLED. `BasketHandler.refreshBasket()` must be called before first redemption can occur, and even then, the redeemer must wait until trading finishes to receive full redemptions. -> -> Current behavior: Can redeem while DISABLED. Will get full share of collateral until `BasketHandler.refreshBasket()` is called. Can use `revertOnPartialRedemption` redemption param to control behavior along this boundary. -> -> We mention this change because functionality under different basket conditions is central to the functioning of our protocol. RToken redemption is how capital primarily exits the system, so any change to this area is fundamentally risky. -> +> +> Previous behavior: Cannot redeem while DISABLED. `BasketHandler.refreshBasket()` must be called before first redemption can occur, and even then, the redeemer must wait until trading finishes to receive full redemptions. +> +> Current behavior: Can redeem while DISABLED. Will get full share of collateral until `BasketHandler.refreshBasket()` is called. Can use `revertOnPartialRedemption` redemption param to control behavior along this boundary. +> +> We mention this change because functionality under different basket conditions is central to the functioning of our protocol. RToken redemption is how capital primarily exits the system, so any change to this area is fundamentally risky. +> > A related change is that `BasketHandler._switchBasket()` now skips over IFFY collateral. ## Mitigation Review Scope -| URL | Mitigation of | Purpose | -| ----------- | ------------- | ----------- | -| https://github.com/reserve-protocol/protocol/pull/571 | M-13, M-15 | This PR removes the non-atomic issuance mechanism and adds an issuance throttle. The redemption battery is rebranded to a redemption throttle. | -| https://github.com/reserve-protocol/protocol/pull/585 | H-02, M-20, M-22 | This PR simplifies and improves the basket range formula. The new logic should provide much tighter basket range estimates and result in smaller haircuts. | -| https://github.com/reserve-protocol/protocol/pull/584 | M-01, M-12, M-23, M-25 | This PR bundles mitigations for many small issues together. The full list is in the PR description. Each of these items are small and local in scope. | -| https://github.com/reserve-protocol/protocol/pull/575 | M-24 | This PR enables redemption while the basket is DISABLED. | -| https://github.com/reserve-protocol/protocol/pull/614 | M-18 | This PR removes the ability to change StRSR token's name and symbol. | -| https://github.com/reserve-protocol/protocol/pull/615 | M-03, M-04 | This PR allows an RToken redeemer to specify when they require full redemptions vs accept partial (prorata) redemptions. | -| https://github.com/reserve-protocol/protocol/pull/617 | M-02 | This PR prevents paying out StRSR rewards until the StRSR supply is at least 1e18. | -| https://github.com/reserve-protocol/protocol/pull/619 | M-05 | This PR prevents melting RToken until the RToken supply is at least 1e18. | -| https://github.com/reserve-protocol/protocol/pull/620 | H-01 | This PR adds universal revenue hiding to all appreciating collateral. | -| https://github.com/reserve-protocol/protocol/pull/622 | M-11 | This PR adds a Furnace.melt()/StRSR.payoutRewards() step when governance changes the rewardRatio. | -| https://github.com/reserve-protocol/protocol/pull/623 | M-16, M-21 | This PR makes the AssetRegistry more resilient to bad collateral during asset unregistration, and disables staking when frozen. | -| https://github.com/reserve-protocol/protocol/pull/628 | M-10 | This PR makes all dangerous uint192 downcasts truncation-safe. | - -*Note from the sponsor: we want to emphasize this is **not** the complete list of changes between the original [df7eca commit](https://github.com/reserve-protocol/protocol/commit/df7ecadc2bae74244ace5e8b39e94bc992903158) and the mitigation review [27a347 commit](https://github.com/reserve-protocol/protocol/commit/27a3472d553b4fa54f896596007765ec91941348). While it is the **vast majority** of the changes, we urge wardens to check out the diff between the two commits for themselves, as changes may have been made due to addressing gas/QA findings.* +| URL | Mitigation of | Purpose | +| ----------------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| https://github.com/reserve-protocol/protocol/pull/571 | M-13, M-15 | This PR removes the non-atomic issuance mechanism and adds an issuance throttle. The redemption battery is rebranded to a redemption throttle. | +| https://github.com/reserve-protocol/protocol/pull/585 | H-02, M-20, M-22 | This PR simplifies and improves the basket range formula. The new logic should provide much tighter basket range estimates and result in smaller haircuts. | +| https://github.com/reserve-protocol/protocol/pull/584 | M-01, M-12, M-23, M-25 | This PR bundles mitigations for many small issues together. The full list is in the PR description. Each of these items are small and local in scope. | +| https://github.com/reserve-protocol/protocol/pull/575 | M-24 | This PR enables redemption while the basket is DISABLED. | +| https://github.com/reserve-protocol/protocol/pull/614 | M-18 | This PR removes the ability to change StRSR token's name and symbol. | +| https://github.com/reserve-protocol/protocol/pull/615 | M-03, M-04 | This PR allows an RToken redeemer to specify when they require full redemptions vs accept partial (prorata) redemptions. | +| https://github.com/reserve-protocol/protocol/pull/617 | M-02 | This PR prevents paying out StRSR rewards until the StRSR supply is at least 1e18. | +| https://github.com/reserve-protocol/protocol/pull/619 | M-05 | This PR prevents melting RToken until the RToken supply is at least 1e18. | +| https://github.com/reserve-protocol/protocol/pull/620 | H-01 | This PR adds universal revenue hiding to all appreciating collateral. | +| https://github.com/reserve-protocol/protocol/pull/622 | M-11 | This PR adds a Furnace.melt()/StRSR.payoutRewards() step when governance changes the rewardRatio. | +| https://github.com/reserve-protocol/protocol/pull/623 | M-16, M-21 | This PR makes the AssetRegistry more resilient to bad collateral during asset unregistration, and disables staking when frozen. | +| https://github.com/reserve-protocol/protocol/pull/628 | M-10 | This PR makes all dangerous uint192 downcasts truncation-safe. | + +_Note from the sponsor: we want to emphasize this is **not** the complete list of changes between the original [df7eca commit](https://github.com/reserve-protocol/protocol/commit/df7ecadc2bae74244ace5e8b39e94bc992903158) and the mitigation review [27a347 commit](https://github.com/reserve-protocol/protocol/commit/27a3472d553b4fa54f896596007765ec91941348). While it is the **vast majority** of the changes, we urge wardens to check out the diff between the two commits for themselves, as changes may have been made due to addressing gas/QA findings._ ## Mitigation Review Summary -| Original Issue | Status | Full Details | -| ----------- | ------------- | ----------- | -| [H-01](https://github.com/code-423n4/2023-01-reserve-findings/issues/310) | Mitigation confirmed w/ comments | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/35), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/23), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/25) | -| [H-02](https://github.com/code-423n4/2023-01-reserve-findings/issues/235) | Not fully mitigated | Report from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/49), and also shared below | -| [M-01](https://github.com/code-423n4/2023-01-reserve-findings/issues/452) | Mitigation confirmed | Reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/50), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/16), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/26) | -| [M-02](https://github.com/code-423n4/2023-01-reserve-findings/issues/439) | Mitigation confirmed w/ comments | Reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/51), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/20), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/27) | -| [M-03](https://github.com/code-423n4/2023-01-reserve-findings/issues/416) | This is a duplicate, see M-04 for status | [Comment from judge](https://github.com/code-423n4/2023-01-reserve-findings/issues/416#issuecomment-1428777177) | -| [M-04](https://github.com/code-423n4/2023-01-reserve-findings/issues/399) | Not fully mitigated | Reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/54), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/14), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/69), and also shared below | -| [M-05](https://github.com/code-423n4/2023-01-reserve-findings/issues/384) | Not fully mitigated | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/13) and 0xA5DF ([here](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/70) and [here](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/55)), and also shared below | -| [M-06](https://github.com/code-423n4/2023-01-reserve-findings/issues/377) | Per judge: invalid | [Comment from judge](https://github.com/code-423n4/2023-01-reserve-findings/issues/377#issuecomment-1428771851) | -| [M-07](https://github.com/code-423n4/2023-01-reserve-findings/issues/347) | Confirmed by sponsor | - | -| [M-08](https://github.com/code-423n4/2023-01-reserve-findings/issues/326) | Acknowledged by sponsor | - | -| [M-09](https://github.com/code-423n4/2023-01-reserve-findings/issues/325) | Per judge: invalid | [Comment from judge](https://github.com/code-423n4/2023-01-reserve-findings/issues/325#issuecomment-1428774156) | -| [M-10](https://github.com/code-423n4/2023-01-reserve-findings/issues/320) | Mitigation confirmed | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/19), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/56), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/33) | -| [M-11](https://github.com/code-423n4/2023-01-reserve-findings/issues/287) | Mitigation confirmed w/ comments | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/18), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/57), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/34) | -| [M-12](https://github.com/code-423n4/2023-01-reserve-findings/issues/276) | Mitigation confirmed | Reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/58), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/11), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/36) | -| [M-13](https://github.com/code-423n4/2023-01-reserve-findings/issues/267) | Mitigation confirmed w/ comments | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/10), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/59), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/37) | -| [M-14](https://github.com/code-423n4/2023-01-reserve-findings/issues/265) | Acknowledged by sponsor | - | -| [M-15](https://github.com/code-423n4/2023-01-reserve-findings/issues/258) | Mitigation confirmed w/ comments | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/9), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/60), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/39) | -| [M-16](https://github.com/code-423n4/2023-01-reserve-findings/issues/254) | Not fully mitigated | Report from [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73), and also shared below | -| [M-17](https://github.com/code-423n4/2023-01-reserve-findings/issues/234) | Acknowledged by sponsor | - | -| [M-18](https://github.com/code-423n4/2023-01-reserve-findings/issues/211) | Mitigation confirmed | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/7), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/62), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/42) | -| [M-19](https://github.com/code-423n4/2023-01-reserve-findings/issues/210) | Acknowledged by sponsor | - | -| [M-20](https://github.com/code-423n4/2023-01-reserve-findings/issues/200) | Mitigation confirmed w/ comments | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/21), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/63), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/77) | -| [M-21](https://github.com/code-423n4/2023-01-reserve-findings/issues/148) | Mitigation confirmed | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/6), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/71), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/44) | -| [M-22](https://github.com/code-423n4/2023-01-reserve-findings/issues/106) | Mitigation confirmed | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/5) and [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/64) | -| [M-23](https://github.com/code-423n4/2023-01-reserve-findings/issues/64) | Mitigation confirmed w/ comments | Reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/65), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/4), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/45) | -| [M-24](https://github.com/code-423n4/2023-01-reserve-findings/issues/39) | Mitigation confirmed | Reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/66), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/3), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/46) | -| [M-25](https://github.com/code-423n4/2023-01-reserve-findings/issues/16) | Mitigation confirmed | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/2), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/67), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/47) | +| Original Issue | Status | Full Details | +| ------------------------------------------------------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [H-01](https://github.com/code-423n4/2023-01-reserve-findings/issues/310) | Mitigation confirmed w/ comments | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/35), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/23), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/25) | +| [H-02](https://github.com/code-423n4/2023-01-reserve-findings/issues/235) | Not fully mitigated | Report from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/49), and also shared below | +| [M-01](https://github.com/code-423n4/2023-01-reserve-findings/issues/452) | Mitigation confirmed | Reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/50), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/16), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/26) | +| [M-02](https://github.com/code-423n4/2023-01-reserve-findings/issues/439) | Mitigation confirmed w/ comments | Reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/51), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/20), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/27) | +| [M-03](https://github.com/code-423n4/2023-01-reserve-findings/issues/416) | This is a duplicate, see M-04 for status | [Comment from judge](https://github.com/code-423n4/2023-01-reserve-findings/issues/416#issuecomment-1428777177) | +| [M-04](https://github.com/code-423n4/2023-01-reserve-findings/issues/399) | Not fully mitigated | Reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/54), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/14), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/69), and also shared below | +| [M-05](https://github.com/code-423n4/2023-01-reserve-findings/issues/384) | Not fully mitigated | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/13) and 0xA5DF ([here](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/70) and [here](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/55)), and also shared below | +| [M-06](https://github.com/code-423n4/2023-01-reserve-findings/issues/377) | Per judge: invalid | [Comment from judge](https://github.com/code-423n4/2023-01-reserve-findings/issues/377#issuecomment-1428771851) | +| [M-07](https://github.com/code-423n4/2023-01-reserve-findings/issues/347) | Confirmed by sponsor | - | +| [M-08](https://github.com/code-423n4/2023-01-reserve-findings/issues/326) | Acknowledged by sponsor | - | +| [M-09](https://github.com/code-423n4/2023-01-reserve-findings/issues/325) | Per judge: invalid | [Comment from judge](https://github.com/code-423n4/2023-01-reserve-findings/issues/325#issuecomment-1428774156) | +| [M-10](https://github.com/code-423n4/2023-01-reserve-findings/issues/320) | Mitigation confirmed | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/19), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/56), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/33) | +| [M-11](https://github.com/code-423n4/2023-01-reserve-findings/issues/287) | Mitigation confirmed w/ comments | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/18), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/57), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/34) | +| [M-12](https://github.com/code-423n4/2023-01-reserve-findings/issues/276) | Mitigation confirmed | Reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/58), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/11), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/36) | +| [M-13](https://github.com/code-423n4/2023-01-reserve-findings/issues/267) | Mitigation confirmed w/ comments | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/10), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/59), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/37) | +| [M-14](https://github.com/code-423n4/2023-01-reserve-findings/issues/265) | Acknowledged by sponsor | - | +| [M-15](https://github.com/code-423n4/2023-01-reserve-findings/issues/258) | Mitigation confirmed w/ comments | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/9), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/60), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/39) | +| [M-16](https://github.com/code-423n4/2023-01-reserve-findings/issues/254) | Not fully mitigated | Report from [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73), and also shared below | +| [M-17](https://github.com/code-423n4/2023-01-reserve-findings/issues/234) | Acknowledged by sponsor | - | +| [M-18](https://github.com/code-423n4/2023-01-reserve-findings/issues/211) | Mitigation confirmed | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/7), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/62), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/42) | +| [M-19](https://github.com/code-423n4/2023-01-reserve-findings/issues/210) | Acknowledged by sponsor | - | +| [M-20](https://github.com/code-423n4/2023-01-reserve-findings/issues/200) | Mitigation confirmed w/ comments | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/21), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/63), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/77) | +| [M-21](https://github.com/code-423n4/2023-01-reserve-findings/issues/148) | Mitigation confirmed | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/6), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/71), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/44) | +| [M-22](https://github.com/code-423n4/2023-01-reserve-findings/issues/106) | Mitigation confirmed | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/5) and [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/64) | +| [M-23](https://github.com/code-423n4/2023-01-reserve-findings/issues/64) | Mitigation confirmed w/ comments | Reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/65), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/4), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/45) | +| [M-24](https://github.com/code-423n4/2023-01-reserve-findings/issues/39) | Mitigation confirmed | Reports from [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/66), [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/3), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/46) | +| [M-25](https://github.com/code-423n4/2023-01-reserve-findings/issues/16) | Mitigation confirmed | Reports from [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/2), [0xA5DF](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/67), and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/47) | **There were also 3 new medium severity issues surfaced by the wardens. See below for details regarding the new issues as well as issues that were not fully mitigated.**
## [Mitigation of H-02: Issue not fully mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/49) -*Submitted by 0xA5DF* + +_Submitted by 0xA5DF_ ### Original Issue @@ -4417,82 +4554,91 @@ H-02: [Basket range formula is inefficient, leading the protocol to unnecessary https://github.com/reserve-protocol/protocol/blob/610cfca553beea41b9508abbfbf4ee4ce16cbc12/contracts/p1/mixins/RecollateralizationLib.sol#L146-L245 ### Not mitigated - top range can still be too high, leading to unnecessary haircut -* The applied mitigation follows the line of the mitigation suggested (disclosure: by me :)) in the original issue, however after reviewing it I found out that it doesn't fully mitigate the issue. -* The original issue was that basket range band is too wide, with both top range being too high and bottom range too low -* The bottom range is mitigated now -* As for the top range - even though it's more efficient now, it still can result in a top range that doesn't make sense. + +- The applied mitigation follows the line of the mitigation suggested (disclosure: by me :)) in the original issue, however after reviewing it I found out that it doesn't fully mitigate the issue. +- The original issue was that basket range band is too wide, with both top range being too high and bottom range too low +- The bottom range is mitigated now +- As for the top range - even though it's more efficient now, it still can result in a top range that doesn't make sense. ### Impact + Protocol might go for an unnecessary haircut, causing a loss for RToken holders. In the scenario below we can trade to get ~99% of baskets needed, but instead the protocol goes for a 50% haircut. After the haircut the baskets held per supply ratio might grow back via `handoutExcessAssets` and `Furnace` however: -* Not all excess asset goes to `Furnace` -* `Furnace` grows slowly over time and in the meantime - * Redemption would be at the lower baskets per supply - * New users can issue in the meanwhile, diluting the melting effect + +- Not all excess asset goes to `Furnace` +- `Furnace` grows slowly over time and in the meantime + - Redemption would be at the lower baskets per supply + - New users can issue in the meanwhile, diluting the melting effect In more extreme cases the baskets held can be an extremely low number that might even cause the haircut to fail due to `exchangeRateIsValidAfter` modifier on `setBasketsNeeded()`. This would mean trading would be disabled till somebody sends enough balance to the undercollateralized asset. ### Proof of Concept Consider the following scenario: -* A basket is composed of 30 USDc and 1 ETH -* The prices are: - * 1 USDc = 1 USD - * ETH = 1500 USD -* Therefore the total basket value is 1515 USD -* Protocol holds 1000 baskets -* Governance changes the USDC quantity to 30 USDC -* Baskets held now is only 500, since we hold only 15K USDC -* Bottom range would be `basketsHeld + (excess_ETH * ETH_lowPrice / basket_highPrice) = 500 + (1500 * 500 * 0.99 / (1530 * 1.01)) = 980` -* Top range would be `basketsHeld + (excess_ETH * ETH_highPrice / basket_lowPrice) = 500 + (1500 * 500 * 1.01 / (1530 * 0.99)) = 1000` -* This is clearly a wrong estimation, which would lead to a haircut of 50% (!) rather than going for a trade. + +- A basket is composed of 30 USDc and 1 ETH +- The prices are: + - 1 USDc = 1 USD + - ETH = 1500 USD +- Therefore the total basket value is 1515 USD +- Protocol holds 1000 baskets +- Governance changes the USDC quantity to 30 USDC +- Baskets held now is only 500, since we hold only 15K USDC +- Bottom range would be `basketsHeld + (excess_ETH * ETH_lowPrice / basket_highPrice) = 500 + (1500 * 500 * 0.99 / (1530 * 1.01)) = 980` +- Top range would be `basketsHeld + (excess_ETH * ETH_highPrice / basket_lowPrice) = 500 + (1500 * 500 * 1.01 / (1530 * 0.99)) = 1000` +- This is clearly a wrong estimation, which would lead to a haircut of 50% (!) rather than going for a trade. Note: I mentioned governance change for simplicity, but this can also happen without governance intervention when a collateral gets disabled, it's value declines and a backup asset kicks in (at first the disabled asset would get traded and cover up some of the deficit and then we'd go for a haircut) ### Mitigation -* A more efficient formula would be to use the max baskets held (i.e. the maximum of (each collateral balance divided by the basket_quantity of that collateral)) and then subtract from that the lowest estimation of baskets missing (i.e. lowest value estimation of needed assets to reach that amount divided by highest estimation of basket value). - * In the case above that would mean `maxBasketsHeld - (USDC_deficit * USDC_lowPrice / basket_highPrice) = 1000 - (500 * 30 * 0.99 / (1530 * 1.01)) = 990.4`. Freeing up 9.6 ETH for sale -* The suggested formula might get us a higher top range estimation when the case is the other way around (the collateral that makes the larger part of the basket value is missing, in our case ETH is missing and USDC not), but it wouldn't result in a haircut and still go for trading (since the top range would be closer to baskets held) +- A more efficient formula would be to use the max baskets held (i.e. the maximum of (each collateral balance divided by the basket_quantity of that collateral)) and then subtract from that the lowest estimation of baskets missing (i.e. lowest value estimation of needed assets to reach that amount divided by highest estimation of basket value). + - In the case above that would mean `maxBasketsHeld - (USDC_deficit * USDC_lowPrice / basket_highPrice) = 1000 - (500 * 30 * 0.99 / (1530 * 1.01)) = 990.4`. Freeing up 9.6 ETH for sale +- The suggested formula might get us a higher top range estimation when the case is the other way around (the collateral that makes the larger part of the basket value is missing, in our case ETH is missing and USDC not), but it wouldn't result in a haircut and still go for trading (since the top range would be closer to baskets held) Even with the mitigation above there can be extreme cases where a single asset holds a very small fraction of the total basket value (e.g. 0.1%) and the mitigation wouldn't help much in this case. There might be a need to come up with a broader mitigation for the issue that haircut is done to the number of baskets held rather than bottom range even though the difference between the two can be significant. Or set a threshold for the fraction of the value that each collateral holds in the total value of the basket. **[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/49#issuecomment-1435296142):** - > Confirming, but I believe this issue can only arise when the basket unit is increased in `{UoA}` terms. Can someone confirm this? Does the issue exist when a basket unit is simply allocated differently, as opposed to being increased in size? + +> Confirming, but I believe this issue can only arise when the basket unit is increased in `{UoA}` terms. Can someone confirm this? Does the issue exist when a basket unit is simply allocated differently, as opposed to being increased in size? **[0xA5DF (warden) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/49#issuecomment-1435768983):** - > > Does the issue exist when a basket unit is simply allocated differently -> + +> > Does the issue exist when a basket unit is simply allocated differently +> > Usually when it's only allocated differently the asset which was decreased in quantity would be used to cover up for the asset that was increased (since top range is capped to baskets needed, the decreased asset would have a surplus). However there can be a scenario under difference in allocation: -> * Asset A (worth 1515 USD) was switched to mostly asset B (1 ETH as above) and some of asset C (quantity increased from 15 USDC to 30 USDC) -> * All of asset A was traded for asset B first (since asset B is missing more in value + oracle error caused a 1% difference in price) -> * We're now facing the same issue as above - we've got 100% of basket B -> +> +> - Asset A (worth 1515 USD) was switched to mostly asset B (1 ETH as above) and some of asset C (quantity increased from 15 USDC to 30 USDC) +> - All of asset A was traded for asset B first (since asset B is missing more in value + oracle error caused a 1% difference in price) +> - We're now facing the same issue as above - we've got 100% of basket B +> > Plus, as mentioned above this can also happen when a collateral gets disabled, went down in value and was switched to backup collateral **[tbrent (Reserve) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/49#issuecomment-1435769665):** - > @0xA5DF shouldn't the `range.top` mitigation algo include a (positive) contribution from assets that are not in the basket? That is: -> -> if `quantity() > 0`: then we subtract out `(asset_deficit * asset_lowPrice / basket_highPrice)`
+ +> @0xA5DF shouldn't the `range.top` mitigation algo include a (positive) contribution from assets that are not in the basket? That is: +> +> if `quantity() > 0`: then we subtract out `(asset_deficit * asset_lowPrice / basket_highPrice)`
> else: then we add in `(asset_balance * asset_highPrice / basket_lowPrice)` **[0xA5DF (warden) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/49#issuecomment-1435771369):** - > Yeah, assets that not in the baskets or in the basket and more than needed. -> + +> Yeah, assets that not in the baskets or in the basket and more than needed. +> > As I mentioned in some edge cases the issue still might persist. Maybe as part of the mitigation we should also don't do a haircut to an amount that's significantly lower than the bottom range.
> E.g. if the baskets held is 50% (of needed baskets) and bottom range is 98% we should do a haircut only down to 95% (and hopefully in the next round there are more chances we'll be able to trade). **[tbrent (Reserve) linked to a mitigation PR](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/49#ref-pullrequest-1590520362):** - > [reserve-protocol/protocol#650](https://github.com/reserve-protocol/protocol/pull/650) - +> [reserve-protocol/protocol#650](https://github.com/reserve-protocol/protocol/pull/650) -*** +--- ## [Mitigation of M-04: Issue not fully mitigated](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/54) -*Submitted by 0xA5DF, also found by [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/14) and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/69)* + +_Submitted by 0xA5DF, also found by [HollaDieWaldfee](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/14) and [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/69)_ ### Original Issue @@ -4508,34 +4654,36 @@ User might be agreeing to a partial redemption expecting to lose only a small fr **Details** -* Issue was that user might get only partial redemption when they didn't intend to - they sent a tx to the pool, in the meanwhile an asset got disabled and replaced. Redeemer doesn't get any share of the disabled collateral, and the backup collateral balance is zero. -* Mitigation adds a parameter named `revertOnPartialRedemption`, if the parameter is false the redeeming would revert if any collateral holds only part of the asset. -* This is suppose to solve this issue since in the case above the user would set it to false and the redeeming tx would revert -* The issue is that there might be a case where the protocol holds only a bit less than the quantity required (e.g. 99%), and in that case the user would be setting `revertOnPartialRedemption` to true, expecting to get 99% of the value of the basket. Then if an asset is disabled and replaced the user would suffer a loss much greater than they've agreed to. +- Issue was that user might get only partial redemption when they didn't intend to - they sent a tx to the pool, in the meanwhile an asset got disabled and replaced. Redeemer doesn't get any share of the disabled collateral, and the backup collateral balance is zero. +- Mitigation adds a parameter named `revertOnPartialRedemption`, if the parameter is false the redeeming would revert if any collateral holds only part of the asset. +- This is suppose to solve this issue since in the case above the user would set it to false and the redeeming tx would revert +- The issue is that there might be a case where the protocol holds only a bit less than the quantity required (e.g. 99%), and in that case the user would be setting `revertOnPartialRedemption` to true, expecting to get 99% of the value of the basket. Then if an asset is disabled and replaced the user would suffer a loss much greater than they've agreed to. ### Proof of Concept **Likelihood** Mostly the protocol wouldn't be undercollateralized for a long time, since there would either by trading going on to cover it or eventually there would be a haircut. But there can still be periods of time where this happens: -* Governance increased the basket quantity of one asset a bit (expecting the yield to cover for it), trading won't start till `tradingDelay` passes. Meaning a few hours where only partial redemption would be possible. -* Another asset got disabled first, and replaced by a backup asset. The protocol either had enough balance of the backup asset or covered up for it via trading. Yet again, this won't last long since eventually all trading would complete and the protocol would go to a haircut, but there can be multiple trading of multiple assets which would make it last up to a few hours. + +- Governance increased the basket quantity of one asset a bit (expecting the yield to cover for it), trading won't start till `tradingDelay` passes. Meaning a few hours where only partial redemption would be possible. +- Another asset got disabled first, and replaced by a backup asset. The protocol either had enough balance of the backup asset or covered up for it via trading. Yet again, this won't last long since eventually all trading would complete and the protocol would go to a haircut, but there can be multiple trading of multiple assets which would make it last up to a few hours. ### Mitigation + The ideal solution would be to allow the user to specify the min amount for each asset or the min ratio of the between the total redemption value and the basket value, but that would be too expensive and complicated. I think the middle way here would be to replace `revertOnPartialRedemption` parameter with a single numeric parameter that specifies the min ratio that the user expects to get (i.e. if that parameter is set to 90%, that means that if any asset holds less than 90% than the quantity it should the redemption would revert). This shouldn't cost much more gas, and would cover most of the cases. **[HollaDieWaldfee (warden) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/54#issuecomment-1435260233):** - > Agreed - -**[tbrent (Reserve) confirmed](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/54#issuecomment-1435343609)** +> Agreed +**[tbrent (Reserve) confirmed](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/54#issuecomment-1435343609)** -*** +--- ## [Early attacker can DOS rToken issuance](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/13) -*Submitted by HollaDieWaldfee, also found by 0xA5DF ([here](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/70) and [here](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/55))* + +_Submitted by HollaDieWaldfee, also found by 0xA5DF ([here](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/70) and [here](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/55))_ Note: related to mitigation for [M-05](https://github.com/code-423n4/2023-01-reserve-findings/issues/384) @@ -4545,81 +4693,89 @@ https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765 https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/RToken.sol#L132 ### Impact -An early attacker can DOS the `issue` functionality in the `RToken` contract. -No issuances can be made. And the DOS cannot be recovered from. It is permanent. +An early attacker can DOS the `issue` functionality in the `RToken` contract. + +No issuances can be made. And the DOS cannot be recovered from. It is permanent. ### Proof of Concept -You can add the following test to the `Furnace.test.ts` file and execute it with `yarn hardhat test --grep 'M-05 Mitigation Error: DOS issue'`. + +You can add the following test to the `Furnace.test.ts` file and execute it with `yarn hardhat test --grep 'M-05 Mitigation Error: DOS issue'`. ```typescript describe('M-05 Mitigation Error', () => { - beforeEach(async () => { - // Approvals for issuance - await token0.connect(addr1).approve(rToken.address, initialBal) - await token1.connect(addr1).approve(rToken.address, initialBal) - await token2.connect(addr1).approve(rToken.address, initialBal) - await token3.connect(addr1).approve(rToken.address, initialBal) + beforeEach(async () => { + // Approvals for issuance + await token0.connect(addr1).approve(rToken.address, initialBal) + await token1.connect(addr1).approve(rToken.address, initialBal) + await token2.connect(addr1).approve(rToken.address, initialBal) + await token3.connect(addr1).approve(rToken.address, initialBal) + + await token0.connect(addr2).approve(rToken.address, initialBal) + await token1.connect(addr2).approve(rToken.address, initialBal) + await token2.connect(addr2).approve(rToken.address, initialBal) + await token3.connect(addr2).approve(rToken.address, initialBal) + + // Issue tokens + const issueAmount: BigNumber = bn('100e18') + // await rToken.connect(addr1).issue(issueAmount) + // await rToken.connect(addr2).issue(issueAmount) + }) - await token0.connect(addr2).approve(rToken.address, initialBal) - await token1.connect(addr2).approve(rToken.address, initialBal) - await token2.connect(addr2).approve(rToken.address, initialBal) - await token3.connect(addr2).approve(rToken.address, initialBal) - - // Issue tokens - const issueAmount: BigNumber = bn('100e18') - // await rToken.connect(addr1).issue(issueAmount) - // await rToken.connect(addr2).issue(issueAmount) - }) + it('M-05 Mitigation Error: DOS issue', async () => { + /* attack vector actually so bad that attacker can block issuance a loooong time? + */ + console.log('Total supply') + console.log(await rToken.totalSupply()) - it('M-05 Mitigation Error: DOS issue', async () => { - /* attack vector actually so bad that attacker can block issuance a loooong time? - */ - console.log("Total supply"); - console.log(await rToken.totalSupply()); + const issueAmount: BigNumber = bn('1e17') + await rToken.connect(addr1).issue(issueAmount) - const issueAmount: BigNumber = bn('1e17') - await rToken.connect(addr1).issue(issueAmount) + console.log('Total supply') + console.log(await rToken.totalSupply()) - console.log("Total supply"); - console.log(await rToken.totalSupply()); + const transferAmount: BigNumber = bn('1e16') + rToken.connect(addr1).transfer(furnace.address, transferAmount) - const transferAmount: BigNumber = bn('1e16') - rToken.connect(addr1).transfer(furnace.address, transferAmount); + await advanceTime(3600) - await advanceTime(3600); + await furnace.connect(addr1).melt() - await furnace.connect(addr1).melt() - - await advanceTime(3600); + await advanceTime(3600) - console.log("rToken balance of furnace"); - console.log(await rToken.balanceOf(furnace.address)); + console.log('rToken balance of furnace') + console.log(await rToken.balanceOf(furnace.address)) - /* rToken can not be issued - */ + /* rToken can not be issued + */ - await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith('rToken supply too low to melt') + await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith( + 'rToken supply too low to melt' + ) - console.log("rToken balance of furnace"); - console.log(await rToken.balanceOf(furnace.address)); + console.log('rToken balance of furnace') + console.log(await rToken.balanceOf(furnace.address)) - /* rToken can not be issued even after time passes - */ + /* rToken can not be issued even after time passes + */ - await advanceTime(3600); + await advanceTime(3600) - await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith('rToken supply too low to melt') + await expect(rToken.connect(addr1).issue(issueAmount)).to.be.revertedWith( + 'rToken supply too low to melt' + ) - /* rToken.melt cannot be called directly either - */ + /* rToken.melt cannot be called directly either + */ - await expect(rToken.connect(addr1).melt(transferAmount)).to.be.revertedWith('rToken supply too low to melt') - }) + await expect(rToken.connect(addr1).melt(transferAmount)).to.be.revertedWith( + 'rToken supply too low to melt' + ) }) +}) ``` -The attack performs the following steps: +The attack performs the following steps: 1. Issue `1e17` rToken 2. Transfer `1e16` rToken to the furnace @@ -4628,10 +4784,12 @@ The attack performs the following steps: 5. Now `RToken.issue` and `RToken.melt` are permanently DOSed ### Tools Used + VSCode ### Recommended Mitigation Steps -Use a try-catch block for `furnace.melt` in the `RToken.issueTo` function. + +Use a try-catch block for `furnace.melt` in the `RToken.issueTo` function. ```diff diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol @@ -4641,28 +4799,27 @@ index 616b1532..fc584688 100644 @@ -129,7 +129,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // Ensure SOUND basket require(basketHandler.status() == CollateralStatus.SOUND, "basket unsound"); - + - furnace.melt(); + try main.furnace().melt() {} catch {} uint256 supply = totalSupply(); - + // Revert if issuance exceeds either supply throttle ``` -The only instance when `furnace.melt` reverts is when the `totalSupply` is too low. But then it is ok to catch the exception and just continue with the issuance and potentially lose rToken appreciation. +The only instance when `furnace.melt` reverts is when the `totalSupply` is too low. But then it is ok to catch the exception and just continue with the issuance and potentially lose rToken appreciation. -Potentially losing some rToken appreciation is definitely better than having this attack vector. +Potentially losing some rToken appreciation is definitely better than having this attack vector. -The `RToken.redeemTo` function already has the call to the `furnance.melt` function wrapped in a try-catch block. So redemption cannot be DOSed. +The `RToken.redeemTo` function already has the call to the `furnance.melt` function wrapped in a try-catch block. So redemption cannot be DOSed. **[tbrent (Reserve) confirmed]()** - - -*** +--- ## [AssetRegistry cannot disable a bad asset](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73) -*Submitted by AkshaySrivastav* + +_Submitted by AkshaySrivastav_ ### Original Issue @@ -4672,8 +4829,8 @@ M-16: [RToken permanently insolvent/unusable if a single collateral in the baske https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/AssetRegistry.sol#L91-L104 - ### Impact + The AssetRegistry contains an `unregister` function which can be used to detach a bad collateral from the RToken system. Previously [M-16](https://github.com/code-423n4/2023-01-reserve-findings/issues/254) was reported as an issue for the RToken system in which a single bad collateral can stop the working of RToken protocol. @@ -4686,69 +4843,76 @@ So, if for some reasons the Collateral contract start consuming more gas than th This essentially prevent governance from unregistering a collateral from the RToken. The unregistering of a collateral is still dependent upon the code execution of the collateral token contract. - ### Proof of Concept + Consider this scenario: - - TokenA was registered as an asset in AssetRegistry. - - Due to an upgrade/bug/hack the TokenA starts consuming all available gas on function calls. - - The RToken governance decides to unregister the TokenA asset and calls the `AssetRegistry.unregister` function. - - Internal call chain invokes any function of TokenA contract. The txn reverts with an out of gas error. - - The governance is now unable to unregister TokenA from RToken protocol and RToken is now unusable. +- TokenA was registered as an asset in AssetRegistry. +- Due to an upgrade/bug/hack the TokenA starts consuming all available gas on function calls. +- The RToken governance decides to unregister the TokenA asset and calls the `AssetRegistry.unregister` function. +- Internal call chain invokes any function of TokenA contract. The txn reverts with an out of gas error. +- The governance is now unable to unregister TokenA from RToken protocol and RToken is now unusable. ### Recommended Mitigation Steps + Consider detaching the interaction with collateral contract completely from the unregistering contract flow. Unregistering a contract must never depend upon the code execution of Collateral token contract. **[0xA5DF (warden) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73#issuecomment-1435246259):** - > Under the [1/64 rule](https://eips.ethereum.org/EIPS/eip-150), even if the call runs out of gas we still remain with 1/64 of the gas available before the call.
+ +> Under the [1/64 rule](https://eips.ethereum.org/EIPS/eip-150), even if the call runs out of gas we still remain with 1/64 of the gas available before the call.
> Even if the remaining of `unregister()` takes about 50K gas units we'd still be fine if we call it with 3.2M gas. **[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73#issuecomment-1435314343):** - > This is a good find! -> -> Seems like another option for mitigation is to make the call to `BasketHandler.quantity()` reserving some quantity (100k?) of gas for later execution. + +> This is a good find! +> +> Seems like another option for mitigation is to make the call to `BasketHandler.quantity()` reserving some quantity (100k?) of gas for later execution. **[AkshaySrivastav (warden) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73#issuecomment-1435483929):** - > Ya reserving some gas could be a mitigation. + +> Ya reserving some gas could be a mitigation. > > > Under the [1/64 rule](https://eips.ethereum.org/EIPS/eip-150), even if the call runs out of gas we still remain with 1/64 of the gas available before the call. -> -> I think this can be bypassed, after the call to broken Token contract, a default returndatacopy is done which can be used to consume the remaining 1/64 gas. +> +> I think this can be bypassed, after the call to broken Token contract, a default returndatacopy is done which can be used to consume the remaining 1/64 gas. **[tbrent (Reserve) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73#issuecomment-1438884674):** - > [reserve-protocol/protocol#647](https://github.com/reserve-protocol/protocol/pull/647) -> -> @AkshaySrivastav - does the linked PR address the issue? Any problems you see with it? + +> [reserve-protocol/protocol#647](https://github.com/reserve-protocol/protocol/pull/647) +> +> @AkshaySrivastav - does the linked PR address the issue? Any problems you see with it? **[AkshaySrivastav (warden) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73#issuecomment-1439457512):** - > @tbrent some issues I still see which could be concerning are: -> + +> @tbrent some issues I still see which could be concerning are: +> > 1. As all external contract calls after completion perform a RETURNDATACOPY, this can be misused to drain the 900k gas that you are reserving. The malicious token contract can return a huge data chunk which can consume the 900k gas.
> 2. The mitigation also opens up another issue. The `unregister()` call can be triggered with ~901k gas (excluding gas cost of require statements for simplicity). This will essentially cause failing of `basketHandler.quantity` call even for non-malicious collateral tokens. This is a more likely scenario as most governance proposals are open to be executed by anyone (once voting is passed and proposal is queued). -> -> The actual mitigation of this issue would be to completely detach the code execution of token contract from the unregistering execution flow. +> +> The actual mitigation of this issue would be to completely detach the code execution of token contract from the unregistering execution flow. **[tbrent (Reserve) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73#issuecomment-1440323917):** - > This seems ok. Not disabling the basket is a UX improvement. If the attack still results in the asset being unregistered then I would say it is not an issue. + +> This seems ok. Not disabling the basket is a UX improvement. If the attack still results in the asset being unregistered then I would say it is not an issue. **[0xean (judge) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73#issuecomment-1446363327):** - > After reviewing the documentation for the mitigation contest, I believe this to be a case of mitigation not confirmed and not a mitigation error. -> + +> After reviewing the documentation for the mitigation contest, I believe this to be a case of mitigation not confirmed and not a mitigation error. +> > If you read the original M-16 report it states: -> +> > > For plugins to function as intended there has to be a dependency on protocol specific function.
> > In a case that the collateral token is corrupted, the governance should be able to replace to corrupted token. The unregistering flow should never be depended on the token functionality. -> -> The key part of this `The unregistering flow should never be depended on the token functionality.` -> -> The gas characteristics of the token are part of its functionality, so the mitigation was not sufficient to handle all cases and it's not a mitigation error / new issue. - - +> +> The key part of this `The unregistering flow should never be depended on the token functionality.` +> +> The gas characteristics of the token are part of its functionality, so the mitigation was not sufficient to handle all cases and it's not a mitigation error / new issue. -*** +--- ## [StRSR: attacker can steal excess rsr that is returned after seizure](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/17) -*Submitted by HollaDieWaldfee, also found by [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/48)* + +_Submitted by HollaDieWaldfee, also found by [AkshaySrivastav](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/48)_ **Severity: Medium** @@ -4757,70 +4921,73 @@ Consider detaching the interaction with collateral contract completely from the https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/BackingManager.sol#L176-L182
https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/StRSR.sol#L496-L530 - ### Vulnerability details **Note:**
This issue deals with excess `rsr` that was seized from `stRSR` but is returned again.
-The `M-12` issue also deals with excess `rsr`. +The `M-12` issue also deals with excess `rsr`. -However `M-12` deals with the fact that not all `rsr` is returned to `stRSR`, whereas this issue deals with the fact that an attacker can steal `rsr` once it is returned to `stRSR`. +However `M-12` deals with the fact that not all `rsr` is returned to `stRSR`, whereas this issue deals with the fact that an attacker can steal `rsr` once it is returned to `stRSR`. -So while the issues seem to be similar they in fact are different. +So while the issues seem to be similar they in fact are different. -They are separate issues. So I chose to report this separately with the `NEW` keyword. +They are separate issues. So I chose to report this separately with the `NEW` keyword. ### Impact -`rsr` can be returned to `stRSR` after a seizure if not all seized `rsr` has been necessary to regain full collateralization. -This happens in the [`BackingManger.handoutExcessAssets`](https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/BackingManager.sol#L176-L182) function. +`rsr` can be returned to `stRSR` after a seizure if not all seized `rsr` has been necessary to regain full collateralization. + +This happens in the [`BackingManger.handoutExcessAssets`](https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/BackingManager.sol#L176-L182) function. -This excess `rsr` is then paid out to ALL stakers just like regular `rsr` rewards using the [`StRSR._payoutRewards`](https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/StRSR.sol#L496-L530) function. +This excess `rsr` is then paid out to ALL stakers just like regular `rsr` rewards using the [`StRSR._payoutRewards`](https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/StRSR.sol#L496-L530) function. -This is unfair. An attacker can abuse this behavior and stake `rsr` to profit from the returned `rsr` which is used to appreciate his `stRSR`. +This is unfair. An attacker can abuse this behavior and stake `rsr` to profit from the returned `rsr` which is used to appreciate his `stRSR`. -It would be fair if the `rsr` was returned only to the users that had staked when the seizure occurred. +It would be fair if the `rsr` was returned only to the users that had staked when the seizure occurred. ### Proof of Concept -Think of the following scenario: -1. There are currently 100 stakers with an equal share of the `1000 rsr` total that is currently in the `stRSR` contract. +Think of the following scenario: + +1. There are currently 100 stakers with an equal share of the `1000 rsr` total that is currently in the `stRSR` contract. 2. A seizure occurs and `500 rsr` are seized. 3. Not all `rsr` is sold and some (say `50 rsr`) is returned to `StRSR` 4. The attacker can front-run the transaction that returns the `rsr` and become a staker himself -5. The attacker will profit from the returned `rsr` once it is paid out as reward. Say the attacker stakes `100 rsr`. He now owns a share of `100 rsr / (500 rsr + 100 rsr) = 20%`. This means he will also get `20%` of the `50 rsr` that are paid out as rewards. +5. The attacker will profit from the returned `rsr` once it is paid out as reward. Say the attacker stakes `100 rsr`. He now owns a share of `100 rsr / (500 rsr + 100 rsr) = 20%`. This means he will also get `20%` of the `50 rsr` that are paid out as rewards. ### Tools Used + VSCode ### Recommended Mitigation Steps -Ideally, as I said above, the `rsr` should be returned only to the users that had staked when the seizure occurred. -With the current architecture of the `stRSR` contract this is not possible. There is no way to differentiate between stakers. +Ideally, as I said above, the `rsr` should be returned only to the users that had staked when the seizure occurred. -Also the scenario described is an edge and relies on a seizure to occur and `rsr` to be returned. +With the current architecture of the `stRSR` contract this is not possible. There is no way to differentiate between stakers. -It seems unrealistic that `10%` of the seized `rsr` is returned again. I think a number like `1% - 5%` is more realistic. +Also the scenario described is an edge and relies on a seizure to occur and `rsr` to be returned. -But still if the amount of `rsr` that is seized is big enough, `1% - 5%` can be a significant amount in terms of dollar value. +It seems unrealistic that `10%` of the seized `rsr` is returned again. I think a number like `1% - 5%` is more realistic. -I estimate this to be `Medium` severity since an attacker can profit at the expense of other stakers and this behavior will decrease the willingness of users to stake as the risk of losing funds is increased. +But still if the amount of `rsr` that is seized is big enough, `1% - 5%` can be a significant amount in terms of dollar value. -This severly damages the incentives involved with staking. Stakers are incentivized to wait for seizures to occur and only then stake as they might profit from returned `rsr`. +I estimate this to be `Medium` severity since an attacker can profit at the expense of other stakers and this behavior will decrease the willingness of users to stake as the risk of losing funds is increased. -I encourage the sponsor to further assess if there is a better way to return excess `rsr`. +This severly damages the incentives involved with staking. Stakers are incentivized to wait for seizures to occur and only then stake as they might profit from returned `rsr`. + +I encourage the sponsor to further assess if there is a better way to return excess `rsr`. **[tbrent (Reserve) acknowledged](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/17#issuecomment-1435353214)** **[0xean (judge) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/17#issuecomment-1440337932):** - > I believe that this issue does amount to a "leak of value" and is a valid Medium finding per C4 documentation. It may be accepted by the sponsors as a design tradeoff, but still should be highlighted to end users. - +> I believe that this issue does amount to a "leak of value" and is a valid Medium finding per C4 documentation. It may be accepted by the sponsors as a design tradeoff, but still should be highlighted to end users. -*** +--- ## [Attacker can temporary deplete available redemption/issuance by running issuance then redemption or vice versa](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/79) -*Submitted by 0xA5DF* + +_Submitted by 0xA5DF_ **Severity: Medium** @@ -4829,11 +4996,13 @@ I encourage the sponsor to further assess if there is a better way to return exc https://github.com/reserve-protocol/protocol/blob/610cfca553beea41b9508abbfbf4ee4ce16cbc12/contracts/libraries/Throttle.sol#L66-L75 ### Impact + Attacker can deplete available issuance or redemption by first issuing and then redeeming in the same tx or vice versa.
The available redemption/issuance will eventually grow back, but this temporary reduces the available amount.
-This can also use to front run other user who tries to redeem/issue in order to fail their tx. +This can also use to front run other user who tries to redeem/issue in order to fail their tx. ### Proof of Concept + In the PoC below a user is able to reduce the redemption available by more than 99% (1e20 to 1e14), and that's without spending anything but gas (they end up with the same amount of RToken as before) ```diff @@ -4844,7 +5013,7 @@ index e04f51db..33044b79 100644 @@ -1293,6 +1293,31 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ) }) - + + it('PoC', async function () { + const rechargePerBlock = config.issuanceThrottle.amtRate.div(BLOCKS_PER_HOUR); + @@ -4872,19 +5041,20 @@ index e04f51db..33044b79 100644 + it('Should update issuance throttle correctly on redemption', async function () { const rechargePerBlock = config.issuanceThrottle.amtRate.div(BLOCKS_PER_HOUR) - + @@ -1335,6 +1360,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { }) }) }) + return; - + describe('Melt/Mint #fast', () => { const issueAmount: BigNumber = bn('100e18') ``` Output: + ``` { redemptionAvailable: BigNumber { value: "10000000000000000000" }, // 1e20 @@ -4898,18 +5068,20 @@ after { ``` ### Recommended Mitigation Steps + Mitigating this issue seems a bit tricky. One way is at the end of `currentlyAvailable()` to return the max of `available` and `throttle.lastAvailable` (up to some limit, in order not to allow to much of it to accumulate). **[HollaDieWaldfee (warden) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/79#issuecomment-1435362326):** - > Seems valid but I have a doubt about this: -> + +> Seems valid but I have a doubt about this: +> > rToken issuance uses rounding mode CEIL when calculating how much a user has to pay.
> rToken redemption uses rounding mode FLOOR when calculating how much a user receives. -> +> > So I think there is a bit more cost involved than just gas and the attack needs to be renewed very often. -> +> > Also: > Might this be mitigated when the redemption limit is chosen significantly higher than the issuance limit?
> Because then when the attack must be renewed and `issue` is called, the redemption limit will be raised and not so much that it hits its limit.
@@ -4918,17 +5090,18 @@ One way is at the end of `currentlyAvailable()` to return the max of `available` **[tbrent (Reserve) confirmed](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/79#issuecomment-1435444036)** **[tbrent (Reserve) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/79#issuecomment-1435444258):** - > @HollaDieWaldfee - that's a really nice mitigation! I think that's exactly the thing to do. -**[tbrent (Reserve) linked a PR](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/79#issuecomment-1435444258):** - > [reserve-protocol/protocol#656](https://github.com/reserve-protocol/protocol/pull/656) +> @HollaDieWaldfee - that's a really nice mitigation! I think that's exactly the thing to do. +**[tbrent (Reserve) linked a PR](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/79#issuecomment-1435444258):** +> [reserve-protocol/protocol#656](https://github.com/reserve-protocol/protocol/pull/656) -*** +--- ## [Attacker can cause loss to rToken holders and stakers by running `BackingManager._manageTokens` before rewards are claimed](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/22) -*Submitted by HollaDieWaldfee* + +_Submitted by HollaDieWaldfee_ **Severity: Medium** @@ -4936,38 +5109,41 @@ One way is at the end of `currentlyAvailable()` to return the max of `available` https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/BackingManager.sol#L105-L153 - ### Impact -The assets that back the rTokens are held by the `BackingManager` and can earn rewards. -The rewards can be claimed via the [`TradingP1.claimRewards`](https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/mixins/Trading.sol#L82-L84) and [`TradingP1.claimRewardsSingle`](https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/mixins/Trading.sol#L90-L92) function. +The assets that back the rTokens are held by the `BackingManager` and can earn rewards. -The `BackingManager` inherits from `TradingP1` and therefore the above functions can be used to claim rewards. +The rewards can be claimed via the [`TradingP1.claimRewards`](https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/mixins/Trading.sol#L82-L84) and [`TradingP1.claimRewardsSingle`](https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/mixins/Trading.sol#L90-L92) function. -The issue is that the `BackingManager` does not claim rewards as part of its [`_manageTokens`](https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/BackingManager.sol#L105-L153) function. +The `BackingManager` inherits from `TradingP1` and therefore the above functions can be used to claim rewards. -So recollateralization can occur before rewards have been claimed. +The issue is that the `BackingManager` does not claim rewards as part of its [`_manageTokens`](https://github.com/reserve-protocol/protocol/blob/27a3472d553b4fa54f896596007765ec91941348/contracts/p1/BackingManager.sol#L105-L153) function. -There exist possibilities how an attacker can exploit this to cause a loss to rToken holders and rsr stakers. +So recollateralization can occur before rewards have been claimed. + +There exist possibilities how an attacker can exploit this to cause a loss to rToken holders and rsr stakers. ### Proof of Concept -Let's think about an example for such a scenario: + +Let's think about an example for such a scenario: Assume that the `RToken` is backed by a considerable amount of `TokenA` -`TokenA` earns rewards but not continuously. Bigger amounts of rewards are paid out periodically. Say 5% rewards every year. +`TokenA` earns rewards but not continuously. Bigger amounts of rewards are paid out periodically. Say 5% rewards every year. -Assume further that the `RToken` is currently undercollateralized. +Assume further that the `RToken` is currently undercollateralized. -The attacker can now front-run the claiming of rewards and perform recollateralization. +The attacker can now front-run the claiming of rewards and perform recollateralization. -The recollateralization might now seize `rsr` from stakers or take an unnecessary haircut. +The recollateralization might now seize `rsr` from stakers or take an unnecessary haircut. ### Tools Used + VSCode ### Recommended Mitigation Steps -I suggest that the `BackingManager._manageTokens` function calls `claimRewards`. Even before it calculates how many baskets it holds: + +I suggest that the `BackingManager._manageTokens` function calls `claimRewards`. Even before it calculates how many baskets it holds: ```diff diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol @@ -4977,25 +5153,25 @@ index fc38ce29..e17bb63d 100644 @@ -113,6 +113,8 @@ contract BackingManagerP1 is TradingP1, IBackingManager { uint48 basketTimestamp = basketHandler.timestamp(); if (block.timestamp < basketTimestamp + tradingDelay) return; - + + this.claimRewards(); + uint192 basketsHeld = basketHandler.basketsHeldBy(address(this)); - + // if (basketHandler.fullyCollateralized()) ``` **[tbrent (Reserve) acknowledged and commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/22#issuecomment-1435352092):** - > `claimRewards()` can be pretty gas-intensive, so we'll have to see whether we decide this is worth doing or not. But nice find! -**[0xA5DF (warden) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/22#issuecomment-1445186383):** - > > `claimRewards()` can be pretty gas-intensive, so we'll have to see whether we decide this is worth doing or not. But nice find! -> -> Maybe instead you can require `claimRewards()` to be called within some time interval - mark the last time it was called using a storage variable and in `_manageTokens()` revert if more than that time interval has passed. +> `claimRewards()` can be pretty gas-intensive, so we'll have to see whether we decide this is worth doing or not. But nice find! +**[0xA5DF (warden) commented](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/22#issuecomment-1445186383):** +> > `claimRewards()` can be pretty gas-intensive, so we'll have to see whether we decide this is worth doing or not. But nice find! +> +> Maybe instead you can require `claimRewards()` to be called within some time interval - mark the last time it was called using a storage variable and in `_manageTokens()` revert if more than that time interval has passed. -*** +--- # Disclosures diff --git a/common/configuration.ts b/common/configuration.ts index b3b3370de8..3a9ddac4db 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -52,6 +52,10 @@ export interface ITokens { ONDO?: string sDAI?: string cbETH?: string + STG?: string + sUSDC?: string + sUSDT?: string + sETH?: string } export interface IFeeds { @@ -139,6 +143,10 @@ export const networkConfig: { [key: string]: INetworkConfig } = { ONDO: '0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3', sDAI: '0x83f20f44975d03b1b09e64809b757c47f942beea', cbETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', + STG: '0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6', + sUSDC: '0xdf0770dF86a8034b3EFEf0A1Bb3c889B8332FF56', + sUSDT: '0x38EA452219524Bb87e18dE1C24D3bB59510BD783', + sETH: '0x101816545F6bd2b1076434B54383a1E633390A2E', }, chainlinkFeeds: { RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', @@ -231,6 +239,10 @@ export const networkConfig: { [key: string]: INetworkConfig } = { ONDO: '0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3', sDAI: '0x83f20f44975d03b1b09e64809b757c47f942beea', cbETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', + STG: '0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6', + sUSDC: '0xdf0770dF86a8034b3EFEf0A1Bb3c889B8332FF56', + sUSDT: '0x38EA452219524Bb87e18dE1C24D3bB59510BD783', + sETH: '0x101816545F6bd2b1076434B54383a1E633390A2E', }, chainlinkFeeds: { RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', diff --git a/contracts/plugins/assets/erc20/RewardableERC20.sol b/contracts/plugins/assets/erc20/RewardableERC20.sol index 90d1846e6b..f095000ad5 100644 --- a/contracts/plugins/assets/erc20/RewardableERC20.sol +++ b/contracts/plugins/assets/erc20/RewardableERC20.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity ^0.8.19; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -8,12 +9,14 @@ import "../../../interfaces/IRewardable.sol"; /** * @title RewardableERC20 - * @notice An abstract class that can be extended to create rewardable wrapper + * @notice An abstract class that can be extended to create rewardable wrapper. + * @notice `_claimAssetRewards` keeps tracks of rewards by snapshotting the balance + * and calculating the difference between the current balance and the previous balance. * @dev To inherit: * - override _claimAssetRewards() * - call ERC20 constructor elsewhere during construction */ -abstract contract RewardableERC20 is IRewardable, ERC20 { +abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard { using SafeERC20 for IERC20; uint256 public immutable one; // {qShare/share} @@ -24,13 +27,18 @@ abstract contract RewardableERC20 is IRewardable, ERC20 { mapping(address => uint256) public accumulatedRewards; // {qRewards} mapping(address => uint256) public claimedRewards; // {qRewards} + // Used to keep track of how many reward the Vault has accumulated + // Whenever _claimAndSyncRewards() is called we will calculate the difference + // between the current balance and `lastRewardBalance` to figure out how much to distribute + uint256 internal lastRewardBalance = 0; + /// @dev Extending class must ensure ERC20 constructor is called constructor(IERC20 _rewardToken, uint8 _decimals) { rewardToken = _rewardToken; one = 10**_decimals; // set via pass-in to prevent inheritance issues } - function claimRewards() external { + function claimRewards() external nonReentrant { _claimAndSyncRewards(); _syncAccount(msg.sender); _claimAccountRewards(msg.sender); @@ -38,37 +46,74 @@ abstract contract RewardableERC20 is IRewardable, ERC20 { function _syncAccount(address account) internal { if (account == address(0)) return; - uint256 shares = balanceOf(account); + + // {qRewards/share} uint256 accountRewardsPerShare = lastRewardsPerShare[account]; - if (rewardsPerShare == accountRewardsPerShare) return; - uint256 delta = rewardsPerShare - accountRewardsPerShare; - // {qRewards} = {qRewards/share} * {qShare} / {qShare/share} - uint256 newRewards = (delta * shares) / one; - lastRewardsPerShare[account] = rewardsPerShare; - accumulatedRewards[account] += newRewards; + // {qShare} + uint256 shares = balanceOf(account); + + // {qRewards} + uint256 _accumuatedRewards = accumulatedRewards[account]; + + // {qRewards/share} + uint256 _rewardsPerShare = rewardsPerShare; + if (accountRewardsPerShare < _rewardsPerShare) { + // {qRewards/share} + uint256 delta = _rewardsPerShare - accountRewardsPerShare; + + // {qRewards} = {qRewards/share} * {qShare} + _accumuatedRewards += (delta * shares) / one; + } + lastRewardsPerShare[account] = _rewardsPerShare; + accumulatedRewards[account] = _accumuatedRewards; } - function _claimAndSyncRewards() internal { - uint256 delta; + function _claimAndSyncRewards() internal virtual { uint256 _totalSupply = totalSupply(); - if (_totalSupply > 0) { - uint256 initialBal = rewardToken.balanceOf(address(this)); - _claimAssetRewards(); - uint256 endingBal = rewardToken.balanceOf(address(this)); - delta = endingBal - initialBal; + if (_totalSupply == 0) { + return; + } + _claimAssetRewards(); + uint256 balanceAfterClaimingRewards = rewardToken.balanceOf(address(this)); + uint256 _rewardsPerShare = rewardsPerShare; + uint256 _previousBalance = lastRewardBalance; + + if (balanceAfterClaimingRewards > _previousBalance) { + uint256 delta = balanceAfterClaimingRewards - _previousBalance; // {qRewards/share} += {qRewards} * {qShare/share} / {qShare} - rewardsPerShare += ((delta) * one) / _totalSupply; + _rewardsPerShare += (delta * one) / _totalSupply; } + lastRewardBalance = balanceAfterClaimingRewards; + rewardsPerShare = _rewardsPerShare; } function _claimAccountRewards(address account) internal { uint256 claimableRewards = accumulatedRewards[account] - claimedRewards[account]; + emit RewardsClaimed(IERC20(address(rewardToken)), claimableRewards); - if (claimableRewards == 0) return; + + if (claimableRewards == 0) { + return; + } + claimedRewards[account] = accumulatedRewards[account]; + + uint256 currentRewardTokenBalance = rewardToken.balanceOf(address(this)); + + // This is just to handle the edge case where totalSupply() == 0 and there + // are still reward tokens in the contract. + uint256 nonDistributed = currentRewardTokenBalance > lastRewardBalance + ? currentRewardTokenBalance - lastRewardBalance + : 0; + rewardToken.safeTransfer(account, claimableRewards); + + currentRewardTokenBalance = rewardToken.balanceOf(address(this)); + lastRewardBalance = currentRewardTokenBalance > nonDistributed + ? currentRewardTokenBalance - nonDistributed + : 0; } function _beforeTokenTransfer( diff --git a/contracts/plugins/assets/stargate/README.md b/contracts/plugins/assets/stargate/README.md new file mode 100644 index 0000000000..8e96d39c42 --- /dev/null +++ b/contracts/plugins/assets/stargate/README.md @@ -0,0 +1,61 @@ +# Stargate Finance as collateral + +This documentation outlines the configuration and deployment of the Collateral Plugin, which is designed to enable Stargate Liquidity pool tokens as Collateral for an Token. It also defines the collateral token, reference unit, and target unit, and provides guidelines for integrating price feeds. + +The Stargate Finance documentation will be a good starting point to understand this plugin. Some quick links: + +- [Stargate User documentation](https://stargateprotocol.gitbook.io/stargate/v/user-docs/) +- [Stargate Developer documentation](https://stargateprotocol.gitbook.io/stargate/) +- [Stargate LP Staking contract source code](https://etherscan.io/address/0xB0D502E938ed5f4df2E681fE6E419ff29631d62b#code#F1#L1) + +> Users can add liquidity to token-chain pools (i.e. USDC-Ethereum) and receive either farm-based or transfer-based rewards +> +> _[From The Stargate User Docs](https://stargateprotocol.gitbook.io/stargate/v/user-docs/stargate-features/pool#overview)_ + +These rewards are added to the total available liquidity, thereby increasing the amount of the underlying token the LP token can be redeemed for. Users can also further stake their LP tokens with the [LPStaking](https://github.com/stargate-protocol/stargate/blob/main/contracts/LPStaking.sol) contract to receive $STG rewards. + +It's therefore required to have a wrapper token that'll automatically stake and collect these rewards so it can be used as additional revenue for the collateral. + +## Wrapper Token for Automatic Staking and Reward Collection + +The wrapper token for automatic staking and reward collection is a smart contract that enables LP token holders to earn rewards automatically without the need to manually stake and collect rewards. It works by automatically staking the LP tokens deposited into the contract, and then collecting rewards in the form of additional tokens, which are then distributed proportionally to the token holders. + +### Methods + +The wrapper token has all ERC20 methods with the following additions: + +- `deposit(uint amount)`: Allows users to deposit LP tokens into the wrapper token contract. The amount parameter specifies the number of LP tokens to deposit. +- `withdraw(uint amount)`: Allows users to withdraw LP tokens from the wrapper token contract. The amount parameter specifies the number of LP tokens to withdraw. + +### Usage + +To use the wrapper token for automatic staking and reward collection, follow these steps: + +1. Obtain LP tokens from the Stargate Finance protocol. +2. Approve the wrapper token contract to spend your LP tokens by calling the `approve` function on the LP token contract with the wrapper token contract address as the parameter. +3. Deposit your LP tokens into the wrapper token contract by calling the `deposit` function with the amount parameter set to the number of LP tokens you wish to deposit. +4. The tokens are automatically staked and rewards start to accrue. +5. Withdraw your LP tokens from the wrapper token contract by calling the `withdraw` function with the amount parameter set to the number of LP tokens you wish to withdraw. + +### Notes + +- Pending rewards are automatically collected upon deposit and withdrawal. +- Token transfers don't transfer pending rewards along with the token. +- Always verify the contract address before interacting with it to avoid phishing attacks. + +## Collateral plugin + +There are 2 variants of this plugin that can be deployed. + +1. **`StargatePoolFiatCollateral`**: This contract serves for the USDC, USDT and any other USD-pegged token. The target for these collaterals is **USD**. +2. **`StargatePoolETHCollateral`**: This contract serves the ETH pool. The underlying token for the Stargate ETH pool is SGETH which is mapped 1:1 with ETH. The chainlink feed that will then be provided during deployment would be an ETH-USD oracle. The target for this collateral is **ETH**. + +The **`{ref/tok}`** is computed as the ratio of the total liquidity to the total LP tokens in circulation. This ratio never drops except for a very rare occasion where the pool's total supply drops to zero, in which case the **`{ref/tok}`** falls back to 1 and the plugin will default under such circumstances. + +### Acounting units + +| Unit | Description | +| :------------------- | ---------------------------------------------------------- | +| **Collateral token** | The wrapper token deployed for that pool. | +| **Target unit** | `USD` for the USD-pegged pools and `ETH` for the ETH pool. | +| **Reference unit** | The pool's underlying token. | diff --git a/contracts/plugins/assets/stargate/StargatePoolETHCollateral.sol b/contracts/plugins/assets/stargate/StargatePoolETHCollateral.sol new file mode 100644 index 0000000000..f33f9f1bb2 --- /dev/null +++ b/contracts/plugins/assets/stargate/StargatePoolETHCollateral.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../../../libraries/Fixed.sol"; +import "../OracleLib.sol"; +import "./interfaces/IStargatePool.sol"; +import "./StargatePoolFiatCollateral.sol"; + +contract StargatePoolETHCollateral is StargatePoolFiatCollateral { + using FixLib for uint192; + using OracleLib for AggregatorV3Interface; + + /// @param config.chainlinkFeed Feed units: {UoA/target} + // solhint-disable no-empty-blocks + constructor(CollateralConfig memory config) StargatePoolFiatCollateral(config) {} + + /// Can revert, used by other contract functions in order to catch errors + /// Should not return FIX_MAX for low + /// Should only return FIX_MAX for high if low is 0 + /// @dev Override this when pricing is more complicated than just a single oracle + /// @param low {UoA/tok} The low price estimate + /// @param high {UoA/tok} The high price estimate + /// @param pegPrice {target/ref} The actual price observed in the peg + function tryPrice() + external + view + virtual + override + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) + { + // Assumption: {target/ref} = 1; SGETH unwraps to ETH at 1:1 + pegPrice = FIX_ONE; // {target/ref} + + // {UoA/target} + uint192 pricePerTarget = chainlinkFeed.price(oracleTimeout); + + // {UoA/tok} = {UoA/target} * {ref/tok} * {target/ref} (1) + uint192 p = pricePerTarget.mul(refPerTok()); + + // this oracleError is already the combined total oracle error + uint192 delta = p.mul(oracleError); + low = p - delta; + high = p + delta; + } +} diff --git a/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol b/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol new file mode 100644 index 0000000000..54a7a203fa --- /dev/null +++ b/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../../../libraries/Fixed.sol"; +import "../OracleLib.sol"; +import "../FiatCollateral.sol"; +import "./interfaces/IStargatePool.sol"; + +import "./StargateRewardableWrapper.sol"; + +contract StargatePoolFiatCollateral is FiatCollateral { + using FixLib for uint192; + using OracleLib for AggregatorV3Interface; + + // does not become nonzero until after first refresh() + uint192 public lastReferencePrice; // {ref/tok} last ref price observed + + IStargatePool private immutable pool; + + /// @param config.chainlinkFeed Feed units: {UoA/ref} + // solhint-disable no-empty-blocks + constructor(CollateralConfig memory config) FiatCollateral(config) { + pool = StargateRewardableWrapper(address(config.erc20)).pool(); + } + + /// Can revert, used by other contract functions in order to catch errors + /// Should not return FIX_MAX for low + /// Should only return FIX_MAX for high if low is 0 + /// @dev Override this when pricing is more complicated than just a single oracle + /// @param low {UoA/tok} The low price estimate + /// @param high {UoA/tok} The high price estimate + /// @param pegPrice {target/tok} The actual price observed in the peg + function tryPrice() + external + view + virtual + override + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) + { + pegPrice = chainlinkFeed.price(oracleTimeout); // {target/ref} + + // Assumption: {UoA/target} = 1; target is same as UoA + // {UoA/tok} = {UoA/target} * {target/ref} * {ref/tok} + uint192 p = pegPrice.mul(refPerTok()); + + // {UoA/tok} = {UoA/tok} * {1} + uint192 delta = p.mul(oracleError); + + low = p - delta; + high = p + delta; + } + + /// Should not revert + /// Refresh exchange rates and update default status. + /// @dev Should not need to override: can handle collateral with variable refPerTok() + function refresh() public virtual override { + if (alreadyDefaulted()) return; + + CollateralStatus oldStatus = status(); + + // Check for hard default + + uint192 referencePrice = refPerTok(); + uint192 lastReferencePrice_ = lastReferencePrice; + + // uint192(<) is equivalent to Fix.lt + if (referencePrice < lastReferencePrice_) { + markStatus(CollateralStatus.DISABLED); + } else if (referencePrice > lastReferencePrice_) { + lastReferencePrice = referencePrice; + } + + // Check for soft default + save prices + try this.tryPrice() returns (uint192 low, uint192 high, uint192 pegPrice) { + // {UoA/tok}, {UoA/tok}, {target/ref} + // (0, 0) is a valid price; (0, FIX_MAX) is unpriced + + // Save prices if priced + if (high < FIX_MAX) { + savedLowPrice = low; + savedHighPrice = high; + lastSave = uint48(block.timestamp); + } else { + // must be unpriced + assert(low == 0); + } + + // If the price is below the default-threshold price, default eventually + // uint192(+/-) is the same as Fix.plus/minus + if (pegPrice < pegBottom || pegPrice > pegTop || low == 0) { + markStatus(CollateralStatus.IFFY); + } else { + markStatus(CollateralStatus.SOUND); + } + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + markStatus(CollateralStatus.IFFY); + } + + CollateralStatus newStatus = status(); + if (oldStatus != newStatus) { + emit CollateralStatusChanged(oldStatus, newStatus); + } + } + + /// @return _rate {ref/tok} Quantity of whole reference units per whole collateral tokens + function refPerTok() public view virtual override returns (uint192 _rate) { + uint256 _totalSupply = pool.totalSupply(); + + if (_totalSupply != 0) { + _rate = divuu(pool.totalLiquidity(), _totalSupply); + } + } + + function claimRewards() external override(Asset, IRewardable) { + StargateRewardableWrapper(address(erc20)).claimRewards(); + } +} diff --git a/contracts/plugins/assets/stargate/StargateRewardableWrapper.sol b/contracts/plugins/assets/stargate/StargateRewardableWrapper.sol new file mode 100644 index 0000000000..44621f3152 --- /dev/null +++ b/contracts/plugins/assets/stargate/StargateRewardableWrapper.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity ^0.8.19; + +import "./interfaces/IStargateLPStaking.sol"; +import "./interfaces/IStargatePool.sol"; + +import "../erc20/RewardableERC20Wrapper.sol"; + +contract StargateRewardableWrapper is RewardableERC20Wrapper { + IStargateLPStaking public immutable stakingContract; + IStargatePool public immutable pool; + IERC20 public immutable stargate; + uint256 public immutable poolId; + + constructor( + string memory name_, + string memory symbol_, + IERC20 stargate_, + IStargateLPStaking stakingContract_, + IStargatePool pool_ + ) RewardableERC20Wrapper(pool_, name_, symbol_, stargate_) { + require( + address(stargate_) != address(0) && + address(stakingContract_) != address(0) && + address(pool_) != address(0), + "Invalid address" + ); + + uint256 poolLength = stakingContract_.poolLength(); + uint256 pid = type(uint256).max; + for (uint256 i = 0; i < poolLength; ++i) { + if (address(stakingContract_.poolInfo(i).lpToken) == address(pool_)) { + pid = i; + break; + } + } + require(pid != type(uint256).max, "Invalid pool"); + + pool_.approve(address(stakingContract_), type(uint256).max); // TODO: Change this! + + pool = pool_; + poolId = pid; + stakingContract = stakingContract_; + stargate = stargate_; + } + + function _claimAssetRewards() internal override { + stakingContract.deposit(poolId, 0); + } + + function _afterDeposit(uint256 _amount, address to) internal override { + require(to == msg.sender, "Only the sender can deposit"); + + stakingContract.deposit(poolId, _amount); + } + + function _beforeWithdraw(uint256 _amount, address to) internal override { + require(to == msg.sender, "Only the sender can withdraw"); + + stakingContract.withdraw(poolId, _amount); + } +} diff --git a/contracts/plugins/assets/stargate/interfaces/IStargateLPStaking.sol b/contracts/plugins/assets/stargate/interfaces/IStargateLPStaking.sol new file mode 100644 index 0000000000..4210c5734f --- /dev/null +++ b/contracts/plugins/assets/stargate/interfaces/IStargateLPStaking.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +interface IStargateLPStaking { + function poolLength() external view returns (uint256); + + // Info of each pool. + struct PoolInfo { + // Address of LP token contract. + IERC20 lpToken; + // How many allocation points assigned to this pool. STGs to distribute per block. + uint256 allocPoint; + // Last block number that STGs distribution occurs. + uint256 lastRewardBlock; + // Accumulated STGs per share, times 1e12. See below. + uint256 accStargatePerShare; + } + + function poolInfo(uint256) external view returns (PoolInfo memory); + + function pendingStargate(uint256 _pid, address _user) external view returns (uint256); + + /// @param _pid The pid specifies the pool + function updatePool(uint256 _pid) external; + + /// @param _pid The pid specifies the pool + /// @param _amount The amount of the LP token to deposit + /// @notice Requires appropriate approval to the specified number of tokens + function deposit(uint256 _pid, uint256 _amount) external; + + /// @param _pid The pid specifies the pool + /// @param _amount The amount of the LP token to withdraw + function withdraw(uint256 _pid, uint256 _amount) external; + + /// @notice Withdraw without caring about rewards. + /// @param _pid The pid specifies the pool + function emergencyWithdraw(uint256 _pid) external; + + /// @notice handles adding a new LP token (Can only be called by the owner) + /// @param _allocPoint The alloc point is used as the weight of + /// the pool against all other alloc points added. + /// @param _lpToken The lp token address + function add(uint256 _allocPoint, IERC20 _lpToken) external; + + function owner() external view returns (address); +} diff --git a/contracts/plugins/assets/stargate/interfaces/IStargatePool.sol b/contracts/plugins/assets/stargate/interfaces/IStargatePool.sol new file mode 100644 index 0000000000..3ddfc90797 --- /dev/null +++ b/contracts/plugins/assets/stargate/interfaces/IStargatePool.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +interface IStargatePool is IERC20Metadata { + function router() external view returns (address); + + function poolId() external view returns (uint256); + + function totalLiquidity() external view returns (uint256); + + function mint(address _to, uint256 _amountLD) external returns (uint256); + + function amountLPtoLD(uint256 _amountLP) external view returns (uint256); +} diff --git a/contracts/plugins/assets/stargate/interfaces/IStargateRouter.sol b/contracts/plugins/assets/stargate/interfaces/IStargateRouter.sol new file mode 100644 index 0000000000..f58298192d --- /dev/null +++ b/contracts/plugins/assets/stargate/interfaces/IStargateRouter.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +interface IStargateRouter { + function addLiquidity( + uint256 _poolId, + uint256 _amountLD, + address _to + ) external; + + function instantRedeemLocal( + uint16 _srcPoolId, + uint256 _amountLP, + address _to + ) external returns (uint256); +} diff --git a/contracts/plugins/assets/stargate/mocks/StargateLPStakingMock.sol b/contracts/plugins/assets/stargate/mocks/StargateLPStakingMock.sol new file mode 100644 index 0000000000..b5c03837f1 --- /dev/null +++ b/contracts/plugins/assets/stargate/mocks/StargateLPStakingMock.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../interfaces/IStargateLPStaking.sol"; +import "../../../mocks/ERC20Mock.sol"; + +contract StargateLPStakingMock is IStargateLPStaking { + PoolInfo[] private _poolInfo; + mapping(uint256 => mapping(address => uint256)) poolToUserRewardsPending; + mapping(uint256 => mapping(address => uint256)) poolToUserBalance; + + ERC20Mock public immutable stargateMock; + + constructor(ERC20Mock stargateMock_) { + stargateMock = stargateMock_; + } + + function poolLength() external view override returns (uint256) { + return _poolInfo.length; + } + + function pendingStargate(uint256 pid, address user) external view override returns (uint256) { + return poolToUserRewardsPending[pid][user]; + } + + function poolInfo(uint256 index) external view override returns (PoolInfo memory) { + return _poolInfo[index]; + } + + function updatePool(uint256 pid) external override {} + + function deposit(uint256 pid, uint256 amount) external override { + address sender = msg.sender; + IERC20 pool = _poolInfo[pid].lpToken; + pool.transferFrom(sender, address(this), amount); + _emitUserRewards(pid, sender); + poolToUserBalance[pid][sender] += amount; + } + + function withdraw(uint256 pid, uint256 amount) external override { + address sender = msg.sender; + require(amount <= poolToUserBalance[pid][sender]); + IERC20 pool = _poolInfo[pid].lpToken; + pool.transfer(sender, amount); + _emitUserRewards(pid, sender); + poolToUserBalance[pid][sender] -= amount; + } + + function emergencyWithdraw(uint256 pid) external override {} + + function addRewardsToUser( + uint256 pid, + address user, + uint256 amount + ) external { + poolToUserRewardsPending[pid][user] += amount; + } + + function addPool(IERC20 lpToken) internal { + PoolInfo memory info; + info.lpToken = lpToken; + _poolInfo.push(info); + } + + function _emitUserRewards(uint256 pid, address user) private { + uint256 amount = poolToUserRewardsPending[pid][user]; + stargateMock.mint(user, amount); + poolToUserRewardsPending[pid][user] = 0; + } + + function add(uint256, IERC20 lpToken) external override { + addPool(lpToken); + } + + function owner() external view override returns (address) {} +} diff --git a/contracts/plugins/assets/stargate/mocks/StargatePoolMock.sol b/contracts/plugins/assets/stargate/mocks/StargatePoolMock.sol new file mode 100644 index 0000000000..75f06d2b0a --- /dev/null +++ b/contracts/plugins/assets/stargate/mocks/StargatePoolMock.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "../../../../libraries/Fixed.sol"; +import "../../../mocks/ERC20Mock.sol"; +import "../interfaces/IStargatePool.sol"; + +contract StargatePoolMock is ERC20Mock { + using FixLib for uint192; + + uint256 public totalLiquidity; + uint8 private _decimals; + + constructor( + string memory name, + string memory symbol, + uint8 decimals_ + ) ERC20Mock(name, symbol) { + totalLiquidity = totalSupply(); + _decimals = decimals_; + } + + function decimals() public view override returns (uint8) { + return _decimals; + } + + function setExchangeRate(uint192 rate) external { + uint192 fixTotalLiquidity = rate.mul(shiftl_toFix(totalSupply(), -int8(decimals()))); + totalLiquidity = shiftl_toFix(fixTotalLiquidity, -(36 - int8(decimals()))); + } +} diff --git a/docs/dev-env.md b/docs/dev-env.md index 1744e0e24b..3934dba202 100644 --- a/docs/dev-env.md +++ b/docs/dev-env.md @@ -20,6 +20,7 @@ These instructions assume you already have standard installations of `node`, `np ## Setup ### Basic Dependencies + Set up yarn and hardhat, needed for compiling and running tests: ```bash @@ -41,15 +42,20 @@ cp .env.example .env ``` ### Tenderly + If you are going to use a Tenderly network, do the following: + 1. Install the [tenderly cli](https://github.com/Tenderly/tenderly-cli) 2. Login + ```bash tenderly login --authentication-method access-key --access-key {your_access_key} --force ``` + 3. Configure the `TENDERLY_RPC_URL` in your `.env` file ### Slither + You should also setup `slither`. The [Trail of Bits tools][tob-suite] require solc-select. Check [the installation instructions](https://github.com/crytic/solc-select) to ensure you have all prerequisites. Then: ```bash diff --git a/docs/system-design.md b/docs/system-design.md index 8fe8d0e8dd..23d3c0491b 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -61,7 +61,7 @@ Some units: - Unit of Account `{UoA}`: Any particular RToken must have a single Unit of Account. This unit is used internally to compare the values of different assets, as when deciding when there's enough revenue to start an auction, or in which of several surplus assets we hold the largest surplus. -- Target unit `{target}`: Outside of default, each collateral in an RToken basket is expected to be stable or appreciating against some exogenous currency. The exogenous currency is that collateral's _target unit_. We expect that in many RTokens that people actually want, all of those target units will be the same, and we can speak of the RToken maintaining stability or appreciation against _its_ target unit. +- Target unit `{target}`: Outside of default, each collateral in an RToken basket is expected to be stable or appreciating against some exogenous unit. The exogenous unit is that collateral's _target unit_. We expect that in many RTokens that people actually want, many of the target units will be the same, and we can speak of the RToken maintaining stability or appreciation against a linear combination of target units. - Reference unit `{ref}`: When collateral tokens are expected to appreciate, it's generally because some defi protocol (or protocols) produces a token that is freely redeemable for some base token, and that redemption rate is expected to monotonically increase over time. That base token is the _reference unit_ for the collateral token. The RToken protocol expects reference units to be in a known, predictable relationship with target units, and will flag a collateral token as defaulting if that relationship appears to be broken. diff --git a/scripts/addresses/5-RTKN-tmp-deployments.json b/scripts/addresses/5-RTKN-tmp-deployments.json index 1e94d56fff..9e37b1f977 100644 --- a/scripts/addresses/5-RTKN-tmp-deployments.json +++ b/scripts/addresses/5-RTKN-tmp-deployments.json @@ -32,4 +32,4 @@ "stRSR": "0xeC12e8412a7AE4598d754f4016D487c269719856" } } -} \ No newline at end of file +} diff --git a/scripts/addresses/5-tmp-assets-collateral.json b/scripts/addresses/5-tmp-assets-collateral.json index a60fdaf59f..428bb8e280 100644 --- a/scripts/addresses/5-tmp-assets-collateral.json +++ b/scripts/addresses/5-tmp-assets-collateral.json @@ -53,4 +53,4 @@ "rETH": "0x178E141a0E3b34152f73Ff610437A7bf9B83267A", "wstETH": "0x6320cD32aA674d2898A68ec82e869385Fc5f7E2f" } -} \ No newline at end of file +} diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_stargate_eth_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_eth_collateral.ts new file mode 100644 index 0000000000..522bc917f0 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_eth_collateral.ts @@ -0,0 +1,104 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn, fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { priceTimeout, oracleTimeout } from '../../utils' +import { + StargatePoolFiatCollateral, + StargatePoolFiatCollateral__factory, +} from '../../../../typechain' +import { ContractFactory } from 'ethers' + +import { + SETH, + STAKING_CONTRACT, +} from '../../../../test/plugins/individual-collateral/stargate/constants' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Stargate ETH Wrapper **************************/ + + const WrapperFactory: ContractFactory = await hre.ethers.getContractFactory('StargatePoolWrapper') + + const erc20 = await WrapperFactory.deploy( + 'Wrapped Stargate ETH', + 'wSTG-ETH', + networkConfig[chainId].tokens.STG, + STAKING_CONTRACT, + SETH + ) + await erc20.deployed() + + console.log( + `Deployed Wrapper for Stargate ETH on ${hre.network.name} (${chainId}): ${erc20.address} ` + ) + + const StargateCollateralFactory: StargatePoolFiatCollateral__factory = + await hre.ethers.getContractFactory('StargatePoolFiatCollateral') + + const collateral = await StargateCollateralFactory.connect( + deployer + ).deploy({ + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, + oracleError: fp('0.005').toString(), // 0.5%, + erc20: erc20.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24h hr, + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.05').toString(), + delayUntilDefault: bn('86400').toString(), // 24h + }) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log(`Deployed Stargate ETH to ${hre.network.name} (${chainId}): ${collateral.address}`) + + assetCollDeployments.collateral.sETH = collateral.address + assetCollDeployments.erc20s.sETH = erc20.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts new file mode 100644 index 0000000000..6b91f8d531 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts @@ -0,0 +1,104 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn, fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { priceTimeout, oracleTimeout } from '../../utils' +import { + StargatePoolFiatCollateral, + StargatePoolFiatCollateral__factory, +} from '../../../../typechain' +import { ContractFactory } from 'ethers' + +import { + STAKING_CONTRACT, + SUSDC, +} from '../../../../test/plugins/individual-collateral/stargate/constants' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Stargate USDC Wrapper **************************/ + + const WrapperFactory: ContractFactory = await hre.ethers.getContractFactory('StargatePoolWrapper') + + const erc20 = await WrapperFactory.deploy( + 'Wrapped Stargate USDC', + 'wSTG-USDC', + networkConfig[chainId].tokens.STG, + STAKING_CONTRACT, + SUSDC + ) + await erc20.deployed() + + console.log( + `Deployed Wrapper for Stargate USDC on ${hre.network.name} (${chainId}): ${erc20.address} ` + ) + + const StargateCollateralFactory: StargatePoolFiatCollateral__factory = + await hre.ethers.getContractFactory('StargatePoolFiatCollateral') + + const collateral = await StargateCollateralFactory.connect( + deployer + ).deploy({ + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, + oracleError: fp('0.001').toString(), // 0.1%, + erc20: erc20.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24h hr, + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.05').toString(), + delayUntilDefault: bn('86400').toString(), // 24h + }) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log(`Deployed Stargate USDC to ${hre.network.name} (${chainId}): ${collateral.address}`) + + assetCollDeployments.collateral.sUSDC = collateral.address + assetCollDeployments.erc20s.sUSDC = erc20.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdt_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdt_collateral.ts new file mode 100644 index 0000000000..6d0d723c7a --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdt_collateral.ts @@ -0,0 +1,104 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn, fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { priceTimeout, oracleTimeout } from '../../utils' +import { + StargatePoolFiatCollateral, + StargatePoolFiatCollateral__factory, +} from '../../../../typechain' +import { ContractFactory } from 'ethers' + +import { + STAKING_CONTRACT, + SUSDT, +} from '../../../../test/plugins/individual-collateral/stargate/constants' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Stargate USDT Wrapper **************************/ + + const WrapperFactory: ContractFactory = await hre.ethers.getContractFactory('StargatePoolWrapper') + + const erc20 = await WrapperFactory.deploy( + 'Wrapped Stargate USDT', + 'wSTG-USDT', + networkConfig[chainId].tokens.STG, + STAKING_CONTRACT, + SUSDT + ) + await erc20.deployed() + + console.log( + `Deployed Wrapper for Stargate USDT on ${hre.network.name} (${chainId}): ${erc20.address} ` + ) + + const StargateCollateralFactory: StargatePoolFiatCollateral__factory = + await hre.ethers.getContractFactory('StargatePoolFiatCollateral') + + const collateral = await StargateCollateralFactory.connect( + deployer + ).deploy({ + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDT!, + oracleError: fp('0.001').toString(), // 0.1%, + erc20: erc20.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24h hr, + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.05').toString(), + delayUntilDefault: bn('86400').toString(), // 24h + }) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log(`Deployed Stargate USDT to ${hre.network.name} (${chainId}): ${collateral.address}`) + + assetCollDeployments.collateral.sUSDT = collateral.address + assetCollDeployments.erc20s.sUSDT = erc20.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/test/plugins/individual-collateral/stargate/StargateETHTestSuite.test.ts b/test/plugins/individual-collateral/stargate/StargateETHTestSuite.test.ts new file mode 100644 index 0000000000..b06b686bf5 --- /dev/null +++ b/test/plugins/individual-collateral/stargate/StargateETHTestSuite.test.ts @@ -0,0 +1,28 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import collateralTests from '../collateralTests' +import { ETH_USD_PRICE_FEED } from './constants' +import { + CollateralType, + defaultStargateCollateralOpts, + stableOpts, + StargateCollateralOpts, +} from './StargateUSDCTestSuite.test' + +export const defaultVolatileStargateCollateralOpts: StargateCollateralOpts = { + ...defaultStargateCollateralOpts, + chainlinkFeed: ETH_USD_PRICE_FEED, +} + +const volatileOpts = { + ...stableOpts, + collateralName: 'Stargate ETH Pool', + makeCollateralFixtureContext: (alice: SignerWithAddress, opts: StargateCollateralOpts) => + stableOpts.makeCollateralFixtureContext(alice, { + ...defaultVolatileStargateCollateralOpts, + ...opts, + }), + deployCollateral: (opts?: StargateCollateralOpts) => + stableOpts.deployCollateral({ ...opts, type: CollateralType.VOLATILE }), +} + +collateralTests(volatileOpts) diff --git a/test/plugins/individual-collateral/stargate/StargateRewardableWrapper.test.ts b/test/plugins/individual-collateral/stargate/StargateRewardableWrapper.test.ts new file mode 100644 index 0000000000..e0c4eb4e37 --- /dev/null +++ b/test/plugins/individual-collateral/stargate/StargateRewardableWrapper.test.ts @@ -0,0 +1,387 @@ +import { networkConfig } from '#/common/configuration' +import { useEnv } from '#/utils/env' +import hre, { ethers } from 'hardhat' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { allocateUSDC, makewstgSUDC, mintWStgUSDC } from './helpers' +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { + IStargatePool, + ERC20Mock, + IStargateRouter, + StargatePoolMock, + StargateLPStakingMock, + StargateRewardableWrapper__factory, + StargateRewardableWrapper, +} from '@typechain/index' +import { expect } from 'chai' +import { ZERO_ADDRESS } from '#/common/constants' +import { STAKING_CONTRACT, WSUSDC_NAME, WSUSDC_SYMBOL, STARGATE, SUSDC } from './constants' +import { bn } from '#/common/numbers' +import { getChainId } from '#/common/blockchain-utils' +import { advanceTime } from '#/test/utils/time' + +const describeFork = useEnv('FORK') ? describe : describe.skip + +describeFork('Wrapped S*USDC', () => { + let bob: SignerWithAddress + let charles: SignerWithAddress + let don: SignerWithAddress + let usdc: ERC20Mock + let wstgUSDC: StargateRewardableWrapper + let stgUSDC: IStargatePool + let router: IStargateRouter + let StargateRewardableWrapperFactory: StargateRewardableWrapper__factory + + let chainId: number + + before(async () => { + chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + StargateRewardableWrapperFactory = ( + await ethers.getContractFactory('StargateRewardableWrapper') + ) + }) + + beforeEach(async () => { + ;[, bob, charles, don] = await ethers.getSigners() + ;({ usdc, wstgUSDC, stgUSDC, router } = await loadFixture(makewstgSUDC)) + }) + + describe('Deployment', () => { + it('reverts if deployed with a 0 address for STG token or LP staking contract', async () => { + await expect( + StargateRewardableWrapperFactory.deploy( + WSUSDC_NAME, + WSUSDC_SYMBOL, + ZERO_ADDRESS, + STAKING_CONTRACT, + SUSDC + ) + ).to.be.reverted + + await expect( + StargateRewardableWrapperFactory.deploy( + WSUSDC_NAME, + WSUSDC_SYMBOL, + STARGATE, + ZERO_ADDRESS, + SUSDC + ) + ).to.be.reverted + }) + + it('reverts if deployed with invalid pool', async () => { + await expect( + StargateRewardableWrapperFactory.deploy( + WSUSDC_NAME, + WSUSDC_SYMBOL, + STARGATE, + STAKING_CONTRACT, + ZERO_ADDRESS + ) + ).to.be.reverted + }) + }) + + describe('Deposit', () => { + const amount = bn(20000e6) + + beforeEach(async () => { + const requiredAmount = await stgUSDC.amountLPtoLD(amount) + + await allocateUSDC(bob.address, requiredAmount.sub(await usdc.balanceOf(bob.address))) + + await usdc.connect(bob).approve(router.address, requiredAmount) + await router.connect(bob).addLiquidity(await stgUSDC.poolId(), requiredAmount, bob.address) + + await stgUSDC.connect(bob).approve(wstgUSDC.address, ethers.constants.MaxUint256) + }) + + it('deposits correct amount', async () => { + await wstgUSDC.connect(bob).deposit(await stgUSDC.balanceOf(bob.address), bob.address) + + expect(await stgUSDC.balanceOf(bob.address)).to.equal(0) + expect(await wstgUSDC.balanceOf(bob.address)).to.closeTo(amount, 10) + expect(await usdc.balanceOf(bob.address)).to.equal(0) + }) + + it('deposits less than available S*USDC', async () => { + const depositAmount = await stgUSDC.balanceOf(bob.address).then((e) => e.div(2)) + + await wstgUSDC.connect(bob).deposit(depositAmount, bob.address) + + expect(await stgUSDC.balanceOf(bob.address)).to.be.closeTo(depositAmount, 10) + expect(await usdc.balanceOf(bob.address)).to.equal(0) + expect(await wstgUSDC.balanceOf(bob.address)).to.closeTo(depositAmount, 10) + }) + + it('has accurate balances when doing multiple deposits', async () => { + const depositAmount = await stgUSDC.balanceOf(bob.address) + + await wstgUSDC.connect(bob).deposit(depositAmount.mul(3).div(4), bob.address) + await advanceTime(1000) + await wstgUSDC.connect(bob).deposit(depositAmount.mul(1).div(4), bob.address) + + expect(await wstgUSDC.balanceOf(bob.address)).to.closeTo(depositAmount, 10) + }) + + it('updates the totalSupply', async () => { + const totalSupplyBefore = await wstgUSDC.totalSupply() + const expectedAmount = await stgUSDC.balanceOf(bob.address) + + await wstgUSDC.connect(bob).deposit(expectedAmount, bob.address) + expect(await wstgUSDC.totalSupply()).to.equal(totalSupplyBefore.add(expectedAmount)) + }) + }) + + describe('Withdraw', () => { + const initwusdcAmt = bn('20000e6') + + beforeEach(async () => { + await mintWStgUSDC(usdc, stgUSDC, wstgUSDC, bob, initwusdcAmt) + await mintWStgUSDC(usdc, stgUSDC, wstgUSDC, charles, initwusdcAmt) + }) + + it('withdraws to own account', async () => { + await wstgUSDC.connect(bob).withdraw(await wstgUSDC.balanceOf(bob.address), bob.address) + const bal = await wstgUSDC.balanceOf(bob.address) + + expect(bal).to.closeTo(bn('0'), 10) + expect(await stgUSDC.balanceOf(bob.address)).to.closeTo(initwusdcAmt, 10) + }) + + it('withdraws all balance via multiple withdrawals', async () => { + const initialBalance = await wstgUSDC.balanceOf(bob.address) + + const withdrawAmt = initialBalance.div(2) + await wstgUSDC.connect(bob).withdraw(withdrawAmt, bob.address) + expect(await wstgUSDC.balanceOf(bob.address)).to.closeTo(initialBalance.sub(withdrawAmt), 0) + + await advanceTime(1000) + + await wstgUSDC.connect(bob).withdraw(withdrawAmt, bob.address) + expect(await wstgUSDC.balanceOf(bob.address)).to.closeTo(bn('0'), 10) + }) + + it('handles complex withdrawal sequence', async () => { + let bobWithdrawn = bn('0') + let charlesWithdrawn = bn('0') + let donWithdrawn = bn('0') + + const firstWithdrawAmt = await wstgUSDC.balanceOf(charles.address).then((e) => e.div(2)) + + charlesWithdrawn = charlesWithdrawn.add(firstWithdrawAmt) + + await wstgUSDC.connect(charles).withdraw(firstWithdrawAmt, charles.address) + const newBalanceCharles = await stgUSDC.balanceOf(charles.address) + expect(newBalanceCharles).to.closeTo(firstWithdrawAmt, 10) + + // don deposits + await mintWStgUSDC(usdc, stgUSDC, wstgUSDC, don, initwusdcAmt) + + // bob withdraws SOME + bobWithdrawn = bobWithdrawn.add(bn('12345e6')) + await wstgUSDC.connect(bob).withdraw(bn('12345e6'), bob.address) + + // don withdraws SOME + donWithdrawn = donWithdrawn.add(bn('123e6')) + await wstgUSDC.connect(don).withdraw(bn('123e6'), don.address) + + // charles withdraws ALL + const charlesRemainingBalance = await wstgUSDC.balanceOf(charles.address) + charlesWithdrawn = charlesWithdrawn.add(charlesRemainingBalance) + await wstgUSDC.connect(charles).withdraw(charlesRemainingBalance, charles.address) + + // don withdraws ALL + const donRemainingBalance = await wstgUSDC.balanceOf(don.address) + donWithdrawn = donWithdrawn.add(donRemainingBalance) + await wstgUSDC.connect(don).withdraw(donRemainingBalance, don.address) + + // bob withdraws ALL + const bobRemainingBalance = await wstgUSDC.balanceOf(bob.address) + bobWithdrawn = bobWithdrawn.add(bobRemainingBalance) + await wstgUSDC.connect(bob).withdraw(bobRemainingBalance, bob.address) + + const bal = await wstgUSDC.balanceOf(bob.address) + + expect(bal).to.closeTo(bn('0'), 10) + expect(await stgUSDC.balanceOf(bob.address)).to.closeTo(bobWithdrawn, 100) + expect(await stgUSDC.balanceOf(charles.address)).to.closeTo(charlesWithdrawn, 100) + expect(await stgUSDC.balanceOf(don.address)).to.closeTo(donWithdrawn, 100) + }) + + it('updates the totalSupply', async () => { + const totalSupplyBefore = await wstgUSDC.totalSupply() + const withdrawAmt = bn('15000e6') + const expectedDiff = withdrawAmt + await wstgUSDC.connect(bob).withdraw(withdrawAmt, bob.address) + + expect(await wstgUSDC.totalSupply()).to.be.closeTo(totalSupplyBefore.sub(expectedDiff), 10) + }) + }) + + describe('Rewards', () => { + let stakingContract: StargateLPStakingMock + let stargate: ERC20Mock + let mockPool: StargatePoolMock + let wrapper: StargateRewardableWrapper + + const initialAmount = bn('20000e6') + + beforeEach(async () => { + stargate = await ( + await ethers.getContractFactory('ERC20Mock') + ).deploy('Stargate Mocked Token', 'S*MT') + stakingContract = await ( + await ethers.getContractFactory('StargateLPStakingMock') + ).deploy(stargate.address) + mockPool = await ( + await ethers.getContractFactory('StargatePoolMock') + ).deploy('Mock S*USDC', 'MS*USDC', 6) + await stakingContract.add(bn('5000'), mockPool.address) + wrapper = await StargateRewardableWrapperFactory.deploy( + 'wMS*USDC', + 'wMS*USDC', + stargate.address, + stakingContract.address, + mockPool.address + ) + await mockPool.connect(bob).approve(wrapper.address, ethers.constants.MaxUint256) + await mockPool.mint(bob.address, initialAmount) + await wrapper.connect(bob).deposit(initialAmount, bob.address) + }) + + it('claims previous rewards', async () => { + await stakingContract.addRewardsToUser(bn('0'), wrapper.address, bn('20000e18')) + const availableReward = await stakingContract.pendingStargate('0', wrapper.address) + await mockPool.mint(bob.address, initialAmount) + await wrapper.connect(bob).deposit(await mockPool.balanceOf(bob.address), bob.address) + await wrapper.connect(bob).claimRewards() + + expect(availableReward).to.be.eq(await stargate.balanceOf(bob.address)) + }) + + describe('Tracking', () => { + it('tracks slightly complex', async () => { + const rewardIncrement = bn('20000e18') + await stakingContract.addRewardsToUser(bn('0'), wrapper.address, rewardIncrement) + expect(await stakingContract.pendingStargate(bn('0'), wrapper.address)).to.be.eq( + rewardIncrement + ) + await mockPool.mint(charles.address, initialAmount) + await mockPool.connect(charles).approve(wrapper.address, ethers.constants.MaxUint256) + await wrapper + .connect(charles) + .deposit(await mockPool.balanceOf(charles.address), charles.address) + await wrapper.connect(charles).claimRewards() + expect(await stargate.balanceOf(wrapper.address)).to.be.eq(rewardIncrement) + await stakingContract.addRewardsToUser(bn('0'), wrapper.address, rewardIncrement.mul(2)) + expect(await stakingContract.pendingStargate(bn('0'), wrapper.address)).to.be.eq( + rewardIncrement.mul(2) + ) + await wrapper.connect(bob).withdraw(await wrapper.balanceOf(bob.address), bob.address) + await wrapper.connect(bob).claimRewards() + expect(await stargate.balanceOf(bob.address)).to.be.eq(rewardIncrement.mul(2)) + await wrapper + .connect(charles) + .withdraw(await wrapper.balanceOf(charles.address), charles.address) + await wrapper.connect(charles).claimRewards() + expect(await stargate.balanceOf(charles.address)).to.be.eq(rewardIncrement) + }) + + it('tracks moderately complex sequence', async () => { + const rewardIncrement = bn('20000e18') + await stakingContract.addRewardsToUser(bn('0'), wrapper.address, rewardIncrement) + expect(await stakingContract.pendingStargate(bn('0'), wrapper.address)).to.be.eq( + rewardIncrement + ) + + // bob rewards - 20k + // charles rewards - 0 + await mockPool.mint(charles.address, initialAmount) + await mockPool.connect(charles).approve(wrapper.address, ethers.constants.MaxUint256) + await wrapper + .connect(charles) + .deposit(await mockPool.balanceOf(charles.address), charles.address) + await wrapper.connect(charles).claimRewards() + expect(await stargate.balanceOf(wrapper.address)).to.be.eq(rewardIncrement) + await stakingContract.addRewardsToUser(bn('0'), wrapper.address, rewardIncrement.mul(2)) + expect(await stakingContract.pendingStargate(bn('0'), wrapper.address)).to.be.eq( + rewardIncrement.mul(2) + ) + + // bob rewards - 40k + // charles rewards - 20k + await wrapper.connect(bob).withdraw(initialAmount.div(2), bob.address) + await wrapper.connect(bob).claimRewards() + expect(await stargate.balanceOf(bob.address)).to.be.eq(rewardIncrement.mul(2)) + expect(await stargate.balanceOf(wrapper.address)).to.be.eq(rewardIncrement) + + // bob rewards - 0 + // charles rewards - 20k + await stakingContract.addRewardsToUser(bn('0'), wrapper.address, rewardIncrement.mul(3)) + expect(await stakingContract.pendingStargate(bn('0'), wrapper.address)).to.be.eq( + rewardIncrement.mul(3) + ) + + // bob rewards - 20k + // charles rewards - 60k + await wrapper + .connect(charles) + .withdraw(await wrapper.balanceOf(charles.address), charles.address) + await wrapper.connect(charles).claimRewards() + expect(await stargate.balanceOf(charles.address)).to.be.eq(rewardIncrement.mul(3)) + + // bob rewards - 20k + // charles rewards - 0 + await wrapper.connect(bob).withdraw(await wrapper.balanceOf(bob.address), bob.address) + await wrapper.connect(bob).claimRewards() + expect(await stargate.balanceOf(bob.address)).to.be.eq(rewardIncrement.mul(3)) + }) + }) + + describe('Transfers', () => { + it('maintains user rewards when transferring tokens', async () => { + const rewardIncrement = bn('20000e18') + await stakingContract.addRewardsToUser(bn('0'), wrapper.address, rewardIncrement) + expect(await stakingContract.pendingStargate(bn('0'), wrapper.address)).to.be.eq( + rewardIncrement + ) + // bob rewards - 20k + // charles rewards - 0 + + // claims pending rewards to wrapper + await wrapper.connect(bob).transfer(charles.address, initialAmount.div(2)) + expect(await stakingContract.pendingStargate(bn('0'), wrapper.address)).to.be.eq(0) + expect(await wrapper.balanceOf(bob.address)).to.be.eq(initialAmount.div(2)) + expect(await wrapper.balanceOf(charles.address)).to.be.eq(initialAmount.div(2)) + // bob rewards - 20k + // charles rewards - 0 + + await stakingContract.addRewardsToUser(bn('0'), wrapper.address, rewardIncrement) + expect(await stakingContract.pendingStargate(bn('0'), wrapper.address)).to.be.eq( + rewardIncrement + ) + // bob rewards - 30k + // charles rewards - 10k + + await wrapper + .connect(charles) + .withdraw(await wrapper.balanceOf(charles.address), charles.address) + await wrapper.connect(charles).claimRewards() + expect(await stargate.balanceOf(charles.address)).to.be.eq(rewardIncrement.div(2)) + // bob rewards - 30k + // charles rewards - 0 + + await wrapper.connect(bob).withdraw(await wrapper.balanceOf(bob.address), bob.address) + await wrapper.connect(bob).claimRewards() + expect(await stargate.balanceOf(bob.address)).to.be.eq(rewardIncrement.mul(3).div(2)) + // bob rewards - 0 + // charles rewards - 0 + }) + }) + }) +}) diff --git a/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts b/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts new file mode 100644 index 0000000000..b7264df4a6 --- /dev/null +++ b/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts @@ -0,0 +1,270 @@ +import collateralTests from '../collateralTests' +import { CollateralFixtureContext, CollateralOpts, MintCollateralFunc } from '../pluginTestTypes' +import { ethers } from 'hardhat' +import { ContractFactory, BigNumberish, BigNumber } from 'ethers' +import { + ERC20Mock, + MockV3Aggregator, + MockV3Aggregator__factory, + TestICollateral, + StargatePoolMock, + IStargateLPStaking, + StargateRewardableWrapper, + StargateRewardableWrapper__factory, +} from '@typechain/index' +import { bn, fp } from '#/common/numbers' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + STARGATE, + USDC_USD_PRICE_FEED, + MAX_TRADE_VOL, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + SUSDC, + ORACLE_TIMEOUT, + ORACLE_ERROR, +} from './constants' +import { noop } from 'lodash' + +/* + Define interfaces +*/ + +interface StargateCollateralFixtureContext extends CollateralFixtureContext { + pool: StargatePoolMock + wpool: StargateRewardableWrapper + stargate: ERC20Mock + stakingContract: IStargateLPStaking +} + +export enum CollateralType { + STABLE, + VOLATILE, +} + +export interface StargateCollateralOpts extends CollateralOpts { + type?: CollateralType +} + +/* + Define deployment functions +*/ + +export const defaultStargateCollateralOpts: StargateCollateralOpts = { + erc20: SUSDC, + targetName: ethers.utils.formatBytes32String('USD'), + rewardERC20: STARGATE, + priceTimeout: ORACLE_TIMEOUT, + chainlinkFeed: USDC_USD_PRICE_FEED, + oracleTimeout: ORACLE_TIMEOUT, + oracleError: ORACLE_ERROR, + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + type: CollateralType.STABLE, +} + +export const deployCollateral = async ( + opts: StargateCollateralOpts = {} +): Promise => { + opts = { ...defaultStargateCollateralOpts, ...opts } + + const StargatePoolCollateralFactory: ContractFactory = await ethers.getContractFactory( + opts.type === CollateralType.STABLE ? 'StargatePoolFiatCollateral' : 'StargatePoolETHCollateral' + ) + + const collateral = await StargatePoolCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + { gasLimit: 2000000000 } + ) + await collateral.deployed() + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + return collateral +} + +type Fixture = () => Promise + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: StargateCollateralOpts = {} +): Fixture => { + const collateralOpts = { ...defaultStargateCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + return deployCollateralStargateMockContext(collateralOpts) + } + + return makeCollateralFixtureContext +} + +const deployCollateralStargateMockContext = async ( + opts: StargateCollateralOpts = {} +): Promise => { + const collateralOpts = { ...defaultStargateCollateralOpts, ...opts } + + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + let chainlinkFeed: MockV3Aggregator + if (collateralOpts.type === CollateralType.STABLE) + chainlinkFeed = await MockV3AggregatorFactory.deploy(6, bn('1e6')) + else { + chainlinkFeed = await MockV3AggregatorFactory.deploy(8, bn('1995e8')) + } + collateralOpts.chainlinkFeed = chainlinkFeed.address + + const StargateRewardableWrapperFactory = ( + await ethers.getContractFactory('StargateRewardableWrapper') + ) + const stargate = await ( + await ethers.getContractFactory('ERC20Mock') + ).deploy('Stargate Mocked token', 'S*MT') + const stakingContract = await ( + await ethers.getContractFactory('StargateLPStakingMock') + ).deploy(stargate.address) + const mockPool = await ( + await ethers.getContractFactory('StargatePoolMock') + ).deploy('Mock Pool', 'MSP', collateralOpts.type === CollateralType.STABLE ? 6 : 8) + await stakingContract.add(bn('5000'), mockPool.address) + await mockPool.mint(stakingContract.address, bn(1)) + await mockPool.setExchangeRate(fp(1)) + const wrapper = await StargateRewardableWrapperFactory.deploy( + 'wMocked Pool', + 'wMSP', + stargate.address, + stakingContract.address, + mockPool.address + ) + collateralOpts.erc20 = wrapper.address + collateralOpts.rewardERC20 = stargate.address + + const collateral = await deployCollateral(collateralOpts) + + const rewardToken = await ethers.getContractAt('ERC20Mock', STARGATE) + + return { + collateral, + chainlinkFeed, + tok: wrapper, + rewardToken, + pool: mockPool, + wpool: wrapper, + stargate, + stakingContract, + } +} + +/* + Define helper functions +*/ + +const mintCollateralTo: MintCollateralFunc = async ( + ctx: StargateCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + const currentExchangeRate = await ctx.collateral.refPerTok() + + // ctx.stakingContract + + await ctx.pool.connect(user).approve(ctx.wpool.address, ethers.constants.MaxUint256) + await ctx.pool.mint(user.address, amount) + await ctx.wpool.connect(user).deposit(amount, user.address) + await ctx.wpool.connect(user).transfer(recipient, amount) + await ctx.pool.setExchangeRate(currentExchangeRate.add(fp('0.000001'))) +} + +const reduceRefPerTok = async ( + ctx: StargateCollateralFixtureContext, + pctDecrease: BigNumberish +) => { + const currentExchangeRate = await ctx.collateral.refPerTok() + await ctx.pool.setExchangeRate( + currentExchangeRate.sub(currentExchangeRate.mul(pctDecrease).div(100)) + ) +} + +const increaseRefPerTok = async ( + ctx: StargateCollateralFixtureContext, + pctIncrease: BigNumberish +) => { + const currentExchangeRate = await ctx.collateral.refPerTok() + await ctx.pool.setExchangeRate( + currentExchangeRate.add(currentExchangeRate.mul(pctIncrease).div(100)) + ) +} + +const getExpectedPrice = async (ctx: StargateCollateralFixtureContext): Promise => { + const initRefPerTok = await ctx.collateral.refPerTok() + + const decimals = await ctx.chainlinkFeed.decimals() + + const initData = await ctx.chainlinkFeed.latestRoundData() + return initData.answer + .mul(bn(10).pow(18 - decimals)) + .mul(initRefPerTok) + .div(fp('1')) +} + +const reduceTargetPerRef = async ( + ctx: StargateCollateralFixtureContext, + pctDecrease: BigNumberish +) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) +} + +const increaseTargetPerRef = async ( + ctx: StargateCollateralFixtureContext, + pctIncrease: BigNumberish +) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) +} + +/* + Run the test suite +*/ + +export const stableOpts = { + deployCollateral, + collateralSpecificConstructorTests: noop, + collateralSpecificStatusTests: noop, + beforeEachRewardsTest: noop, + makeCollateralFixtureContext, + mintCollateralTo, + reduceRefPerTok, + increaseRefPerTok, + resetFork: noop, + collateralName: 'Stargate USDC Pool', + reduceTargetPerRef, + increaseTargetPerRef, + itClaimsRewards: it.skip, // reward growth not supported in mock + itChecksTargetPerRefDefault: it, + itChecksRefPerTokDefault: it, + itHasRevenueHiding: it.skip, // no revenue hiding + itIsPricedByPeg: true, + chainlinkDefaultAnswer: 1e8, + itChecksPriceChanges: it, + getExpectedPrice, +} + +collateralTests(stableOpts) diff --git a/test/plugins/individual-collateral/stargate/constants.ts b/test/plugins/individual-collateral/stargate/constants.ts new file mode 100644 index 0000000000..2d91eb56c5 --- /dev/null +++ b/test/plugins/individual-collateral/stargate/constants.ts @@ -0,0 +1,23 @@ +import { bn, fp } from '#/common/numbers' +import { networkConfig } from '#/common/configuration' + +export const STARGATE = networkConfig['1'].tokens['STG']! +export const STAKING_CONTRACT = '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b' +export const SUSDC = networkConfig['1'].tokens['sUSDC']! +export const SUSDT = networkConfig['1'].tokens['sUSDT']! +export const SETH = networkConfig['1'].tokens['sETH']! +export const USDC = networkConfig['1'].tokens['USDC']! +export const USDT = networkConfig['1'].tokens['USDT']! +export const USDC_HOLDER = '0x0a59649758aa4d66e25f08dd01271e891fe52199' +export const USDC_USD_PRICE_FEED = networkConfig['1'].chainlinkFeeds['USDC']! +export const ETH_USD_PRICE_FEED = networkConfig['1'].chainlinkFeeds['ETH']! +export const SUSDC_POOL_ID = bn('1') +export const WSUSDC_NAME = 'Wrapped S*USDC' +export const WSUSDC_SYMBOL = 'wS*USDC' + +export const ORACLE_TIMEOUT = bn(86400) // 24 hours in seconds +export const ORACLE_ERROR = fp('0.005') +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(1000000) +export const USDC_DECIMALS = bn(6) diff --git a/test/plugins/individual-collateral/stargate/helpers.ts b/test/plugins/individual-collateral/stargate/helpers.ts new file mode 100644 index 0000000000..4d8de6b811 --- /dev/null +++ b/test/plugins/individual-collateral/stargate/helpers.ts @@ -0,0 +1,93 @@ +import { whileImpersonating } from '#/test/utils/impersonation' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { ERC20Mock } from '@typechain/ERC20Mock' +import { + IStargatePool, + IStargateRouter, + StargateRewardableWrapper__factory, + StargateRewardableWrapper, +} from '@typechain/index' +import { BigNumberish } from 'ethers' +import { ethers } from 'hardhat' +import { + STAKING_CONTRACT, + STARGATE, + SUSDC, + USDC, + WSUSDC_NAME, + WSUSDC_SYMBOL, + USDC_HOLDER, +} from './constants' + +interface WrappedstgUSDCFixture { + usdc: ERC20Mock + wstgUSDC: StargateRewardableWrapper + stgUSDC: IStargatePool + router: IStargateRouter +} + +export const makewstgSUDC = async (susdc?: string): Promise => { + const stgUSDC = await ethers.getContractAt('IStargatePool', susdc ?? SUSDC) + const router = ( + await ethers.getContractAt('IStargateRouter', await stgUSDC.router()) + ) + + const StargateRewardableWrapperFactory = ( + await ethers.getContractFactory('StargateRewardableWrapper') + ) + const wstgUSDC = await StargateRewardableWrapperFactory.deploy( + WSUSDC_NAME, + WSUSDC_SYMBOL, + STARGATE, + STAKING_CONTRACT, + stgUSDC.address + ) + const usdc = await ethers.getContractAt('ERC20Mock', USDC) + + return { stgUSDC, wstgUSDC, usdc, router } +} + +const allocateERC20 = async (token: ERC20Mock, from: string, to: string, balance: BigNumberish) => { + await whileImpersonating(from, async (signer) => { + await token.connect(signer).transfer(to, balance) + }) +} + +export const allocateUSDC = async ( + to: string, + balance: BigNumberish, + from: string = USDC_HOLDER, + token: string = USDC +) => { + const usdc = await ethers.getContractAt('ERC20Mock', token) + + await allocateERC20(usdc, from, to, balance) +} + +export const mintWStgUSDC = async ( + usdc: ERC20Mock, + susdc: IStargatePool, + wsusdc: StargateRewardableWrapper, + account: SignerWithAddress, + amount: BigNumberish +) => { + const router = ( + await ethers.getContractAt('IStargateRouter', await susdc.router()) + ) + const initBal = await susdc.balanceOf(account.address) + const usdcAmount = await susdc.amountLPtoLD(amount) + + await allocateUSDC(account.address, usdcAmount) + + await usdc.connect(account).approve(router.address, ethers.constants.MaxUint256) + await susdc.connect(account).approve(wsusdc.address, ethers.constants.MaxUint256) + + await router.connect(account).addLiquidity(await susdc.poolId(), usdcAmount, account.address) + + const nowBal = await susdc.balanceOf(account.address) + + const realAmount = nowBal.sub(initBal) + await wsusdc.connect(account).deposit(realAmount, account.address) + + return realAmount +} diff --git a/yarn.lock b/yarn.lock index 5e74124a72..b3f0942254 100644 --- a/yarn.lock +++ b/yarn.lock @@ -558,7 +558,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abi@npm:5.7.0, @ethersproject/abi@npm:^5.0.9, @ethersproject/abi@npm:^5.7.0": +"@ethersproject/abi@npm:5.7.0, @ethersproject/abi@npm:^5.0.0-beta.146, @ethersproject/abi@npm:^5.0.9, @ethersproject/abi@npm:^5.1.2, @ethersproject/abi@npm:^5.6.3, @ethersproject/abi@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abi@npm:5.7.0" dependencies: @@ -575,23 +575,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abi@npm:^5.0.0-beta.146, @ethersproject/abi@npm:^5.1.2, @ethersproject/abi@npm:^5.6.3": - version: 5.6.4 - resolution: "@ethersproject/abi@npm:5.6.4" - dependencies: - "@ethersproject/address": ^5.6.1 - "@ethersproject/bignumber": ^5.6.2 - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/constants": ^5.6.1 - "@ethersproject/hash": ^5.6.1 - "@ethersproject/keccak256": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/strings": ^5.6.1 - checksum: b5e70fa13a29e1143131a0ed25053a3d355c07353e13d436f42add33f40753b5541a088cf31a1ccca6448bb1d773a41ece0bf8367490d3f2ad394a4c26f4876f - languageName: node - linkType: hard - "@ethersproject/abstract-provider@npm:5.7.0, @ethersproject/abstract-provider@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abstract-provider@npm:5.7.0" @@ -607,21 +590,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abstract-provider@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/abstract-provider@npm:5.6.1" - dependencies: - "@ethersproject/bignumber": ^5.6.2 - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/networks": ^5.6.3 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/transactions": ^5.6.2 - "@ethersproject/web": ^5.6.1 - checksum: a1be8035d9e67fd41a336e2d38f5cf03b7a2590243749b4cf807ad73906b5a298e177ebe291cb5b54262ded4825169bf82968e0e5b09fbea17444b903faeeab0 - languageName: node - linkType: hard - "@ethersproject/abstract-signer@npm:5.7.0, @ethersproject/abstract-signer@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abstract-signer@npm:5.7.0" @@ -635,20 +603,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abstract-signer@npm:^5.6.2": - version: 5.6.2 - resolution: "@ethersproject/abstract-signer@npm:5.6.2" - dependencies: - "@ethersproject/abstract-provider": ^5.6.1 - "@ethersproject/bignumber": ^5.6.2 - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - checksum: 09f3dd1309b37bb3803057d618e4a831668e010e22047f52f1719f2b6f50b63805f1bec112b1603880d6c6b7d403ed187611ff1b14ae1f151141ede186a04996 - languageName: node - linkType: hard - -"@ethersproject/address@npm:5.7.0, @ethersproject/address@npm:^5.7.0": +"@ethersproject/address@npm:5.7.0, @ethersproject/address@npm:^5.0.2, @ethersproject/address@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/address@npm:5.7.0" dependencies: @@ -661,19 +616,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/address@npm:^5.0.2, @ethersproject/address@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/address@npm:5.6.1" - dependencies: - "@ethersproject/bignumber": ^5.6.2 - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/keccak256": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/rlp": ^5.6.1 - checksum: 262096ef05a1b626c161a72698a5d8b06aebf821fe01a1651ab40f80c29ca2481b96be7f972745785fd6399906509458c4c9a38f3bc1c1cb5afa7d2f76f7309a - languageName: node - linkType: hard - "@ethersproject/base64@npm:5.7.0, @ethersproject/base64@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/base64@npm:5.7.0" @@ -683,15 +625,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/base64@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/base64@npm:5.6.1" - dependencies: - "@ethersproject/bytes": ^5.6.1 - checksum: d21c5c297e1b8bc48fe59012c0cd70a90df7772fac07d9cc3da499d71d174d9f48edfd83495d4a1496cb70e8d1b33fb5b549a9529c5c2f97bb3a07d3f33a3fe8 - languageName: node - linkType: hard - "@ethersproject/basex@npm:5.7.0, @ethersproject/basex@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/basex@npm:5.7.0" @@ -713,17 +646,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bignumber@npm:^5.6.2": - version: 5.6.2 - resolution: "@ethersproject/bignumber@npm:5.6.2" - dependencies: - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - bn.js: ^5.2.1 - checksum: 9cf31c10274f1b6d45b16aed29f43729e8f5edec38c8ec8bb90d6b44f0eae14fda6519536228d23916a375ce11e71a77279a912d653ea02503959910b6bf9de7 - languageName: node - linkType: hard - "@ethersproject/bytes@npm:5.7.0, @ethersproject/bytes@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bytes@npm:5.7.0" @@ -733,15 +655,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bytes@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/bytes@npm:5.6.1" - dependencies: - "@ethersproject/logger": ^5.6.0 - checksum: d06ffe3bf12aa8a6588d99b82e40b46a2cbb8b057fc650aad836e3e8c95d4559773254eeeb8fed652066dcf8082e527e37cd2b9fff7ac8cabc4de7c49459a7eb - languageName: node - linkType: hard - "@ethersproject/constants@npm:5.7.0, @ethersproject/constants@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/constants@npm:5.7.0" @@ -751,15 +664,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/constants@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/constants@npm:5.6.1" - dependencies: - "@ethersproject/bignumber": ^5.6.2 - checksum: 3c6abcee60f1620796dc40210a638b601ad8a2d3f6668a69c42a5ca361044f21296b16d1d43b8a00f7c28b385de4165983a8adf671e0983f5ef07459dfa84997 - languageName: node - linkType: hard - "@ethersproject/contracts@npm:5.7.0, @ethersproject/contracts@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/contracts@npm:5.7.0" @@ -795,22 +699,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/hash@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/hash@npm:5.6.1" - dependencies: - "@ethersproject/abstract-signer": ^5.6.2 - "@ethersproject/address": ^5.6.1 - "@ethersproject/bignumber": ^5.6.2 - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/keccak256": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/strings": ^5.6.1 - checksum: 1338b578a51bc5cb692c17b1cabc51e484e9e3e009c4ffec13032332fc7e746c115968de1c259133cdcdad55fa96c5c8a5144170190c62b968a3fedb5b1d2cdb - languageName: node - linkType: hard - "@ethersproject/hdnode@npm:5.7.0, @ethersproject/hdnode@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/hdnode@npm:5.7.0" @@ -862,16 +750,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/keccak256@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/keccak256@npm:5.6.1" - dependencies: - "@ethersproject/bytes": ^5.6.1 - js-sha3: 0.8.0 - checksum: fdc950e22a1aafc92fdf749cdc5b8952b85e8cee8872d807c5f40be31f58675d30e0eca5e676876b93f2cd22ac63a344d384d116827ee80928c24b7c299991f5 - languageName: node - linkType: hard - "@ethersproject/logger@npm:5.7.0, @ethersproject/logger@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/logger@npm:5.7.0" @@ -879,13 +757,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/logger@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/logger@npm:5.6.0" - checksum: 6eee38a973c7a458552278971c109a3e5df3c257e433cb959da9a287ea04628d1f510d41b83bd5f9da5ddc05d97d307ed2162a9ba1b4fcc50664e4f60061636c - languageName: node - linkType: hard - "@ethersproject/networks@npm:5.7.1, @ethersproject/networks@npm:^5.7.0": version: 5.7.1 resolution: "@ethersproject/networks@npm:5.7.1" @@ -895,15 +766,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/networks@npm:^5.6.3": - version: 5.6.4 - resolution: "@ethersproject/networks@npm:5.6.4" - dependencies: - "@ethersproject/logger": ^5.6.0 - checksum: d41c07497de4ace3f57e972428685a8703a867600cf01f2bc15a21fcb7f99afb3f05b3d8dbb29ac206473368f30d60b98dc445cc38403be4cbe6f804f70e5173 - languageName: node - linkType: hard - "@ethersproject/pbkdf2@npm:5.7.0, @ethersproject/pbkdf2@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/pbkdf2@npm:5.7.0" @@ -923,15 +785,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/properties@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/properties@npm:5.6.0" - dependencies: - "@ethersproject/logger": ^5.6.0 - checksum: adcb6a843dcdf809262d77d6fbe52acdd48703327b298f78e698b76784e89564fb81791d27eaee72b1a6aaaf5688ea2ae7a95faabdef8b4aecc99989fec55901 - languageName: node - linkType: hard - "@ethersproject/providers@npm:5.7.2, @ethersproject/providers@npm:^5.7.0, @ethersproject/providers@npm:^5.7.1, @ethersproject/providers@npm:^5.7.2": version: 5.7.2 resolution: "@ethersproject/providers@npm:5.7.2" @@ -980,16 +833,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/rlp@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/rlp@npm:5.6.1" - dependencies: - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - checksum: 43a281d0e7842606e2337b5552c13f4b5dad209dce173de39ef6866e02c9d7b974f1cae945782f4c4b74a8e22d8272bfd0348c1cd1bfeb2c278078ef95565488 - languageName: node - linkType: hard - "@ethersproject/sha2@npm:5.7.0, @ethersproject/sha2@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/sha2@npm:5.7.0" @@ -1015,20 +858,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/signing-key@npm:^5.6.2": - version: 5.6.2 - resolution: "@ethersproject/signing-key@npm:5.6.2" - dependencies: - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - bn.js: ^5.2.1 - elliptic: 6.5.4 - hash.js: 1.1.7 - checksum: 7889d0934c9664f87e7b7e021794e2d2ddb2e81c1392498e154cf2d5909b922d74d3df78cec44187f63dc700eddad8f8ea5ded47d2082a212a591818014ca636 - languageName: node - linkType: hard - "@ethersproject/solidity@npm:5.7.0, @ethersproject/solidity@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/solidity@npm:5.7.0" @@ -1054,17 +883,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/strings@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/strings@npm:5.6.1" - dependencies: - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/constants": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - checksum: dcf33c2ddb22a48c3d7afc151a5f37e5a4da62a742a298988d517dc9adfaff9c5a0ebd8f476ec9792704cfc8142abd541e97432bc47cb121093edac7a5cfaf22 - languageName: node - linkType: hard - "@ethersproject/transactions@npm:5.7.0, @ethersproject/transactions@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/transactions@npm:5.7.0" @@ -1082,23 +900,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/transactions@npm:^5.6.2": - version: 5.6.2 - resolution: "@ethersproject/transactions@npm:5.6.2" - dependencies: - "@ethersproject/address": ^5.6.1 - "@ethersproject/bignumber": ^5.6.2 - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/constants": ^5.6.1 - "@ethersproject/keccak256": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/rlp": ^5.6.1 - "@ethersproject/signing-key": ^5.6.2 - checksum: 5cf13936ce406f97b71fc1e99090698c2e4276dcb17c5a022aa3c3f55825961edcb53d4a59166acab797275afa45fb93f1b9b602ebc709da6afa66853f849609 - languageName: node - linkType: hard - "@ethersproject/units@npm:5.7.0": version: 5.7.0 resolution: "@ethersproject/units@npm:5.7.0" @@ -1146,19 +947,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/web@npm:^5.6.1": - version: 5.6.1 - resolution: "@ethersproject/web@npm:5.6.1" - dependencies: - "@ethersproject/base64": ^5.6.1 - "@ethersproject/bytes": ^5.6.1 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/strings": ^5.6.1 - checksum: 4acb62bb04431f5a1b1ec27e88847087676dd2fd72ba40c789f2885493e5eed6b6d387d5b47d4cdfc2775bcbe714e04bfaf0d04a6f30e929310384362e6be429 - languageName: node - linkType: hard - "@ethersproject/wordlists@npm:5.7.0, @ethersproject/wordlists@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/wordlists@npm:5.7.0" @@ -3060,14 +2848,7 @@ __metadata: languageName: node linkType: hard -"bignumber.js@npm:^9.0.1": - version: 9.0.2 - resolution: "bignumber.js@npm:9.0.2" - checksum: 8637b71d0a99104b20413c47578953970006fec6b4df796b9dcfd9835ea9c402ea0e727eba9a5ca9f9a393c1d88b6168c5bbe0887598b708d4f8b4870ad62e1f - languageName: node - linkType: hard - -"bignumber.js@npm:^9.1.1": +"bignumber.js@npm:^9.0.1, bignumber.js@npm:^9.1.1": version: 9.1.1 resolution: "bignumber.js@npm:9.1.1" checksum: ad243b7e2f9120b112d670bb3d674128f0bd2ca1745b0a6c9df0433bd2c0252c43e6315d944c2ac07b4c639e7496b425e46842773cf89c6a2dcd4f31e5c4b11e @@ -3615,20 +3396,7 @@ __metadata: languageName: node linkType: hard -"cli-table3@npm:^0.6.0": - version: 0.6.2 - resolution: "cli-table3@npm:0.6.2" - dependencies: - "@colors/colors": 1.5.0 - string-width: ^4.2.0 - dependenciesMeta: - "@colors/colors": - optional: true - checksum: 2f82391698b8a2a2a5e45d2adcfea5d93e557207f90455a8d4c1aac688e9b18a204d9eb4ba1d322fa123b17d64ea3dc5e11de8b005529f3c3e7dbeb27cb4d9be - languageName: node - linkType: hard - -"cli-table3@npm:^0.6.2": +"cli-table3@npm:^0.6.0, cli-table3@npm:^0.6.2": version: 0.6.3 resolution: "cli-table3@npm:0.6.3" dependencies: @@ -5385,17 +5153,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.12.1, follow-redirects@npm:^1.14.4": - version: 1.15.1 - resolution: "follow-redirects@npm:1.15.1" - peerDependenciesMeta: - debug: - optional: true - checksum: 6aa4e3e3cdfa3b9314801a1cd192ba756a53479d9d8cca65bf4db3a3e8834e62139245cd2f9566147c8dfe2efff1700d3e6aefd103de4004a7b99985e71dd533 - languageName: node - linkType: hard - -"follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.9": +"follow-redirects@npm:^1.12.1, follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.4, follow-redirects@npm:^1.14.9": version: 1.15.2 resolution: "follow-redirects@npm:1.15.2" peerDependenciesMeta: @@ -7909,7 +7667,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"node-fetch@npm:^2.6.0": +"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1": version: 2.6.12 resolution: "node-fetch@npm:2.6.12" dependencies: @@ -7923,20 +7681,6 @@ fsevents@~2.1.1: languageName: node linkType: hard -"node-fetch@npm:^2.6.1": - version: 2.6.11 - resolution: "node-fetch@npm:2.6.11" - dependencies: - whatwg-url: ^5.0.0 - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - checksum: 249d0666a9497553384d46b5ab296ba223521ac88fed4d8a17d6ee6c2efb0fc890f3e8091cafe7f9fba8151a5b8d925db2671543b3409a56c3cd522b468b47b3 - languageName: node - linkType: hard - "node-gyp-build@npm:^4.2.0, node-gyp-build@npm:^4.3.0": version: 4.5.0 resolution: "node-gyp-build@npm:4.5.0" @@ -8629,7 +8373,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"qs@npm:6.11.0, qs@npm:^6.4.0, qs@npm:^6.7.0": +"qs@npm:6.11.0": version: 6.11.0 resolution: "qs@npm:6.11.0" dependencies: @@ -8638,7 +8382,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"qs@npm:^6.9.4": +"qs@npm:^6.4.0, qs@npm:^6.7.0, qs@npm:^6.9.4": version: 6.11.2 resolution: "qs@npm:6.11.2" dependencies: @@ -10334,7 +10078,7 @@ typescript@^4.4.2: languageName: node linkType: hard -"undici@npm:^5.14.0": +"undici@npm:^5.14.0, undici@npm:^5.4.0": version: 5.22.0 resolution: "undici@npm:5.22.0" dependencies: @@ -10343,13 +10087,6 @@ typescript@^4.4.2: languageName: node linkType: hard -"undici@npm:^5.4.0": - version: 5.6.0 - resolution: "undici@npm:5.6.0" - checksum: b9052c2cb9c44b000d375468c266b629032fc72d2024fb07b2988fd42d54937bedb54f44e76bb0ee6ca4c4960c0eee0e77573d178c156647da7ebaa088174baf - languageName: node - linkType: hard - "unfetch@npm:^4.2.0": version: 4.2.0 resolution: "unfetch@npm:4.2.0" From 9ee60f142f9f5c1fe8bc50eef915cf33124a534f Mon Sep 17 00:00:00 2001 From: brr Date: Tue, 18 Jul 2023 10:44:10 +0100 Subject: [PATCH 349/462] Update mattjurenka/morpho to 3.0.0 (#853) Co-authored-by: Patrick McKelvy --- common/configuration.ts | 29 ++ .../plugins/assets/erc20/RewardableERC20.sol | 2 +- .../plugins/assets/morpho-aave/IMorpho.sol | 47 +++ .../MorphoAaveV2TokenisedDeposit.sol | 48 +++ .../morpho-aave/MorphoFiatCollateral.sol | 41 +++ .../morpho-aave/MorphoNonFiatCollateral.sol | 62 ++++ .../MorphoSelfReferentialCollateral.sol | 67 ++++ .../morpho-aave/MorphoTokenisedDeposit.sol | 86 +++++ .../plugins/assets/morpho-aave/README.md | 42 +++ .../MorphoAaveV2TokenisedDepositMock.sol | 40 +++ .../mocks/RewardableERC20WrapperTest.sol | 4 + .../mocks/RewardableERC4626VaultTest.sol | 4 + .../deploy_morpho_aavev2_plugin.ts | 151 ++++++++ .../upgrade-checker-utils/constants.ts | 10 +- tasks/testing/upgrade-checker-utils/trades.ts | 6 +- .../upgrade-checker-utils/upgrades/2_1_0.ts | 6 +- test/plugins/RewardableERC20.test.ts | 96 ++++- .../MorphoAAVEFiatCollateral.test.ts | 333 ++++++++++++++++++ .../MorphoAAVENonFiatCollateral.test.ts | 260 ++++++++++++++ ...orphoAAVESelfReferentialCollateral.test.ts | 236 +++++++++++++ .../MorphoAaveV2TokenisedDeposit.test.ts | 220 ++++++++++++ .../morpho-aave/constants.ts | 10 + .../morpho-aave/mintCollateralTo.ts | 46 +++ 23 files changed, 1830 insertions(+), 16 deletions(-) create mode 100644 contracts/plugins/assets/morpho-aave/IMorpho.sol create mode 100644 contracts/plugins/assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol create mode 100644 contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol create mode 100644 contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol create mode 100644 contracts/plugins/assets/morpho-aave/MorphoSelfReferentialCollateral.sol create mode 100644 contracts/plugins/assets/morpho-aave/MorphoTokenisedDeposit.sol create mode 100644 contracts/plugins/assets/morpho-aave/README.md create mode 100644 contracts/plugins/mocks/MorphoAaveV2TokenisedDepositMock.sol create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts create mode 100644 test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts create mode 100644 test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts create mode 100644 test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts create mode 100644 test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts create mode 100644 test/plugins/individual-collateral/morpho-aave/constants.ts create mode 100644 test/plugins/individual-collateral/morpho-aave/mintCollateralTo.ts diff --git a/common/configuration.ts b/common/configuration.ts index 3a9ddac4db..ab3fff947d 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -23,6 +23,8 @@ export interface ITokens { aBUSD?: string aUSDP?: string aWETH?: string + aWBTC?: string + aCRV?: string cDAI?: string cUSDC?: string cUSDT?: string @@ -56,11 +58,19 @@ export interface ITokens { sUSDC?: string sUSDT?: string sETH?: string + MORPHO?: string + astETH?: string + + // Morpho Aave + maUSDC?: string + maUSDT?: string + maDAI?: string } export interface IFeeds { stETHETH?: string stETHUSD?: string + wBTCBTC?: string } export interface IPools { @@ -86,6 +96,9 @@ interface INetworkConfig { FLUX_FINANCE_COMPTROLLER?: string GNOSIS_EASY_AUCTION?: string EASY_AUCTION_OWNER?: string + MORPHO_AAVE_CONTROLLER?: string + MORPHO_REWARDS_DISTRIBUTOR?: string + MORPHO_AAVE_LENS?: string } export const networkConfig: { [key: string]: INetworkConfig } = { @@ -114,6 +127,8 @@ export const networkConfig: { [key: string]: INetworkConfig } = { aBUSD: '0xA361718326c15715591c299427c62086F69923D9', aUSDP: '0x2e8F4bdbE3d47d7d7DE490437AeA9915D930F1A3', aWETH: '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e', + aWBTC: '0x9ff58f4ffb29fa2266ab25e75e2a8b3503311656', + aCRV: '0x8dae6cb04688c62d939ed9b68d32bc62e49970b1', cDAI: '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', cUSDC: '0x39AA39c021dfbaE8faC545936693aC917d5E7563', cUSDT: '0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9', @@ -147,6 +162,8 @@ export const networkConfig: { [key: string]: INetworkConfig } = { sUSDC: '0xdf0770dF86a8034b3EFEf0A1Bb3c889B8332FF56', sUSDT: '0x38EA452219524Bb87e18dE1C24D3bB59510BD783', sETH: '0x101816545F6bd2b1076434B54383a1E633390A2E', + MORPHO: '0x9994e35db50125e0df82e4c2dde62496ce330999', + astETH: "0x1982b2F5814301d4e9a8b0201555376e62F82428" }, chainlinkFeeds: { RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', @@ -172,6 +189,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH + wBTCBTC: "0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23", // "WBTC/BTC" }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5', @@ -181,6 +199,9 @@ export const networkConfig: { [key: string]: INetworkConfig } = { COMPTROLLER: '0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B', GNOSIS_EASY_AUCTION: '0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101', EASY_AUCTION_OWNER: '0x0da0c3e52c977ed3cbc641ff02dd271c3ed55afe', + MORPHO_AAVE_LENS: '0x507fA343d0A90786d86C7cd885f5C49263A91FF4', + MORPHO_AAVE_CONTROLLER: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', + MORPHO_REWARDS_DISTRIBUTOR: "0x3b14e5c73e0a56d607a8688098326fd4b4292135" }, '3': { name: 'ropsten', @@ -224,6 +245,8 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stkAAVE: '0x4da27a545c0c5B758a6BA100e3a049001de870f5', COMP: '0xc00e94Cb662C3520282E6f5717214004A7f26888', WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + aWBTC: '0x9ff58f4ffb29fa2266ab25e75e2a8b3503311656', + aCRV: '0x8dae6cb04688c62d939ed9b68d32bc62e49970b1', WBTC: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', EURT: '0xC581b735A1688071A1746c968e0798D642EDE491', RSR: '0x320623b8e4ff03373931769a31fc52a4e78b5d70', @@ -243,6 +266,8 @@ export const networkConfig: { [key: string]: INetworkConfig } = { sUSDC: '0xdf0770dF86a8034b3EFEf0A1Bb3c889B8332FF56', sUSDT: '0x38EA452219524Bb87e18dE1C24D3bB59510BD783', sETH: '0x101816545F6bd2b1076434B54383a1E633390A2E', + astETH: "0x1982b2F5814301d4e9a8b0201555376e62F82428", + MORPHO: '0x9994e35db50125e0df82e4c2dde62496ce330999', }, chainlinkFeeds: { RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', @@ -268,12 +293,16 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH + wBTCBTC: "0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23" // "WBTC/BTC" }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', FLUX_FINANCE_COMPTROLLER: '0x95Af143a021DF745bc78e845b54591C53a8B3A51', COMPTROLLER: '0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B', GNOSIS_EASY_AUCTION: '0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101', + MORPHO_AAVE_LENS: '0x507fA343d0A90786d86C7cd885f5C49263A91FF4', + MORPHO_AAVE_CONTROLLER: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', + MORPHO_REWARDS_DISTRIBUTOR: "0x3b14e5c73e0a56d607a8688098326fd4b4292135" }, '5': { name: 'goerli', diff --git a/contracts/plugins/assets/erc20/RewardableERC20.sol b/contracts/plugins/assets/erc20/RewardableERC20.sol index f095000ad5..cfa132d7cf 100644 --- a/contracts/plugins/assets/erc20/RewardableERC20.sol +++ b/contracts/plugins/assets/erc20/RewardableERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity ^0.8.19; -import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/plugins/assets/morpho-aave/IMorpho.sol b/contracts/plugins/assets/morpho-aave/IMorpho.sol new file mode 100644 index 0000000000..9883baa8ca --- /dev/null +++ b/contracts/plugins/assets/morpho-aave/IMorpho.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import { IERC20Metadata } from "../erc20/RewardableERC4626Vault.sol"; +import { IERC4626 } from "../../../vendor/oz/IERC4626.sol"; + +interface IMorpho { + function supply(address _poolToken, uint256 _amount) external; + + function withdraw(address _poolToken, uint256 _amount) external; +} + +interface IMorphoRewardsDistributor { + function claim( + address _account, + uint256 _claimable, + bytes32[] calldata _proof + ) external; +} + +// Used by Morphos Aave V2 and Compound V2 vaults +interface IMorphoUsersLens { + function getCurrentSupplyBalanceInOf(address _poolToken, address _user) + external + view + returns ( + uint256 balanceInP2P, + uint256 balanceOnPool, + uint256 totalBalance + ); +} + +interface IMorphoToken is IERC20Metadata { + function setPublicCapability(bytes4 functionSig, bool enabled) external; + + function setUserRole( + address user, + uint8 role, + bool enabled + ) external; + + function setRoleCapability( + uint8 role, + bytes4 functionSig, + bool enabled + ) external; +} diff --git a/contracts/plugins/assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol b/contracts/plugins/assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol new file mode 100644 index 0000000000..3d4d0d240e --- /dev/null +++ b/contracts/plugins/assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; +import { IMorpho, IMorphoRewardsDistributor, IMorphoUsersLens } from "./IMorpho.sol"; +import { MorphoTokenisedDeposit, MorphoTokenisedDepositConfig } from "./MorphoTokenisedDeposit.sol"; + +struct MorphoAaveV2TokenisedDepositConfig { + IMorpho morphoController; + IMorphoUsersLens morphoLens; + IMorphoRewardsDistributor rewardsDistributor; + IERC20Metadata underlyingERC20; + IERC20Metadata poolToken; + ERC20 rewardToken; +} + +contract MorphoAaveV2TokenisedDeposit is MorphoTokenisedDeposit { + IMorphoUsersLens public immutable morphoLens; + + constructor(MorphoAaveV2TokenisedDepositConfig memory config) + MorphoTokenisedDeposit( + MorphoTokenisedDepositConfig({ + morphoController: config.morphoController, + rewardsDistributor: config.rewardsDistributor, + underlyingERC20: config.underlyingERC20, + poolToken: config.poolToken, + rewardToken: config.rewardToken + }) + ) + { + morphoLens = config.morphoLens; + } + + function getMorphoPoolBalance(address poolToken) + internal + view + virtual + override + returns (uint256) + { + (, , uint256 supplyBalance) = morphoLens.getCurrentSupplyBalanceInOf( + poolToken, + address(this) + ); + return supplyBalance; + } +} diff --git a/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol b/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol new file mode 100644 index 0000000000..bc4debb54e --- /dev/null +++ b/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +// solhint-disable-next-line max-line-length +import { AppreciatingFiatCollateral, CollateralConfig } from "../AppreciatingFiatCollateral.sol"; +import { MorphoTokenisedDeposit } from "./MorphoTokenisedDeposit.sol"; +import { OracleLib } from "../OracleLib.sol"; +// solhint-disable-next-line max-line-length +import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { shiftl_toFix, FIX_ONE } from "../../../libraries/Fixed.sol"; + +/** + * @title MorphoFiatCollateral + * @notice Collateral plugin for a Morpho pool with fiat collateral, like USDC or USDT + * Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} == {UoA} + */ +contract MorphoFiatCollateral is AppreciatingFiatCollateral { + using OracleLib for AggregatorV3Interface; + + MorphoTokenisedDeposit public immutable vault; + uint256 private immutable oneShare; + int8 private immutable refDecimals; + + /// @param config Configuration of this collateral + /// config.erc20 must be a MorphoTokenisedDeposit + /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide + constructor(CollateralConfig memory config, uint192 revenueHiding) + AppreciatingFiatCollateral(config, revenueHiding) + { + require(address(config.erc20) != address(0), "missing erc20"); + vault = MorphoTokenisedDeposit(address(config.erc20)); + oneShare = 10**vault.decimals(); + refDecimals = int8(uint8(IERC20Metadata(vault.asset()).decimals())); + } + + /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens + function _underlyingRefPerTok() internal view override returns (uint192) { + return shiftl_toFix(vault.convertToAssets(oneShare), -refDecimals); + } +} diff --git a/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol b/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol new file mode 100644 index 0000000000..0e39fb12d6 --- /dev/null +++ b/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import { CollateralConfig, MorphoFiatCollateral } from "./MorphoFiatCollateral.sol"; +import { FixLib, CEIL } from "../../../libraries/Fixed.sol"; +import { OracleLib } from "../OracleLib.sol"; +// solhint-disable-next-line max-line-length +import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; + +/** + * @title MorphoNonFiatCollateral + * @notice Collateral plugin for a Morpho pool with non-fiat collateral, like WBTC + * Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} != {UoA} + */ +contract MorphoNonFiatCollateral is MorphoFiatCollateral { + using OracleLib for AggregatorV3Interface; + using FixLib for uint192; + + AggregatorV3Interface public immutable targetUnitChainlinkFeed; // {target/ref} + uint48 public immutable targetUnitOracleTimeout; // {s} + + /// @param config Configuration of this collateral. + /// config.erc20 must be a MorphoTokenisedDeposit + /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide + /// @param targetUnitChainlinkFeed_ Feed units: {target/ref} + /// @param targetUnitOracleTimeout_ {s} oracle timeout to use for targetUnitChainlinkFeed + constructor( + CollateralConfig memory config, + uint192 revenueHiding, + AggregatorV3Interface targetUnitChainlinkFeed_, + uint48 targetUnitOracleTimeout_ + ) MorphoFiatCollateral(config, revenueHiding) { + targetUnitChainlinkFeed = targetUnitChainlinkFeed_; + targetUnitOracleTimeout = targetUnitOracleTimeout_; + } + + /// 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 + /// @return pegPrice {target/ref} The actual price observed in the peg + function tryPrice() + external + view + override + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) + { + // {tar/ref} Get current market peg ({btc/wbtc}) + pegPrice = targetUnitChainlinkFeed.price(targetUnitOracleTimeout); + + // {UoA/tok} = {UoA/ref} * {ref/tok} + uint192 p = chainlinkFeed.price(oracleTimeout).mul(_underlyingRefPerTok()); + uint192 err = p.mul(oracleError, CEIL); + + high = p + err; + low = p - err; + // assert(low <= high); obviously true just by inspection + } +} diff --git a/contracts/plugins/assets/morpho-aave/MorphoSelfReferentialCollateral.sol b/contracts/plugins/assets/morpho-aave/MorphoSelfReferentialCollateral.sol new file mode 100644 index 0000000000..941320bcdf --- /dev/null +++ b/contracts/plugins/assets/morpho-aave/MorphoSelfReferentialCollateral.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; +// solhint-disable-next-line max-line-length +import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import { AppreciatingFiatCollateral, CollateralConfig } from "../AppreciatingFiatCollateral.sol"; +import { MorphoTokenisedDeposit } from "./MorphoTokenisedDeposit.sol"; +import { OracleLib } from "../OracleLib.sol"; +// solhint-disable-next-line max-line-length +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { shiftl_toFix, FIX_ONE, FixLib, CEIL } from "../../../libraries/Fixed.sol"; + +/** + * @title MorphoSelfReferentialCollateral + * @notice Collateral plugin for a Morpho pool with self referential collateral, like WETH + * Expected: {tok} == {ref}, {ref} == {target}, {target} != {UoA} + */ +contract MorphoSelfReferentialCollateral is AppreciatingFiatCollateral { + using OracleLib for AggregatorV3Interface; + using FixLib for uint192; + + MorphoTokenisedDeposit public immutable vault; + uint256 private immutable oneShare; + int8 private immutable refDecimals; + + /// @param config Configuration of this collateral. + /// config.erc20 must be a MorphoTokenisedDeposit + /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide + constructor(CollateralConfig memory config, uint192 revenueHiding) + AppreciatingFiatCollateral(config, revenueHiding) + { + require(config.defaultThreshold == 0, "default threshold not supported"); + require(address(config.erc20) != address(0), "missing erc20"); + vault = MorphoTokenisedDeposit(address(config.erc20)); + oneShare = 10**vault.decimals(); + refDecimals = int8(uint8(IERC20Metadata(vault.asset()).decimals())); + } + + /// 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 + /// @return pegPrice {target/ref} + function tryPrice() + external + view + override + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) + { + // {UoA/tok} = {UoA/ref} * {ref/tok} + uint192 p = chainlinkFeed.price(oracleTimeout).mul(_underlyingRefPerTok()); + uint192 err = p.mul(oracleError, CEIL); + + low = p - err; + high = p + err; + // assert(low <= high); obviously true just by inspection + + pegPrice = targetPerRef(); + } + + /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens + function _underlyingRefPerTok() internal view override returns (uint192) { + return shiftl_toFix(vault.convertToAssets(oneShare), -refDecimals); + } +} diff --git a/contracts/plugins/assets/morpho-aave/MorphoTokenisedDeposit.sol b/contracts/plugins/assets/morpho-aave/MorphoTokenisedDeposit.sol new file mode 100644 index 0000000000..6e33542725 --- /dev/null +++ b/contracts/plugins/assets/morpho-aave/MorphoTokenisedDeposit.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; +import { IMorpho, IMorphoRewardsDistributor, IMorphoUsersLens } from "./IMorpho.sol"; +import { RewardableERC4626Vault } from "../erc20/RewardableERC4626Vault.sol"; + +struct MorphoTokenisedDepositConfig { + IMorpho morphoController; + IMorphoRewardsDistributor rewardsDistributor; + IERC20Metadata underlyingERC20; + IERC20Metadata poolToken; + ERC20 rewardToken; +} + +abstract contract MorphoTokenisedDeposit is RewardableERC4626Vault { + IMorphoRewardsDistributor public immutable rewardsDistributor; + IMorpho public immutable morphoController; + address public immutable poolToken; + address public immutable underlying; + + constructor(MorphoTokenisedDepositConfig memory config) + RewardableERC4626Vault( + config.underlyingERC20, + string.concat("Tokenised Morpho Position - ", config.poolToken.name()), + string.concat("mrp-", config.poolToken.symbol()), + config.rewardToken + ) + { + underlying = address(config.underlyingERC20); + morphoController = config.morphoController; + poolToken = address(config.poolToken); + rewardsDistributor = config.rewardsDistributor; + } + + function rewardTokenBalance(address account) external returns (uint256 claimableRewards) { + _claimAndSyncRewards(); + _syncAccount(account); + claimableRewards = accumulatedRewards[account] - claimedRewards[account]; + } + + // solhint-disable-next-line no-empty-blocks + function _claimAssetRewards() internal virtual override {} + + function getMorphoPoolBalance(address poolToken) internal view virtual returns (uint256); + + function totalAssets() public view virtual override returns (uint256) { + return getMorphoPoolBalance(poolToken); + } + + function _deposit( + address caller, + address receiver, + uint256 assets, + uint256 shares + ) internal virtual override { + SafeERC20.safeTransferFrom(IERC20(underlying), caller, address(this), assets); + SafeERC20.safeApprove(IERC20(underlying), address(morphoController), assets); + morphoController.supply(poolToken, assets); + + _mint(receiver, shares); + emit Deposit(caller, receiver, assets, shares); + } + + function _decimalsOffset() internal view virtual override returns (uint8) { + return 0; + } + + function _withdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal virtual override { + if (caller != owner) { + _spendAllowance(owner, caller, shares); + } + morphoController.withdraw(poolToken, assets); + + _burn(owner, shares); + SafeERC20.safeTransfer(IERC20(underlying), receiver, assets); + emit Withdraw(caller, receiver, owner, assets, shares); + } +} diff --git a/contracts/plugins/assets/morpho-aave/README.md b/contracts/plugins/assets/morpho-aave/README.md new file mode 100644 index 0000000000..451582692d --- /dev/null +++ b/contracts/plugins/assets/morpho-aave/README.md @@ -0,0 +1,42 @@ +Submission Documentation + +Twitter: MatthewJurenka, TG: mjurenka, Discord: fronts#7618, + +This submission mirrors the Compound V2 family of plugins to allow support +for all pools on Morpho AAVE. As such the collateral token, reference unit, +and target unit would change depending on the desired pool that is desired. +For example for the USDT pool, the reference unit would be USDT, the target unit +would be USD, and the collateral token would be an instance of +MorphoAAVEPositionWrapper deployed alongside the plugin. + +To deploy an instance of this plugin, you must deploy an instance of +MorphoAAVEPositionWrapper and then pass it as config to the +MorphoAAVEFiatCollateral, MorphoAAVESelfReferentialCollateral, or +MorphoAAVENonFiatCollateral, depending on the specific pool token that is +desired to be supported. More specific documentation on contructor parameters +can be found in the respective solidity files of these contracts. + +Morpho is a lending protocol built on top of AAVE that allows users to deposit +one token as collateral, then get a significant percentage (i.e. 80%) of that +collateral back to be repayed later. Suppliers earn APY by providing the borrowed +tokens to Morpho. Suppliers do not lose money if the borrower do not repay +Morpho, because Morpho can choose to seize the borrower's collateral and repay +the supplier. The only way for a supplier to lose money is if the value of that +collateral experiences a flash crash that happens before Morpho can sell off the +collateral. Alternatively this could occur if there is a serious issue with +Chainlink and the price feeds break, causing incorrect liquidations and other logic. +MorphoAAVEPositionWrapper is designed to maintain an exchange rate on mint and burn +similar to vault tokens and will ensure that yield of the contract's morpho pool +is proportionately distributed to token burners. + +This plugin follows the established and accepted logic for cTokens for disabling +the collateral plugin if it were to default. + +# Claiming rewards + +Unfortunately Morpho uses a rewards scheme that requires the results +of off-chain computation to be piped into an on-chain function, +which is not possible to do with Reserve's collateral plugin interface. +https://integration.morpho.xyz/track-and-manage-position/manage-positions-on-morpho/claim-morpho-rewards +claiming rewards for this wrapper can be done by any account, and must be done on Morpho's rewards distributor contract +https://etherscan.io/address/0x3b14e5c73e0a56d607a8688098326fd4b4292135 diff --git a/contracts/plugins/mocks/MorphoAaveV2TokenisedDepositMock.sol b/contracts/plugins/mocks/MorphoAaveV2TokenisedDepositMock.sol new file mode 100644 index 0000000000..4c999e69eb --- /dev/null +++ b/contracts/plugins/mocks/MorphoAaveV2TokenisedDepositMock.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import { MorphoAaveV2TokenisedDeposit, MorphoAaveV2TokenisedDepositConfig } from "../assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { shiftl_toFix } from "../../libraries/Fixed.sol"; + +contract MorphoAaveV2TokenisedDepositMock is MorphoAaveV2TokenisedDeposit { + using Math for uint256; + + constructor(MorphoAaveV2TokenisedDepositConfig memory config) + MorphoAaveV2TokenisedDeposit(config) + {} + + uint192 internal exchangeRate = 10**18; + + function setExchangeRate(uint192 rate) external { + exchangeRate = rate; + } + + function getExchangeRate() external view returns (uint192) { + return exchangeRate; + } + + function _convertToAssets(uint256 shares, Math.Rounding rounding) + internal + view + virtual + override + returns (uint256) + { + uint256 out = shares.mulDiv( + totalAssets() + 1, + totalSupply() + 10**_decimalsOffset(), + rounding + ); + + return (out * exchangeRate) / 10**18; + } +} diff --git a/contracts/plugins/mocks/RewardableERC20WrapperTest.sol b/contracts/plugins/mocks/RewardableERC20WrapperTest.sol index a0629bc11d..52fa49cc90 100644 --- a/contracts/plugins/mocks/RewardableERC20WrapperTest.sol +++ b/contracts/plugins/mocks/RewardableERC20WrapperTest.sol @@ -15,4 +15,8 @@ contract RewardableERC20WrapperTest is RewardableERC20Wrapper { function _claimAssetRewards() internal virtual override { ERC20MockRewarding(address(underlying)).claim(); } + + function sync() external { + _claimAndSyncRewards(); + } } diff --git a/contracts/plugins/mocks/RewardableERC4626VaultTest.sol b/contracts/plugins/mocks/RewardableERC4626VaultTest.sol index 24f7b690af..27ee042b33 100644 --- a/contracts/plugins/mocks/RewardableERC4626VaultTest.sol +++ b/contracts/plugins/mocks/RewardableERC4626VaultTest.sol @@ -15,4 +15,8 @@ contract RewardableERC4626VaultTest is RewardableERC4626Vault { function _claimAssetRewards() internal virtual override { ERC20MockRewarding(asset()).claim(); } + + function sync() external { + _claimAndSyncRewards(); + } } diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts new file mode 100644 index 0000000000..72fa5d3dea --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts @@ -0,0 +1,151 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { networkConfig } from '../../../../common/configuration' +import { bn, fp } from '../../../../common/numbers' +import { ethers } from 'hardhat' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { priceTimeout, oracleTimeout } from '../../utils' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + const revenueHiding = fp('1e-6').toString() // revenueHiding = 0.0001% + + /******** Deploy Morpho - AaveV2 **************************/ + + /******** Morpho token vaults **************************/ + console.log(`Deploying morpho token vaults to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + const MorphoTokenisedDepositFactory = await ethers.getContractFactory("MorphoAaveV2TokenisedDeposit") + const maUSDT = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, + underlyingERC20: networkConfig[chainId].tokens.USDT!, + poolToken: networkConfig[chainId].tokens.aUSDT!, + rewardToken: networkConfig[chainId].tokens.MORPHO!, + }) + + const maUSDC = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, + underlyingERC20: networkConfig[chainId].tokens.USDC!, + poolToken: networkConfig[chainId].tokens.aUSDC!, + rewardToken: networkConfig[chainId].tokens.MORPHO!, + }) + const maDAI = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, + underlyingERC20: networkConfig[chainId].tokens.DAI!, + poolToken: networkConfig[chainId].tokens.aDAI!, + rewardToken: networkConfig[chainId].tokens.MORPHO!, + }) + + await maUSDT.deployed() + await maUSDC.deployed() + await maDAI.deployed() + + assetCollDeployments.erc20s.maUSDT = maUSDT.address + assetCollDeployments.erc20s.maUSDC = maUSDC.address + assetCollDeployments.erc20s.maDAI = maDAI.address + + /******** Morpho collateral **************************/ + const FiatCollateralFactory = await hre.ethers.getContractFactory( + "MorphoFiatCollateral" + ) + const stablesOracleError = fp('0.0025') // 0.25% + + { + const collateral = await FiatCollateralFactory.connect(deployer).deploy({ + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDT!, + oracleError: stablesOracleError.toString(), + erc20: maUSDT.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 1 hr + targetName: ethers.utils.formatBytes32String("USD"), + defaultThreshold: stablesOracleError.add(fp("0.01")), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + }, + revenueHiding + ); + assetCollDeployments.collateral.maUSDT = collateral.address + deployedCollateral.push(collateral.address.toString()) + + } + { + + const collateral = await FiatCollateralFactory.connect(deployer).deploy({ + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, + oracleError: stablesOracleError.toString(), + erc20: maUSDC.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 1 hr + targetName: ethers.utils.formatBytes32String("USD"), + defaultThreshold: stablesOracleError.add(fp("0.01")), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + }, + revenueHiding + ); + assetCollDeployments.collateral.maUSDC = collateral.address + deployedCollateral.push(collateral.address.toString()) + } + { + const collateral = await FiatCollateralFactory.connect(deployer).deploy({ + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI!, + oracleError: stablesOracleError.toString(), + erc20: maDAI.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: ethers.utils.formatBytes32String("USD"), + defaultThreshold: stablesOracleError.add(fp("0.01")), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + }, + revenueHiding + ); + assetCollDeployments.collateral.maDAI = collateral.address + deployedCollateral.push(collateral.address.toString()) + } + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/tasks/testing/upgrade-checker-utils/constants.ts b/tasks/testing/upgrade-checker-utils/constants.ts index e9a3afbc34..fe5640245c 100644 --- a/tasks/testing/upgrade-checker-utils/constants.ts +++ b/tasks/testing/upgrade-checker-utils/constants.ts @@ -1,17 +1,23 @@ import { networkConfig } from '#/common/configuration' export const whales: { [key: string]: string } = { - [networkConfig['1'].tokens.USDT!.toLowerCase()]: '0x5754284f345afc66a98fbB0a0Afe71e0F007B949', + [networkConfig['1'].tokens.USDT!.toLowerCase()]: '0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503', + [networkConfig['1'].tokens.USDC!.toLowerCase()]: '0x756D64Dc5eDb56740fC617628dC832DDBCfd373c', + [networkConfig['1'].tokens.RSR!.toLowerCase()]: '0x6bab6EB87Aa5a1e4A8310C73bDAAA8A5dAAd81C1', [networkConfig['1'].tokens.cUSDT!.toLowerCase()]: '0x5754284f345afc66a98fbB0a0Afe71e0F007B949', [networkConfig['1'].tokens.aUSDT!.toLowerCase()]: '0x5754284f345afc66a98fbB0a0Afe71e0F007B949', ['0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9'.toLowerCase()]: '0x5754284f345afc66a98fbB0a0Afe71e0F007B949', // saUSDT - [networkConfig['1'].tokens.USDC!.toLowerCase()]: '0x51eDF02152EBfb338e03E30d65C15fBf06cc9ECC', [networkConfig['1'].tokens.aUSDC!.toLowerCase()]: '0x51eDF02152EBfb338e03E30d65C15fBf06cc9ECC', [networkConfig['1'].tokens.cUSDC!.toLowerCase()]: '0x51eDF02152EBfb338e03E30d65C15fBf06cc9ECC', ['0x60C384e226b120d93f3e0F4C502957b2B9C32B15'.toLowerCase()]: '0x51eDF02152EBfb338e03E30d65C15fBf06cc9ECC', // saUSDC [networkConfig['1'].tokens.RSR!.toLowerCase()]: '0x6bab6EB87Aa5a1e4A8310C73bDAAA8A5dAAd81C1', + [networkConfig['1'].tokens.WBTC!.toLowerCase()]: '0x8eb8a3b98659cce290402893d0123abb75e3ab28', + [networkConfig['1'].tokens.stETH!.toLowerCase()]: '0x176F3DAb24a159341c0509bB36B833E7fdd0a132', + [networkConfig['1'].tokens.WETH!.toLowerCase()]: '0x8EB8a3b98659Cce290402893d0123abb75E3ab28', + [networkConfig['1'].tokens.DAI!.toLowerCase()]: '0x8EB8a3b98659Cce290402893d0123abb75E3ab28', + [networkConfig['1'].tokens.CRV!.toLowerCase()]: "0xf977814e90da44bfa03b6295a0616a897441acec" } export const collateralToUnderlying: { [key: string]: string } = { diff --git a/tasks/testing/upgrade-checker-utils/trades.ts b/tasks/testing/upgrade-checker-utils/trades.ts index 516d891e58..4db79d0220 100644 --- a/tasks/testing/upgrade-checker-utils/trades.ts +++ b/tasks/testing/upgrade-checker-utils/trades.ts @@ -9,7 +9,6 @@ import { collateralToUnderlying, whales } from './constants' import { bn, fp } from '#/common/numbers' import { logToken } from './logs' import { networkConfig } from '#/common/configuration' -import { ERC20Mock } from '@typechain/ERC20Mock' export const runTrade = async ( hre: HardhatRuntimeEnvironment, @@ -42,10 +41,8 @@ export const runTrade = async ( buyAmount = buyAmount.add(fp('1').div(bn(10 ** (18 - buyDecimals)))) const gnosis = await hre.ethers.getContractAt('EasyAuction', await trade.gnosis()) - console.log('impersonate', whales[buyTokenAddress.toLowerCase()], buyTokenAddress) await whileImpersonating(hre, whales[buyTokenAddress.toLowerCase()], async (whale) => { const sellToken = await hre.ethers.getContractAt('ERC20Mock', buyTokenAddress) - // await mintTokensIfNeeded(hre, buyTokenAddress, buyAmount, whale.address) await sellToken.connect(whale).approve(gnosis.address, buyAmount) await gnosis .connect(whale) @@ -63,7 +60,6 @@ export const runTrade = async ( await trader.settleTrade(tradeToken) console.log(`Settled trade for ${logToken(buyTokenAddress)}.`) } - // impersonate the whale to get the token const mintTokensIfNeeded = async ( hre: HardhatRuntimeEnvironment, @@ -121,4 +117,4 @@ const mintAToken = async ( await underlying.connect(usdtSigner).approve(collateral.address, amount) await collateral.connect(usdtSigner).deposit(recipient, amount, 0, true) }) -} +} \ No newline at end of file diff --git a/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts b/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts index c5398e5744..6b36c96ec1 100644 --- a/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts +++ b/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts @@ -127,13 +127,12 @@ export default async ( let parsedLog: LogDescription | undefined try { parsedLog = iface.parseLog(event) - } catch {} + } catch { } if (parsedLog && parsedLog.name == 'TradeStarted') { console.log( `\n====== Trade Started: sell ${logToken(parsedLog.args.sell)} / buy ${logToken( parsedLog.args.buy - )} ======\n\tmbuyAmount: ${parsedLog.args.minBuyAmount}\n\tsellAmount: ${ - parsedLog.args.sellAmount + )} ======\n\tmbuyAmount: ${parsedLog.args.minBuyAmount}\n\tsellAmount: ${parsedLog.args.sellAmount }` ) // @@ -188,6 +187,7 @@ export default async ( console.log('Trying again...') } } + }) const lastTimestamp = await getLatestBlockTimestamp(hre) diff --git a/test/plugins/RewardableERC20.test.ts b/test/plugins/RewardableERC20.test.ts index 5e16d63c85..53459332d9 100644 --- a/test/plugins/RewardableERC20.test.ts +++ b/test/plugins/RewardableERC20.test.ts @@ -7,17 +7,20 @@ import { ERC20MockDecimals, ERC20MockRewarding, RewardableERC20Wrapper, + RewardableERC20WrapperTest, RewardableERC4626Vault, + RewardableERC4626VaultTest, } from '../../typechain' import { cartesianProduct } from '../utils/cases' import { useEnv } from '#/utils/env' import { Implementation } from '../fixtures' import snapshotGasCost from '../utils/snapshotGasCost' +import { formatUnits, parseUnits } from 'ethers/lib/utils' type Fixture = () => Promise interface RewardableERC20Fixture { - rewardableVault: RewardableERC4626Vault | RewardableERC20Wrapper + rewardableVault: RewardableERC4626VaultTest | RewardableERC20WrapperTest rewardableAsset: ERC20MockRewarding rewardToken: ERC20MockDecimals } @@ -59,7 +62,7 @@ for (const wrapperName of wrapperNames) { ) const rewardableVaultFactory: ContractFactory = await ethers.getContractFactory(wrapperName) - const rewardableVault = ( + const rewardableVault = ( await rewardableVaultFactory.deploy( rewardableAsset.address, 'Rewarding Test Asset Vault', @@ -95,6 +98,20 @@ for (const wrapperName of wrapperNames) { return wrapperERC4626.withdraw(amount, to, to) } } + const withdrawAll = async ( + wrapper: RewardableERC4626Vault | RewardableERC20Wrapper, + to?: string + ): Promise => { + const owner = await wrapper.signer.getAddress() + to = to || owner + if (wrapperName == Wrapper.ERC20) { + const wrapperERC20 = wrapper as RewardableERC20Wrapper + return wrapperERC20.withdraw(await wrapperERC20.balanceOf(owner), to) + } else { + const wrapperERC4626 = wrapper as RewardableERC4626Vault + return wrapperERC4626.withdraw(await wrapperERC4626.maxWithdraw(owner), to, owner) + } + } const runTests = (assetDecimals: number, rewardDecimals: number) => { describe(wrapperName, () => { @@ -102,7 +119,7 @@ for (const wrapperName of wrapperNames) { let shareDecimals: number // Assets - let rewardableVault: RewardableERC20Wrapper | RewardableERC4626Vault + let rewardableVault: RewardableERC20WrapperTest | RewardableERC4626VaultTest let rewardableAsset: ERC20MockRewarding let rewardToken: ERC20MockDecimals @@ -110,8 +127,8 @@ for (const wrapperName of wrapperNames) { let alice: Wallet let bob: Wallet - const initBalance = fp('10000').div(bn(10).pow(18 - assetDecimals)) - const rewardAmount = fp('200').div(bn(10).pow(18 - rewardDecimals)) + const initBalance = parseUnits('10000', assetDecimals) + const rewardAmount = parseUnits('200', rewardDecimals) let oneShare: BigNumber let initShares: BigNumber @@ -154,6 +171,75 @@ for (const wrapperName of wrapperNames) { expect(await rewardableVault.rewardsPerShare()).to.equal(bn(0)) expect(await rewardableVault.lastRewardsPerShare(alice.address)).to.equal(bn(0)) }) + + it('supports direct airdrops', async () => { + await rewardableVault + .connect(alice) + .deposit(parseUnits('10', assetDecimals), alice.address) + expect(await rewardableVault.rewardsPerShare()).to.equal(bn(0)) + expect(await rewardableVault.lastRewardsPerShare(alice.address)).to.equal(bn(0)) + await rewardToken.mint(rewardableVault.address, parseUnits('10', rewardDecimals)) + await rewardableVault.sync() + expect(await rewardableVault.rewardsPerShare()).to.equal(parseUnits('1', rewardDecimals)) + }) + + it('correctly handles reward tracking if supply is burned', async () => { + await rewardableVault + .connect(alice) + .deposit(parseUnits('10', assetDecimals), alice.address) + expect(await rewardableVault.rewardsPerShare()).to.equal(bn(0)) + expect(await rewardableVault.lastRewardsPerShare(alice.address)).to.equal(bn(0)) + await rewardToken.mint(rewardableVault.address, parseUnits('10', rewardDecimals)) + await rewardableVault.sync() + expect(await rewardableVault.rewardsPerShare()).to.equal(parseUnits('1', rewardDecimals)) + + // Setting supply to 0 + await withdrawAll(rewardableVault.connect(alice)) + expect(await rewardableVault.totalSupply()).to.equal(bn(0)) + + // Add some undistributed reward tokens to the vault + await rewardToken.mint(rewardableVault.address, parseUnits('10', rewardDecimals)) + + // Claim whatever rewards are available + expect(await rewardToken.balanceOf(alice.address)).to.be.equal(bn(0)) + await rewardableVault.connect(alice).claimRewards() + + expect(await rewardToken.balanceOf(alice.address)).to.be.equal( + parseUnits('10', rewardDecimals) + ) + + // Nothing updates.. as totalSupply as totalSupply is 0 + await rewardableVault.sync() + expect(await rewardableVault.rewardsPerShare()).to.equal(parseUnits('1', rewardDecimals)) + await rewardableVault + .connect(alice) + .deposit(parseUnits('10', assetDecimals), alice.address) + await rewardableVault.sync() + + await rewardableVault.connect(alice).claimRewards() + expect(await rewardToken.balanceOf(alice.address)).to.be.equal( + parseUnits('20', rewardDecimals) + ) + }) + + it('1 wei supply', async () => { + await rewardableVault.connect(alice).deposit('1', alice.address) + expect(await rewardableVault.rewardsPerShare()).to.equal(bn(0)) + expect(await rewardableVault.lastRewardsPerShare(alice.address)).to.equal(bn(0)) + await rewardToken.mint(rewardableVault.address, parseUnits('1', rewardDecimals)) + await rewardableVault.sync() + await rewardableVault.connect(bob).deposit('10', bob.address) + await rewardableVault.connect(alice).deposit('10', alice.address) + await rewardToken.mint(rewardableVault.address, parseUnits('99', rewardDecimals)) + await rewardableVault.connect(alice).claimRewards() + await rewardableVault.connect(bob).claimRewards() + const aliceBalance = await rewardToken.balanceOf(await alice.getAddress()) + const bobBalance = await rewardToken.balanceOf(await bob.getAddress()) + + expect(parseFloat(formatUnits(aliceBalance, rewardDecimals))).to.be.closeTo(52.8, 0.1) + + expect(parseFloat(formatUnits(bobBalance, rewardDecimals))).to.be.closeTo(47.1, 0.1) + }) }) describe('alice deposit, accrue, alice deposit, bob deposit', () => { diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts new file mode 100644 index 0000000000..f03e2b6c1d --- /dev/null +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts @@ -0,0 +1,333 @@ +import { networkConfig } from '#/common/configuration' +import { bn, fp } from '#/common/numbers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { MockV3Aggregator } from '@typechain/MockV3Aggregator' +import { TestICollateral } from '@typechain/TestICollateral' +import { ERC20Mock, MockV3Aggregator__factory } from '@typechain/index' +import { expect } from 'chai' +import { BigNumber, BigNumberish, ContractFactory, utils } from 'ethers' +import { ethers } from 'hardhat' +import collateralTests from '../collateralTests' +import { getResetFork } from '../helpers' +import { CollateralOpts } from '../pluginTestTypes' +import { + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + FORK_BLOCK, + ORACLE_ERROR, + ORACLE_TIMEOUT, + PRICE_TIMEOUT, +} from './constants' +import hre from 'hardhat' +import { MorphoAaveCollateralFixtureContext, mintCollateralTo } from './mintCollateralTo' +import { setCode } from '@nomicfoundation/hardhat-network-helpers' +import { whileImpersonating } from '#/utils/impersonation' +import { whales } from '#/tasks/testing/upgrade-checker-utils/constants' +import { formatEther } from 'ethers/lib/utils' + +interface MAFiatCollateralOpts extends CollateralOpts { + underlyingToken?: string + poolToken?: string + defaultPrice?: BigNumberish + defaultRefPerTok?: BigNumberish +} + +export const deployCollateral = async ( + opts: MAFiatCollateralOpts = {} +): Promise => { + opts = { ...defaultCollateralOpts, ...opts } + + const MorphoAAVECollateralFactory: ContractFactory = await ethers.getContractFactory( + 'MorphoFiatCollateral' + ) + if (opts.erc20 == null) { + const MorphoTokenisedDepositFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDepositMock' + ) + const wrapperMock = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, + underlyingERC20: opts.underlyingToken!, + poolToken: opts.poolToken!, + rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfig[1].tokens.MORPHO!, + }) + opts.erc20 = wrapperMock.address + } + + const collateral = await MorphoAAVECollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + { gasLimit: 2000000000 } + ) + await collateral.deployed() + + await expect(collateral.refresh()) + + return collateral +} + +type Fixture = () => Promise + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + inOpts: MAFiatCollateralOpts = {} +): Fixture => { + const makeCollateralFixtureContext = async () => { + const opts = { ...defaultCollateralOpts, ...inOpts } + + const MorphoTokenisedDepositFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDepositMock' + ) + const erc20Factory = await ethers.getContractFactory('ERC20Mock') + const underlyingErc20 = await erc20Factory.attach(opts.underlyingToken!) + const wrapperMock = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, + underlyingERC20: opts.underlyingToken!, + poolToken: opts.poolToken!, + rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfig[1].tokens.MORPHO!, + }) + + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, opts.defaultPrice!) + ) + const collateralOpts = { + ...opts, + erc20: wrapperMock.address, + chainlinkFeed: chainlinkFeed.address, + } + + const collateral = await deployCollateral(collateralOpts) + + return { + alice, + collateral, + underlyingErc20: underlyingErc20, + chainlinkFeed, + tok: wrapperMock as unknown as ERC20Mock, + morphoWrapper: wrapperMock, + } as MorphoAaveCollateralFixtureContext + } + + return makeCollateralFixtureContext +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const reduceTargetPerRef = async () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const increaseTargetPerRef = async () => {} + +const changeRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + percentChange: BigNumber +) => { + const rate = await ctx.morphoWrapper.getExchangeRate() + await ctx.morphoWrapper.setExchangeRate(rate.add(rate.mul(percentChange).div(bn('100')))) +} + +// prettier-ignore +const reduceRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctDecrease: BigNumberish +) => { + await changeRefPerTok( + ctx, + bn(pctDecrease).mul(-1) + ) +} +// prettier-ignore +const increaseRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctIncrease: BigNumberish +) => { + await changeRefPerTok( + ctx, + bn(pctIncrease) + ) +} +const getExpectedPrice = async (ctx: MorphoAaveCollateralFixtureContext): Promise => { + const clData = await ctx.chainlinkFeed.latestRoundData() + const clDecimals = await ctx.chainlinkFeed.decimals() + + const refPerTok = await ctx.collateral.refPerTok() + return clData.answer + .mul(bn(10).pow(18 - clDecimals)) + .mul(refPerTok) + .div(fp('1')) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => { + it('tokenised deposits can correctly claim rewards', async () => { + const morphoTokenOwner = '0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa' + const forkBlock = 17574117 + const claimer = '0x05e818959c2Aa4CD05EDAe9A099c38e7Bdc377C6' + const reset = getResetFork(forkBlock) + await reset() + const MorphoTokenisedDepositFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDeposit' + ) + const usdtVault = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, + underlyingERC20: networkConfig[1].tokens.USDT!, + poolToken: networkConfig[1].tokens.aUSDT!, + rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfig[1].tokens.MORPHO!, + }) + const vaultCode = await ethers.provider.getCode(usdtVault.address) + await setCode(claimer, vaultCode) + + const vaultWithClaimableRewards = usdtVault.attach(claimer) + const erc20Factory = await ethers.getContractFactory('ERC20Mock') + const underlyingERC20 = await erc20Factory.attach(networkConfig[1].tokens.USDT!) + const depositAmount = utils.parseUnits('1000', 6) + + const user = hre.ethers.provider.getSigner(0) + const userAddress = await user.getAddress() + + expect( + formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) + ).to.be.equal('0.0') + + await whileImpersonating( + hre, + whales[networkConfig[1].tokens.USDT!.toLowerCase()], + async (whaleSigner) => { + await underlyingERC20.connect(whaleSigner).approve(vaultWithClaimableRewards.address, 0) + await underlyingERC20 + .connect(whaleSigner) + .approve(vaultWithClaimableRewards.address, ethers.constants.MaxUint256) + await vaultWithClaimableRewards.connect(whaleSigner).mint(depositAmount, userAddress) + } + ) + + expect( + formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) + ).to.be.equal('8.60295466891613') + + const morphoRewards = await ethers.getContractAt( + 'IMorphoRewardsDistributor', + networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR! + ) + await morphoRewards.claim(vaultWithClaimableRewards.address, '14162082619942089266', [ + '0x49bb35f20573d5b927c5b5c15c904839cacdf83c6119450ccb6c2ed0647aa71b', + '0xfb9f4530177774effb7af9c1723c7087f60cd135a0cb5f409ec7bbc792a79235', + '0x16dcb8d895b9520c20f476bfc23125aa8f47b800a3bea63b63f89abe158a16fe', + '0x70b3bcf266272051262da958e86efb68a3621977aab0fa0205a5e47a83f3b129', + '0xc06f6781c002b96e5860094fec5ac0692e6e39b3aafa0e02a2c9f87a993a55cb', + '0x679aafaa2e4772160288874aa86f2f1baf6ab7409109da7ad96d3b6d5cf2c3ee', + '0x5b9f1e5d9dfbdc65ec0166a6f1e2fe4a31396fa31739cce54962f1ed43638ff1', + '0xb2db22839637b4c40c7ecc800df0ed8a205c9c31d7d49c41c3d105a62d1c5526', + '0xa26071ec1b113e9033dcbccd7680617d3e75fa626b9f1c43dbc778f641f162da', + '0x53eb58db4c07b67b3bce54b530c950a4ef0c229a3ed2506c53d7c4e31ecc6bfc', + '0x14c512bd39f8b1d13d4cfaad2b4473c4022d01577249ecc97fbf0a64244378ee', + '0xea8c2ee8d43e37ceb7b0c04d59106eff88afbe3e911b656dec7caebd415ea696', + ]) + + expect( + formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) + ).to.be.equal('14.162082619942089') + + // MORPHO is not a transferable token. + // POST Launch we could ask the Morpho team if our TokenVaults could get permission to transfer the MORPHO tokens. + // Otherwise owners of the TokenVault shares need to wait until the protocol enables the transfer function on the MORPHO token. + + await whileImpersonating(hre, morphoTokenOwner, async (signer) => { + const morphoTokenInst = await ethers.getContractAt( + 'IMorphoToken', + networkConfig[1].tokens.MORPHO!, + signer + ) + + await morphoTokenInst.connect(signer).setUserRole(vaultWithClaimableRewards.address, 0, true) + }) + + const morphoTokenInst = await ethers.getContractAt( + 'IMorphoToken', + networkConfig[1].tokens.MORPHO!, + user + ) + expect(formatEther(await morphoTokenInst.balanceOf(userAddress))).to.be.equal('0.0') + + await vaultWithClaimableRewards.claimRewards() + + expect( + formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) + ).to.be.equal('0.0') + + expect(formatEther(await morphoTokenInst.balanceOf(userAddress))).to.be.equal( + '14.162082619942089' + ) + }) +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} +// eslint-disable-next-line @typescript-eslint/no-empty-function +const beforeEachRewardsTest = async () => {} + +export const defaultCollateralOpts: MAFiatCollateralOpts = { + targetName: ethers.utils.formatBytes32String('USDT'), + underlyingToken: networkConfig[1].tokens.USDT!, + poolToken: networkConfig[1].tokens.aUSDT!, + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: networkConfig[1].chainlinkFeeds.USDT!, + oracleTimeout: ORACLE_TIMEOUT, + oracleError: ORACLE_ERROR, + maxTradeVolume: bn(1000000), + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: fp('0'), + defaultPrice: bn('1e8'), + defaultRefPerTok: fp('1'), +} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it.skip, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itHasRevenueHiding: it, + resetFork: getResetFork(FORK_BLOCK), + collateralName: 'MorphoAAVEV2FiatCollateral', + chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts new file mode 100644 index 0000000000..77a2a56e20 --- /dev/null +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts @@ -0,0 +1,260 @@ +import { networkConfig } from '#/common/configuration' +import { bn, fp } from '#/common/numbers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { MockV3Aggregator } from '@typechain/MockV3Aggregator' +import { TestICollateral } from '@typechain/TestICollateral' +import { + ERC20Mock, + MockV3Aggregator__factory, + MorphoNonFiatCollateral__factory, +} from '@typechain/index' +import { expect } from 'chai' +import { BigNumber, BigNumberish } from 'ethers' +import { parseEther, parseUnits } from 'ethers/lib/utils' +import { ethers } from 'hardhat' +import collateralTests from '../collateralTests' +import { getResetFork } from '../helpers' +import { CollateralOpts } from '../pluginTestTypes' +import { + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + FORK_BLOCK, + ORACLE_ERROR, + ORACLE_TIMEOUT, + PRICE_TIMEOUT, +} from './constants' +import { MorphoAaveCollateralFixtureContext, mintCollateralTo } from './mintCollateralTo' + +interface MAFiatCollateralOpts extends CollateralOpts { + underlyingToken?: string + poolToken?: string + defaultPrice?: BigNumberish + defaultRefPerTok?: BigNumberish + + targetPrRefFeed?: string + refPerTokChainlinkTimeout?: BigNumberish +} + +export const deployCollateral = async ( + opts: MAFiatCollateralOpts = {} +): Promise => { + opts = { ...defaultCollateralOpts, ...opts } + + const MorphoAAVECollateralFactory: MorphoNonFiatCollateral__factory = + await ethers.getContractFactory('MorphoNonFiatCollateral') + if (opts.erc20 == null) { + const MorphoTokenisedDepositMockFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDepositMock' + ) + const wrapperMock = await MorphoTokenisedDepositMockFactory.deploy({ + morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, + underlyingERC20: opts.underlyingToken!, + poolToken: opts.poolToken!, + rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfig[1].tokens.MORPHO!, + }) + opts.erc20 = wrapperMock.address + } + const collateral = (await MorphoAAVECollateralFactory.deploy( + { + erc20: opts.erc20!, + targetName: opts.targetName!, + priceTimeout: opts.priceTimeout!, + chainlinkFeed: opts.chainlinkFeed!, + oracleError: opts.oracleError!, + oracleTimeout: opts.oracleTimeout!, + maxTradeVolume: opts.maxTradeVolume!, + defaultThreshold: opts.defaultThreshold!, + delayUntilDefault: opts.delayUntilDefault!, + }, + opts.revenueHiding!, + opts.targetPrRefFeed!, + opts.refPerTokChainlinkTimeout!, + { gasLimit: 2000000000 } + )) as unknown as TestICollateral + await collateral.deployed() + + await expect(collateral.refresh()) + + return collateral +} + +type Fixture = () => Promise + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + inOpts: MAFiatCollateralOpts = {} +): Fixture => { + const makeCollateralFixtureContext = async () => { + const opts = { ...defaultCollateralOpts, ...inOpts } + const MorphoTokenisedDepositMockFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDepositMock' + ) + const erc20Factory = await ethers.getContractFactory('ERC20Mock') + const underlyingErc20 = erc20Factory.attach(opts.underlyingToken!) + const wrapperMock = await MorphoTokenisedDepositMockFactory.deploy({ + morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, + underlyingERC20: opts.underlyingToken!, + poolToken: opts.poolToken!, + rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfig[1].tokens.MORPHO!, + }) + + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, opts.defaultPrice!) + ) + + const targetPrRefFeed = ( + await MockV3AggregatorFactory.deploy(8, opts.defaultRefPerTok!) + ) + + const collateralOpts = { + ...opts, + erc20: wrapperMock.address, + chainlinkFeed: chainlinkFeed.address, + targetPrRefFeed: targetPrRefFeed.address, + } + const collateral = await deployCollateral(collateralOpts) + + return { + alice, + collateral, + chainlinkFeed, + targetPrRefFeed, + tok: wrapperMock as unknown as ERC20Mock, + morphoWrapper: wrapperMock, + underlyingErc20: underlyingErc20, + } as MorphoAaveCollateralFixtureContext + } + + return makeCollateralFixtureContext +} + +/* + Define helper functions +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const reduceTargetPerRef = async () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const increaseTargetPerRef = async () => {} + +const changeRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + percentChange: BigNumber +) => { + const rate = await ctx.morphoWrapper.getExchangeRate() + await ctx.morphoWrapper.setExchangeRate(rate.add(rate.mul(percentChange).div(bn('100')))) + + // { + // const lastRound = await ctx.targetPrRefFeed!.latestRoundData() + // const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) + // await ctx.targetPrRefFeed!.updateAnswer(nextAnswer) + // } + + // { + // const lastRound = await ctx.chainlinkFeed.latestRoundData() + // const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) + // await ctx.chainlinkFeed.updateAnswer(nextAnswer) + // } +} + +// prettier-ignore +const reduceRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctDecrease: BigNumberish +) => { + await changeRefPerTok( + ctx, + bn(pctDecrease).mul(-1) + ) +} +// prettier-ignore +const increaseRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctIncrease: BigNumberish +) => { + await changeRefPerTok( + ctx, + bn(pctIncrease) + ) +} + +const getExpectedPrice = async (ctx: MorphoAaveCollateralFixtureContext): Promise => { + const clData = await ctx.chainlinkFeed.latestRoundData() + const clDecimals = await ctx.chainlinkFeed.decimals() + + const clRptData = await ctx.targetPrRefFeed!.latestRoundData() + const clRptDecimals = await ctx.targetPrRefFeed!.decimals() + + const expctPrice = clData.answer + .mul(bn(10).pow(18 - clDecimals)) + .mul(clRptData.answer.mul(bn(10).pow(18 - clRptDecimals))) + .div(fp('1')) + return expctPrice +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} +// eslint-disable-next-line @typescript-eslint/no-empty-function +const beforeEachRewardsTest = async () => {} + +export const defaultCollateralOpts: MAFiatCollateralOpts = { + targetName: ethers.utils.formatBytes32String('BTC'), + underlyingToken: networkConfig[1].tokens.WBTC!, + poolToken: networkConfig[1].tokens.aWBTC!, + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: networkConfig[1].chainlinkFeeds.WBTC!, + targetPrRefFeed: networkConfig[1].chainlinkFeeds.wBTCBTC!, + oracleTimeout: ORACLE_TIMEOUT, + oracleError: ORACLE_ERROR, + maxTradeVolume: parseEther('100'), + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: fp('0'), + defaultPrice: parseUnits('30000', 8), + defaultRefPerTok: parseUnits('1', 8), + refPerTokChainlinkTimeout: PRICE_TIMEOUT, +} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it.skip, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itHasRevenueHiding: it, + resetFork: getResetFork(FORK_BLOCK), + collateralName: 'MorphoAAVEV2NonFiatCollateral', + chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts new file mode 100644 index 0000000000..1544f99966 --- /dev/null +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts @@ -0,0 +1,236 @@ +import { networkConfig } from '#/common/configuration' +import { bn, fp } from '#/common/numbers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { MockV3Aggregator } from '@typechain/MockV3Aggregator' +import { TestICollateral } from '@typechain/TestICollateral' +import { + ERC20Mock, + MockV3Aggregator__factory, + MorphoSelfReferentialCollateral__factory, +} from '@typechain/index' +import { expect } from 'chai' +import { BigNumber, BigNumberish } from 'ethers' +import { parseEther } from 'ethers/lib/utils' +import { ethers } from 'hardhat' +import collateralTests from '../collateralTests' +import { getResetFork } from '../helpers' +import { CollateralOpts } from '../pluginTestTypes' +import { + DELAY_UNTIL_DEFAULT, + FORK_BLOCK, + ORACLE_ERROR, + ORACLE_TIMEOUT, + PRICE_TIMEOUT, +} from './constants' +import { MorphoAaveCollateralFixtureContext, mintCollateralTo } from './mintCollateralTo' + +interface MAFiatCollateralOpts extends CollateralOpts { + underlyingToken?: string + poolToken?: string + defaultPrice?: BigNumberish + defaultRefPerTok?: BigNumberish +} + +export const deployCollateral = async ( + opts: MAFiatCollateralOpts = {} +): Promise => { + if (opts.defaultThreshold == null && opts.delayUntilDefault === 0) { + opts.defaultThreshold = fp('0.001') + } + opts = { ...defaultCollateralOpts, ...opts } + + const MorphoAAVESelfReferentialCollateral: MorphoSelfReferentialCollateral__factory = + await ethers.getContractFactory('MorphoSelfReferentialCollateral') + if (opts.erc20 == null) { + const MorphoTokenisedDepositMockFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDepositMock' + ) + const wrapperMock = await MorphoTokenisedDepositMockFactory.deploy({ + morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, + underlyingERC20: opts.underlyingToken!, + poolToken: opts.poolToken!, + rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfig[1].tokens.MORPHO!, + }) + opts.erc20 = wrapperMock.address + } + const collateral = (await MorphoAAVESelfReferentialCollateral.deploy( + { + erc20: opts.erc20!, + targetName: opts.targetName!, + priceTimeout: opts.priceTimeout!, + chainlinkFeed: opts.chainlinkFeed!, + oracleError: opts.oracleError!, + oracleTimeout: opts.oracleTimeout!, + maxTradeVolume: opts.maxTradeVolume!, + defaultThreshold: opts.defaultThreshold!, + delayUntilDefault: opts.delayUntilDefault!, + }, + opts.revenueHiding!, + { gasLimit: 2000000000 } + )) as unknown as TestICollateral + await collateral.deployed() + + await expect(collateral.refresh()) + + return collateral +} + +type Fixture = () => Promise + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + inOpts: MAFiatCollateralOpts = {} +): Fixture => { + const makeCollateralFixtureContext = async () => { + const opts = { ...defaultCollateralOpts, ...inOpts } + const MorphoTokenisedDepositMockFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDepositMock' + ) + const erc20Factory = await ethers.getContractFactory('ERC20Mock') + const underlyingErc20 = erc20Factory.attach(opts.underlyingToken!) + const wrapperMock = await MorphoTokenisedDepositMockFactory.deploy({ + morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, + underlyingERC20: opts.underlyingToken!, + poolToken: opts.poolToken!, + rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfig[1].tokens.MORPHO!, + }) + + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, opts.defaultPrice!) + ) + const collateralOpts = { + ...opts, + erc20: wrapperMock.address, + chainlinkFeed: chainlinkFeed.address, + } + const collateral = await deployCollateral(collateralOpts) + + return { + alice, + collateral, + chainlinkFeed, + tok: wrapperMock as unknown as ERC20Mock, + morphoWrapper: wrapperMock, + underlyingErc20: underlyingErc20, + } as MorphoAaveCollateralFixtureContext + } + + return makeCollateralFixtureContext +} + +/* + Define helper functions +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const reduceTargetPerRef = async () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const increaseTargetPerRef = async () => {} + +const changeRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + percentChange: BigNumber +) => { + const rate = await ctx.morphoWrapper.getExchangeRate() + await ctx.morphoWrapper.setExchangeRate(rate.add(rate.mul(percentChange).div(bn('100')))) + + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) +} + +// prettier-ignore +const reduceRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctDecrease: BigNumberish +) => { + await changeRefPerTok( + ctx, + bn(pctDecrease).mul(-1) + ) +} +// prettier-ignore +const increaseRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctIncrease: BigNumberish +) => { + await changeRefPerTok( + ctx, + bn(pctIncrease) + ) +} + +const getExpectedPrice = async (ctx: MorphoAaveCollateralFixtureContext): Promise => { + // UoA/tok feed + const clData = await ctx.chainlinkFeed.latestRoundData() + const clDecimals = await ctx.chainlinkFeed.decimals() + + const refPerTok = await ctx.collateral.refPerTok() + const expectedPegPrice = clData.answer.mul(bn(10).pow(18 - clDecimals)) + return expectedPegPrice.mul(refPerTok).div(fp('1')) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => {} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} +// eslint-disable-next-line @typescript-eslint/no-empty-function +const beforeEachRewardsTest = async () => {} + +export const defaultCollateralOpts: MAFiatCollateralOpts = { + targetName: ethers.utils.formatBytes32String('ETH'), + underlyingToken: networkConfig[1].tokens.stETH!, + poolToken: networkConfig[1].tokens.astETH!, + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: networkConfig[1].chainlinkFeeds.stETHUSD!, + oracleTimeout: ORACLE_TIMEOUT, + oracleError: ORACLE_ERROR, + maxTradeVolume: parseEther('1000'), + defaultThreshold: bn(0), // 0.05 + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: fp('0'), + defaultPrice: bn('1600e8'), + defaultRefPerTok: fp('1'), +} + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it.skip, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itHasRevenueHiding: it, + resetFork: getResetFork(FORK_BLOCK), + collateralName: 'MorphoAAVEV2SelfReferentialCollateral', + chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts new file mode 100644 index 0000000000..1b4122b604 --- /dev/null +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts @@ -0,0 +1,220 @@ +import { ITokens, networkConfig } from '#/common/configuration' +import { ethers } from 'hardhat' +import { whileImpersonating } from '../../../utils/impersonation' +import { whales } from '#/tasks/testing/upgrade-checker-utils/constants' +import { Signer } from 'ethers' +import { formatUnits, parseUnits } from 'ethers/lib/utils' +import { expect } from 'chai' +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' + +type ITokenSymbol = keyof ITokens + +const mkToken = (symbol: ITokenSymbol) => ({ + address: networkConfig[1].tokens[symbol]! as string, + symbol: symbol, +}) +const mkTestCase = (symbol: T, amount: string) => ({ + token: mkToken(symbol), + poolToken: mkToken(`a${symbol}` as ITokenSymbol), + amount, +}) + +const TOKENS_TO_TEST = [ + mkTestCase('USDT', '1000.0'), + mkTestCase('DAI', '1000.0'), + mkTestCase('WETH', '1.0'), + mkTestCase('stETH', '1.0'), + mkTestCase('WBTC', '1.0'), +] +type ITestSuiteVariant = typeof TOKENS_TO_TEST[number] + +const execTestForToken = ({ token, poolToken, amount }: ITestSuiteVariant) => { + describe('Tokenised Morpho Position - ' + token.symbol, () => { + const beforeEachFn = async () => { + const factories = { + ERC20Mock: await ethers.getContractFactory('ERC20Mock'), + MorphoTokenisedDeposit: await ethers.getContractFactory('MorphoAaveV2TokenisedDeposit'), + } + const instances = { + underlying: factories.ERC20Mock.attach(token.address), + morpho: factories.ERC20Mock.attach(networkConfig[1].tokens.MORPHO!), + tokenVault: await factories.MorphoTokenisedDeposit.deploy({ + underlyingERC20: token.address, + poolToken: poolToken.address, + morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfig[1].tokens.MORPHO!, + }), + } + const underlyingDecimals = await instances.underlying.decimals() + const shareDecimals = await instances.tokenVault.decimals() + const signers = await ethers.getSigners() + const users = { + alice: signers[0], + bob: signers[1], + charlie: signers[2], + } + + await whileImpersonating(whales[token.address.toLowerCase()], async (whaleSigner) => { + await instances.underlying + .connect(whaleSigner) + .transfer(users.alice.address, parseUnits(amount, underlyingDecimals)) + await instances.underlying + .connect(whaleSigner) + .transfer(users.bob.address, parseUnits(amount, underlyingDecimals)) + await instances.underlying + .connect(whaleSigner) + .transfer(users.charlie.address, parseUnits(amount, underlyingDecimals)) + }) + return { + factories, + instances, + users, + methods: { + deposit: async (user: Signer, amount: string, dest?: string) => { + await instances.underlying + .connect(user) + .approve(instances.tokenVault.address, parseUnits(amount, underlyingDecimals)) + await instances.tokenVault + .connect(user) + .deposit(parseUnits(amount, underlyingDecimals), dest ?? (await user.getAddress())) + }, + shares: async (user: Signer) => { + return formatUnits( + await instances.tokenVault.connect(user).maxRedeem(await user.getAddress()), + shareDecimals + ) + }, + assets: async (user: Signer) => { + return formatUnits( + await instances.tokenVault.connect(user).maxWithdraw(await user.getAddress()), + underlyingDecimals + ) + }, + withdraw: async (user: Signer, amount: string, dest?: string) => { + await instances.tokenVault + .connect(user) + .withdraw( + parseUnits(amount, underlyingDecimals), + dest ?? (await user.getAddress()), + await user.getAddress() + ) + }, + balanceUnderlying: async (user: Signer) => { + return formatUnits( + await instances.underlying.connect(user).balanceOf(await user.getAddress()), + underlyingDecimals + ) + }, + balanceMorpho: async (user: Signer) => { + return formatUnits( + await instances.morpho.connect(user).balanceOf(await user.getAddress()), + 18 + ) + }, + transferShares: async (from: Signer, to: Signer, amount: string) => { + await instances.tokenVault + .connect(from) + .transfer(await to.getAddress(), parseUnits(amount, shareDecimals)) + }, + unclaimedRewards: async (owner: Signer) => { + return formatUnits( + await instances.tokenVault + .connect(owner) + .callStatic.rewardTokenBalance(await owner.getAddress()), + 18 + ) + }, + claimRewards: async (owner: Signer) => { + await instances.tokenVault.connect(owner).claimRewards() + }, + }, + } + } + + type ITestContext = ReturnType extends Promise ? U : never + let context: ITestContext + + // const resetFork = getResetFork(17591000) + beforeEach(async () => { + context = await loadFixture(beforeEachFn) + }) + const amountAsNumber = parseInt(amount) + const fraction = (percent: number) => ((amountAsNumber * percent) / 100).toFixed(1) + + const closeTo = async (actual: Promise, expected: string) => { + await new Promise((r) => setTimeout(r, 200)) + expect(parseFloat(await actual)).to.closeTo(parseFloat(expected), 0.5) + } + + it('Deposits', async () => { + const { + users: { alice, bob, charlie }, + methods, + } = context + expect(await methods.shares(alice)).to.equal('0.0') + expect(await methods.shares(bob)).to.equal('0.0') + expect(await methods.shares(charlie)).to.equal('0.0') + await methods.deposit(alice, fraction(10)) + await closeTo(methods.shares(alice), fraction(10)) + await methods.deposit(bob, fraction(20)) + await closeTo(methods.shares(bob), fraction(20)) + await methods.deposit(charlie, fraction(5)) + await closeTo(methods.shares(charlie), fraction(5)) + }) + + it('Deposits and withdraw', async () => { + const { + users: { alice, bob }, + methods, + } = context + await closeTo(methods.balanceUnderlying(alice), fraction(100)) + expect(await methods.shares(alice)).to.equal('0.0') + await methods.deposit(alice, fraction(10)) + await methods.deposit(bob, fraction(20)) + await closeTo(methods.balanceUnderlying(alice), fraction(90)) + + const aliceShares = await methods.shares(alice) + await closeTo(Promise.resolve(aliceShares), fraction(10)) + await closeTo(methods.assets(alice), fraction(10)) + await methods.withdraw(alice, (parseFloat(aliceShares) / 2).toString()) + await closeTo(methods.shares(alice), fraction(5)) + await closeTo(methods.assets(alice), fraction(5)) + await closeTo(methods.balanceUnderlying(alice), fraction(95)) + await methods.withdraw(alice, (parseFloat(aliceShares) / 2).toString()) + await closeTo(methods.shares(alice), fraction(0)) + await closeTo(methods.assets(alice), fraction(0)) + await closeTo(methods.balanceUnderlying(alice), fraction(100)) + }) + + it('Transfers deposit', async () => { + const { + users: { alice, bob }, + methods, + } = context + await closeTo(methods.balanceUnderlying(alice), fraction(100)) + expect(await methods.shares(alice)).to.equal('0.0') + await methods.deposit(alice, fraction(100)) + + await closeTo(methods.balanceUnderlying(alice), fraction(0)) + await closeTo(methods.shares(bob), fraction(0)) + await closeTo(methods.balanceUnderlying(bob), fraction(100)) + await closeTo(methods.shares(alice), fraction(100)) + + await methods.transferShares(alice, bob, fraction(50)) + await closeTo(methods.shares(alice), fraction(50)) + await closeTo(methods.shares(bob), fraction(50)) + + await closeTo(methods.assets(alice), fraction(50)) + await closeTo(methods.assets(bob), fraction(50)) + }) + + /** + * There is a test for claiming rewards in the MorphoAAVEFiatCollateral.test.ts + */ + }) +} +describe('MorphoAaveV2TokenisedDeposit', () => { + TOKENS_TO_TEST.forEach(execTestForToken) +}) diff --git a/test/plugins/individual-collateral/morpho-aave/constants.ts b/test/plugins/individual-collateral/morpho-aave/constants.ts new file mode 100644 index 0000000000..4c485c3e5d --- /dev/null +++ b/test/plugins/individual-collateral/morpho-aave/constants.ts @@ -0,0 +1,10 @@ +import { bn, fp } from '../../../../common/numbers' + +// Mainnet Addresses +export const PRICE_TIMEOUT = bn(604800) // 1 week +export const ORACLE_TIMEOUT = bn(86400) // 24 hours in seconds +export const ORACLE_ERROR = fp('0.0025') +export const DEFAULT_THRESHOLD = ORACLE_ERROR.add(fp('0.01')) // 1% + ORACLE_ERROR +export const DELAY_UNTIL_DEFAULT = bn(86400) + +export const FORK_BLOCK = 17528677 diff --git a/test/plugins/individual-collateral/morpho-aave/mintCollateralTo.ts b/test/plugins/individual-collateral/morpho-aave/mintCollateralTo.ts new file mode 100644 index 0000000000..300d04712d --- /dev/null +++ b/test/plugins/individual-collateral/morpho-aave/mintCollateralTo.ts @@ -0,0 +1,46 @@ +import { CollateralFixtureContext, MintCollateralFunc } from '../pluginTestTypes' +import hre from 'hardhat' +import { BigNumberish, constants } from 'ethers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { whales } from '#/tasks/testing/upgrade-checker-utils/constants' +import { whileImpersonating } from '#/utils/impersonation' +import { IERC20 } from '@typechain/IERC20' +import { MockV3Aggregator } from '@typechain/MockV3Aggregator' +import { MorphoAaveV2TokenisedDepositMock } from '@typechain/MorphoAaveV2TokenisedDepositMock' + +/** + * Interface representing the context object for the MorphoAaveCollateralFixture. + * Extends the CollateralFixtureContext interface. + * Contains the MorphoAAVEPositionWrapperMock contract and the underlying ERC20 token. + */ +export interface MorphoAaveCollateralFixtureContext extends CollateralFixtureContext { + morphoWrapper: MorphoAaveV2TokenisedDepositMock + underlyingErc20: IERC20 + targetPrRefFeed?: MockV3Aggregator +} + +/** + * Mint collateral to a recipient using the MorphoAAVEPositionWrapperMock contract. + * @param ctx The MorphoAaveCollateralFixtureContext object. + * @param amount The amount of collateral to mint. + * @param _ The signer with address (not used in this function). + * @param recipient The address of the recipient of the minted collateral. + */ +export const mintCollateralTo: MintCollateralFunc = async ( + ctx: MorphoAaveCollateralFixtureContext, + amount: BigNumberish, + _: SignerWithAddress, + recipient: string +) => { + await whileImpersonating( + hre, + whales[ctx.underlyingErc20.address.toLowerCase()], + async (whaleSigner) => { + await ctx.underlyingErc20.connect(whaleSigner).approve(ctx.morphoWrapper.address, 0) + await ctx.underlyingErc20 + .connect(whaleSigner) + .approve(ctx.morphoWrapper.address, constants.MaxUint256) + await ctx.morphoWrapper.connect(whaleSigner).mint(amount, recipient) + } + ) +} From e92b43a9b71e18d46df983e5ab45b24b36f9cfd3 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Fri, 28 Jul 2023 12:26:57 +0530 Subject: [PATCH 350/462] Allow `settleTrade` when paused or frozen (#876) --- contracts/p1/BackingManager.sol | 7 +------ contracts/p1/RevenueTrader.sol | 7 +------ test/Recollateralization.test.ts | 14 -------------- test/Revenues.test.ts | 14 -------------- 4 files changed, 2 insertions(+), 40 deletions(-) diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index b16d4a1677..6dde9dae27 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -85,12 +85,7 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) - public - override(ITrading, TradingP1) - notTradingPausedOrFrozen - returns (ITrade trade) - { + function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { trade = super.settleTrade(sell); // nonReentrant // if the settler is the trade contract itself, try chaining with another rebalance() diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 9c7346edef..47123b02e2 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -49,12 +49,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) - public - override(ITrading, TradingP1) - notTradingPausedOrFrozen - returns (ITrade trade) - { + function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { trade = super.settleTrade(sell); // nonReentrant _distributeTokenToBuy(); // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 9f707e1d1a..16ffc14f07 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -1009,20 +1009,6 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) }) - it('Should not settle trades if trading paused', async () => { - await main.connect(owner).pauseTrading() - await expect(backingManager.settleTrade(token0.address)).to.be.revertedWith( - 'frozen or trading paused' - ) - }) - - it('Should not settle trades if frozen', async () => { - await main.connect(owner).freezeShort() - await expect(backingManager.settleTrade(token0.address)).to.be.revertedWith( - 'frozen or trading paused' - ) - }) - it('Should not recollateralize when switching basket if all assets are UNPRICED', async () => { // Set price to use lot price await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 746815c077..ecf2a581a1 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -428,20 +428,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(balAfter).to.equal(rewardAmt) }) - it('Should not settle trade if paused', async () => { - await main.connect(owner).pauseTrading() - await expect(rTokenTrader.settleTrade(ZERO_ADDRESS)).to.be.revertedWith( - 'frozen or trading paused' - ) - }) - - it('Should not settle trade if frozen', async () => { - await main.connect(owner).freezeShort() - await expect(rTokenTrader.settleTrade(ZERO_ADDRESS)).to.be.revertedWith( - 'frozen or trading paused' - ) - }) - it('Should still launch revenue auction if IFFY', async () => { // Depeg one of the underlying tokens - Reducing price 30% await setOraclePrice(collateral0.address, bn('7e7')) From 3c20f04512938c997ac0c590fe4e2a4166c1f4e1 Mon Sep 17 00:00:00 2001 From: brr Date: Tue, 1 Aug 2023 01:06:12 +0100 Subject: [PATCH 351/462] Morpho - Add missing tokens to deployment and expand unit tests (#874) --- .github/workflows/tests.yml | 8 - common/configuration.ts | 3 + contracts/facade/FacadeRead.sol | 6 +- contracts/p0/BackingManager.sol | 2 +- contracts/p0/BasketHandler.sol | 4 +- contracts/p0/RToken.sol | 4 +- contracts/p1/BasketHandler.sol | 17 +- contracts/plugins/assets/OracleLib.sol | 2 +- .../morpho-aave/MorphoFiatCollateral.sol | 9 +- .../morpho-aave/MorphoNonFiatCollateral.sol | 2 +- hardhat.config.ts | 1 + package.json | 34 +- .../deploy_morpho_aavev2_plugin.ts | 149 +- .../MorphoAAVEFiatCollateral.test.ts | 560 ++-- .../MorphoAAVENonFiatCollateral.test.ts | 379 +-- ...orphoAAVESelfReferentialCollateral.test.ts | 8 +- .../MorphoAaveV2TokenisedDeposit.test.ts | 14 +- yarn.lock | 2815 +++++++++-------- 18 files changed, 2239 insertions(+), 1778 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1f3a2cdc5a..a0215996f0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,6 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - run: yarn devchain & env: MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} @@ -37,7 +36,6 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - run: yarn lint plugin-tests: @@ -50,7 +48,6 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - run: yarn test:plugins - name: 'Cache hardhat network fork' uses: actions/cache@v3 @@ -76,7 +73,6 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - run: yarn test:p0 env: NODE_OPTIONS: '--max-old-space-size=8192' @@ -91,7 +87,6 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - run: yarn test:p1 env: NODE_OPTIONS: '--max-old-space-size=8192' @@ -106,7 +101,6 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - run: yarn test:scenario env: NODE_OPTIONS: '--max-old-space-size=8192' @@ -121,7 +115,6 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - run: yarn test:extreme - name: 'Cache hardhat network fork' uses: actions/cache@v3 @@ -157,7 +150,6 @@ jobs: hardhat-network-fork-${{ runner.os }}- hardhat-network-fork- - run: yarn install --immutable - - run: yarn compile - run: yarn test:integration env: NODE_OPTIONS: '--max-old-space-size=8192' diff --git a/common/configuration.ts b/common/configuration.ts index ab3fff947d..5edd69b99f 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -65,6 +65,9 @@ export interface ITokens { maUSDC?: string maUSDT?: string maDAI?: string + maWBTC?: string + maWETH?: string + maStETH?: string } export interface IFeeds { diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 4896b2d756..9c0d0ffa5f 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -348,9 +348,9 @@ contract FacadeRead is IFacadeRead { uint192 uoaHeldInBaskets; // {UoA} { (address[] memory basketERC20s, uint256[] memory quantities) = rToken - .main() - .basketHandler() - .quote(basketsNeeded, FLOOR); + .main() + .basketHandler() + .quote(basketsNeeded, FLOOR); IAssetRegistry reg = rToken.main().assetRegistry(); IBackingManager bm = rToken.main().backingManager(); diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 0bdc2249c7..c22df732c7 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -124,7 +124,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { BasketRange memory basketsHeld = main.basketHandler().basketsHeldBy(address(this)); (bool doTrade, TradeRequest memory req, TradePrices memory prices) = TradingLibP0 - .prepareRecollateralizationTrade(this, basketsHeld); + .prepareRecollateralizationTrade(this, basketsHeld); if (doTrade) { // Seize RSR if needed diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 602ec43339..357b0a7251 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -516,8 +516,8 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // {tok} = {BU} * {ref/BU} / {ref/tok} quantities[i] = amount - .safeMulDiv(refAmtsAll[i], collsAll[i].refPerTok(), FLOOR) - .shiftl_toUint(int8(collsAll[i].erc20Decimals()), FLOOR); + .safeMulDiv(refAmtsAll[i], collsAll[i].refPerTok(), FLOOR) + .shiftl_toUint(int8(collsAll[i].erc20Decimals()), FLOOR); // marginally more penalizing than its sibling calculation that uses _quantity() // because does not intermediately CEIL as part of the division } diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index a943324371..2ba631e3a2 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -214,8 +214,8 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { } (address[] memory erc20s, uint256[] memory amounts) = main - .basketHandler() - .quoteCustomRedemption(basketNonces, portions, basketsRedeemed); + .basketHandler() + .quoteCustomRedemption(basketNonces, portions, basketsRedeemed); // === Save initial recipient balances === diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index b684b8c3e2..7bcd48028d 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -376,11 +376,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // {qTok} = {tok/BU} * {BU} * {tok} * {qTok/tok} quantities[i] = _quantity(basket.erc20s[i], coll) - .safeMul(amount, rounding) - .shiftl_toUint( - int8(IERC20Metadata(address(basket.erc20s[i])).decimals()), - rounding - ); + .safeMul(amount, rounding) + .shiftl_toUint(int8(IERC20Metadata(address(basket.erc20s[i])).decimals()), rounding); } } @@ -461,8 +458,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // {tok} = {BU} * {ref/BU} / {ref/tok} quantities[i] = amount - .safeMulDiv(refAmtsAll[i], collsAll[i].refPerTok(), FLOOR) - .shiftl_toUint(int8(collsAll[i].erc20Decimals()), FLOOR); + .safeMulDiv(refAmtsAll[i], collsAll[i].refPerTok(), FLOOR) + .shiftl_toUint(int8(collsAll[i].erc20Decimals()), FLOOR); // marginally more penalizing than its sibling calculation that uses _quantity() // because does not intermediately CEIL as part of the division } @@ -608,9 +605,9 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // {tok} = {BU} * {ref/BU} / {ref/tok} quantities[i] = b - .refAmts[erc20s[i]] - .safeDiv(ICollateral(address(asset)).refPerTok(), FLOOR) - .shiftl_toUint(int8(asset.erc20Decimals()), FLOOR); + .refAmts[erc20s[i]] + .safeDiv(ICollateral(address(asset)).refPerTok(), FLOOR) + .shiftl_toUint(int8(asset.erc20Decimals()), FLOOR); } catch (bytes memory errData) { // untested: // OOG pattern tested in other contracts, cost to test here is high diff --git a/contracts/plugins/assets/OracleLib.sol b/contracts/plugins/assets/OracleLib.sol index e15605f881..b0e2538761 100644 --- a/contracts/plugins/assets/OracleLib.sol +++ b/contracts/plugins/assets/OracleLib.sol @@ -17,7 +17,7 @@ library OracleLib { returns (uint192) { (uint80 roundId, int256 p, , uint256 updateTime, uint80 answeredInRound) = chainlinkFeed - .latestRoundData(); + .latestRoundData(); if (updateTime == 0 || answeredInRound < roundId) { revert StalePrice(); diff --git a/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol b/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol index bc4debb54e..778725866c 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol @@ -18,7 +18,6 @@ import { shiftl_toFix, FIX_ONE } from "../../../libraries/Fixed.sol"; contract MorphoFiatCollateral is AppreciatingFiatCollateral { using OracleLib for AggregatorV3Interface; - MorphoTokenisedDeposit public immutable vault; uint256 private immutable oneShare; int8 private immutable refDecimals; @@ -29,13 +28,17 @@ contract MorphoFiatCollateral is AppreciatingFiatCollateral { AppreciatingFiatCollateral(config, revenueHiding) { require(address(config.erc20) != address(0), "missing erc20"); - vault = MorphoTokenisedDeposit(address(config.erc20)); + MorphoTokenisedDeposit vault = MorphoTokenisedDeposit(address(config.erc20)); oneShare = 10**vault.decimals(); refDecimals = int8(uint8(IERC20Metadata(vault.asset()).decimals())); } /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens function _underlyingRefPerTok() internal view override returns (uint192) { - return shiftl_toFix(vault.convertToAssets(oneShare), -refDecimals); + return + shiftl_toFix( + MorphoTokenisedDeposit(address(erc20)).convertToAssets(oneShare), + -refDecimals + ); } } diff --git a/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol b/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol index 0e39fb12d6..b37db03207 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol @@ -48,7 +48,7 @@ contract MorphoNonFiatCollateral is MorphoFiatCollateral { uint192 pegPrice ) { - // {tar/ref} Get current market peg ({btc/wbtc}) + // {tar/ref} Get current market peg pegPrice = targetUnitChainlinkFeed.price(targetUnitOracleTimeout); // {UoA/tok} = {UoA/ref} * {ref/tok} diff --git a/hardhat.config.ts b/hardhat.config.ts index f45506c31d..2fa2cc5eed 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -117,6 +117,7 @@ const config: HardhatUserConfig = { mocha: { timeout: TIMEOUT, slow: 1000, + retries: 3 }, contractSizer: { alphaSort: false, diff --git a/package.json b/package.json index 7d9411dee6..a9e2497e3e 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,12 @@ "test:extreme:integration": "FORK=1 EXTREME=1 PROTO_IMPL=1 npx hardhat test test/integration/**/*.test.ts", "test:unit": "yarn test:plugins && yarn test:p0 && yarn test:p1", "test:fast": "bash tools/fast-test.sh", - "test:p0": "PROTO_IMPL=0 hardhat test test/*.test.ts", - "test:p1": "PROTO_IMPL=1 hardhat test test/*.test.ts", - "test:plugins": "hardhat test test/{libraries,plugins}/*.test.ts", + "test:p0": "PROTO_IMPL=0 hardhat test test/*.test.ts --parallel", + "test:p1": "PROTO_IMPL=1 hardhat test test/*.test.ts --parallel", + "test:plugins": "hardhat test test/{libraries,plugins}/*.test.ts --parallel", "test:plugins:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/plugins/individual-collateral/**/*.test.ts", - "test:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/integration/**/*.test.ts", - "test:scenario": "PROTO_IMPL=1 hardhat test test/scenario/*.test.ts", + "test:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/integration/**/*.test.ts --parallel", + "test:scenario": "PROTO_IMPL=1 hardhat test test/scenario/*.test.ts --parallel", "test:gas": "yarn test:gas:protocol && yarn test:gas:integration", "test:gas:protocol": "REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/{libraries,plugins,scenario}/*.test.ts test/*.test.ts", "test:gas:integration": "FORK=1 REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/integration/**/*.test.ts", @@ -68,21 +68,21 @@ "@types/lodash": "^4.14.177", "@types/mocha": "^9.0.0", "@types/node": "^12.20.37", - "@typescript-eslint/eslint-plugin": "^5.17.0", - "@typescript-eslint/parser": "^5.17.0", + "@typescript-eslint/eslint-plugin": "5.17.0", + "@typescript-eslint/parser": "5.17.0", "axios": "^0.24.0", "bignumber.js": "^9.1.1", "caip": "^1.1.0", "chai": "^4.3.4", "decimal.js": "^10.4.3", "dotenv": "^16.0.0", - "eslint": "^8.14.0", - "eslint-config-prettier": "^8.5.0", - "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-promise": "^6.0.0", + "eslint": "8.14.0", + "eslint-config-prettier": "8.5.0", + "eslint-config-standard": "16.0.3", + "eslint-plugin-import": "2.25.4", + "eslint-plugin-node": "11.1.0", + "eslint-plugin-prettier": "4.0.0", + "eslint-plugin-promise": "6.0.0", "eth-permit": "^0.2.1", "ethers": "^5.7.2", "fast-check": "^2.24.0", @@ -96,9 +96,9 @@ "lodash.get": "^4.4.2", "mocha-chai-jest-snapshot": "^1.1.3", "prettier": "2.5.1", - "prettier-plugin-solidity": "^1.0.0-beta.13", - "solhint": "^3.3.6", - "solhint-plugin-prettier": "^0.0.5", + "prettier-plugin-solidity": "1.0.0-beta.13", + "solhint": "3.3.6", + "solhint-plugin-prettier": "0.0.5", "solidity-coverage": "^0.8.2", "ts-node": "^10.4.0", "tsconfig-paths": "^4.1.0", diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts index 72fa5d3dea..6bc493a48c 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts @@ -11,7 +11,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { priceTimeout, oracleTimeout } from '../../utils' +import { priceTimeout, oracleTimeout, combinedError } from '../../utils' async function main() { // ==== Read Configuration ==== @@ -36,7 +36,7 @@ async function main() { const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) const deployedCollateral: string[] = [] - const revenueHiding = fp('1e-6').toString() // revenueHiding = 0.0001% + const revenueHiding = fp('1e-6') // revenueHiding = 0.0001% /******** Deploy Morpho - AaveV2 **************************/ @@ -61,6 +61,7 @@ async function main() { poolToken: networkConfig[chainId].tokens.aUSDC!, rewardToken: networkConfig[chainId].tokens.MORPHO!, }) + const maDAI = await MorphoTokenisedDepositFactory.deploy({ morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, @@ -70,50 +71,85 @@ async function main() { rewardToken: networkConfig[chainId].tokens.MORPHO!, }) + const maWBTC = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, + underlyingERC20: networkConfig[chainId].tokens.WBTC!, + poolToken: networkConfig[chainId].tokens.aWBTC!, + rewardToken: networkConfig[chainId].tokens.MORPHO!, + }) + + const maWETH = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, + underlyingERC20: networkConfig[chainId].tokens.WETH!, + poolToken: networkConfig[chainId].tokens.aWETH!, + rewardToken: networkConfig[chainId].tokens.MORPHO!, + }) + + const maStETH = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, + underlyingERC20: networkConfig[chainId].tokens.stETH!, + poolToken: networkConfig[chainId].tokens.astETH!, + rewardToken: networkConfig[chainId].tokens.MORPHO!, + }) + await maUSDT.deployed() await maUSDC.deployed() await maDAI.deployed() + await maWBTC.deployed() + await maWETH.deployed() + await maStETH.deployed() assetCollDeployments.erc20s.maUSDT = maUSDT.address assetCollDeployments.erc20s.maUSDC = maUSDC.address assetCollDeployments.erc20s.maDAI = maDAI.address + assetCollDeployments.erc20s.maWBTC = maWBTC.address + assetCollDeployments.erc20s.maWETH = maWETH.address + assetCollDeployments.erc20s.maStETH = maStETH.address /******** Morpho collateral **************************/ const FiatCollateralFactory = await hre.ethers.getContractFactory( "MorphoFiatCollateral" ) + const NonFiatCollateralFactory = await hre.ethers.getContractFactory( + "MorphoNonFiatCollateral" + ) + const SelfReferentialFactory = await hre.ethers.getContractFactory( + "MorphoSelfReferentialCollateral" + ) const stablesOracleError = fp('0.0025') // 0.25% + const baseStableConfig = { + priceTimeout: priceTimeout.toString(), + oracleError: stablesOracleError.toString(), + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 1 hr + targetName: ethers.utils.formatBytes32String("USD"), + defaultThreshold: stablesOracleError.add(fp("0.01")), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + } + { const collateral = await FiatCollateralFactory.connect(deployer).deploy({ - priceTimeout: priceTimeout.toString(), + ...baseStableConfig, chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDT!, - oracleError: stablesOracleError.toString(), erc20: maUSDT.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 1 hr - targetName: ethers.utils.formatBytes32String("USD"), - defaultThreshold: stablesOracleError.add(fp("0.01")), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h }, revenueHiding ); assetCollDeployments.collateral.maUSDT = collateral.address deployedCollateral.push(collateral.address.toString()) - } { - const collateral = await FiatCollateralFactory.connect(deployer).deploy({ - priceTimeout: priceTimeout.toString(), + ...baseStableConfig, chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, - oracleError: stablesOracleError.toString(), erc20: maUSDC.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 1 hr - targetName: ethers.utils.formatBytes32String("USD"), - defaultThreshold: stablesOracleError.add(fp("0.01")), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h }, revenueHiding ); @@ -122,15 +158,9 @@ async function main() { } { const collateral = await FiatCollateralFactory.connect(deployer).deploy({ - priceTimeout: priceTimeout.toString(), + ...baseStableConfig, chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI!, - oracleError: stablesOracleError.toString(), erc20: maDAI.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr - targetName: ethers.utils.formatBytes32String("USD"), - defaultThreshold: stablesOracleError.add(fp("0.01")), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h }, revenueHiding ); @@ -138,6 +168,75 @@ async function main() { deployedCollateral.push(collateral.address.toString()) } + { + const wbtcOracleError = fp('0.02') // 2% + const btcOracleError = fp('0.005') // 0.5% + const combinedBTCWBTCError = combinedError(wbtcOracleError, btcOracleError) + const collateral = await NonFiatCollateralFactory.connect(deployer).deploy({ + priceTimeout: priceTimeout, + oracleError: combinedBTCWBTCError, + maxTradeVolume: fp('1e6'), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + targetName: ethers.utils.formatBytes32String("BTC"), + defaultThreshold: fp('0.01').add(combinedBTCWBTCError), // ~3.5% + delayUntilDefault: bn('86400'), // 24h + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.WBTC!, + erc20: maWBTC.address, + }, + revenueHiding, + networkConfig[chainId].chainlinkFeeds.wBTCBTC!, + oracleTimeout(chainId, '86400').toString(), // 1 hr + ); + assetCollDeployments.collateral.maWBTC = collateral.address + deployedCollateral.push(collateral.address.toString()) + } + + { + const collateral = await SelfReferentialFactory.connect(deployer).deploy({ + priceTimeout: priceTimeout, + oracleError: fp('0.005'), + maxTradeVolume: fp('1e6'), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + targetName: ethers.utils.formatBytes32String("ETH"), + defaultThreshold: fp('0.05'), // 5% + delayUntilDefault: bn('86400'), // 24h + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, + erc20: maWBTC.address, + }, + revenueHiding, + ); + assetCollDeployments.collateral.maWETH = collateral.address + deployedCollateral.push(collateral.address.toString()) + } + + { + const ethStEthOracleError = fp('0.005') // 0.5% + const ethOracleError = fp('0.005') // 0.5% + + const combinedOracleErrors = combinedError(ethStEthOracleError, ethOracleError) + + // TAR: ETH + // REF: stETH + // TOK: maETH + const collateral = await NonFiatCollateralFactory.connect(deployer).deploy({ + priceTimeout: priceTimeout, + oracleError: combinedOracleErrors, + maxTradeVolume: fp('1e6'), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + targetName: ethers.utils.formatBytes32String("ETH"), + defaultThreshold: fp('0.01').add(combinedOracleErrors), // ~1.5% + delayUntilDefault: bn('86400'), // 24h + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, + erc20: maStETH.address, + }, + revenueHiding, + networkConfig[chainId].chainlinkFeeds.stETHETH!, // {target/ref} + oracleTimeout(chainId, '86400').toString(), // 1 hr + ); + assetCollDeployments.collateral.maWBTC = collateral.address + deployedCollateral.push(collateral.address.toString()) + } + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) console.log(`Deployed collateral to ${hre.network.name} (${chainId}) diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts index f03e2b6c1d..39e467e2d0 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts @@ -32,302 +32,332 @@ interface MAFiatCollateralOpts extends CollateralOpts { defaultRefPerTok?: BigNumberish } -export const deployCollateral = async ( - opts: MAFiatCollateralOpts = {} -): Promise => { - opts = { ...defaultCollateralOpts, ...opts } - - const MorphoAAVECollateralFactory: ContractFactory = await ethers.getContractFactory( - 'MorphoFiatCollateral' - ) - if (opts.erc20 == null) { - const MorphoTokenisedDepositFactory = await ethers.getContractFactory( - 'MorphoAaveV2TokenisedDepositMock' +const makeAaveFiatCollateralTestSuite = ( + collateralName: string, + defaultCollateralOpts: MAFiatCollateralOpts +) => { + const networkConfigToUse = networkConfig[31337] + const deployCollateral = async (opts: MAFiatCollateralOpts = {}): Promise => { + opts = { ...defaultCollateralOpts, ...opts } + + const MorphoAAVECollateralFactory: ContractFactory = await ethers.getContractFactory( + 'MorphoFiatCollateral' ) - const wrapperMock = await MorphoTokenisedDepositFactory.deploy({ - morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, - morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, - underlyingERC20: opts.underlyingToken!, - poolToken: opts.poolToken!, - rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, - rewardToken: networkConfig[1].tokens.MORPHO!, - }) - opts.erc20 = wrapperMock.address - } + if (opts.erc20 == null) { + const MorphoTokenisedDepositFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDepositMock' + ) + const wrapperMock = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfigToUse.MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfigToUse.MORPHO_AAVE_LENS!, + underlyingERC20: opts.underlyingToken!, + poolToken: opts.poolToken!, + rewardsDistributor: networkConfigToUse.MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfigToUse.tokens.MORPHO!, + }) + opts.erc20 = wrapperMock.address + } - const collateral = await MorphoAAVECollateralFactory.deploy( - { - erc20: opts.erc20, - targetName: opts.targetName, - priceTimeout: opts.priceTimeout, - chainlinkFeed: opts.chainlinkFeed, - oracleError: opts.oracleError, - oracleTimeout: opts.oracleTimeout, - maxTradeVolume: opts.maxTradeVolume, - defaultThreshold: opts.defaultThreshold, - delayUntilDefault: opts.delayUntilDefault, - }, - opts.revenueHiding, - { gasLimit: 2000000000 } - ) - await collateral.deployed() - - await expect(collateral.refresh()) - - return collateral -} + const collateral = await MorphoAAVECollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + { gasLimit: 2000000000 } + ) + await collateral.deployed() -type Fixture = () => Promise + await expect(collateral.refresh()) -const makeCollateralFixtureContext = ( - alice: SignerWithAddress, - inOpts: MAFiatCollateralOpts = {} -): Fixture => { - const makeCollateralFixtureContext = async () => { - const opts = { ...defaultCollateralOpts, ...inOpts } + return collateral + } - const MorphoTokenisedDepositFactory = await ethers.getContractFactory( - 'MorphoAaveV2TokenisedDepositMock' - ) - const erc20Factory = await ethers.getContractFactory('ERC20Mock') - const underlyingErc20 = await erc20Factory.attach(opts.underlyingToken!) - const wrapperMock = await MorphoTokenisedDepositFactory.deploy({ - morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, - morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, - underlyingERC20: opts.underlyingToken!, - poolToken: opts.poolToken!, - rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, - rewardToken: networkConfig[1].tokens.MORPHO!, - }) + type Fixture = () => Promise - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) + const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + inOpts: MAFiatCollateralOpts = {} + ): Fixture => { + const makeCollateralFixtureContext = async () => { + const opts = { ...defaultCollateralOpts, ...inOpts } - const chainlinkFeed = ( - await MockV3AggregatorFactory.deploy(8, opts.defaultPrice!) - ) - const collateralOpts = { - ...opts, - erc20: wrapperMock.address, - chainlinkFeed: chainlinkFeed.address, - } - - const collateral = await deployCollateral(collateralOpts) + const MorphoTokenisedDepositFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDepositMock' + ) + const erc20Factory = await ethers.getContractFactory('ERC20Mock') + const underlyingErc20 = await erc20Factory.attach(opts.underlyingToken!) + const wrapperMock = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfigToUse.MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfigToUse.MORPHO_AAVE_LENS!, + underlyingERC20: opts.underlyingToken!, + poolToken: opts.poolToken!, + rewardsDistributor: networkConfigToUse.MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfigToUse.tokens.MORPHO!, + }) + + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) - return { - alice, - collateral, - underlyingErc20: underlyingErc20, - chainlinkFeed, - tok: wrapperMock as unknown as ERC20Mock, - morphoWrapper: wrapperMock, - } as MorphoAaveCollateralFixtureContext - } + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, opts.defaultPrice!) + ) + const collateralOpts = { + ...opts, + erc20: wrapperMock.address, + chainlinkFeed: chainlinkFeed.address, + } - return makeCollateralFixtureContext -} + const collateral = await deployCollateral(collateralOpts) -// eslint-disable-next-line @typescript-eslint/no-empty-function -const reduceTargetPerRef = async () => {} + return { + alice, + collateral, + underlyingErc20: underlyingErc20, + chainlinkFeed, + tok: wrapperMock as unknown as ERC20Mock, + morphoWrapper: wrapperMock, + } as MorphoAaveCollateralFixtureContext + } -// eslint-disable-next-line @typescript-eslint/no-empty-function -const increaseTargetPerRef = async () => {} + return makeCollateralFixtureContext + } -const changeRefPerTok = async ( - ctx: MorphoAaveCollateralFixtureContext, - percentChange: BigNumber -) => { - const rate = await ctx.morphoWrapper.getExchangeRate() - await ctx.morphoWrapper.setExchangeRate(rate.add(rate.mul(percentChange).div(bn('100')))) -} + // eslint-disable-next-line @typescript-eslint/no-empty-function + const reduceTargetPerRef = async () => {} -// prettier-ignore -const reduceRefPerTok = async ( - ctx: MorphoAaveCollateralFixtureContext, - pctDecrease: BigNumberish -) => { - await changeRefPerTok( - ctx, - bn(pctDecrease).mul(-1) - ) -} -// prettier-ignore -const increaseRefPerTok = async ( - ctx: MorphoAaveCollateralFixtureContext, - pctIncrease: BigNumberish -) => { - await changeRefPerTok( - ctx, - bn(pctIncrease) - ) -} -const getExpectedPrice = async (ctx: MorphoAaveCollateralFixtureContext): Promise => { - const clData = await ctx.chainlinkFeed.latestRoundData() - const clDecimals = await ctx.chainlinkFeed.decimals() - - const refPerTok = await ctx.collateral.refPerTok() - return clData.answer - .mul(bn(10).pow(18 - clDecimals)) - .mul(refPerTok) - .div(fp('1')) -} + // eslint-disable-next-line @typescript-eslint/no-empty-function + const increaseTargetPerRef = async () => {} -/* - Define collateral-specific tests -*/ + const changeRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + percentChange: BigNumber + ) => { + const rate = await ctx.morphoWrapper.getExchangeRate() + await ctx.morphoWrapper.setExchangeRate(rate.add(rate.mul(percentChange).div(bn('100')))) + } -// eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificConstructorTests = () => { - it('tokenised deposits can correctly claim rewards', async () => { - const morphoTokenOwner = '0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa' - const forkBlock = 17574117 - const claimer = '0x05e818959c2Aa4CD05EDAe9A099c38e7Bdc377C6' - const reset = getResetFork(forkBlock) - await reset() - const MorphoTokenisedDepositFactory = await ethers.getContractFactory( - 'MorphoAaveV2TokenisedDeposit' + // prettier-ignore + const reduceRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctDecrease: BigNumberish + ) => { + await changeRefPerTok( + ctx, + bn(pctDecrease).mul(-1) ) - const usdtVault = await MorphoTokenisedDepositFactory.deploy({ - morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, - morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, - underlyingERC20: networkConfig[1].tokens.USDT!, - poolToken: networkConfig[1].tokens.aUSDT!, - rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, - rewardToken: networkConfig[1].tokens.MORPHO!, - }) - const vaultCode = await ethers.provider.getCode(usdtVault.address) - await setCode(claimer, vaultCode) - - const vaultWithClaimableRewards = usdtVault.attach(claimer) - const erc20Factory = await ethers.getContractFactory('ERC20Mock') - const underlyingERC20 = await erc20Factory.attach(networkConfig[1].tokens.USDT!) - const depositAmount = utils.parseUnits('1000', 6) - - const user = hre.ethers.provider.getSigner(0) - const userAddress = await user.getAddress() - - expect( - formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) - ).to.be.equal('0.0') - - await whileImpersonating( - hre, - whales[networkConfig[1].tokens.USDT!.toLowerCase()], - async (whaleSigner) => { - await underlyingERC20.connect(whaleSigner).approve(vaultWithClaimableRewards.address, 0) - await underlyingERC20 - .connect(whaleSigner) - .approve(vaultWithClaimableRewards.address, ethers.constants.MaxUint256) - await vaultWithClaimableRewards.connect(whaleSigner).mint(depositAmount, userAddress) - } + } + // prettier-ignore + const increaseRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctIncrease: BigNumberish + ) => { + await changeRefPerTok( + ctx, + bn(pctIncrease) ) + } + const getExpectedPrice = async (ctx: MorphoAaveCollateralFixtureContext): Promise => { + const clData = await ctx.chainlinkFeed.latestRoundData() + const clDecimals = await ctx.chainlinkFeed.decimals() + + const refPerTok = await ctx.collateral.refPerTok() + return clData.answer + .mul(bn(10).pow(18 - clDecimals)) + .mul(refPerTok) + .div(fp('1')) + } - expect( - formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) - ).to.be.equal('8.60295466891613') + /* + Define collateral-specific tests + */ + const collateralSpecificConstructorTests = () => { + it('tokenised deposits can correctly claim rewards', async () => { + const morphoTokenOwner = '0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa' + const forkBlock = 17574117 + const claimer = '0x05e818959c2Aa4CD05EDAe9A099c38e7Bdc377C6' + const reset = getResetFork(forkBlock) + await reset() + const MorphoTokenisedDepositFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDeposit' + ) + const usdtVault = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfigToUse.MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfigToUse.MORPHO_AAVE_LENS!, + underlyingERC20: defaultCollateralOpts.underlyingToken!, + poolToken: defaultCollateralOpts.poolToken!, + rewardsDistributor: networkConfigToUse.MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfigToUse.tokens.MORPHO!, + }) + const vaultCode = await ethers.provider.getCode(usdtVault.address) + await setCode(claimer, vaultCode) + + const vaultWithClaimableRewards = usdtVault.attach(claimer) + const erc20Factory = await ethers.getContractFactory('ERC20Mock') + const underlyingERC20 = erc20Factory.attach(defaultCollateralOpts.underlyingToken!) + const depositAmount = utils.parseUnits('1000', 6) + + const user = hre.ethers.provider.getSigner(0) + const userAddress = await user.getAddress() + + expect( + formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) + ).to.be.equal('0.0') + + await whileImpersonating( + hre, + whales[defaultCollateralOpts.underlyingToken!.toLowerCase()], + async (whaleSigner) => { + await underlyingERC20.connect(whaleSigner).approve(vaultWithClaimableRewards.address, 0) + await underlyingERC20 + .connect(whaleSigner) + .approve(vaultWithClaimableRewards.address, ethers.constants.MaxUint256) + await vaultWithClaimableRewards.connect(whaleSigner).mint(depositAmount, userAddress) + } + ) + + expect( + formatEther( + await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress) + ).slice(0, '8.60295466891613'.length) + ).to.be.equal('8.60295466891613') + + const morphoRewards = await ethers.getContractAt( + 'IMorphoRewardsDistributor', + networkConfigToUse.MORPHO_REWARDS_DISTRIBUTOR! + ) + await morphoRewards.claim(vaultWithClaimableRewards.address, '14162082619942089266', [ + '0x49bb35f20573d5b927c5b5c15c904839cacdf83c6119450ccb6c2ed0647aa71b', + '0xfb9f4530177774effb7af9c1723c7087f60cd135a0cb5f409ec7bbc792a79235', + '0x16dcb8d895b9520c20f476bfc23125aa8f47b800a3bea63b63f89abe158a16fe', + '0x70b3bcf266272051262da958e86efb68a3621977aab0fa0205a5e47a83f3b129', + '0xc06f6781c002b96e5860094fec5ac0692e6e39b3aafa0e02a2c9f87a993a55cb', + '0x679aafaa2e4772160288874aa86f2f1baf6ab7409109da7ad96d3b6d5cf2c3ee', + '0x5b9f1e5d9dfbdc65ec0166a6f1e2fe4a31396fa31739cce54962f1ed43638ff1', + '0xb2db22839637b4c40c7ecc800df0ed8a205c9c31d7d49c41c3d105a62d1c5526', + '0xa26071ec1b113e9033dcbccd7680617d3e75fa626b9f1c43dbc778f641f162da', + '0x53eb58db4c07b67b3bce54b530c950a4ef0c229a3ed2506c53d7c4e31ecc6bfc', + '0x14c512bd39f8b1d13d4cfaad2b4473c4022d01577249ecc97fbf0a64244378ee', + '0xea8c2ee8d43e37ceb7b0c04d59106eff88afbe3e911b656dec7caebd415ea696', + ]) + + expect( + formatEther( + await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress) + ).slice(0, '14.162082619942089'.length) + ).to.be.equal('14.162082619942089') + + // MORPHO is not a transferable token. + // POST Launch we could ask the Morpho team if our TokenVaults could get permission to transfer the MORPHO tokens. + // Otherwise owners of the TokenVault shares need to wait until the protocol enables the transfer function on the MORPHO token. + + await whileImpersonating(hre, morphoTokenOwner, async (signer) => { + const morphoTokenInst = await ethers.getContractAt( + 'IMorphoToken', + networkConfigToUse.tokens.MORPHO!, + signer + ) + + await morphoTokenInst + .connect(signer) + .setUserRole(vaultWithClaimableRewards.address, 0, true) + }) - const morphoRewards = await ethers.getContractAt( - 'IMorphoRewardsDistributor', - networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR! - ) - await morphoRewards.claim(vaultWithClaimableRewards.address, '14162082619942089266', [ - '0x49bb35f20573d5b927c5b5c15c904839cacdf83c6119450ccb6c2ed0647aa71b', - '0xfb9f4530177774effb7af9c1723c7087f60cd135a0cb5f409ec7bbc792a79235', - '0x16dcb8d895b9520c20f476bfc23125aa8f47b800a3bea63b63f89abe158a16fe', - '0x70b3bcf266272051262da958e86efb68a3621977aab0fa0205a5e47a83f3b129', - '0xc06f6781c002b96e5860094fec5ac0692e6e39b3aafa0e02a2c9f87a993a55cb', - '0x679aafaa2e4772160288874aa86f2f1baf6ab7409109da7ad96d3b6d5cf2c3ee', - '0x5b9f1e5d9dfbdc65ec0166a6f1e2fe4a31396fa31739cce54962f1ed43638ff1', - '0xb2db22839637b4c40c7ecc800df0ed8a205c9c31d7d49c41c3d105a62d1c5526', - '0xa26071ec1b113e9033dcbccd7680617d3e75fa626b9f1c43dbc778f641f162da', - '0x53eb58db4c07b67b3bce54b530c950a4ef0c229a3ed2506c53d7c4e31ecc6bfc', - '0x14c512bd39f8b1d13d4cfaad2b4473c4022d01577249ecc97fbf0a64244378ee', - '0xea8c2ee8d43e37ceb7b0c04d59106eff88afbe3e911b656dec7caebd415ea696', - ]) - - expect( - formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) - ).to.be.equal('14.162082619942089') - - // MORPHO is not a transferable token. - // POST Launch we could ask the Morpho team if our TokenVaults could get permission to transfer the MORPHO tokens. - // Otherwise owners of the TokenVault shares need to wait until the protocol enables the transfer function on the MORPHO token. - - await whileImpersonating(hre, morphoTokenOwner, async (signer) => { const morphoTokenInst = await ethers.getContractAt( 'IMorphoToken', - networkConfig[1].tokens.MORPHO!, - signer + networkConfigToUse.tokens.MORPHO!, + user ) + expect(formatEther(await morphoTokenInst.balanceOf(userAddress))).to.be.equal('0.0') - await morphoTokenInst.connect(signer).setUserRole(vaultWithClaimableRewards.address, 0, true) - }) + await vaultWithClaimableRewards.claimRewards() - const morphoTokenInst = await ethers.getContractAt( - 'IMorphoToken', - networkConfig[1].tokens.MORPHO!, - user - ) - expect(formatEther(await morphoTokenInst.balanceOf(userAddress))).to.be.equal('0.0') + expect( + formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) + ).to.be.equal('0.0') - await vaultWithClaimableRewards.claimRewards() + expect( + formatEther(await morphoTokenInst.balanceOf(userAddress)).slice( + 0, + '14.162082619942089'.length + ) + ).to.be.equal('14.162082619942089') + }) + } - expect( - formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) - ).to.be.equal('0.0') + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificStatusTests = () => {} + // eslint-disable-next-line @typescript-eslint/no-empty-function + const beforeEachRewardsTest = async () => {} + + const opts = { + deployCollateral, + collateralSpecificConstructorTests: collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it.skip, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itHasRevenueHiding: it, + resetFork: getResetFork(FORK_BLOCK), + collateralName, + chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, + } - expect(formatEther(await morphoTokenInst.balanceOf(userAddress))).to.be.equal( - '14.162082619942089' - ) - }) + collateralTests(opts) } -// eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificStatusTests = () => {} -// eslint-disable-next-line @typescript-eslint/no-empty-function -const beforeEachRewardsTest = async () => {} - -export const defaultCollateralOpts: MAFiatCollateralOpts = { - targetName: ethers.utils.formatBytes32String('USDT'), - underlyingToken: networkConfig[1].tokens.USDT!, - poolToken: networkConfig[1].tokens.aUSDT!, - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: networkConfig[1].chainlinkFeeds.USDT!, - oracleTimeout: ORACLE_TIMEOUT, - oracleError: ORACLE_ERROR, - maxTradeVolume: bn(1000000), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - revenueHiding: fp('0'), - defaultPrice: bn('1e8'), - defaultRefPerTok: fp('1'), +const makeOpts = ( + underlyingToken: string, + poolToken: string, + chainlinkFeed: string +): MAFiatCollateralOpts => { + return { + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT, + oracleError: ORACLE_ERROR, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + maxTradeVolume: bn(1000000), + revenueHiding: fp('0'), + defaultPrice: bn('1e8'), + defaultRefPerTok: fp('1'), + underlyingToken, + poolToken, + chainlinkFeed, + } } /* Run the test suite */ - -const opts = { - deployCollateral, - collateralSpecificConstructorTests, - collateralSpecificStatusTests, - beforeEachRewardsTest, - makeCollateralFixtureContext, - mintCollateralTo, - reduceTargetPerRef, - increaseTargetPerRef, - reduceRefPerTok, - increaseRefPerTok, - getExpectedPrice, - itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, - itChecksRefPerTokDefault: it, - itChecksPriceChanges: it, - itHasRevenueHiding: it, - resetFork: getResetFork(FORK_BLOCK), - collateralName: 'MorphoAAVEV2FiatCollateral', - chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, -} - -collateralTests(opts) +const { tokens, chainlinkFeeds } = networkConfig[31337] +makeAaveFiatCollateralTestSuite( + 'MorphoAAVEV2FiatCollateral - USDT', + makeOpts(tokens.USDT!, tokens.aUSDT!, chainlinkFeeds.USDT!) +) +makeAaveFiatCollateralTestSuite( + 'MorphoAAVEV2FiatCollateral - USDC', + makeOpts(tokens.USDC!, tokens.aUSDC!, chainlinkFeeds.USDC!) +) +makeAaveFiatCollateralTestSuite( + 'MorphoAAVEV2FiatCollateral - DAI', + makeOpts(tokens.DAI!, tokens.aDAI!, chainlinkFeeds.DAI!) +) diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts index 77a2a56e20..0e902705fd 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts @@ -10,7 +10,7 @@ import { } from '@typechain/index' import { expect } from 'chai' import { BigNumber, BigNumberish } from 'ethers' -import { parseEther, parseUnits } from 'ethers/lib/utils' +import { parseUnits } from 'ethers/lib/utils' import { ethers } from 'hardhat' import collateralTests from '../collateralTests' import { getResetFork } from '../helpers' @@ -24,6 +24,7 @@ import { PRICE_TIMEOUT, } from './constants' import { MorphoAaveCollateralFixtureContext, mintCollateralTo } from './mintCollateralTo' +const configToUse = networkConfig[31337] interface MAFiatCollateralOpts extends CollateralOpts { underlyingToken?: string @@ -34,227 +35,233 @@ interface MAFiatCollateralOpts extends CollateralOpts { targetPrRefFeed?: string refPerTokChainlinkTimeout?: BigNumberish } +const makeAaveNonFiatCollateralTestSuite = ( + collateralName: string, + defaultCollateralOpts: MAFiatCollateralOpts +) => { + const deployCollateral = async (opts: MAFiatCollateralOpts = {}): Promise => { + opts = { ...defaultCollateralOpts, ...opts } -export const deployCollateral = async ( - opts: MAFiatCollateralOpts = {} -): Promise => { - opts = { ...defaultCollateralOpts, ...opts } + const MorphoAAVECollateralFactory: MorphoNonFiatCollateral__factory = + await ethers.getContractFactory('MorphoNonFiatCollateral') + if (opts.erc20 == null) { + const MorphoTokenisedDepositMockFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDepositMock' + ) + const wrapperMock = await MorphoTokenisedDepositMockFactory.deploy({ + morphoController: configToUse.MORPHO_AAVE_CONTROLLER!, + morphoLens: configToUse.MORPHO_AAVE_LENS!, + underlyingERC20: opts.underlyingToken!, + poolToken: opts.poolToken!, + rewardsDistributor: configToUse.MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: configToUse.tokens.MORPHO!, + }) + opts.erc20 = wrapperMock.address + } + const collateral = (await MorphoAAVECollateralFactory.deploy( + { + erc20: opts.erc20!, + targetName: opts.targetName!, + priceTimeout: opts.priceTimeout!, + chainlinkFeed: opts.chainlinkFeed!, + oracleError: opts.oracleError!, + oracleTimeout: opts.oracleTimeout!, + maxTradeVolume: opts.maxTradeVolume!, + defaultThreshold: opts.defaultThreshold!, + delayUntilDefault: opts.delayUntilDefault!, + }, + opts.revenueHiding!, + opts.targetPrRefFeed!, + opts.refPerTokChainlinkTimeout!, + { gasLimit: 2000000000 } + )) as unknown as TestICollateral + await collateral.deployed() - const MorphoAAVECollateralFactory: MorphoNonFiatCollateral__factory = - await ethers.getContractFactory('MorphoNonFiatCollateral') - if (opts.erc20 == null) { - const MorphoTokenisedDepositMockFactory = await ethers.getContractFactory( - 'MorphoAaveV2TokenisedDepositMock' - ) - const wrapperMock = await MorphoTokenisedDepositMockFactory.deploy({ - morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, - morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, - underlyingERC20: opts.underlyingToken!, - poolToken: opts.poolToken!, - rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, - rewardToken: networkConfig[1].tokens.MORPHO!, - }) - opts.erc20 = wrapperMock.address - } - const collateral = (await MorphoAAVECollateralFactory.deploy( - { - erc20: opts.erc20!, - targetName: opts.targetName!, - priceTimeout: opts.priceTimeout!, - chainlinkFeed: opts.chainlinkFeed!, - oracleError: opts.oracleError!, - oracleTimeout: opts.oracleTimeout!, - maxTradeVolume: opts.maxTradeVolume!, - defaultThreshold: opts.defaultThreshold!, - delayUntilDefault: opts.delayUntilDefault!, - }, - opts.revenueHiding!, - opts.targetPrRefFeed!, - opts.refPerTokChainlinkTimeout!, - { gasLimit: 2000000000 } - )) as unknown as TestICollateral - await collateral.deployed() + await expect(collateral.refresh()) - await expect(collateral.refresh()) + return collateral + } - return collateral -} + type Fixture = () => Promise -type Fixture = () => Promise + const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + inOpts: MAFiatCollateralOpts = {} + ): Fixture => { + const makeCollateralFixtureContext = async () => { + const opts = { ...defaultCollateralOpts, ...inOpts } + const MorphoTokenisedDepositMockFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDepositMock' + ) + const erc20Factory = await ethers.getContractFactory('ERC20Mock') + const underlyingErc20 = erc20Factory.attach(opts.underlyingToken!) + const wrapperMock = await MorphoTokenisedDepositMockFactory.deploy({ + morphoController: configToUse.MORPHO_AAVE_CONTROLLER!, + morphoLens: configToUse.MORPHO_AAVE_LENS!, + underlyingERC20: opts.underlyingToken!, + poolToken: opts.poolToken!, + rewardsDistributor: configToUse.MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: configToUse.tokens.MORPHO!, + }) -const makeCollateralFixtureContext = ( - alice: SignerWithAddress, - inOpts: MAFiatCollateralOpts = {} -): Fixture => { - const makeCollateralFixtureContext = async () => { - const opts = { ...defaultCollateralOpts, ...inOpts } - const MorphoTokenisedDepositMockFactory = await ethers.getContractFactory( - 'MorphoAaveV2TokenisedDepositMock' - ) - const erc20Factory = await ethers.getContractFactory('ERC20Mock') - const underlyingErc20 = erc20Factory.attach(opts.underlyingToken!) - const wrapperMock = await MorphoTokenisedDepositMockFactory.deploy({ - morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, - morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, - underlyingERC20: opts.underlyingToken!, - poolToken: opts.poolToken!, - rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, - rewardToken: networkConfig[1].tokens.MORPHO!, - }) + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, opts.defaultPrice!) + ) - const chainlinkFeed = ( - await MockV3AggregatorFactory.deploy(8, opts.defaultPrice!) - ) + const targetPrRefFeed = ( + await MockV3AggregatorFactory.deploy(8, opts.defaultRefPerTok!) + ) - const targetPrRefFeed = ( - await MockV3AggregatorFactory.deploy(8, opts.defaultRefPerTok!) - ) + const collateralOpts = { + ...opts, + erc20: wrapperMock.address, + chainlinkFeed: chainlinkFeed.address, + targetPrRefFeed: targetPrRefFeed.address, + } + const collateral = await deployCollateral(collateralOpts) - const collateralOpts = { - ...opts, - erc20: wrapperMock.address, - chainlinkFeed: chainlinkFeed.address, - targetPrRefFeed: targetPrRefFeed.address, + return { + alice, + collateral, + chainlinkFeed, + targetPrRefFeed, + tok: wrapperMock as unknown as ERC20Mock, + morphoWrapper: wrapperMock, + underlyingErc20: underlyingErc20, + } as MorphoAaveCollateralFixtureContext } - const collateral = await deployCollateral(collateralOpts) - return { - alice, - collateral, - chainlinkFeed, - targetPrRefFeed, - tok: wrapperMock as unknown as ERC20Mock, - morphoWrapper: wrapperMock, - underlyingErc20: underlyingErc20, - } as MorphoAaveCollateralFixtureContext + return makeCollateralFixtureContext } - return makeCollateralFixtureContext -} + /* + Define helper functions + */ -/* - Define helper functions -*/ + // eslint-disable-next-line @typescript-eslint/no-empty-function + const reduceTargetPerRef = async () => {} -// eslint-disable-next-line @typescript-eslint/no-empty-function -const reduceTargetPerRef = async () => {} + // eslint-disable-next-line @typescript-eslint/no-empty-function + const increaseTargetPerRef = async () => {} -// eslint-disable-next-line @typescript-eslint/no-empty-function -const increaseTargetPerRef = async () => {} + const changeRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + percentChange: BigNumber + ) => { + const rate = await ctx.morphoWrapper.getExchangeRate() + await ctx.morphoWrapper.setExchangeRate(rate.add(rate.mul(percentChange).div(bn('100')))) + } -const changeRefPerTok = async ( - ctx: MorphoAaveCollateralFixtureContext, - percentChange: BigNumber -) => { - const rate = await ctx.morphoWrapper.getExchangeRate() - await ctx.morphoWrapper.setExchangeRate(rate.add(rate.mul(percentChange).div(bn('100')))) + // prettier-ignore + const reduceRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctDecrease: BigNumberish + ) => { + await changeRefPerTok( + ctx, + bn(pctDecrease).mul(-1) + ) + } + // prettier-ignore + const increaseRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctIncrease: BigNumberish + ) => { + await changeRefPerTok( + ctx, + bn(pctIncrease) + ) + } - // { - // const lastRound = await ctx.targetPrRefFeed!.latestRoundData() - // const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) - // await ctx.targetPrRefFeed!.updateAnswer(nextAnswer) - // } + const getExpectedPrice = async (ctx: MorphoAaveCollateralFixtureContext): Promise => { + const clData = await ctx.chainlinkFeed.latestRoundData() + const clDecimals = await ctx.chainlinkFeed.decimals() - // { - // const lastRound = await ctx.chainlinkFeed.latestRoundData() - // const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) - // await ctx.chainlinkFeed.updateAnswer(nextAnswer) - // } -} + const clRptData = await ctx.targetPrRefFeed!.latestRoundData() + const clRptDecimals = await ctx.targetPrRefFeed!.decimals() -// prettier-ignore -const reduceRefPerTok = async ( - ctx: MorphoAaveCollateralFixtureContext, - pctDecrease: BigNumberish -) => { - await changeRefPerTok( - ctx, - bn(pctDecrease).mul(-1) - ) -} -// prettier-ignore -const increaseRefPerTok = async ( - ctx: MorphoAaveCollateralFixtureContext, - pctIncrease: BigNumberish -) => { - await changeRefPerTok( - ctx, - bn(pctIncrease) - ) -} + const expctPrice = clData.answer + .mul(bn(10).pow(18 - clDecimals)) + .mul(clRptData.answer.mul(bn(10).pow(18 - clRptDecimals))) + .div(fp('1')) + return expctPrice + } + + /* + Define collateral-specific tests + */ -const getExpectedPrice = async (ctx: MorphoAaveCollateralFixtureContext): Promise => { - const clData = await ctx.chainlinkFeed.latestRoundData() - const clDecimals = await ctx.chainlinkFeed.decimals() + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificConstructorTests = () => {} - const clRptData = await ctx.targetPrRefFeed!.latestRoundData() - const clRptDecimals = await ctx.targetPrRefFeed!.decimals() + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificStatusTests = () => {} + // eslint-disable-next-line @typescript-eslint/no-empty-function + const beforeEachRewardsTest = async () => {} + const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it.skip, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itHasRevenueHiding: it, + resetFork: getResetFork(FORK_BLOCK), + collateralName, + chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, + } - const expctPrice = clData.answer - .mul(bn(10).pow(18 - clDecimals)) - .mul(clRptData.answer.mul(bn(10).pow(18 - clRptDecimals))) - .div(fp('1')) - return expctPrice + collateralTests(opts) } /* - Define collateral-specific tests + Run the test suite */ - -// eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificConstructorTests = () => {} - -// eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificStatusTests = () => {} -// eslint-disable-next-line @typescript-eslint/no-empty-function -const beforeEachRewardsTest = async () => {} - -export const defaultCollateralOpts: MAFiatCollateralOpts = { +makeAaveNonFiatCollateralTestSuite('MorphoAAVEV2NonFiatCollateral - WBTC', { targetName: ethers.utils.formatBytes32String('BTC'), - underlyingToken: networkConfig[1].tokens.WBTC!, - poolToken: networkConfig[1].tokens.aWBTC!, + underlyingToken: configToUse.tokens.WBTC!, + poolToken: configToUse.tokens.aWBTC!, priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: networkConfig[1].chainlinkFeeds.WBTC!, - targetPrRefFeed: networkConfig[1].chainlinkFeeds.wBTCBTC!, + chainlinkFeed: configToUse.chainlinkFeeds.WBTC!, + targetPrRefFeed: configToUse.chainlinkFeeds.wBTCBTC!, oracleTimeout: ORACLE_TIMEOUT, oracleError: ORACLE_ERROR, - maxTradeVolume: parseEther('100'), + maxTradeVolume: fp('1e6'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, revenueHiding: fp('0'), defaultPrice: parseUnits('30000', 8), defaultRefPerTok: parseUnits('1', 8), refPerTokChainlinkTimeout: PRICE_TIMEOUT, -} +}) -/* - Run the test suite -*/ - -const opts = { - deployCollateral, - collateralSpecificConstructorTests, - collateralSpecificStatusTests, - beforeEachRewardsTest, - makeCollateralFixtureContext, - mintCollateralTo, - reduceTargetPerRef, - increaseTargetPerRef, - reduceRefPerTok, - increaseRefPerTok, - getExpectedPrice, - itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, - itChecksRefPerTokDefault: it, - itChecksPriceChanges: it, - itHasRevenueHiding: it, - resetFork: getResetFork(FORK_BLOCK), - collateralName: 'MorphoAAVEV2NonFiatCollateral', - chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, -} - -collateralTests(opts) +makeAaveNonFiatCollateralTestSuite('MorphoAAVEV2NonFiatCollateral - stETH', { + targetName: ethers.utils.formatBytes32String('ETH'), + underlyingToken: configToUse.tokens.stETH!, + poolToken: configToUse.tokens.astETH!, + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: configToUse.chainlinkFeeds.ETH!, + targetPrRefFeed: configToUse.chainlinkFeeds.stETHETH!, + oracleTimeout: ORACLE_TIMEOUT, + oracleError: ORACLE_ERROR, + maxTradeVolume: fp('1e6'), + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: fp('0'), + defaultPrice: parseUnits('1800', 8), + defaultRefPerTok: parseUnits('1', 8), + refPerTokChainlinkTimeout: PRICE_TIMEOUT, +}) diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts index 1544f99966..16dd346ae7 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts @@ -31,9 +31,7 @@ interface MAFiatCollateralOpts extends CollateralOpts { defaultRefPerTok?: BigNumberish } -export const deployCollateral = async ( - opts: MAFiatCollateralOpts = {} -): Promise => { +const deployCollateral = async (opts: MAFiatCollateralOpts = {}): Promise => { if (opts.defaultThreshold == null && opts.delayUntilDefault === 0) { opts.defaultThreshold = fp('0.001') } @@ -191,7 +189,7 @@ const collateralSpecificStatusTests = () => {} // eslint-disable-next-line @typescript-eslint/no-empty-function const beforeEachRewardsTest = async () => {} -export const defaultCollateralOpts: MAFiatCollateralOpts = { +const defaultCollateralOpts: MAFiatCollateralOpts = { targetName: ethers.utils.formatBytes32String('ETH'), underlyingToken: networkConfig[1].tokens.stETH!, poolToken: networkConfig[1].tokens.astETH!, @@ -229,7 +227,7 @@ const opts = { itChecksPriceChanges: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), - collateralName: 'MorphoAAVEV2SelfReferentialCollateral', + collateralName: 'MorphoAAVEV2SelfReferentialCollateral - WETH', chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, } diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts index 1b4122b604..c529655694 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts @@ -8,9 +8,10 @@ import { expect } from 'chai' import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' type ITokenSymbol = keyof ITokens +const networkConfigToUse = networkConfig[31337] const mkToken = (symbol: ITokenSymbol) => ({ - address: networkConfig[1].tokens[symbol]! as string, + address: networkConfigToUse.tokens[symbol]! as string, symbol: symbol, }) const mkTestCase = (symbol: T, amount: string) => ({ @@ -20,6 +21,7 @@ const mkTestCase = (symbol: T, amount: string) => ({ }) const TOKENS_TO_TEST = [ + mkTestCase('USDC', '1000.0'), mkTestCase('USDT', '1000.0'), mkTestCase('DAI', '1000.0'), mkTestCase('WETH', '1.0'), @@ -37,14 +39,14 @@ const execTestForToken = ({ token, poolToken, amount }: ITestSuiteVariant) => { } const instances = { underlying: factories.ERC20Mock.attach(token.address), - morpho: factories.ERC20Mock.attach(networkConfig[1].tokens.MORPHO!), + morpho: factories.ERC20Mock.attach(networkConfigToUse.tokens.MORPHO!), tokenVault: await factories.MorphoTokenisedDeposit.deploy({ underlyingERC20: token.address, poolToken: poolToken.address, - morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, - morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, - rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, - rewardToken: networkConfig[1].tokens.MORPHO!, + morphoController: networkConfigToUse.MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfigToUse.MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfigToUse.MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfigToUse.tokens.MORPHO!, }), } const underlyingDecimals = await instances.underlying.decimals() diff --git a/yarn.lock b/yarn.lock index b3f0942254..cd38842265 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,13 @@ __metadata: version: 6 cacheKey: 8 +"@aashutoshrathi/word-wrap@npm:^1.2.3": + version: 1.2.6 + resolution: "@aashutoshrathi/word-wrap@npm:1.2.6" + checksum: ada901b9e7c680d190f1d012c84217ce0063d8f5c5a7725bb91ec3c5ed99bb7572680eb2d2938a531ccbaec39a95422fcd8a6b4a13110c7d98dd75402f66a0cd + languageName: node + linkType: hard + "@aave/protocol-v2@npm:^1.0.1": version: 1.0.1 resolution: "@aave/protocol-v2@npm:1.0.1" @@ -14,13 +21,13 @@ __metadata: languageName: node linkType: hard -"@ampproject/remapping@npm:^2.1.0": - version: 2.2.0 - resolution: "@ampproject/remapping@npm:2.2.0" +"@ampproject/remapping@npm:^2.2.0": + version: 2.2.1 + resolution: "@ampproject/remapping@npm:2.2.1" dependencies: - "@jridgewell/gen-mapping": ^0.1.0 + "@jridgewell/gen-mapping": ^0.3.0 "@jridgewell/trace-mapping": ^0.3.9 - checksum: d74d170d06468913921d72430259424b7e4c826b5a7d39ff839a29d547efb97dc577caa8ba3fb5cf023624e9af9d09651afc3d4112a45e2050328abc9b3a2292 + checksum: 03c04fd526acc64a1f4df22651186f3e5ef0a9d6d6530ce4482ec9841269cf7a11dbb8af79237c282d721c5312024ff17529cd72cc4768c11e999b58e2302079 languageName: node linkType: hard @@ -47,11 +54,12 @@ __metadata: linkType: hard "@aws-sdk/types@npm:^3.1.0": - version: 3.329.0 - resolution: "@aws-sdk/types@npm:3.329.0" + version: 3.378.0 + resolution: "@aws-sdk/types@npm:3.378.0" dependencies: + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 2bbcd8e6ba2f813dc220c60e7ac0fa0d0990d49f88030fd8235479586716016c8f9d7aeaf1f4c1a64694275f33690bd1dd4ed46e127983201061e063da73f426 + checksum: c4c7ebb48a625cb990a1288466f2dd8f0d770078cc77b60d5ee4a803b473ff41df474271dff26d3dadad151d5a016b398167738dd4926266ff1cd04585d4d8e8 languageName: node linkType: hard @@ -64,188 +72,196 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/code-frame@npm:7.18.6" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/code-frame@npm:7.22.5" dependencies: - "@babel/highlight": ^7.18.6 - checksum: 195e2be3172d7684bf95cff69ae3b7a15a9841ea9d27d3c843662d50cdd7d6470fd9c8e64be84d031117e4a4083486effba39f9aef6bbb2c89f7f21bcfba33ba + "@babel/highlight": ^7.22.5 + checksum: cfe804f518f53faaf9a1d3e0f9f74127ab9a004912c3a16fda07fb6a633393ecb9918a053cb71804204c1b7ec3d49e1699604715e2cfb0c9f7bc4933d324ebb6 languageName: node linkType: hard -"@babel/compat-data@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/compat-data@npm:7.18.6" - checksum: fd73a1bd7bc29be5528d2ef78248929ed3ee72e0edb69cef6051e0aad0bf8087594db6cd9e981f0d7f5bfc274fdbb77306d8abea8ceb71e95c18afc3ebd81828 +"@babel/compat-data@npm:^7.22.9": + version: 7.22.9 + resolution: "@babel/compat-data@npm:7.22.9" + checksum: bed77d9044ce948b4327b30dd0de0779fa9f3a7ed1f2d31638714ed00229fa71fc4d1617ae0eb1fad419338d3658d0e9a5a083297451e09e73e078d0347ff808 languageName: node linkType: hard "@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3": - version: 7.18.6 - resolution: "@babel/core@npm:7.18.6" - dependencies: - "@ampproject/remapping": ^2.1.0 - "@babel/code-frame": ^7.18.6 - "@babel/generator": ^7.18.6 - "@babel/helper-compilation-targets": ^7.18.6 - "@babel/helper-module-transforms": ^7.18.6 - "@babel/helpers": ^7.18.6 - "@babel/parser": ^7.18.6 - "@babel/template": ^7.18.6 - "@babel/traverse": ^7.18.6 - "@babel/types": ^7.18.6 + version: 7.22.9 + resolution: "@babel/core@npm:7.22.9" + dependencies: + "@ampproject/remapping": ^2.2.0 + "@babel/code-frame": ^7.22.5 + "@babel/generator": ^7.22.9 + "@babel/helper-compilation-targets": ^7.22.9 + "@babel/helper-module-transforms": ^7.22.9 + "@babel/helpers": ^7.22.6 + "@babel/parser": ^7.22.7 + "@babel/template": ^7.22.5 + "@babel/traverse": ^7.22.8 + "@babel/types": ^7.22.5 convert-source-map: ^1.7.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 - json5: ^2.2.1 - semver: ^6.3.0 - checksum: 711459ebf7afab7b8eff88b7155c3f4a62690545f1c8c2eb6ba5ebaed01abeecb984cf9657847a2151ad24a5645efce765832aa343ce0f0386f311b67b59589a + json5: ^2.2.2 + semver: ^6.3.1 + checksum: 7bf069aeceb417902c4efdaefab1f7b94adb7dea694a9aed1bda2edf4135348a080820529b1a300c6f8605740a00ca00c19b2d5e74b5dd489d99d8c11d5e56d1 languageName: node linkType: hard -"@babel/generator@npm:^7.18.6, @babel/generator@npm:^7.7.2": - version: 7.18.7 - resolution: "@babel/generator@npm:7.18.7" +"@babel/generator@npm:^7.22.7, @babel/generator@npm:^7.22.9, @babel/generator@npm:^7.7.2": + version: 7.22.9 + resolution: "@babel/generator@npm:7.22.9" dependencies: - "@babel/types": ^7.18.7 + "@babel/types": ^7.22.5 "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 jsesc: ^2.5.1 - checksum: aad4b6873130165e9483af2888bce5a3a5ad9cca0757fc90ae11a0396757d0b295a3bff49282c8df8ab01b31972cc855ae88fd9ddc9ab00d9427dc0e01caeea9 + checksum: 7c9d2c58b8d5ac5e047421a6ab03ec2ff5d9a5ff2c2212130a0055e063ac349e0b19d435537d6886c999771aef394832e4f54cd9fc810100a7f23d982f6af06b languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-compilation-targets@npm:7.18.6" +"@babel/helper-compilation-targets@npm:^7.22.9": + version: 7.22.9 + resolution: "@babel/helper-compilation-targets@npm:7.22.9" dependencies: - "@babel/compat-data": ^7.18.6 - "@babel/helper-validator-option": ^7.18.6 - browserslist: ^4.20.2 - semver: ^6.3.0 + "@babel/compat-data": ^7.22.9 + "@babel/helper-validator-option": ^7.22.5 + browserslist: ^4.21.9 + lru-cache: ^5.1.1 + semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0 - checksum: f09ddaddc83c241cb7a040025e2ba558daa1c950ce878604d91230aed8d8a90f10dfd5bb0b67bc5b3db8af1576a0d0dac1d65959a06a17259243dbb5730d0ed1 + checksum: ea0006c6a93759025f4a35a25228ae260538c9f15023e8aac2a6d45ca68aef4cf86cfc429b19af9a402cbdd54d5de74ad3fbcf6baa7e48184dc079f1a791e178 languageName: node linkType: hard -"@babel/helper-environment-visitor@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-environment-visitor@npm:7.18.6" - checksum: 64fce65a26efb50d2496061ab2de669dc4c42175a8e05c82279497127e5c542538ed22b38194f6f5a4e86bed6ef5a4890aed23408480db0555728b4ca660fc9c +"@babel/helper-environment-visitor@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-environment-visitor@npm:7.22.5" + checksum: 248532077d732a34cd0844eb7b078ff917c3a8ec81a7f133593f71a860a582f05b60f818dc5049c2212e5baa12289c27889a4b81d56ef409b4863db49646c4b1 languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-function-name@npm:7.18.6" +"@babel/helper-function-name@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-function-name@npm:7.22.5" dependencies: - "@babel/template": ^7.18.6 - "@babel/types": ^7.18.6 - checksum: bf84c2e0699aa07c3559d4262d199d4a9d0320037c2932efe3246866c3e01ce042c9c2131b5db32ba2409a9af01fb468171052819af759babc8ca93bdc6c9aeb + "@babel/template": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: 6b1f6ce1b1f4e513bf2c8385a557ea0dd7fa37971b9002ad19268ca4384bbe90c09681fe4c076013f33deabc63a53b341ed91e792de741b4b35e01c00238177a languageName: node linkType: hard -"@babel/helper-hoist-variables@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-hoist-variables@npm:7.18.6" +"@babel/helper-hoist-variables@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-hoist-variables@npm:7.22.5" dependencies: - "@babel/types": ^7.18.6 - checksum: fd9c35bb435fda802bf9ff7b6f2df06308a21277c6dec2120a35b09f9de68f68a33972e2c15505c1a1a04b36ec64c9ace97d4a9e26d6097b76b4396b7c5fa20f + "@babel/types": ^7.22.5 + checksum: 394ca191b4ac908a76e7c50ab52102669efe3a1c277033e49467913c7ed6f7c64d7eacbeabf3bed39ea1f41731e22993f763b1edce0f74ff8563fd1f380d92cc languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-module-imports@npm:7.18.6" +"@babel/helper-module-imports@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-module-imports@npm:7.22.5" dependencies: - "@babel/types": ^7.18.6 - checksum: f393f8a3b3304b1b7a288a38c10989de754f01d29caf62ce7c4e5835daf0a27b81f3ac687d9d2780d39685aae7b55267324b512150e7b2be967b0c493b6a1def + "@babel/types": ^7.22.5 + checksum: 9ac2b0404fa38b80bdf2653fbeaf8e8a43ccb41bd505f9741d820ed95d3c4e037c62a1bcdcb6c9527d7798d2e595924c4d025daed73283badc180ada2c9c49ad languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-module-transforms@npm:7.18.6" +"@babel/helper-module-transforms@npm:^7.22.9": + version: 7.22.9 + resolution: "@babel/helper-module-transforms@npm:7.22.9" dependencies: - "@babel/helper-environment-visitor": ^7.18.6 - "@babel/helper-module-imports": ^7.18.6 - "@babel/helper-simple-access": ^7.18.6 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/helper-validator-identifier": ^7.18.6 - "@babel/template": ^7.18.6 - "@babel/traverse": ^7.18.6 - "@babel/types": ^7.18.6 - checksum: 75d90be9ecd314fe2f1b668ce065d7e8b3dff82eddea88480259c5d4bd54f73a909d0998909ffe734a44ba8be85ba233359033071cc800db209d37173bd26db2 + "@babel/helper-environment-visitor": ^7.22.5 + "@babel/helper-module-imports": ^7.22.5 + "@babel/helper-simple-access": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/helper-validator-identifier": ^7.22.5 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 2751f77660518cf4ff027514d6f4794f04598c6393be7b04b8e46c6e21606e11c19f3f57ab6129a9c21bacdf8b3ffe3af87bb401d972f34af2d0ffde02ac3001 languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.8.0": - version: 7.18.6 - resolution: "@babel/helper-plugin-utils@npm:7.18.6" - checksum: 3dbfceb6c10fdf6c78a0e57f24e991ff8967b8a0bd45fe0314fb4a8ccf7c8ad4c3778c319a32286e7b1f63d507173df56b4e69fb31b71e1b447a73efa1ca723e +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.22.5 + resolution: "@babel/helper-plugin-utils@npm:7.22.5" + checksum: c0fc7227076b6041acd2f0e818145d2e8c41968cc52fb5ca70eed48e21b8fe6dd88a0a91cbddf4951e33647336eb5ae184747ca706817ca3bef5e9e905151ff5 languageName: node linkType: hard -"@babel/helper-simple-access@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-simple-access@npm:7.18.6" +"@babel/helper-simple-access@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-simple-access@npm:7.22.5" dependencies: - "@babel/types": ^7.18.6 - checksum: 37cd36eef199e0517845763c1e6ff6ea5e7876d6d707a6f59c9267c547a50aa0e84260ba9285d49acfaf2cfa0a74a772d92967f32ac1024c961517d40b6c16a5 + "@babel/types": ^7.22.5 + checksum: fe9686714caf7d70aedb46c3cce090f8b915b206e09225f1e4dbc416786c2fdbbee40b38b23c268b7ccef749dd2db35f255338fb4f2444429874d900dede5ad2 languageName: node linkType: hard -"@babel/helper-split-export-declaration@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-split-export-declaration@npm:7.18.6" +"@babel/helper-split-export-declaration@npm:^7.22.6": + version: 7.22.6 + resolution: "@babel/helper-split-export-declaration@npm:7.22.6" dependencies: - "@babel/types": ^7.18.6 - checksum: c6d3dede53878f6be1d869e03e9ffbbb36f4897c7cc1527dc96c56d127d834ffe4520a6f7e467f5b6f3c2843ea0e81a7819d66ae02f707f6ac057f3d57943a2b + "@babel/types": ^7.22.5 + checksum: e141cace583b19d9195f9c2b8e17a3ae913b7ee9b8120246d0f9ca349ca6f03cb2c001fd5ec57488c544347c0bb584afec66c936511e447fd20a360e591ac921 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-string-parser@npm:7.22.5" + checksum: 836851ca5ec813077bbb303acc992d75a360267aa3b5de7134d220411c852a6f17de7c0d0b8c8dcc0f567f67874c00f4528672b2a4f1bc978a3ada64c8c78467 languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-validator-identifier@npm:7.18.6" - checksum: e295254d616bbe26e48c196a198476ab4d42a73b90478c9842536cf910ead887f5af6b5c4df544d3052a25ccb3614866fa808dc1e3a5a4291acd444e243c0648 +"@babel/helper-validator-identifier@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-validator-identifier@npm:7.22.5" + checksum: 7f0f30113474a28298c12161763b49de5018732290ca4de13cdaefd4fd0d635a6fe3f6686c37a02905fb1e64f21a5ee2b55140cf7b070e729f1bd66866506aea languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-validator-option@npm:7.18.6" - checksum: f9cc6eb7cc5d759c5abf006402180f8d5e4251e9198197428a97e05d65eb2f8ae5a0ce73b1dfd2d35af41d0eb780627a64edf98a4e71f064eeeacef8de58f2cf +"@babel/helper-validator-option@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-validator-option@npm:7.22.5" + checksum: bbeca8a85ee86990215c0424997438b388b8d642d69b9f86c375a174d3cdeb270efafd1ff128bc7a1d370923d13b6e45829ba8581c027620e83e3a80c5c414b3 languageName: node linkType: hard -"@babel/helpers@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helpers@npm:7.18.6" +"@babel/helpers@npm:^7.22.6": + version: 7.22.6 + resolution: "@babel/helpers@npm:7.22.6" dependencies: - "@babel/template": ^7.18.6 - "@babel/traverse": ^7.18.6 - "@babel/types": ^7.18.6 - checksum: 5dea4fa53776703ae4190cacd3f81464e6e00cf0b6908ea9b0af2b3d9992153f3746dd8c33d22ec198f77a8eaf13a273d83cd8847f7aef983801e7bfafa856ec + "@babel/template": ^7.22.5 + "@babel/traverse": ^7.22.6 + "@babel/types": ^7.22.5 + checksum: 5c1f33241fe7bf7709868c2105134a0a86dca26a0fbd508af10a89312b1f77ca38ebae43e50be3b208613c5eacca1559618af4ca236f0abc55d294800faeff30 languageName: node linkType: hard -"@babel/highlight@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/highlight@npm:7.18.6" +"@babel/highlight@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/highlight@npm:7.22.5" dependencies: - "@babel/helper-validator-identifier": ^7.18.6 + "@babel/helper-validator-identifier": ^7.22.5 chalk: ^2.0.0 js-tokens: ^4.0.0 - checksum: 92d8ee61549de5ff5120e945e774728e5ccd57fd3b2ed6eace020ec744823d4a98e242be1453d21764a30a14769ecd62170fba28539b211799bbaf232bbb2789 + checksum: f61ae6de6ee0ea8d9b5bcf2a532faec5ab0a1dc0f7c640e5047fc61630a0edb88b18d8c92eb06566d30da7a27db841aca11820ecd3ebe9ce514c9350fbed39c4 languageName: node linkType: hard -"@babel/parser@npm:^7.14.7, @babel/parser@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/parser@npm:7.18.6" +"@babel/parser@npm:^7.14.7, @babel/parser@npm:^7.22.5, @babel/parser@npm:^7.22.7": + version: 7.22.7 + resolution: "@babel/parser@npm:7.22.7" bin: parser: ./bin/babel-parser.js - checksum: 533ffc26667b7e2e0d87ae11368d90b6a3a468734d6dfe9c4697c24f48373cf9cc35ee08e416728f087fc56531b68022f752097941feddc60e0223d69a4d4cad + checksum: 02209ddbd445831ee8bf966fdf7c29d189ed4b14343a68eb2479d940e7e3846340d7cc6bd654a5f3d87d19dc84f49f50a58cf9363bee249dc5409ff3ba3dab54 languageName: node linkType: hard @@ -382,52 +398,53 @@ __metadata: linkType: hard "@babel/plugin-syntax-typescript@npm:^7.7.2": - version: 7.18.6 - resolution: "@babel/plugin-syntax-typescript@npm:7.18.6" + version: 7.22.5 + resolution: "@babel/plugin-syntax-typescript@npm:7.22.5" dependencies: - "@babel/helper-plugin-utils": ^7.18.6 + "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 2cde73725ec51118ebf410bf02d78781c03fa4d3185993fcc9d253b97443381b621c44810084c5dd68b92eb8bdfae0e5b163e91b32bebbb33852383d1815c05d + checksum: 8ab7718fbb026d64da93681a57797d60326097fd7cb930380c8bffd9eb101689e90142c760a14b51e8e69c88a73ba3da956cb4520a3b0c65743aee5c71ef360a languageName: node linkType: hard -"@babel/template@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/template@npm:7.18.6" +"@babel/template@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/template@npm:7.22.5" dependencies: - "@babel/code-frame": ^7.18.6 - "@babel/parser": ^7.18.6 - "@babel/types": ^7.18.6 - checksum: cb02ed804b7b1938dbecef4e01562013b80681843dd391933315b3dd9880820def3b5b1bff6320d6e4c6a1d63d1d5799630d658ec6b0369c5505e7e4029c38fb + "@babel/code-frame": ^7.22.5 + "@babel/parser": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: c5746410164039aca61829cdb42e9a55410f43cace6f51ca443313f3d0bdfa9a5a330d0b0df73dc17ef885c72104234ae05efede37c1cc8a72dc9f93425977a3 languageName: node linkType: hard -"@babel/traverse@npm:^7.18.6, @babel/traverse@npm:^7.7.2": - version: 7.18.6 - resolution: "@babel/traverse@npm:7.18.6" +"@babel/traverse@npm:^7.22.6, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.7.2": + version: 7.22.8 + resolution: "@babel/traverse@npm:7.22.8" dependencies: - "@babel/code-frame": ^7.18.6 - "@babel/generator": ^7.18.6 - "@babel/helper-environment-visitor": ^7.18.6 - "@babel/helper-function-name": ^7.18.6 - "@babel/helper-hoist-variables": ^7.18.6 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/parser": ^7.18.6 - "@babel/types": ^7.18.6 + "@babel/code-frame": ^7.22.5 + "@babel/generator": ^7.22.7 + "@babel/helper-environment-visitor": ^7.22.5 + "@babel/helper-function-name": ^7.22.5 + "@babel/helper-hoist-variables": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/parser": ^7.22.7 + "@babel/types": ^7.22.5 debug: ^4.1.0 globals: ^11.1.0 - checksum: 5427a9db63984b2600f62b257dab18e3fc057997b69d708573bfc88eb5eacd6678fb24fddba082d6ac050734b8846ce110960be841ea1e461d66e2cde72b6b07 + checksum: a381369bc3eedfd13ed5fef7b884657f1c29024ea7388198149f0edc34bd69ce3966e9f40188d15f56490a5e12ba250ccc485f2882b53d41b054fccefb233e33 languageName: node linkType: hard -"@babel/types@npm:^7.18.6, @babel/types@npm:^7.18.7, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3": - version: 7.18.7 - resolution: "@babel/types@npm:7.18.7" +"@babel/types@npm:^7.20.7, @babel/types@npm:^7.22.5, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3": + version: 7.22.5 + resolution: "@babel/types@npm:7.22.5" dependencies: - "@babel/helper-validator-identifier": ^7.18.6 + "@babel/helper-string-parser": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.5 to-fast-properties: ^2.0.0 - checksum: 3114ce161c4ebcb70271e168aa5af5cecedf3278209161d5ba6124bd3f9cb02e3f3ace587ad1b53f7baa153b6b3714720721c72a9ef3ec451663862f9cc1f014 + checksum: c13a9c1dc7d2d1a241a2f8363540cb9af1d66e978e8984b400a20c4f38ba38ca29f06e26a0f2d49a70bad9e57615dac09c35accfddf1bb90d23cd3e0a0bab892 languageName: node linkType: hard @@ -504,20 +521,20 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^1.3.0": - version: 1.3.0 - resolution: "@eslint/eslintrc@npm:1.3.0" +"@eslint/eslintrc@npm:^1.2.2": + version: 1.4.1 + resolution: "@eslint/eslintrc@npm:1.4.1" dependencies: ajv: ^6.12.4 debug: ^4.3.2 - espree: ^9.3.2 - globals: ^13.15.0 + espree: ^9.4.0 + globals: ^13.19.0 ignore: ^5.2.0 import-fresh: ^3.2.1 js-yaml: ^4.1.0 minimatch: ^3.1.2 strip-json-comments: ^3.1.1 - checksum: a1e734ad31a8b5328dce9f479f185fd4fc83dd7f06c538e1fa457fd8226b89602a55cc6458cd52b29573b01cdfaf42331be8cfc1fec732570086b591f4ed6515 + checksum: cd3e5a8683db604739938b1c1c8b77927dc04fce3e28e0c88e7f2cd4900b89466baf83dfbad76b2b9e4d2746abdd00dd3f9da544d3e311633d8693f327d04cd7 languageName: node linkType: hard @@ -960,19 +977,12 @@ __metadata: languageName: node linkType: hard -"@gar/promisify@npm:^1.1.3": - version: 1.1.3 - resolution: "@gar/promisify@npm:1.1.3" - checksum: 4059f790e2d07bf3c3ff3e0fec0daa8144fe35c1f6e0111c9921bd32106adaa97a4ab096ad7dab1e28ee6a9060083c4d1a4ada42a7f5f3f7a96b8812e2b757c1 - languageName: node - linkType: hard - "@graphql-typed-document-node/core@npm:^3.1.1": - version: 3.1.2 - resolution: "@graphql-typed-document-node/core@npm:3.1.2" + version: 3.2.0 + resolution: "@graphql-typed-document-node/core@npm:3.2.0" peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: a61afa025acdabd7833e4f654a5802fc1a526171f81e0c435c8e651050a5a0682499a2c7a51304ceb61fde36cd69fc7975ce5e1b16b9ba7ea474c649f33eea8b + checksum: fa44443accd28c8cf4cb96aaaf39d144a22e8b091b13366843f4e97d19c7bfeaf609ce3c7603a4aeffe385081eaf8ea245d078633a7324c11c5ec4b2011bb76d languageName: node linkType: hard @@ -994,6 +1004,20 @@ __metadata: languageName: node linkType: hard +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: ^5.1.2 + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: ^7.0.1 + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: ^8.1.0 + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 4a473b9b32a7d4d3cfb7a614226e555091ff0c5a29a1734c28c72a182c2f6699b26fc6b5c2131dfd841e86b185aea714c72201d7c98c2fba5f17709333a67aeb + languageName: node + linkType: hard + "@istanbuljs/load-nyc-config@npm:^1.0.0": version: 1.1.0 resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" @@ -1014,129 +1038,133 @@ __metadata: languageName: node linkType: hard -"@jest/console@npm:^28.1.1": - version: 28.1.1 - resolution: "@jest/console@npm:28.1.1" +"@jest/console@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/console@npm:28.1.3" dependencies: - "@jest/types": ^28.1.1 + "@jest/types": ^28.1.3 "@types/node": "*" chalk: ^4.0.0 - jest-message-util: ^28.1.1 - jest-util: ^28.1.1 + jest-message-util: ^28.1.3 + jest-util: ^28.1.3 slash: ^3.0.0 - checksum: ddf3b9e9b003a99d6686ecd89c263fda8f81303277f64cca6e434106fa3556c456df6023cdba962851df16880e044bfbae264daa5f67f7ac28712144b5f1007e + checksum: fe50d98d26d02ce2901c76dff4bd5429a33c13affb692c9ebf8a578ca2f38a5dd854363d40d6c394f215150791fd1f692afd8e730a4178dda24107c8dfd9750a languageName: node linkType: hard -"@jest/expect-utils@npm:^28.1.1": - version: 28.1.1 - resolution: "@jest/expect-utils@npm:28.1.1" +"@jest/expect-utils@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/expect-utils@npm:28.1.3" dependencies: jest-get-type: ^28.0.2 - checksum: 46a2ad754b10bc649c36a5914f887bea33a43bb868946508892a73f1da99065b17167dc3c0e3e299c7cea82c6be1e9d816986e120d7ae3e1be511f64cfc1d3d3 + checksum: 808ea3a68292a7e0b95490fdd55605c430b4cf209ea76b5b61bfb2a1badcb41bc046810fe4e364bd5fe04663978aa2bd73d8f8465a761dd7c655aeb44cf22987 languageName: node linkType: hard -"@jest/schemas@npm:^28.0.2": - version: 28.0.2 - resolution: "@jest/schemas@npm:28.0.2" +"@jest/schemas@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/schemas@npm:28.1.3" dependencies: - "@sinclair/typebox": ^0.23.3 - checksum: 6a177e97b112c99f377697fe803a34f4489b92cd07949876250c69edc9029c7cbda771fcbb03caebd20ffbcfa89b9c22b4dc9d1e9a7fbc9873185459b48ba780 + "@sinclair/typebox": ^0.24.1 + checksum: 3cf1d4b66c9c4ffda58b246de1ddcba8e6ad085af63dccdf07922511f13b68c0cc480a7bc620cb4f3099a6f134801c747e1df7bfc7a4ef4dceefbdea3e31e1de languageName: node linkType: hard "@jest/test-result@npm:^28.1.1": - version: 28.1.1 - resolution: "@jest/test-result@npm:28.1.1" + version: 28.1.3 + resolution: "@jest/test-result@npm:28.1.3" dependencies: - "@jest/console": ^28.1.1 - "@jest/types": ^28.1.1 + "@jest/console": ^28.1.3 + "@jest/types": ^28.1.3 "@types/istanbul-lib-coverage": ^2.0.0 collect-v8-coverage: ^1.0.0 - checksum: 8812db2649a09ed423ccb33cf76162a996fc781156a489d4fd86e22615b523d72ca026c68b3699a1ea1ea274146234e09db636c49d7ea2516e0e1bb229f3013d + checksum: 957a5dd2fd2e84aabe86698f93c0825e96128ccaa23abf548b159a9b08ac74e4bde7acf4bec48479243dbdb27e4ea1b68c171846d21fb64855c6b55cead9ef27 languageName: node linkType: hard -"@jest/transform@npm:^28.1.2": - version: 28.1.2 - resolution: "@jest/transform@npm:28.1.2" +"@jest/transform@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/transform@npm:28.1.3" dependencies: "@babel/core": ^7.11.6 - "@jest/types": ^28.1.1 + "@jest/types": ^28.1.3 "@jridgewell/trace-mapping": ^0.3.13 babel-plugin-istanbul: ^6.1.1 chalk: ^4.0.0 convert-source-map: ^1.4.0 fast-json-stable-stringify: ^2.0.0 graceful-fs: ^4.2.9 - jest-haste-map: ^28.1.1 + jest-haste-map: ^28.1.3 jest-regex-util: ^28.0.2 - jest-util: ^28.1.1 + jest-util: ^28.1.3 micromatch: ^4.0.4 pirates: ^4.0.4 slash: ^3.0.0 write-file-atomic: ^4.0.1 - checksum: cd8d1bdf1a5831cdf91934dd0af1d29d4d2bcad92feb9bf7555fc0e1152cb01a9206410380af0f6221a623ffc9b6f6e6dded429d01d87b85b0777cf9d4425127 + checksum: dadf618936e0aa84342f07f532801d5bed43cdf95d1417b929e4f8782c872cff1adc84096d5a287a796d0039a2691c06d8450cce5a713a8b52fbb9f872a1e760 languageName: node linkType: hard -"@jest/types@npm:^28.1.1": - version: 28.1.1 - resolution: "@jest/types@npm:28.1.1" +"@jest/types@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/types@npm:28.1.3" dependencies: - "@jest/schemas": ^28.0.2 + "@jest/schemas": ^28.1.3 "@types/istanbul-lib-coverage": ^2.0.0 "@types/istanbul-reports": ^3.0.0 "@types/node": "*" "@types/yargs": ^17.0.8 chalk: ^4.0.0 - checksum: 3c35d3674e08da1e4bb27b8303a59c71fd19a852ff7c7827305462f48ef224b5334aa50e0d547470e1cca1f2dd15a0cff51b46618b8e61e7196908504b29f08f - languageName: node - linkType: hard - -"@jridgewell/gen-mapping@npm:^0.1.0": - version: 0.1.1 - resolution: "@jridgewell/gen-mapping@npm:0.1.1" - dependencies: - "@jridgewell/set-array": ^1.0.0 - "@jridgewell/sourcemap-codec": ^1.4.10 - checksum: 3bcc21fe786de6ffbf35c399a174faab05eb23ce6a03e8769569de28abbf4facc2db36a9ddb0150545ae23a8d35a7cf7237b2aa9e9356a7c626fb4698287d5cc + checksum: 1e258d9c063fcf59ebc91e46d5ea5984674ac7ae6cae3e50aa780d22b4405bf2c925f40350bf30013839eb5d4b5e521d956ddf8f3b7c78debef0e75a07f57350 languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.2": - version: 0.3.2 - resolution: "@jridgewell/gen-mapping@npm:0.3.2" +"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2": + version: 0.3.3 + resolution: "@jridgewell/gen-mapping@npm:0.3.3" dependencies: "@jridgewell/set-array": ^1.0.1 "@jridgewell/sourcemap-codec": ^1.4.10 "@jridgewell/trace-mapping": ^0.3.9 - checksum: 1832707a1c476afebe4d0fbbd4b9434fdb51a4c3e009ab1e9938648e21b7a97049fa6009393bdf05cab7504108413441df26d8a3c12193996e65493a4efb6882 + checksum: 4a74944bd31f22354fc01c3da32e83c19e519e3bbadafa114f6da4522ea77dd0c2842607e923a591d60a76699d819a2fbb6f3552e277efdb9b58b081390b60ab languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:^3.0.3": +"@jridgewell/resolve-uri@npm:3.1.0": version: 3.1.0 resolution: "@jridgewell/resolve-uri@npm:3.1.0" checksum: b5ceaaf9a110fcb2780d1d8f8d4a0bfd216702f31c988d8042e5f8fbe353c55d9b0f55a1733afdc64806f8e79c485d2464680ac48a0d9fcadb9548ee6b81d267 languageName: node linkType: hard -"@jridgewell/set-array@npm:^1.0.0, @jridgewell/set-array@npm:^1.0.1": +"@jridgewell/resolve-uri@npm:^3.0.3": + version: 3.1.1 + resolution: "@jridgewell/resolve-uri@npm:3.1.1" + checksum: f5b441fe7900eab4f9155b3b93f9800a916257f4e8563afbcd3b5a5337b55e52bd8ae6735453b1b745457d9f6cdb16d74cd6220bbdd98cf153239e13f6cbb653 + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.0.1": version: 1.1.2 resolution: "@jridgewell/set-array@npm:1.1.2" checksum: 69a84d5980385f396ff60a175f7177af0b8da4ddb81824cb7016a9ef914eee9806c72b6b65942003c63f7983d4f39a5c6c27185bbca88eb4690b62075602e28e languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10": +"@jridgewell/sourcemap-codec@npm:1.4.14": version: 1.4.14 resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97 languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.4.10": + version: 1.4.15 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" + checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:0.3.9": version: 0.3.9 resolution: "@jridgewell/trace-mapping@npm:0.3.9" @@ -1147,13 +1175,13 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.13, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.14 - resolution: "@jridgewell/trace-mapping@npm:0.3.14" +"@jridgewell/trace-mapping@npm:^0.3.13, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.9": + version: 0.3.18 + resolution: "@jridgewell/trace-mapping@npm:0.3.18" dependencies: - "@jridgewell/resolve-uri": ^3.0.3 - "@jridgewell/sourcemap-codec": ^1.4.10 - checksum: b9537b9630ffb631aef9651a085fe361881cde1772cd482c257fe3c78c8fd5388d681f504a9c9fe1081b1c05e8f75edf55ee10fdb58d92bbaa8dbf6a7bd6b18c + "@jridgewell/resolve-uri": 3.1.0 + "@jridgewell/sourcemap-codec": 1.4.14 + checksum: 0572669f855260808c16fe8f78f5f1b4356463b11d3f2c7c0b5580c8ba1cbf4ae53efe9f627595830856e57dbac2325ac17eb0c3dd0ec42102e6f227cc289c02 languageName: node linkType: hard @@ -1170,24 +1198,17 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.1.1": - version: 1.1.1 - resolution: "@noble/hashes@npm:1.1.1" - checksum: 3bd98d7a6dcc01c5e72478975073e12c79639636f4eb5710b665dd8ac462fcdff5b235d0c3b113ac83e7e56c43eee5ccba3759f9262964edc123bd1713dd2180 - languageName: node - linkType: hard - -"@noble/hashes@npm:~1.1.1": - version: 1.1.2 - resolution: "@noble/hashes@npm:1.1.2" - checksum: 3c2a8cb7c2e053811032f242155d870c5eb98844d924d69702244d48804cb03b42d4a666c49c2b71164420d8229cb9a6f242b972d50d5bb2f1d673b98b041de2 +"@noble/hashes@npm:1.2.0, @noble/hashes@npm:~1.2.0": + version: 1.2.0 + resolution: "@noble/hashes@npm:1.2.0" + checksum: 8ca080ce557b8f40fb2f78d3aedffd95825a415ac8e13d7ffe3643f8626a8c2d99a3e5975b555027ac24316d8b3c02a35b8358567c0c23af681e6573602aa434 languageName: node linkType: hard -"@noble/secp256k1@npm:1.6.0, @noble/secp256k1@npm:~1.6.0": - version: 1.6.0 - resolution: "@noble/secp256k1@npm:1.6.0" - checksum: e99df3b776515e6a8b3193870e69ff3a7d22c6a4733245dceb9d1d229d5b0859bd478b7213f31d556ba3745647ec07262d0f9df845d79204b7ce4ae1648b27c7 +"@noble/secp256k1@npm:1.7.1, @noble/secp256k1@npm:~1.7.0": + version: 1.7.1 + resolution: "@noble/secp256k1@npm:1.7.1" + checksum: d2301f1f7690368d8409a3152450458f27e54df47e3f917292de3de82c298770890c2de7c967d237eff9c95b70af485389a9695f73eb05a43e2bd562d18b18cb languageName: node linkType: hard @@ -1406,8 +1427,8 @@ __metadata: linkType: hard "@nomicfoundation/hardhat-toolbox@npm:^2.0.1": - version: 2.0.1 - resolution: "@nomicfoundation/hardhat-toolbox@npm:2.0.1" + version: 2.0.2 + resolution: "@nomicfoundation/hardhat-toolbox@npm:2.0.2" peerDependencies: "@ethersproject/abi": ^5.4.7 "@ethersproject/providers": ^5.4.7 @@ -1428,94 +1449,94 @@ __metadata: ts-node: ">=8.0.0" typechain: ^8.1.0 typescript: ">=4.5.0" - checksum: 053236c47745c65f0fb79a34e2570a193dc99aee972c8ec667503fd0a8a5da20f21cf060cf85af8c72fba7a601a604fc36828c791bf21239aafb9e7031bbfe1d + checksum: a2eafb709acbabe40de4871c4e8684a03098f045dba4fc6c6e9281358d072f386a668488c109e2a36b8eade01dc4c4f9e8a76fa45c92591857c590c6e19f1ae7 languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-darwin-x64@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-darwin-x64@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-darwin-x64@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-darwin-x64@npm:0.1.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-freebsd-x64@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-freebsd-x64@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-freebsd-x64@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-freebsd-x64@npm:0.1.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-linux-arm64-gnu@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-linux-arm64-gnu@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-linux-arm64-gnu@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-linux-arm64-gnu@npm:0.1.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-linux-arm64-musl@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-linux-arm64-musl@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-linux-arm64-musl@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-linux-arm64-musl@npm:0.1.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-linux-x64-gnu@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-linux-x64-gnu@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-linux-x64-gnu@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-linux-x64-gnu@npm:0.1.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-linux-x64-musl@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-linux-x64-musl@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-linux-x64-musl@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-linux-x64-musl@npm:0.1.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-win32-arm64-msvc@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-win32-arm64-msvc@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-win32-arm64-msvc@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-win32-arm64-msvc@npm:0.1.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-win32-ia32-msvc@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-win32-ia32-msvc@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-win32-ia32-msvc@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-win32-ia32-msvc@npm:0.1.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-win32-x64-msvc@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-win32-x64-msvc@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-win32-x64-msvc@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-win32-x64-msvc@npm:0.1.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@nomicfoundation/solidity-analyzer@npm:^0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer@npm:0.1.0" - dependencies: - "@nomicfoundation/solidity-analyzer-darwin-arm64": 0.1.0 - "@nomicfoundation/solidity-analyzer-darwin-x64": 0.1.0 - "@nomicfoundation/solidity-analyzer-freebsd-x64": 0.1.0 - "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": 0.1.0 - "@nomicfoundation/solidity-analyzer-linux-arm64-musl": 0.1.0 - "@nomicfoundation/solidity-analyzer-linux-x64-gnu": 0.1.0 - "@nomicfoundation/solidity-analyzer-linux-x64-musl": 0.1.0 - "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": 0.1.0 - "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": 0.1.0 - "@nomicfoundation/solidity-analyzer-win32-x64-msvc": 0.1.0 + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer@npm:0.1.1" + dependencies: + "@nomicfoundation/solidity-analyzer-darwin-arm64": 0.1.1 + "@nomicfoundation/solidity-analyzer-darwin-x64": 0.1.1 + "@nomicfoundation/solidity-analyzer-freebsd-x64": 0.1.1 + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": 0.1.1 + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": 0.1.1 + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": 0.1.1 + "@nomicfoundation/solidity-analyzer-linux-x64-musl": 0.1.1 + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": 0.1.1 + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": 0.1.1 + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": 0.1.1 dependenciesMeta: "@nomicfoundation/solidity-analyzer-darwin-arm64": optional: true @@ -1537,7 +1558,7 @@ __metadata: optional: true "@nomicfoundation/solidity-analyzer-win32-x64-msvc": optional: true - checksum: 42dc5ba40e76bf14945fb6a423554bbbc6c99596675065d7d6f3c9a49ec39e37f3f77ecfedcf906fdb1bb33b033a5d92a90c645c886d6ff23334c8af8b14ff67 + checksum: 038cffafd5769e25256b5b8bef88d95cc1c021274a65c020cf84aceb3237752a3b51645fdb0687f5516a2bdfebf166fcf50b08ab64857925100213e0654b266b languageName: node linkType: hard @@ -1552,42 +1573,31 @@ __metadata: linkType: hard "@nomiclabs/hardhat-etherscan@npm:^3.1.0": - version: 3.1.0 - resolution: "@nomiclabs/hardhat-etherscan@npm:3.1.0" + version: 3.1.7 + resolution: "@nomiclabs/hardhat-etherscan@npm:3.1.7" dependencies: "@ethersproject/abi": ^5.1.2 "@ethersproject/address": ^5.0.2 - cbor: ^5.0.2 + cbor: ^8.1.0 chalk: ^2.4.2 debug: ^4.1.1 fs-extra: ^7.0.1 lodash: ^4.17.11 semver: ^6.3.0 table: ^6.8.0 - undici: ^5.4.0 + undici: ^5.14.0 peerDependencies: hardhat: ^2.0.4 - checksum: 3f28abc39edce2936226b6d0087c3be78bffcba68b6935f3f60767f0e10233e940ddc74803dff91f7ddf9464a7199aab00fba08d8b3865dbc2f8936f53a7a5a5 + checksum: 32d74e567e78a940a79cbe49c5dee0eb5cda0a4c0c34a9badfaf13d45e6054d9e717c28b8d2b0b20f29721a484af15a52d391fb60768222c4b13de92ef0f72b3 languageName: node linkType: hard -"@npmcli/fs@npm:^2.1.0": - version: 2.1.0 - resolution: "@npmcli/fs@npm:2.1.0" +"@npmcli/fs@npm:^3.1.0": + version: 3.1.0 + resolution: "@npmcli/fs@npm:3.1.0" dependencies: - "@gar/promisify": ^1.1.3 semver: ^7.3.5 - checksum: 6ec6d678af6da49f9dac50cd882d7f661934dd278972ffbaacde40d9eaa2871292d634000a0cca9510f6fc29855fbd4af433e1adbff90a524ec3eaf140f1219b - languageName: node - linkType: hard - -"@npmcli/move-file@npm:^2.0.0": - version: 2.0.0 - resolution: "@npmcli/move-file@npm:2.0.0" - dependencies: - mkdirp: ^1.0.4 - rimraf: ^3.0.2 - checksum: 1388777b507b0c592d53f41b9d182e1a8de7763bc625fc07999b8edbc22325f074e5b3ec90af79c89d6987fdb2325bc66d59f483258543c14a43661621f841b0 + checksum: a50a6818de5fc557d0b0e6f50ec780a7a02ab8ad07e5ac8b16bf519e0ad60a144ac64f97d05c443c3367235d337182e1d012bbac0eb8dbae8dc7b40b193efd0e languageName: node linkType: hard @@ -1606,9 +1616,9 @@ __metadata: linkType: hard "@openzeppelin/contracts@npm:^4.3.3": - version: 4.8.2 - resolution: "@openzeppelin/contracts@npm:4.8.2" - checksum: 1d362f0b9c880549cb82544e23fb70270fbbbe24a69e10bd5aa07649fd82347686173998ae484defafdc473d04004d519f839e3cd3d3e7733d0895b950622243 + version: 4.9.2 + resolution: "@openzeppelin/contracts@npm:4.9.2" + checksum: 0538b18fe222e5414a5a539c240b155e0bef2a23c5182fb8e137d71a0c390fe899160f2d55701f75b127f54cc61aee4375370acc832475f19829368ac65c1fc6 languageName: node linkType: hard @@ -1619,15 +1629,28 @@ __metadata: languageName: node linkType: hard +"@openzeppelin/defender-base-client@npm:^1.46.0": + version: 1.47.0 + resolution: "@openzeppelin/defender-base-client@npm:1.47.0" + dependencies: + amazon-cognito-identity-js: ^6.0.1 + async-retry: ^1.3.3 + axios: ^1.4.0 + lodash: ^4.17.19 + node-fetch: ^2.6.0 + checksum: 7fcba4417805aa3920cc2870e4dcd74d3c6874e6f38ce6c1a3e370c140daf253a1cd5baab5204ec40730aaf3ed052e2129c64bbd5f1daacf3838339d06b559ab + languageName: node + linkType: hard + "@openzeppelin/hardhat-upgrades@npm:^1.23.0": - version: 1.25.0 - resolution: "@openzeppelin/hardhat-upgrades@npm:1.25.0" + version: 1.28.0 + resolution: "@openzeppelin/hardhat-upgrades@npm:1.28.0" dependencies: - "@openzeppelin/upgrades-core": ^1.26.0 + "@openzeppelin/defender-base-client": ^1.46.0 + "@openzeppelin/platform-deploy-client": ^0.8.0 + "@openzeppelin/upgrades-core": ^1.27.0 chalk: ^4.1.0 debug: ^4.1.1 - defender-admin-client: ^1.39.0 - platform-deploy-client: ^0.3.2 proper-lockfile: ^4.1.1 peerDependencies: "@nomiclabs/hardhat-ethers": ^2.0.0 @@ -1639,22 +1662,45 @@ __metadata: optional: true bin: migrate-oz-cli-project: dist/scripts/migrate-oz-cli-project.js - checksum: 1647917371d2a4316287940f12e471200c0d143f01ddf485aacc309531d6597e0377243f5c7463d56167c0eefbc983964c15fd7228b7852cc1e47545b1a78e87 + checksum: b37a5eb7c3a5c1fb4ae6754f5fe1d6e93eb6bc143861f57babf5c7d66706ee3e44ca7d57db17ce2ec6c7014f09c269d506f62b3b116897407fdb0d1ff68f4925 languageName: node linkType: hard -"@openzeppelin/upgrades-core@npm:^1.26.0": - version: 1.26.0 - resolution: "@openzeppelin/upgrades-core@npm:1.26.0" +"@openzeppelin/platform-deploy-client@npm:^0.8.0": + version: 0.8.0 + resolution: "@openzeppelin/platform-deploy-client@npm:0.8.0" + dependencies: + "@ethersproject/abi": ^5.6.3 + "@openzeppelin/defender-base-client": ^1.46.0 + axios: ^0.21.2 + lodash: ^4.17.19 + node-fetch: ^2.6.0 + checksum: 0ce050e185a812c366ceef7dcfce526815babab9396275d9724f324a548ddfdca92ea9913ce61356dcd8c014fc495890c8e21afab4a197e0e14e761c698cce68 + languageName: node + linkType: hard + +"@openzeppelin/upgrades-core@npm:^1.27.0": + version: 1.27.3 + resolution: "@openzeppelin/upgrades-core@npm:1.27.3" dependencies: cbor: ^8.0.0 chalk: ^4.1.0 - compare-versions: ^5.0.0 + compare-versions: ^6.0.0 debug: ^4.1.1 ethereumjs-util: ^7.0.3 + minimist: ^1.2.7 proper-lockfile: ^4.1.1 solidity-ast: ^0.4.15 - checksum: 7bc90d2157fc887be24f1351ca00904203f28bca138f89ff9f7ac6ac18910feca889f0b59766bd9ff4a8698ec58aec35348c504fd37fe48a4311156b81802d00 + bin: + openzeppelin-upgrades-core: dist/cli/cli.js + checksum: fd6a6624adbd81cce42afa6da9bd670bcf718fa42e72d8bae2195e6784dc31f75b1915bf2c5d1689c65587ef6f19c3d4e2f81222ca289718bc107647648d69c7 + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 6ad6a00fc4f2f2cfc6bff76fb1d88b8ee20bc0601e18ebb01b6d4be583733a860239a521a7fbca73b612e66705078809483549d2b18f370eb346c5155c8e4a0f languageName: node linkType: hard @@ -1665,24 +1711,24 @@ __metadata: languageName: node linkType: hard -"@scure/bip32@npm:1.1.0": - version: 1.1.0 - resolution: "@scure/bip32@npm:1.1.0" +"@scure/bip32@npm:1.1.5": + version: 1.1.5 + resolution: "@scure/bip32@npm:1.1.5" dependencies: - "@noble/hashes": ~1.1.1 - "@noble/secp256k1": ~1.6.0 + "@noble/hashes": ~1.2.0 + "@noble/secp256k1": ~1.7.0 "@scure/base": ~1.1.0 - checksum: e6102ab9038896861fca5628b8a97f3c4cb24a073cc9f333c71c747037d82e4423d1d111fd282ba212efaf73cbc5875702567fb4cf13b5f0eb23a5bab402e37e + checksum: b08494ab0d2b1efee7226d1b5100db5157ebea22a78bb87126982a76a186cb3048413e8be0ba2622d00d048a20acbba527af730de86c132a77de616eb9907a3b languageName: node linkType: hard -"@scure/bip39@npm:1.1.0": - version: 1.1.0 - resolution: "@scure/bip39@npm:1.1.0" +"@scure/bip39@npm:1.1.1": + version: 1.1.1 + resolution: "@scure/bip39@npm:1.1.1" dependencies: - "@noble/hashes": ~1.1.1 + "@noble/hashes": ~1.2.0 "@scure/base": ~1.1.0 - checksum: c4361406f092a45e511dc572c89f497af6665ad81cb3fd7bf78e6772f357f7ae885e129ef0b985cb3496a460b4811318f77bc61634d9b0a8446079a801b6003c + checksum: fbb594c50696fa9c14e891d872f382e50a3f919b6c96c55ef2fb10c7102c546dafb8f099a62bd114c12a00525b595dcf7381846f383f0ddcedeaa6e210747d2f languageName: node linkType: hard @@ -1768,10 +1814,19 @@ __metadata: languageName: node linkType: hard -"@sinclair/typebox@npm:^0.23.3": - version: 0.23.5 - resolution: "@sinclair/typebox@npm:0.23.5" - checksum: c96056d35d9cb862aeb635ff8873e2e7633e668dd544e162aee2690a82c970d0b3f90aa2b3501fe374dfa8e792388559a3e3a86712b23ebaef10061add534f47 +"@sinclair/typebox@npm:^0.24.1": + version: 0.24.51 + resolution: "@sinclair/typebox@npm:0.24.51" + checksum: fd0d855e748ef767eb19da1a60ed0ab928e91e0f358c1dd198d600762c0015440b15755e96d1176e2a0db7e09c6a64ed487828ee10dd0c3e22f61eb09c478cd0 + languageName: node + linkType: hard + +"@smithy/types@npm:^2.0.2": + version: 2.0.2 + resolution: "@smithy/types@npm:2.0.2" + dependencies: + tslib: ^2.5.0 + checksum: 4afdd7c77b212abd9e0770a1489057aa0470f8a59061c4fb2175b1f12e02180db3d85e16f2cd870a95c17bd28a5a4b8ef1dff1ade6852f85eafea12872d9588e languageName: node linkType: hard @@ -1832,9 +1887,9 @@ __metadata: linkType: hard "@tsconfig/node16@npm:^1.0.2": - version: 1.0.3 - resolution: "@tsconfig/node16@npm:1.0.3" - checksum: 3a8b657dd047495b7ad23437d6afd20297ce90380ff0bdee93fc7d39a900dbd8d9e26e53ff6b465e7967ce2adf0b218782590ce9013285121e6a5928fbd6819f + version: 1.0.4 + resolution: "@tsconfig/node16@npm:1.0.4" + checksum: 202319785901f942a6e1e476b872d421baec20cf09f4b266a1854060efbf78cde16a4d256e8bc949d31e6cd9a90f1e8ef8fb06af96a65e98338a2b6b0de0a0ff languageName: node linkType: hard @@ -1869,11 +1924,11 @@ __metadata: linkType: hard "@types/babel__traverse@npm:^7.0.6": - version: 7.17.1 - resolution: "@types/babel__traverse@npm:7.17.1" + version: 7.20.1 + resolution: "@types/babel__traverse@npm:7.20.1" dependencies: - "@babel/types": ^7.3.0 - checksum: 8992d8c1eaaf1c793e9184b930767883446939d2744c40ea4e9591086e79b631189dc519931ed8864f1e016742a189703c217db59b800aca84870b865009d8b4 + "@babel/types": ^7.20.7 + checksum: 58341e23c649c0eba134a1682d4f20d027fad290d92e5740faa1279978f6ed476fc467ae51ce17a877e2566d805aeac64eae541168994367761ec883a4150221 languageName: node linkType: hard @@ -1887,11 +1942,11 @@ __metadata: linkType: hard "@types/bn.js@npm:^5.1.0": - version: 5.1.0 - resolution: "@types/bn.js@npm:5.1.0" + version: 5.1.1 + resolution: "@types/bn.js@npm:5.1.1" dependencies: "@types/node": "*" - checksum: 1dc1cbbd7a1e8bf3614752e9602f558762a901031f499f3055828b5e3e2bba16e5b88c27b3c4152ad795248fbe4086c731a5c4b0f29bb243f1875beeeabee59c + checksum: e50ed2dd3abe997e047caf90e0352c71e54fc388679735217978b4ceb7e336e51477791b715f49fd77195ac26dd296c7bad08a3be9750e235f9b2e1edb1b51c2 languageName: node linkType: hard @@ -1905,9 +1960,9 @@ __metadata: linkType: hard "@types/chai@npm:*, @types/chai@npm:^4.3.0": - version: 4.3.1 - resolution: "@types/chai@npm:4.3.1" - checksum: 2ee246b76c469cd620a7a1876a73bc597074361b67d547b4bd96a0c1adb43597ede2d8589ab626192e14349d83cbb646cc11e2c179eeeb43ff11596de94d82c4 + version: 4.3.5 + resolution: "@types/chai@npm:4.3.5" + checksum: c8f26a88c6b5b53a3275c7f5ff8f107028e3cbb9ff26795fff5f3d9dea07106a54ce9e2dce5e40347f7c4cc35657900aaf0c83934a25a1ae12e61e0f5516e431 languageName: node linkType: hard @@ -1940,11 +1995,11 @@ __metadata: linkType: hard "@types/graceful-fs@npm:^4.1.3": - version: 4.1.5 - resolution: "@types/graceful-fs@npm:4.1.5" + version: 4.1.6 + resolution: "@types/graceful-fs@npm:4.1.6" dependencies: "@types/node": "*" - checksum: d076bb61f45d0fc42dee496ef8b1c2f8742e15d5e47e90e20d0243386e426c04d4efd408a48875ab432f7960b4ce3414db20ed0fbbfc7bcc89d84e574f6e045a + checksum: c3070ccdc9ca0f40df747bced1c96c71a61992d6f7c767e8fd24bb6a3c2de26e8b84135ede000b7e79db530a23e7e88dcd9db60eee6395d0f4ce1dae91369dd4 languageName: node linkType: hard @@ -1981,9 +2036,9 @@ __metadata: linkType: hard "@types/json-schema@npm:^7.0.9": - version: 7.0.11 - resolution: "@types/json-schema@npm:7.0.11" - checksum: 527bddfe62db9012fccd7627794bd4c71beb77601861055d87e3ee464f2217c85fca7a4b56ae677478367bbd248dbde13553312b7d4dbc702a2f2bbf60c4018d + version: 7.0.12 + resolution: "@types/json-schema@npm:7.0.12" + checksum: 00239e97234eeb5ceefb0c1875d98ade6e922bfec39dd365ec6bd360b5c2f825e612ac4f6e5f1d13601b8b30f378f15e6faa805a3a732f4a1bbe61915163d293 languageName: node linkType: hard @@ -1995,9 +2050,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.177": - version: 4.14.182 - resolution: "@types/lodash@npm:4.14.182" - checksum: 7dd137aa9dbabd632408bd37009d984655164fa1ecc3f2b6eb94afe35bf0a5852cbab6183148d883e9c73a958b7fec9a9bcf7c8e45d41195add6a18c34958209 + version: 4.14.196 + resolution: "@types/lodash@npm:4.14.196" + checksum: 201d17c3e62ae02a93c99ec78e024b2be9bd75564dd8fd8c26f6ac51a985ab280d28ce2688c3bcdfe785b0991cd9814edff19ee000234c7b45d9a697f09feb6a languageName: node linkType: hard @@ -2009,9 +2064,9 @@ __metadata: linkType: hard "@types/minimatch@npm:*": - version: 3.0.5 - resolution: "@types/minimatch@npm:3.0.5" - checksum: c41d136f67231c3131cf1d4ca0b06687f4a322918a3a5adddc87ce90ed9dbd175a3610adee36b106ae68c0b92c637c35e02b58c8a56c424f71d30993ea220b92 + version: 5.1.2 + resolution: "@types/minimatch@npm:5.1.2" + checksum: 0391a282860c7cb6fe262c12b99564732401bdaa5e395bee9ca323c312c1a0f45efbf34dce974682036e857db59a5c9b1da522f3d6055aeead7097264c8705a8 languageName: node linkType: hard @@ -2023,9 +2078,9 @@ __metadata: linkType: hard "@types/node@npm:*": - version: 18.0.3 - resolution: "@types/node@npm:18.0.3" - checksum: 5dec59fbbc1186c808b53df1ca717dad034dbd6a901c75f5b052c845618b531b05f27217122c6254db99529a68618e4cfc534ae3dbf4e88754e9e572df80defa + version: 20.4.5 + resolution: "@types/node@npm:20.4.5" + checksum: 36a0304a8dc346a1b2d2edac4c4633eecf70875793d61a5274d0df052d7a7af7a8e34f29884eac4fbd094c4f0201477dcb39c0ecd3307ca141688806538d1138 languageName: node linkType: hard @@ -2060,9 +2115,9 @@ __metadata: linkType: hard "@types/prettier@npm:^2.1.1, @types/prettier@npm:^2.1.5": - version: 2.6.3 - resolution: "@types/prettier@npm:2.6.3" - checksum: e1836699ca189fff6d2a73dc22e028b6a6f693ed1180d5998ac29fa197caf8f85aa92cb38db642e4a370e616b451cb5722ad2395dab11c78e025a1455f37d1f0 + version: 2.7.3 + resolution: "@types/prettier@npm:2.7.3" + checksum: 705384209cea6d1433ff6c187c80dcc0b95d99d5c5ce21a46a9a58060c527973506822e428789d842761e0280d25e3359300f017fbe77b9755bc772ab3dc2f83 languageName: node linkType: hard @@ -2107,26 +2162,26 @@ __metadata: linkType: hard "@types/yargs@npm:^17.0.8": - version: 17.0.10 - resolution: "@types/yargs@npm:17.0.10" + version: 17.0.24 + resolution: "@types/yargs@npm:17.0.24" dependencies: "@types/yargs-parser": "*" - checksum: f0673cbfc08e17239dc58952a88350d6c4db04a027a28a06fbad27d87b670e909f9cd9e66f9c64cebdd5071d1096261e33454a55868395f125297e5c50992ca8 + checksum: 5f3ac4dc4f6e211c1627340160fbe2fd247ceba002190da6cf9155af1798450501d628c9165a183f30a224fc68fa5e700490d740ff4c73e2cdef95bc4e8ba7bf languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^5.17.0": - version: 5.30.5 - resolution: "@typescript-eslint/eslint-plugin@npm:5.30.5" +"@typescript-eslint/eslint-plugin@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/eslint-plugin@npm:5.17.0" dependencies: - "@typescript-eslint/scope-manager": 5.30.5 - "@typescript-eslint/type-utils": 5.30.5 - "@typescript-eslint/utils": 5.30.5 - debug: ^4.3.4 + "@typescript-eslint/scope-manager": 5.17.0 + "@typescript-eslint/type-utils": 5.17.0 + "@typescript-eslint/utils": 5.17.0 + debug: ^4.3.2 functional-red-black-tree: ^1.0.1 - ignore: ^5.2.0 + ignore: ^5.1.8 regexpp: ^3.2.0 - semver: ^7.3.7 + semver: ^7.3.5 tsutils: ^3.21.0 peerDependencies: "@typescript-eslint/parser": ^5.0.0 @@ -2134,105 +2189,105 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: cf763fb091dcdfd6c25843251a220b654ca83968b17266e0f343771f489085c6afc4e41fcf2187b4c72c4d12a787070c64b5e5367069460f95a8174573f48905 + checksum: 62ec611fb384f27fc5b101fc8a0642ae94b2975618d37d3157c2f887cf89b389624e9d476bff303073d038076c05e6c00f3b205af3b2302967e720e99cd18d38 languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.17.0": - version: 5.30.5 - resolution: "@typescript-eslint/parser@npm:5.30.5" +"@typescript-eslint/parser@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/parser@npm:5.17.0" dependencies: - "@typescript-eslint/scope-manager": 5.30.5 - "@typescript-eslint/types": 5.30.5 - "@typescript-eslint/typescript-estree": 5.30.5 - debug: ^4.3.4 + "@typescript-eslint/scope-manager": 5.17.0 + "@typescript-eslint/types": 5.17.0 + "@typescript-eslint/typescript-estree": 5.17.0 + debug: ^4.3.2 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 6c16821e122b891420a538f200f6e576ad1167855a67e87f9a7d3a08c0513fe26006f6411b8ba6f4662a81526bd0339ae37c47dd88fa5943e6f27ff70da9f989 + checksum: 15b855ea84e44371366d44b5add87ed0dc34b856ca8a6949ecc4066faaf3ea3d7e016ea92db06ab97a637530148c472c38c19cc5eff68b691701ff89dc5c1abc languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.30.5": - version: 5.30.5 - resolution: "@typescript-eslint/scope-manager@npm:5.30.5" +"@typescript-eslint/scope-manager@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/scope-manager@npm:5.17.0" dependencies: - "@typescript-eslint/types": 5.30.5 - "@typescript-eslint/visitor-keys": 5.30.5 - checksum: 509bee6d62cca1716e8f4792d9180c189974992ba13d8103ca04423a64006cf184c4b2c606d55c776305458140c798a3a9a414d07a60790b83dd714f56c457b0 + "@typescript-eslint/types": 5.17.0 + "@typescript-eslint/visitor-keys": 5.17.0 + checksum: 8fc28d5742f36994ce05f09b0000f696a600d6f757f39ccae7875c08398b266f21d48ed1dfb027549d9c6692255a1fb3e8482ef94d765bb134371824da7d5ba7 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.30.5": - version: 5.30.5 - resolution: "@typescript-eslint/type-utils@npm:5.30.5" +"@typescript-eslint/type-utils@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/type-utils@npm:5.17.0" dependencies: - "@typescript-eslint/utils": 5.30.5 - debug: ^4.3.4 + "@typescript-eslint/utils": 5.17.0 + debug: ^4.3.2 tsutils: ^3.21.0 peerDependencies: eslint: "*" peerDependenciesMeta: typescript: optional: true - checksum: 080cc1231729c34b778395658374e32d034474056f9b777dbc89d20d15eb93d93d0959328ad47c2a6623d40c6552364ababadce439842a944bce001f55b731b3 + checksum: 9aad46ea7a757ec4584b9d9c995e94543bf40af7d85b2f502d66db08d7f03468c858320fccb4942238b0bb9e2d432df3d9861cf21624b0c57660c88b1d91a7d4 languageName: node linkType: hard -"@typescript-eslint/types@npm:5.30.5": - version: 5.30.5 - resolution: "@typescript-eslint/types@npm:5.30.5" - checksum: c70420618cb875d4e964a20a3fa4cf40cb97a8ad3123e24860e3d829edf3b081c77fa1fe25644700499d27e44aee5783abc7765deee61e2ef59a928db96b2175 +"@typescript-eslint/types@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/types@npm:5.17.0" + checksum: 06ed4c3c3f0a05bee9c23b6cb5eb679336c0f4769beb28848e8ce674f726fec88adba059f20e0b0f7271685d7f5480931b3bcafcf6b60044b93da162e29f3f68 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.30.5": - version: 5.30.5 - resolution: "@typescript-eslint/typescript-estree@npm:5.30.5" +"@typescript-eslint/typescript-estree@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.17.0" dependencies: - "@typescript-eslint/types": 5.30.5 - "@typescript-eslint/visitor-keys": 5.30.5 - debug: ^4.3.4 - globby: ^11.1.0 + "@typescript-eslint/types": 5.17.0 + "@typescript-eslint/visitor-keys": 5.17.0 + debug: ^4.3.2 + globby: ^11.0.4 is-glob: ^4.0.3 - semver: ^7.3.7 + semver: ^7.3.5 tsutils: ^3.21.0 peerDependenciesMeta: typescript: optional: true - checksum: 19dce426c826cddd4aadf2fa15be943c6ad7d2038685cc2665749486a5f44a47819aab5d260b54f8a4babf6acf2500e9f62e709d61fce337b12d5468ff285277 + checksum: 589829b1bb1d7e704de6a35dd9a39c70a3ca54b0885b68aad54a864bc5e5a11ce43f917c3f15f0afe9bc734a250288efdf03dfbed70b8fe0cc12f759e2e1f8ef languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.30.5": - version: 5.30.5 - resolution: "@typescript-eslint/utils@npm:5.30.5" +"@typescript-eslint/utils@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/utils@npm:5.17.0" dependencies: "@types/json-schema": ^7.0.9 - "@typescript-eslint/scope-manager": 5.30.5 - "@typescript-eslint/types": 5.30.5 - "@typescript-eslint/typescript-estree": 5.30.5 + "@typescript-eslint/scope-manager": 5.17.0 + "@typescript-eslint/types": 5.17.0 + "@typescript-eslint/typescript-estree": 5.17.0 eslint-scope: ^5.1.1 eslint-utils: ^3.0.0 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 12f68cb34a150d39708f4e09a54964360f29589885cd50f119a2061660011752ec72eff3d90111f0e597575d32aae7250a6e2c730a84963e5e30352759d5f1f4 + checksum: 88de02eafb7d39950c520c53aa07ffe63c95ca7ef2262c39d2afd3c6aabcd5d717ba61f74314f5bc9c27588b721ff016b45af6fc1de88801c6ac4bf5ebaf8775 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.30.5": - version: 5.30.5 - resolution: "@typescript-eslint/visitor-keys@npm:5.30.5" +"@typescript-eslint/visitor-keys@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.17.0" dependencies: - "@typescript-eslint/types": 5.30.5 - eslint-visitor-keys: ^3.3.0 - checksum: c0de9ae48378eec2682b860a059518bed213ea29575aad538d8d2f8137875e7279e375a7f23d38c1c183466fdd9cf1ca1db4ed5a1d374968f9460d83e48b2437 + "@typescript-eslint/types": 5.17.0 + eslint-visitor-keys: ^3.0.0 + checksum: 333468277b50e2fc381ba1b99ccb410046c422e0329c791c51bea62e705edd16ba97f75b668c6945a3ea3dc43b89a1739693ea60bfa241c67ce42e8b474e5048 languageName: node linkType: hard -"abbrev@npm:1": +"abbrev@npm:1, abbrev@npm:^1.0.0": version: 1.1.1 resolution: "abbrev@npm:1.1.1" checksum: a4a97ec07d7ea112c517036882b2ac22f3109b7b19077dc656316d07d308438aac28e4d9746dc4d84bf6b1e75b4a7b0a5f3cb30592419f128ca9a8cee3bcfa17 @@ -2305,19 +2360,19 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.4.1, acorn@npm:^8.7.1": - version: 8.7.1 - resolution: "acorn@npm:8.7.1" +"acorn@npm:^8.4.1, acorn@npm:^8.9.0": + version: 8.10.0 + resolution: "acorn@npm:8.10.0" bin: acorn: bin/acorn - checksum: aca0aabf98826717920ac2583fdcad0a6fbe4e583fdb6e843af2594e907455aeafe30b1e14f1757cd83ce1776773cf8296ffc3a4acf13f0bd3dfebcf1db6ae80 + checksum: 538ba38af0cc9e5ef983aee196c4b8b4d87c0c94532334fa7e065b2c8a1f85863467bb774231aae91613fcda5e68740c15d97b1967ae3394d20faddddd8af61d languageName: node linkType: hard "address@npm:^1.0.1": - version: 1.2.0 - resolution: "address@npm:1.2.0" - checksum: 2ef3aa9d23bbe0f9f2745a634b16f3a2f2b18c43146c0913c7b26c8be410e20d59b8c3808d0bb7fe94d50fc2448b4b91e65dd9f33deb4aed53c14f0dedc3ddd8 + version: 1.2.2 + resolution: "address@npm:1.2.2" + checksum: ace439960c1e3564d8f523aff23a841904bf33a2a7c2e064f7f60a064194075758b9690e65bd9785692a4ef698a998c57eb74d145881a1cecab8ba658ddb1607 languageName: node linkType: hard @@ -2345,13 +2400,13 @@ __metadata: linkType: hard "agentkeepalive@npm:^4.2.1": - version: 4.2.1 - resolution: "agentkeepalive@npm:4.2.1" + version: 4.3.0 + resolution: "agentkeepalive@npm:4.3.0" dependencies: debug: ^4.1.0 - depd: ^1.1.2 + depd: ^2.0.0 humanize-ms: ^1.2.1 - checksum: 39cb49ed8cf217fd6da058a92828a0a84e0b74c35550f82ee0a10e1ee403c4b78ade7948be2279b188b7a7303f5d396ea2738b134731e464bf28de00a4f72a18 + checksum: 982453aa44c11a06826c836025e5162c846e1200adb56f2d075400da7d32d87021b3b0a58768d949d824811f5654223d5a8a3dad120921a2439625eb847c6260 languageName: node linkType: hard @@ -2378,27 +2433,27 @@ __metadata: linkType: hard "ajv@npm:^8.0.1": - version: 8.11.0 - resolution: "ajv@npm:8.11.0" + version: 8.12.0 + resolution: "ajv@npm:8.12.0" dependencies: fast-deep-equal: ^3.1.1 json-schema-traverse: ^1.0.0 require-from-string: ^2.0.2 uri-js: ^4.2.2 - checksum: 5e0ff226806763be73e93dd7805b634f6f5921e3e90ca04acdf8db81eed9d8d3f0d4c5f1213047f45ebbf8047ffe0c840fa1ef2ec42c3a644899f69aa72b5bef + checksum: 4dc13714e316e67537c8b31bc063f99a1d9d9a497eb4bbd55191ac0dcd5e4985bbb71570352ad6f1e76684fb6d790928f96ba3b2d4fd6e10024be9612fe3f001 languageName: node linkType: hard "amazon-cognito-identity-js@npm:^6.0.1": - version: 6.2.0 - resolution: "amazon-cognito-identity-js@npm:6.2.0" + version: 6.3.1 + resolution: "amazon-cognito-identity-js@npm:6.3.1" dependencies: "@aws-crypto/sha256-js": 1.2.2 buffer: 4.9.2 fast-base64-decode: ^1.0.0 isomorphic-unfetch: ^3.0.0 js-cookie: ^2.2.1 - checksum: 9b976ceac2a2648bfa707190d683214168cf1a70083152355b36e5b87b1aedbbb38dab8b71904ee968de25e90ebcf1e86ffaa21d809373d31acd73f62b6c7c1c + checksum: a38d1c809417d2894613a2fba896434cad3514ed2a16ec6be8084b14788440a4829b50c0dd0ecae3c878ff76bcd1f8df1a862b545eeeb0ca219c6e28cdf598fc languageName: node linkType: hard @@ -2467,6 +2522,13 @@ __metadata: languageName: node linkType: hard +"ansi-regex@npm:^6.0.1": + version: 6.0.1 + resolution: "ansi-regex@npm:6.0.1" + checksum: 1ff8b7667cded1de4fa2c9ae283e979fc87036864317da86a2e546725f96406746411d0d85e87a2d12fa5abd715d90006de7fa4fa0477c92321ad3b4c7d4e169 + languageName: node + linkType: hard + "ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" @@ -2492,6 +2554,13 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: ef940f2f0ced1a6347398da88a91da7930c33ecac3c77b72c5905f8b8fe402c52e6fde304ff5347f616e27a742da3f1dc76de98f6866c69251ad0b07a66776d9 + languageName: node + linkType: hard + "antlr4@npm:4.7.1": version: 4.7.1 resolution: "antlr4@npm:4.7.1" @@ -2509,12 +2578,12 @@ __metadata: linkType: hard "anymatch@npm:^3.0.3, anymatch@npm:~3.1.1, anymatch@npm:~3.1.2": - version: 3.1.2 - resolution: "anymatch@npm:3.1.2" + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" dependencies: normalize-path: ^3.0.0 picomatch: ^2.0.4 - checksum: 985163db2292fac9e5a1e072bf99f1b5baccf196e4de25a0b0b81865ebddeb3b3eb4480734ef0a2ac8c002845396b91aa89121f5b84f93981a4658164a9ec6e9 + checksum: 3e044fd6d1d26545f235a9fe4d7a534e2029d8e59fa7fd9f2a6eb21230f6b5380ea1eaf55136e60cbf8e613544b3b766e7a6fa2102e2a3a117505466e3025dc2 languageName: node linkType: hard @@ -2526,12 +2595,12 @@ __metadata: linkType: hard "are-we-there-yet@npm:^3.0.0": - version: 3.0.0 - resolution: "are-we-there-yet@npm:3.0.0" + version: 3.0.1 + resolution: "are-we-there-yet@npm:3.0.1" dependencies: delegates: ^1.0.0 readable-stream: ^3.6.0 - checksum: 348edfdd931b0b50868b55402c01c3f64df1d4c229ab6f063539a5025fd6c5f5bb8a0cab409bbed8d75d34762d22aa91b7c20b4204eb8177063158d9ba792981 + checksum: 52590c24860fa7173bedeb69a4c05fb573473e860197f618b9a28432ee4379049336727ae3a1f9c4cb083114601c1140cee578376164d0e651217a9843f9fe83 languageName: node linkType: hard @@ -2576,6 +2645,16 @@ __metadata: languageName: node linkType: hard +"array-buffer-byte-length@npm:^1.0.0": + version: 1.0.0 + resolution: "array-buffer-byte-length@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + is-array-buffer: ^3.0.1 + checksum: 044e101ce150f4804ad19c51d6c4d4cfa505c5b2577bd179256e4aa3f3f6a0a5e9874c78cd428ee566ac574c8a04d7ce21af9fe52e844abfdccb82b33035a7c3 + languageName: node + linkType: hard + "array-flatten@npm:1.1.1": version: 1.1.1 resolution: "array-flatten@npm:1.1.1" @@ -2584,15 +2663,15 @@ __metadata: linkType: hard "array-includes@npm:^3.1.4": - version: 3.1.5 - resolution: "array-includes@npm:3.1.5" + version: 3.1.6 + resolution: "array-includes@npm:3.1.6" dependencies: call-bind: ^1.0.2 define-properties: ^1.1.4 - es-abstract: ^1.19.5 - get-intrinsic: ^1.1.1 + es-abstract: ^1.20.4 + get-intrinsic: ^1.1.3 is-string: ^1.0.7 - checksum: f6f24d834179604656b7bec3e047251d5cc87e9e87fab7c175c61af48e80e75acd296017abcde21fb52292ab6a2a449ab2ee37213ee48c8709f004d75983f9c5 + checksum: f22f8cd8ba8a6448d91eebdc69f04e4e55085d09232b5216ee2d476dab3ef59984e8d1889e662c6a0ed939dcb1b57fd05b2c0209c3370942fc41b752c82a2ca5 languageName: node linkType: hard @@ -2611,27 +2690,41 @@ __metadata: linkType: hard "array.prototype.flat@npm:^1.2.5": - version: 1.3.0 - resolution: "array.prototype.flat@npm:1.3.0" + version: 1.3.1 + resolution: "array.prototype.flat@npm:1.3.1" dependencies: call-bind: ^1.0.2 - define-properties: ^1.1.3 - es-abstract: ^1.19.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 es-shim-unscopables: ^1.0.0 - checksum: 2a652b3e8dc0bebb6117e42a5ab5738af0203a14c27341d7bb2431467bdb4b348e2c5dc555dfcda8af0a5e4075c400b85311ded73861c87290a71a17c3e0a257 + checksum: 5a8415949df79bf6e01afd7e8839bbde5a3581300e8ad5d8449dea52639e9e59b26a467665622783697917b43bf39940a6e621877c7dd9b3d1c1f97484b9b88b languageName: node linkType: hard -"array.prototype.reduce@npm:^1.0.4": - version: 1.0.4 - resolution: "array.prototype.reduce@npm:1.0.4" +"array.prototype.reduce@npm:^1.0.5": + version: 1.0.5 + resolution: "array.prototype.reduce@npm:1.0.5" dependencies: call-bind: ^1.0.2 - define-properties: ^1.1.3 - es-abstract: ^1.19.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 es-array-method-boxes-properly: ^1.0.0 is-string: ^1.0.7 - checksum: 6a57a1a2d3b77a9543db139cd52211f43a5af8e8271cb3c173be802076e3a6f71204ba8f090f5937ebc0842d5876db282f0f63dffd0e86b153e6e5a45681e4a5 + checksum: f44691395f9202aba5ec2446468d4c27209bfa81464f342ae024b7157dbf05b164e47cca01250b8c7c2a8219953fb57651cca16aab3d16f43b85c0d92c26eef3 + languageName: node + linkType: hard + +"arraybuffer.prototype.slice@npm:^1.0.1": + version: 1.0.1 + resolution: "arraybuffer.prototype.slice@npm:1.0.1" + dependencies: + array-buffer-byte-length: ^1.0.0 + call-bind: ^1.0.2 + define-properties: ^1.2.0 + get-intrinsic: ^1.2.1 + is-array-buffer: ^3.0.2 + is-shared-array-buffer: ^1.0.2 + checksum: e3e9b2a3e988ebfeddce4c7e8f69df730c9e48cb04b0d40ff0874ce3d86b3d1339dd520ffde5e39c02610bc172ecfbd4bc93324b1cabd9554c44a56b131ce0ce languageName: node linkType: hard @@ -2716,6 +2809,13 @@ __metadata: languageName: node linkType: hard +"available-typed-arrays@npm:^1.0.5": + version: 1.0.5 + resolution: "available-typed-arrays@npm:1.0.5" + checksum: 20eb47b3cefd7db027b9bbb993c658abd36d4edd3fe1060e83699a03ee275b0c9b216cc076ff3f2db29073225fb70e7613987af14269ac1fe2a19803ccc97f1a + languageName: node + linkType: hard + "aws-sign2@npm:~0.7.0": version: 0.7.0 resolution: "aws-sign2@npm:0.7.0" @@ -2724,9 +2824,9 @@ __metadata: linkType: hard "aws4@npm:^1.8.0": - version: 1.11.0 - resolution: "aws4@npm:1.11.0" - checksum: 5a00d045fd0385926d20ebebcfba5ec79d4482fe706f63c27b324d489a04c68edb0db99ed991e19eda09cb8c97dc2452059a34d97545cebf591d7a2b5a10999f + version: 1.12.0 + resolution: "aws4@npm:1.12.0" + checksum: 68f79708ac7c335992730bf638286a3ee0a645cf12575d557860100767c500c08b30e24726b9f03265d74116417f628af78509e1333575e9f8d52a80edfe8cbc languageName: node linkType: hard @@ -2758,6 +2858,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.4.0": + version: 1.4.0 + resolution: "axios@npm:1.4.0" + dependencies: + follow-redirects: ^1.15.0 + form-data: ^4.0.0 + proxy-from-env: ^1.1.0 + checksum: 7fb6a4313bae7f45e89d62c70a800913c303df653f19eafec88e56cea2e3821066b8409bc68be1930ecca80e861c52aa787659df0ffec6ad4d451c7816b9386b + languageName: node + linkType: hard + "babel-plugin-istanbul@npm:^6.1.1": version: 6.1.1 resolution: "babel-plugin-istanbul@npm:6.1.1" @@ -2833,22 +2944,13 @@ __metadata: linkType: hard "bigint-crypto-utils@npm:^3.0.23": - version: 3.1.7 - resolution: "bigint-crypto-utils@npm:3.1.7" - dependencies: - bigint-mod-arith: ^3.1.0 - checksum: 10fa35d3e3d37639c8d501f45e0044c9062e7aa60783ae514e4d4ed3235ac24ac180e0dd0c77dad8cb5410ef24de42e1ea12527a997fec4c59f15fa83ea477ba - languageName: node - linkType: hard - -"bigint-mod-arith@npm:^3.1.0": - version: 3.1.2 - resolution: "bigint-mod-arith@npm:3.1.2" - checksum: badddd745f6e6c45674b22335d26a9ea83250e749abde20c5f84b24afbc747e259bc36798530953332349ed898f38ec39125b326cae8b8ee2dddfaea7ddf8448 + version: 3.3.0 + resolution: "bigint-crypto-utils@npm:3.3.0" + checksum: 9598ce57b23f776c8936d44114c9f051e62b5fa654915b664784cbcbacc5aa0485f4479571c51ff58008abb1210c0d6a234853742f07cf84bda890f2a1e01000 languageName: node linkType: hard -"bignumber.js@npm:^9.0.1, bignumber.js@npm:^9.1.1": +"bignumber.js@npm:^9.1.1": version: 9.1.1 resolution: "bignumber.js@npm:9.1.1" checksum: ad243b7e2f9120b112d670bb3d674128f0bd2ca1745b0a6c9df0433bd2c0252c43e6315d944c2ac07b4c639e7496b425e46842773cf89c6a2dcd4f31e5c4b11e @@ -2978,17 +3080,17 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.20.2": - version: 4.21.1 - resolution: "browserslist@npm:4.21.1" +"browserslist@npm:^4.21.9": + version: 4.21.9 + resolution: "browserslist@npm:4.21.9" dependencies: - caniuse-lite: ^1.0.30001359 - electron-to-chromium: ^1.4.172 - node-releases: ^2.0.5 - update-browserslist-db: ^1.0.4 + caniuse-lite: ^1.0.30001503 + electron-to-chromium: ^1.4.431 + node-releases: ^2.0.12 + update-browserslist-db: ^1.0.11 bin: browserslist: cli.js - checksum: 4904a9ded0702381adc495e003e7f77970abb7f8c8b8edd9e54f026354b5a96b1bddc26e6d9a7df9f043e468ecd2fcff2c8f40fc489909a042880117c2aca8ff + checksum: 80d3820584e211484ad1b1a5cfdeca1dd00442f47be87e117e1dda34b628c87e18b81ae7986fa5977b3e6a03154f6d13cd763baa6b8bf5dd9dd19f4926603698 languageName: node linkType: hard @@ -3079,29 +3181,23 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^16.1.0": - version: 16.1.1 - resolution: "cacache@npm:16.1.1" +"cacache@npm:^17.0.0": + version: 17.1.3 + resolution: "cacache@npm:17.1.3" dependencies: - "@npmcli/fs": ^2.1.0 - "@npmcli/move-file": ^2.0.0 - chownr: ^2.0.0 - fs-minipass: ^2.1.0 - glob: ^8.0.1 - infer-owner: ^1.0.4 + "@npmcli/fs": ^3.1.0 + fs-minipass: ^3.0.0 + glob: ^10.2.2 lru-cache: ^7.7.1 - minipass: ^3.1.6 + minipass: ^5.0.0 minipass-collect: ^1.0.2 minipass-flush: ^1.0.5 minipass-pipeline: ^1.2.4 - mkdirp: ^1.0.4 p-map: ^4.0.0 - promise-inflight: ^1.0.1 - rimraf: ^3.0.2 - ssri: ^9.0.0 + ssri: ^10.0.0 tar: ^6.1.11 - unique-filename: ^1.1.1 - checksum: 488524617008b793f0249b0c4ea2c330c710ca997921376e15650cc2415a8054491ae2dee9f01382c2015602c0641f3f977faf2fa7361aa33d2637dcfb03907a + unique-filename: ^3.0.0 + checksum: 385756781e1e21af089160d89d7462b7ed9883c978e848c7075b90b73cb823680e66092d61513050164588387d2ca87dd6d910e28d64bc13a9ac82cd8580c796 languageName: node linkType: hard @@ -3168,10 +3264,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001359": - version: 1.0.30001363 - resolution: "caniuse-lite@npm:1.0.30001363" - checksum: 8dfcb2fa97724349cbbe61d988810bd90bfb40106a289ed6613188fa96dd1f5885c7e9924e46bb30a641bd1579ec34096fdc2b21b47d8500f8a2bfb0db069323 +"caniuse-lite@npm:^1.0.30001503": + version: 1.0.30001517 + resolution: "caniuse-lite@npm:1.0.30001517" + checksum: e4e87436ae1c4408cf4438aac22902b31eb03f3f5bad7f33bc518d12ffb35f3fd9395ccf7efc608ee046f90ce324ec6f7f26f8a8172b8c43c26a06ecee612a29 languageName: node linkType: hard @@ -3196,17 +3292,7 @@ __metadata: languageName: node linkType: hard -"cbor@npm:^5.0.2": - version: 5.2.0 - resolution: "cbor@npm:5.2.0" - dependencies: - bignumber.js: ^9.0.1 - nofilter: ^1.0.4 - checksum: b3c39dae64370f361526dbec88f51d0f1b47027224cdd21dbd64c228f0fe7eaa945932d349ec5324068a6c6dcdbb1e3b46242852524fd53c526d14cb60514bdc - languageName: node - linkType: hard - -"cbor@npm:^8.0.0": +"cbor@npm:^8.0.0, cbor@npm:^8.1.0": version: 8.1.0 resolution: "cbor@npm:8.1.0" dependencies: @@ -3227,17 +3313,17 @@ __metadata: linkType: hard "chai@npm:^4.3.4": - version: 4.3.6 - resolution: "chai@npm:4.3.6" + version: 4.3.7 + resolution: "chai@npm:4.3.7" dependencies: assertion-error: ^1.1.0 check-error: ^1.0.2 - deep-eql: ^3.0.1 + deep-eql: ^4.1.2 get-func-name: ^2.0.0 loupe: ^2.3.1 pathval: ^1.1.1 type-detect: ^4.0.5 - checksum: acff93fd537f96d4a4d62dd83810285dffcfccb5089e1bf2a1205b28ec82d93dff551368722893cf85004282df10ee68802737c33c90c5493957ed449ed7ce71 + checksum: 0bba7d267848015246a66995f044ce3f0ebc35e530da3cbdf171db744e14cbe301ab913a8d07caf7952b430257ccbb1a4a983c570a7c5748dc537897e5131f7c languageName: node linkType: hard @@ -3336,9 +3422,9 @@ __metadata: linkType: hard "ci-info@npm:^3.2.0": - version: 3.3.2 - resolution: "ci-info@npm:3.3.2" - checksum: fd81f1edd2d3b0f6cb077b2e84365136d87b9db8c055928c1ad69da8a76c2c2f19cba8ea51b90238302157ca927f91f92b653e933f2398dde4867500f08d6e62 + version: 3.8.0 + resolution: "ci-info@npm:3.8.0" + checksum: d0a4d3160497cae54294974a7246202244fff031b0a6ea20dd57b10ec510aa17399c41a1b0982142c105f3255aff2173e5c0dd7302ee1b2f28ba3debda375098 languageName: node linkType: hard @@ -3353,16 +3439,16 @@ __metadata: linkType: hard "classic-level@npm:^1.2.0": - version: 1.2.0 - resolution: "classic-level@npm:1.2.0" + version: 1.3.0 + resolution: "classic-level@npm:1.3.0" dependencies: abstract-level: ^1.0.2 catering: ^2.1.0 module-error: ^1.0.1 - napi-macros: ~2.0.0 + napi-macros: ^2.2.2 node-gyp: latest node-gyp-build: ^4.3.0 - checksum: 88ddd12f2192c2775107d5e462998ac01095cb0222ca01dc2be77d8dcbbf9883c4c0a0248529cceee40a2f1232c68027b1aca731da9f767ad8e9483cbd61dd37 + checksum: 773da48aef52a041115d413fee8340b357a4da2eb505764f327183b155edd7cc9d24819eb4f707c83dbdae8588024f5dddeb322125567c59d5d1f6f16334cdb9 languageName: node linkType: hard @@ -3438,10 +3524,21 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.1 + wrap-ansi: ^7.0.0 + checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56 + languageName: node + linkType: hard + "collect-v8-coverage@npm:^1.0.0": - version: 1.0.1 - resolution: "collect-v8-coverage@npm:1.0.1" - checksum: 4efe0a1fccd517b65478a2364b33dadd0a43fc92a56f59aaece9b6186fe5177b2de471253587de7c91516f07c7268c2f6770b6cbcffc0e0ece353b766ec87e55 + version: 1.0.2 + resolution: "collect-v8-coverage@npm:1.0.2" + checksum: c10f41c39ab84629d16f9f6137bc8a63d332244383fc368caf2d2052b5e04c20cd1fd70f66fcf4e2422b84c8226598b776d39d5f2d2a51867cc1ed5d1982b4da languageName: node linkType: hard @@ -3543,10 +3640,10 @@ __metadata: languageName: node linkType: hard -"compare-versions@npm:^5.0.0": - version: 5.0.1 - resolution: "compare-versions@npm:5.0.1" - checksum: 302a4e46224b47b9280cf894c6c87d8df912671fa391dcdbf0e63438d9b0a69fe20dd747fb439e8d54c43af016ff4eaaf0a4c9d8e7ca358bcd12dadf4ad2935e +"compare-versions@npm:^6.0.0": + version: 6.0.0 + resolution: "compare-versions@npm:6.0.0" + checksum: bf2fa355b2139cbdb0576f2f0328c112dd3c2eb808eff8b70b808b3ed05f8a40b8317c323ff4797a6a5a7ab32d508876749584c626ee5840dc0119361afffc4d languageName: node linkType: hard @@ -3593,11 +3690,9 @@ __metadata: linkType: hard "convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.7.0": - version: 1.8.0 - resolution: "convert-source-map@npm:1.8.0" - dependencies: - safe-buffer: ~5.1.1 - checksum: 985d974a2d33e1a2543ada51c93e1ba2f73eaed608dc39f229afc78f71dcc4c8b7d7c684aa647e3c6a3a204027444d69e53e169ce94e8d1fa8d7dee80c9c8fed + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: dc55a1f28ddd0e9485ef13565f8f756b342f9a46c4ae18b843fe3c30c675d058d6a4823eff86d472f187b176f0adf51ea7b69ea38be34be4a63cbbf91b0593c8 languageName: node linkType: hard @@ -3692,11 +3787,11 @@ __metadata: linkType: hard "cross-fetch@npm:^3.1.5": - version: 3.1.5 - resolution: "cross-fetch@npm:3.1.5" + version: 3.1.8 + resolution: "cross-fetch@npm:3.1.8" dependencies: - node-fetch: 2.6.7 - checksum: f6b8c6ee3ef993ace6277fd789c71b6acf1b504fd5f5c7128df4ef2f125a429e29cd62dc8c127523f04a5f2fa4771ed80e3f3d9695617f441425045f505cf3bb + node-fetch: ^2.6.12 + checksum: 78f993fa099eaaa041122ab037fe9503ecbbcb9daef234d1d2e0b9230a983f64d645d088c464e21a247b825a08dc444a6e7064adfa93536d3a9454b4745b3632 languageName: node linkType: hard @@ -3713,7 +3808,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.2": +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" dependencies: @@ -3747,7 +3842,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:2.6.9, debug@npm:^2.6.0, debug@npm:^2.6.9": +"debug@npm:2.6.9, debug@npm:^2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" dependencies: @@ -3765,7 +3860,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -3807,21 +3902,12 @@ __metadata: languageName: node linkType: hard -"deep-eql@npm:^3.0.1": - version: 3.0.1 - resolution: "deep-eql@npm:3.0.1" - dependencies: - type-detect: ^4.0.0 - checksum: 4f4c9fb79eb994fb6e81d4aa8b063adc40c00f831588aa65e20857d5d52f15fb23034a6576ecf886f7ff6222d5ae42e71e9b7d57113e0715b1df7ea1e812b125 - languageName: node - linkType: hard - -"deep-eql@npm:^4.0.1": - version: 4.1.0 - resolution: "deep-eql@npm:4.1.0" +"deep-eql@npm:^4.0.1, deep-eql@npm:^4.1.2": + version: 4.1.3 + resolution: "deep-eql@npm:4.1.3" dependencies: type-detect: ^4.0.0 - checksum: 2fccd527df9a70a92a1dfa8c771d139753625938e137b09fc946af8577d22360ef28d3c74f0e9c5aaa399bab20542d0899da1529c71db76f280a30147cd2a110 + checksum: 7f6d30cb41c713973dc07eaadded848b2ab0b835e518a88b91bea72f34e08c4c71d167a722a6f302d3a6108f05afd8e6d7650689a84d5d29ec7fe6220420397f languageName: node linkType: hard @@ -3832,32 +3918,6 @@ __metadata: languageName: node linkType: hard -"defender-admin-client@npm:^1.39.0": - version: 1.43.0 - resolution: "defender-admin-client@npm:1.43.0" - dependencies: - axios: ^0.21.2 - defender-base-client: 1.43.0 - ethers: ^5.7.2 - lodash: ^4.17.19 - node-fetch: ^2.6.0 - checksum: 489e1f9822d5f36d04b2668eee411f7b46747a88128e4184ac4b6c9fd2fc6caacfac3541429ffa086b3639c3aeb4fc5b87f53bc0e151790c27de5db5804dca61 - languageName: node - linkType: hard - -"defender-base-client@npm:1.43.0, defender-base-client@npm:^1.40.0": - version: 1.43.0 - resolution: "defender-base-client@npm:1.43.0" - dependencies: - amazon-cognito-identity-js: ^6.0.1 - async-retry: ^1.3.3 - axios: ^0.21.2 - lodash: ^4.17.19 - node-fetch: ^2.6.0 - checksum: e0862837112fd8e00252e11dc32b7f7afa1e25b48ca0ad0cd8b2d443024fa3bf17b683bc0922e8464251b7919378925ce36e8227f7355a7978ef303db7b2ba77 - languageName: node - linkType: hard - "define-lazy-prop@npm:^2.0.0": version: 2.0.0 resolution: "define-lazy-prop@npm:2.0.0" @@ -3865,13 +3925,13 @@ __metadata: languageName: node linkType: hard -"define-properties@npm:^1.1.2, define-properties@npm:^1.1.3, define-properties@npm:^1.1.4": - version: 1.1.4 - resolution: "define-properties@npm:1.1.4" +"define-properties@npm:^1.1.2, define-properties@npm:^1.1.3, define-properties@npm:^1.1.4, define-properties@npm:^1.2.0": + version: 1.2.0 + resolution: "define-properties@npm:1.2.0" dependencies: has-property-descriptors: ^1.0.0 object-keys: ^1.1.1 - checksum: ce0aef3f9eb193562b5cfb79b2d2c86b6a109dfc9fdcb5f45d680631a1a908c06824ddcdb72b7573b54e26ace07f0a23420aaba0d5c627b34d2c1de8ef527e2b + checksum: e60aee6a19b102df4e2b1f301816804e81ab48bb91f00d0d935f269bf4b3f79c88b39e4f89eaa132890d23267335fd1140dfcd8d5ccd61031a0a2c41a54e33a6 languageName: node linkType: hard @@ -3889,20 +3949,13 @@ __metadata: languageName: node linkType: hard -"depd@npm:2.0.0": +"depd@npm:2.0.0, depd@npm:^2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: abbe19c768c97ee2eed6282d8ce3031126662252c58d711f646921c9623f9052e3e1906443066beec1095832f534e57c523b7333f8e7e0d93051ab6baef5ab3a languageName: node linkType: hard -"depd@npm:^1.1.2": - version: 1.1.2 - resolution: "depd@npm:1.1.2" - checksum: 6b406620d269619852885ce15965272b829df6f409724415e0002c8632ab6a8c0a08ec1f0bd2add05dc7bd7507606f7e2cc034fa24224ab829580040b835ecd9 - languageName: node - linkType: hard - "destroy@npm:1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" @@ -3911,15 +3964,15 @@ __metadata: linkType: hard "detect-port@npm:^1.3.0": - version: 1.3.0 - resolution: "detect-port@npm:1.3.0" + version: 1.5.1 + resolution: "detect-port@npm:1.5.1" dependencies: address: ^1.0.1 - debug: ^2.6.0 + debug: 4 bin: - detect: ./bin/detect-port - detect-port: ./bin/detect-port - checksum: 93c40febe714f56711d1fedc2b7a9cc4cbaa0fcddec0509876c46b9dd6099ed6bfd6662a4f35e5fa0301660f48ed516829253ab0fc90b9e79b823dd77786b379 + detect: bin/detect-port.js + detect-port: bin/detect-port.js + checksum: b48da9340481742547263d5d985e65d078592557863402ecf538511735e83575867e94f91fe74405ea19b61351feb99efccae7e55de9a151d5654e3417cea05b languageName: node linkType: hard @@ -3988,9 +4041,16 @@ __metadata: linkType: hard "dotenv@npm:^16.0.0": - version: 16.0.3 - resolution: "dotenv@npm:16.0.3" - checksum: afcf03f373d7a6d62c7e9afea6328e62851d627a4e73f2e12d0a8deae1cd375892004f3021883f8aec85932cd2834b091f568ced92b4774625b321db83b827f8 + version: 16.3.1 + resolution: "dotenv@npm:16.3.1" + checksum: 15d75e7279018f4bafd0ee9706593dd14455ddb71b3bcba9c52574460b7ccaf67d5cf8b2c08a5af1a9da6db36c956a04a1192b101ee102a3e0cf8817bbcf3dfd + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 7d00d7cd8e49b9afa762a813faac332dee781932d6f2c848dc348939c4253f1d4564341b7af1d041853bc3f32c2ef141b58e0a4d9862c17a7f08f68df1e0f1ed languageName: node linkType: hard @@ -4011,10 +4071,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.4.172": - version: 1.4.180 - resolution: "electron-to-chromium@npm:1.4.180" - checksum: 98df07cfb0f67c383aa124ffc12fd9e9d3fd50e1c79548fc3d1524f3dbc3ac92c41f145a97e4e434d3d959f31e8ceab1e7426a6a5ef846af19e5b30e60bb7e98 +"electron-to-chromium@npm:^1.4.431": + version: 1.4.474 + resolution: "electron-to-chromium@npm:1.4.474" + checksum: 16e55823064dfa6f64088a3d5124c0c5c2a577c981a35e58199a2baa6a237b4d9505ddf406d4c8761cabdf6f7b347013a57a887883781dfb04d1940f8be379b0 languageName: node linkType: hard @@ -4033,13 +4093,6 @@ __metadata: languageName: node linkType: hard -"emoji-regex@npm:^10.0.0": - version: 10.1.0 - resolution: "emoji-regex@npm:10.1.0" - checksum: 5bc780fc4d75f89369155a87c55f7e83a0bf72bcccda7df7f2c570cde4738d8b17d112d12afdadfec16647d1faef6501307b4304f81d35c823a938fe6547df0f - languageName: node - linkType: hard - "emoji-regex@npm:^7.0.1": version: 7.0.3 resolution: "emoji-regex@npm:7.0.3" @@ -4054,6 +4107,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 8487182da74aabd810ac6d6f1994111dfc0e331b01271ae01ec1eb0ad7b5ecc2bbbbd2f053c05cb55a1ac30449527d819bbfbf0e3de1023db308cbcb47f86601 + languageName: node + linkType: hard + "encode-utf8@npm:^1.0.2": version: 1.0.3 resolution: "encode-utf8@npm:1.0.3" @@ -4078,11 +4138,12 @@ __metadata: linkType: hard "enquirer@npm:^2.3.0, enquirer@npm:^2.3.6": - version: 2.3.6 - resolution: "enquirer@npm:2.3.6" + version: 2.4.0 + resolution: "enquirer@npm:2.4.0" dependencies: ansi-colors: ^4.1.1 - checksum: 1c0911e14a6f8d26721c91e01db06092a5f7675159f0261d69c403396a385afd13dd76825e7678f66daffa930cfaa8d45f506fb35f818a2788463d022af1b884 + strip-ansi: ^6.0.1 + checksum: bbdecde92679ed847c751dc5337ff39ce0c32d85e76fb2e47245e831e9cc7f84c12bd35b70f7b1de9b9e1c730d90cccd04201e69f1ecf7986a7a70d8d39349db languageName: node linkType: hard @@ -4109,34 +4170,50 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.19.0, es-abstract@npm:^1.19.1, es-abstract@npm:^1.19.2, es-abstract@npm:^1.19.5, es-abstract@npm:^1.20.1": - version: 1.20.1 - resolution: "es-abstract@npm:1.20.1" +"es-abstract@npm:^1.19.0, es-abstract@npm:^1.20.4, es-abstract@npm:^1.21.2": + version: 1.22.1 + resolution: "es-abstract@npm:1.22.1" dependencies: + array-buffer-byte-length: ^1.0.0 + arraybuffer.prototype.slice: ^1.0.1 + available-typed-arrays: ^1.0.5 call-bind: ^1.0.2 + es-set-tostringtag: ^2.0.1 es-to-primitive: ^1.2.1 - function-bind: ^1.1.1 function.prototype.name: ^1.1.5 - get-intrinsic: ^1.1.1 + get-intrinsic: ^1.2.1 get-symbol-description: ^1.0.0 + globalthis: ^1.0.3 + gopd: ^1.0.1 has: ^1.0.3 has-property-descriptors: ^1.0.0 + has-proto: ^1.0.1 has-symbols: ^1.0.3 - internal-slot: ^1.0.3 - is-callable: ^1.2.4 + internal-slot: ^1.0.5 + is-array-buffer: ^3.0.2 + is-callable: ^1.2.7 is-negative-zero: ^2.0.2 is-regex: ^1.1.4 is-shared-array-buffer: ^1.0.2 is-string: ^1.0.7 + is-typed-array: ^1.1.10 is-weakref: ^1.0.2 - object-inspect: ^1.12.0 + object-inspect: ^1.12.3 object-keys: ^1.1.1 - object.assign: ^4.1.2 - regexp.prototype.flags: ^1.4.3 - string.prototype.trimend: ^1.0.5 - string.prototype.trimstart: ^1.0.5 + object.assign: ^4.1.4 + regexp.prototype.flags: ^1.5.0 + safe-array-concat: ^1.0.0 + safe-regex-test: ^1.0.0 + string.prototype.trim: ^1.2.7 + string.prototype.trimend: ^1.0.6 + string.prototype.trimstart: ^1.0.6 + typed-array-buffer: ^1.0.0 + typed-array-byte-length: ^1.0.0 + typed-array-byte-offset: ^1.0.0 + typed-array-length: ^1.0.4 unbox-primitive: ^1.0.2 - checksum: 28da27ae0ed9c76df7ee8ef5c278df79dcfdb554415faf7068bb7c58f8ba8e2a16bfb59e586844be6429ab4c302ca7748979d48442224cb1140b051866d74b7f + which-typed-array: ^1.1.10 + checksum: 614e2c1c3717cb8d30b6128ef12ea110e06fd7d75ad77091ca1c5dbfb00da130e62e4bbbbbdda190eada098a22b27fe0f99ae5a1171dac2c8663b1e8be8a3a9b languageName: node linkType: hard @@ -4147,6 +4224,17 @@ __metadata: languageName: node linkType: hard +"es-set-tostringtag@npm:^2.0.1": + version: 2.0.1 + resolution: "es-set-tostringtag@npm:2.0.1" + dependencies: + get-intrinsic: ^1.1.3 + has: ^1.0.3 + has-tostringtag: ^1.0.0 + checksum: ec416a12948cefb4b2a5932e62093a7cf36ddc3efd58d6c58ca7ae7064475ace556434b869b0bbeb0c365f1032a8ccd577211101234b69837ad83ad204fff884 + languageName: node + linkType: hard + "es-shim-unscopables@npm:^1.0.0": version: 1.0.0 resolution: "es-shim-unscopables@npm:1.0.0" @@ -4221,7 +4309,7 @@ __metadata: languageName: node linkType: hard -"eslint-config-prettier@npm:^8.5.0": +"eslint-config-prettier@npm:8.5.0": version: 8.5.0 resolution: "eslint-config-prettier@npm:8.5.0" peerDependencies: @@ -4232,7 +4320,7 @@ __metadata: languageName: node linkType: hard -"eslint-config-standard@npm:^16.0.3": +"eslint-config-standard@npm:16.0.3": version: 16.0.3 resolution: "eslint-config-standard@npm:16.0.3" peerDependencies: @@ -4245,22 +4333,25 @@ __metadata: linkType: hard "eslint-import-resolver-node@npm:^0.3.6": - version: 0.3.6 - resolution: "eslint-import-resolver-node@npm:0.3.6" + version: 0.3.7 + resolution: "eslint-import-resolver-node@npm:0.3.7" dependencies: debug: ^3.2.7 - resolve: ^1.20.0 - checksum: 6266733af1e112970e855a5bcc2d2058fb5ae16ad2a6d400705a86b29552b36131ffc5581b744c23d550de844206fb55e9193691619ee4dbf225c4bde526b1c8 + is-core-module: ^2.11.0 + resolve: ^1.22.1 + checksum: 3379aacf1d2c6952c1b9666c6fa5982c3023df695430b0d391c0029f6403a7775414873d90f397e98ba6245372b6c8960e16e74d9e4a3b0c0a4582f3bdbe3d6e languageName: node linkType: hard -"eslint-module-utils@npm:^2.7.3": - version: 2.7.3 - resolution: "eslint-module-utils@npm:2.7.3" +"eslint-module-utils@npm:^2.7.2": + version: 2.8.0 + resolution: "eslint-module-utils@npm:2.8.0" dependencies: debug: ^3.2.7 - find-up: ^2.1.0 - checksum: 77048263f309167a1e6a1e1b896bfb5ddd1d3859b2e2abbd9c32c432aee13d610d46e6820b1ca81b37fba437cf423a404bc6649be64ace9148a3062d1886a678 + peerDependenciesMeta: + eslint: + optional: true + checksum: 74c6dfea7641ebcfe174be61168541a11a14aa8d72e515f5f09af55cd0d0862686104b0524aa4b8e0ce66418a44aa38a94d2588743db5fd07a6b49ffd16921d2 languageName: node linkType: hard @@ -4276,30 +4367,30 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-import@npm:^2.25.4": - version: 2.26.0 - resolution: "eslint-plugin-import@npm:2.26.0" +"eslint-plugin-import@npm:2.25.4": + version: 2.25.4 + resolution: "eslint-plugin-import@npm:2.25.4" dependencies: array-includes: ^3.1.4 array.prototype.flat: ^1.2.5 debug: ^2.6.9 doctrine: ^2.1.0 eslint-import-resolver-node: ^0.3.6 - eslint-module-utils: ^2.7.3 + eslint-module-utils: ^2.7.2 has: ^1.0.3 - is-core-module: ^2.8.1 + is-core-module: ^2.8.0 is-glob: ^4.0.3 - minimatch: ^3.1.2 + minimatch: ^3.0.4 object.values: ^1.1.5 - resolve: ^1.22.0 - tsconfig-paths: ^3.14.1 + resolve: ^1.20.0 + tsconfig-paths: ^3.12.0 peerDependencies: eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - checksum: 0bf77ad80339554481eafa2b1967449e1f816b94c7a6f9614ce33fb4083c4e6c050f10d241dd50b4975d47922880a34de1e42ea9d8e6fd663ebb768baa67e655 + checksum: 0af24f5c7c6ca692f42e3947127f0ae7dfe44f1e02740f7cbe988b510a9c52bab0065d7df04e2d953dcc88a4595a00cbdcf14018acf8cd75cfd47b72efcbb734 languageName: node linkType: hard -"eslint-plugin-node@npm:^11.1.0": +"eslint-plugin-node@npm:11.1.0": version: 11.1.0 resolution: "eslint-plugin-node@npm:11.1.0" dependencies: @@ -4315,9 +4406,9 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-prettier@npm:^4.0.0": - version: 4.2.1 - resolution: "eslint-plugin-prettier@npm:4.2.1" +"eslint-plugin-prettier@npm:4.0.0": + version: 4.0.0 + resolution: "eslint-plugin-prettier@npm:4.0.0" dependencies: prettier-linter-helpers: ^1.0.0 peerDependencies: @@ -4326,11 +4417,11 @@ __metadata: peerDependenciesMeta: eslint-config-prettier: optional: true - checksum: b9e839d2334ad8ec7a5589c5cb0f219bded260839a857d7a486997f9870e95106aa59b8756ff3f37202085ebab658de382b0267cae44c3a7f0eb0bcc03a4f6d6 + checksum: 03d69177a3c21fa2229c7e427ce604429f0b20ab7f411e2e824912f572a207c7f5a41fd1f0a95b9b8afe121e291c1b1f1dc1d44c7aad4b0837487f9c19f5210d languageName: node linkType: hard -"eslint-plugin-promise@npm:^6.0.0": +"eslint-plugin-promise@npm:6.0.0": version: 6.0.0 resolution: "eslint-plugin-promise@npm:6.0.0" peerDependencies: @@ -4360,12 +4451,12 @@ __metadata: linkType: hard "eslint-scope@npm:^7.1.1": - version: 7.1.1 - resolution: "eslint-scope@npm:7.1.1" + version: 7.2.1 + resolution: "eslint-scope@npm:7.2.1" dependencies: esrecurse: ^4.3.0 estraverse: ^5.2.0 - checksum: 9f6e974ab2db641ca8ab13508c405b7b859e72afe9f254e8131ff154d2f40c99ad4545ce326fd9fde3212ff29707102562a4834f1c48617b35d98c71a97fbf3e + checksum: dccda5c8909216f6261969b72c77b95e385f9086bed4bc09d8a6276df8439d8f986810fd9ac3bd02c94c0572cefc7fdbeae392c69df2e60712ab8263986522c5 languageName: node linkType: hard @@ -4412,10 +4503,55 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0": - version: 3.3.0 - resolution: "eslint-visitor-keys@npm:3.3.0" - checksum: d59e68a7c5a6d0146526b0eec16ce87fbf97fe46b8281e0d41384224375c4e52f5ffb9e16d48f4ea50785cde93f766b0c898e31ab89978d88b0e1720fbfb7808 +"eslint-visitor-keys@npm:^3.0.0, eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1": + version: 3.4.1 + resolution: "eslint-visitor-keys@npm:3.4.1" + checksum: f05121d868202736b97de7d750847a328fcfa8593b031c95ea89425333db59676ac087fa905eba438d0a3c5769632f828187e0c1a0d271832a2153c1d3661c2c + languageName: node + linkType: hard + +"eslint@npm:8.14.0": + version: 8.14.0 + resolution: "eslint@npm:8.14.0" + dependencies: + "@eslint/eslintrc": ^1.2.2 + "@humanwhocodes/config-array": ^0.9.2 + ajv: ^6.10.0 + chalk: ^4.0.0 + cross-spawn: ^7.0.2 + debug: ^4.3.2 + doctrine: ^3.0.0 + escape-string-regexp: ^4.0.0 + eslint-scope: ^7.1.1 + eslint-utils: ^3.0.0 + eslint-visitor-keys: ^3.3.0 + espree: ^9.3.1 + esquery: ^1.4.0 + esutils: ^2.0.2 + fast-deep-equal: ^3.1.3 + file-entry-cache: ^6.0.1 + functional-red-black-tree: ^1.0.1 + glob-parent: ^6.0.1 + globals: ^13.6.0 + ignore: ^5.2.0 + import-fresh: ^3.0.0 + imurmurhash: ^0.1.4 + is-glob: ^4.0.0 + js-yaml: ^4.1.0 + json-stable-stringify-without-jsonify: ^1.0.1 + levn: ^0.4.1 + lodash.merge: ^4.6.2 + minimatch: ^3.0.4 + natural-compare: ^1.4.0 + optionator: ^0.9.1 + regexpp: ^3.2.0 + strip-ansi: ^6.0.1 + strip-json-comments: ^3.1.0 + text-table: ^0.2.0 + v8-compile-cache: ^2.0.3 + bin: + eslint: bin/eslint.js + checksum: 87d2e3e5eb93216d4ab36006e7b8c0bfad02f40b0a0f193f1d42754512cd3a9d8244152f1c69df5db2e135b3c4f1c10d0ed2f0881fe8a8c01af55465968174c1 languageName: node linkType: hard @@ -4465,51 +4601,6 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.14.0": - version: 8.19.0 - resolution: "eslint@npm:8.19.0" - dependencies: - "@eslint/eslintrc": ^1.3.0 - "@humanwhocodes/config-array": ^0.9.2 - ajv: ^6.10.0 - chalk: ^4.0.0 - cross-spawn: ^7.0.2 - debug: ^4.3.2 - doctrine: ^3.0.0 - escape-string-regexp: ^4.0.0 - eslint-scope: ^7.1.1 - eslint-utils: ^3.0.0 - eslint-visitor-keys: ^3.3.0 - espree: ^9.3.2 - esquery: ^1.4.0 - esutils: ^2.0.2 - fast-deep-equal: ^3.1.3 - file-entry-cache: ^6.0.1 - functional-red-black-tree: ^1.0.1 - glob-parent: ^6.0.1 - globals: ^13.15.0 - ignore: ^5.2.0 - import-fresh: ^3.0.0 - imurmurhash: ^0.1.4 - is-glob: ^4.0.0 - js-yaml: ^4.1.0 - json-stable-stringify-without-jsonify: ^1.0.1 - levn: ^0.4.1 - lodash.merge: ^4.6.2 - minimatch: ^3.1.2 - natural-compare: ^1.4.0 - optionator: ^0.9.1 - regexpp: ^3.2.0 - strip-ansi: ^6.0.1 - strip-json-comments: ^3.1.0 - text-table: ^0.2.0 - v8-compile-cache: ^2.0.3 - bin: - eslint: bin/eslint.js - checksum: 0bc9df1a3a09dcd5a781ec728f280aa8af3ab19c2d1f14e2668b5ee5b8b1fb0e72dde5c3acf738e7f4281685fb24ec149b6154255470b06cf41de76350bca7a4 - languageName: node - linkType: hard - "espree@npm:^5.0.1": version: 5.0.1 resolution: "espree@npm:5.0.1" @@ -4521,14 +4612,14 @@ __metadata: languageName: node linkType: hard -"espree@npm:^9.3.2": - version: 9.3.2 - resolution: "espree@npm:9.3.2" +"espree@npm:^9.3.1, espree@npm:^9.4.0": + version: 9.6.1 + resolution: "espree@npm:9.6.1" dependencies: - acorn: ^8.7.1 + acorn: ^8.9.0 acorn-jsx: ^5.3.2 - eslint-visitor-keys: ^3.3.0 - checksum: 9a790d6779847051e87f70d720a0f6981899a722419e80c92ab6dee01e1ab83b8ce52d11b4dc96c2c490182efb5a4c138b8b0d569205bfe1cd4629e658e58c30 + eslint-visitor-keys: ^3.4.1 + checksum: eb8c149c7a2a77b3f33a5af80c10875c3abd65450f60b8af6db1bfcfa8f101e21c1e56a561c6dc13b848e18148d43469e7cd208506238554fb5395a9ea5a1ab9 languageName: node linkType: hard @@ -4553,11 +4644,11 @@ __metadata: linkType: hard "esquery@npm:^1.0.1, esquery@npm:^1.4.0": - version: 1.4.0 - resolution: "esquery@npm:1.4.0" + version: 1.5.0 + resolution: "esquery@npm:1.5.0" dependencies: estraverse: ^5.1.0 - checksum: a0807e17abd7fbe5fbd4fab673038d6d8a50675cdae6b04fbaa520c34581be0c5fa24582990e8acd8854f671dd291c78bb2efb9e0ed5b62f33bac4f9cf820210 + checksum: aefb0d2596c230118656cd4ec7532d447333a410a48834d80ea648b1e7b5c9bc9ed8b5e33a89cb04e487b60d622f44cf5713bf4abed7c97343edefdc84a35900 languageName: node linkType: hard @@ -4605,7 +4696,7 @@ __metadata: languageName: node linkType: hard -"eth-gas-reporter@npm:^0.2.24": +"eth-gas-reporter@npm:^0.2.25": version: 0.2.25 resolution: "eth-gas-reporter@npm:0.2.25" dependencies: @@ -4634,11 +4725,11 @@ __metadata: linkType: hard "eth-permit@npm:^0.2.1": - version: 0.2.1 - resolution: "eth-permit@npm:0.2.1" + version: 0.2.3 + resolution: "eth-permit@npm:0.2.3" dependencies: utf8: ^3.0.0 - checksum: 8ffd51659cbd33ccf50abccd1e0fd2abd4067de328af0bfe32b251f298b9daea40bafed995dfe40d78a28b9af4863e5d2eb69fd3b91dfe405643e8c7e7da3359 + checksum: 9bf3eed8ecb8c914aadfff97d6c3d19fc432928c72fbe205075153e84289ea95f3a101f77e2dae78ad629e09682700241f207b5436b575ca7d4fbae68eacd3f6 languageName: node linkType: hard @@ -4675,14 +4766,14 @@ __metadata: linkType: hard "ethereum-cryptography@npm:^1.0.3": - version: 1.1.0 - resolution: "ethereum-cryptography@npm:1.1.0" + version: 1.2.0 + resolution: "ethereum-cryptography@npm:1.2.0" dependencies: - "@noble/hashes": 1.1.1 - "@noble/secp256k1": 1.6.0 - "@scure/bip32": 1.1.0 - "@scure/bip39": 1.1.0 - checksum: cba0bc58272ccc9eca4cf045bd4b6edb083486069ae0a62fba0fea5385a8c4257ea0faf135868440044fb37047bc0d7f39090c21ea409be106a9f9004a3792b5 + "@noble/hashes": 1.2.0 + "@noble/secp256k1": 1.7.1 + "@scure/bip32": 1.1.5 + "@scure/bip39": 1.1.1 + checksum: 97e8e8253cb9f5a9271bd0201c37609c451c890eb85883b9c564f14743c3d7c673287406c93bf5604307593ee298ad9a03983388b85c11ca61461b9fc1a4f2c7 languageName: node linkType: hard @@ -4817,16 +4908,23 @@ __metadata: languageName: node linkType: hard -"expect@npm:^28.1.1": - version: 28.1.1 - resolution: "expect@npm:28.1.1" +"expect@npm:^28.1.3": + version: 28.1.3 + resolution: "expect@npm:28.1.3" dependencies: - "@jest/expect-utils": ^28.1.1 + "@jest/expect-utils": ^28.1.3 jest-get-type: ^28.0.2 - jest-matcher-utils: ^28.1.1 - jest-message-util: ^28.1.1 - jest-util: ^28.1.1 - checksum: 6e557b681f4cfb0bf61efad50c5787cc6eb4596a3c299be69adc83fcad0265b5f329b997c2bb7ec92290e609681485616e51e16301a7f0ba3c57139b337c9351 + jest-matcher-utils: ^28.1.3 + jest-message-util: ^28.1.3 + jest-util: ^28.1.3 + checksum: 101e0090de300bcafedb7dbfd19223368a2251ce5fe0105bbb6de5720100b89fb6b64290ebfb42febc048324c76d6a4979cdc4b61eb77747857daf7a5de9b03d + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.1 + resolution: "exponential-backoff@npm:3.1.1" + checksum: 3d21519a4f8207c99f7457287291316306255a328770d320b401114ec8481986e4e467e854cb9914dd965e0a1ca810a23ccb559c642c88f4c7f55c55778a9b48 languageName: node linkType: hard @@ -4932,22 +5030,22 @@ __metadata: linkType: hard "fast-diff@npm:^1.1.2": - version: 1.2.0 - resolution: "fast-diff@npm:1.2.0" - checksum: 1b5306eaa9e826564d9e5ffcd6ebd881eb5f770b3f977fcbf38f05c824e42172b53c79920e8429c54eb742ce15a0caf268b0fdd5b38f6de52234c4a8368131ae + version: 1.3.0 + resolution: "fast-diff@npm:1.3.0" + checksum: d22d371b994fdc8cce9ff510d7b8dc4da70ac327bcba20df607dd5b9cae9f908f4d1028f5fe467650f058d1e7270235ae0b8230809a262b4df587a3b3aa216c3 languageName: node linkType: hard "fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.9": - version: 3.2.11 - resolution: "fast-glob@npm:3.2.11" + version: 3.3.1 + resolution: "fast-glob@npm:3.3.1" dependencies: "@nodelib/fs.stat": ^2.0.2 "@nodelib/fs.walk": ^1.2.3 glob-parent: ^5.1.2 merge2: ^1.3.0 micromatch: ^4.0.4 - checksum: f473105324a7780a20c06de842e15ddbb41d3cb7e71d1e4fe6e8373204f22245d54f5ab9e2061e6a1c613047345954d29b022e0e76f5c28b1df9858179a0e6d7 + checksum: b6f3add6403e02cf3a798bfbb1183d0f6da2afd368f27456010c0bc1f9640aea308243d4cb2c0ab142f618276e65ecb8be1661d7c62a7b4e5ba774b9ce5432e5 languageName: node linkType: hard @@ -4966,20 +5064,20 @@ __metadata: linkType: hard "fastq@npm:^1.6.0": - version: 1.13.0 - resolution: "fastq@npm:1.13.0" + version: 1.15.0 + resolution: "fastq@npm:1.15.0" dependencies: reusify: ^1.0.4 - checksum: 32cf15c29afe622af187d12fc9cd93e160a0cb7c31a3bb6ace86b7dea3b28e7b72acde89c882663f307b2184e14782c6c664fa315973c03626c7d4bff070bb0b + checksum: 0170e6bfcd5d57a70412440b8ef600da6de3b2a6c5966aeaf0a852d542daff506a0ee92d6de7679d1de82e644bce69d7a574a6c93f0b03964b5337eed75ada1a languageName: node linkType: hard "fb-watchman@npm:^2.0.0": - version: 2.0.1 - resolution: "fb-watchman@npm:2.0.1" + version: 2.0.2 + resolution: "fb-watchman@npm:2.0.2" dependencies: bser: 2.1.1 - checksum: 8510230778ab3a51c27dffb1b76ef2c24fab672a42742d3c0a45c2e9d1e5f20210b1fbca33486088da4a9a3958bde96b5aec0a63aac9894b4e9df65c88b2cbd6 + checksum: b15a124cef28916fe07b400eb87cbc73ca082c142abf7ca8e8de6af43eca79ca7bd13eb4d4d48240b3bd3136eaac40d16e42d6edf87a8e5d1dd8070626860c78 languageName: node linkType: hard @@ -5138,9 +5236,9 @@ __metadata: linkType: hard "flatted@npm:^3.1.0": - version: 3.2.6 - resolution: "flatted@npm:3.2.6" - checksum: 33b87aa88dfa40ca6ee31d7df61712bbbad3d3c05c132c23e59b9b61d34631b337a18ff2b8dc5553acdc871ec72b741e485f78969cf006124a3f57174de29a0e + version: 3.2.7 + resolution: "flatted@npm:3.2.7" + checksum: 427633049d55bdb80201c68f7eb1cbd533e03eac541f97d3aecab8c5526f12a20ccecaeede08b57503e772c769e7f8680b37e8d482d1e5f8d7e2194687f9ea35 languageName: node linkType: hard @@ -5153,7 +5251,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.12.1, follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.4, follow-redirects@npm:^1.14.9": +"follow-redirects@npm:^1.12.1, follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.4, follow-redirects@npm:^1.14.9, follow-redirects@npm:^1.15.0": version: 1.15.2 resolution: "follow-redirects@npm:1.15.2" peerDependenciesMeta: @@ -5163,6 +5261,25 @@ __metadata: languageName: node linkType: hard +"for-each@npm:^0.3.3": + version: 0.3.3 + resolution: "for-each@npm:0.3.3" + dependencies: + is-callable: ^1.1.3 + checksum: 6c48ff2bc63362319c65e2edca4a8e1e3483a2fabc72fbe7feaf8c73db94fc7861bd53bc02c8a66a0c1dd709da6b04eec42e0abdd6b40ce47305ae92a25e5d28 + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0": + version: 3.1.1 + resolution: "foreground-child@npm:3.1.1" + dependencies: + cross-spawn: ^7.0.0 + signal-exit: ^4.0.1 + checksum: 139d270bc82dc9e6f8bc045fe2aae4001dc2472157044fdfad376d0a3457f77857fa883c1c8b21b491c6caade9a926a4bed3d3d2e8d3c9202b151a4cbbd0bcd5 + languageName: node + linkType: hard + "forever-agent@npm:~0.6.1": version: 0.6.1 resolution: "forever-agent@npm:0.6.1" @@ -5300,7 +5417,7 @@ __metadata: languageName: node linkType: hard -"fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0": +"fs-minipass@npm:^2.0.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" dependencies: @@ -5309,6 +5426,15 @@ __metadata: languageName: node linkType: hard +"fs-minipass@npm:^3.0.0": + version: 3.0.2 + resolution: "fs-minipass@npm:3.0.2" + dependencies: + minipass: ^5.0.0 + checksum: e9cc0e1f2d01c6f6f62f567aee59530aba65c6c7b2ae88c5027bc34c711ebcfcfaefd0caf254afa6adfe7d1fba16bc2537508a6235196bac7276747d078aef0a + languageName: node + linkType: hard + "fs-readdir-recursive@npm:^1.1.0": version: 1.1.0 resolution: "fs-readdir-recursive@npm:1.1.0" @@ -5323,7 +5449,7 @@ __metadata: languageName: node linkType: hard -"fsevents@^2.3.2, fsevents@~2.3.2": +"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": version: 2.3.2 resolution: "fsevents@npm:2.3.2" dependencies: @@ -5333,30 +5459,30 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@^2.3.2#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin": - version: 2.3.2 - resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=df0bf1" +"fsevents@npm:~2.1.1": + version: 2.1.3 + resolution: "fsevents@npm:2.1.3" dependencies: node-gyp: latest + checksum: b5ec0516b44d75b60af5c01ff80a80cd995d175e4640d2a92fbabd02991dd664d76b241b65feef0775c23d531c3c74742c0fbacd6205af812a9c3cef59f04292 conditions: os=darwin languageName: node linkType: hard -"fsevents@patch:fsevents@~2.1.1#~builtin": - version: 2.1.3 - resolution: "fsevents@patch:fsevents@npm%3A2.1.3#~builtin::version=2.1.3&hash=31d12a" +"fsevents@patch:fsevents@^2.3.2#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin": + version: 2.3.2 + resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=df0bf1" dependencies: node-gyp: latest conditions: os=darwin languageName: node linkType: hard -fsevents@~2.1.1: +"fsevents@patch:fsevents@~2.1.1#~builtin": version: 2.1.3 - resolution: "fsevents@npm:2.1.3" + resolution: "fsevents@patch:fsevents@npm%3A2.1.3#~builtin::version=2.1.3&hash=31d12a" dependencies: node-gyp: latest - checksum: b5ec0516b44d75b60af5c01ff80a80cd995d175e4640d2a92fbabd02991dd664d76b241b65feef0775c23d531c3c74742c0fbacd6205af812a9c3cef59f04292 conditions: os=darwin languageName: node linkType: hard @@ -5387,7 +5513,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"functions-have-names@npm:^1.2.2": +"functions-have-names@npm:^1.2.2, functions-have-names@npm:^1.2.3": version: 1.2.3 resolution: "functions-have-names@npm:1.2.3" checksum: c3f1f5ba20f4e962efb71344ce0a40722163e85bee2101ce25f88214e78182d2d2476aa85ef37950c579eb6cf6ee811c17b3101bb84004bb75655f3e33f3fdb5 @@ -5431,14 +5557,15 @@ fsevents@~2.1.1: languageName: node linkType: hard -"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1": - version: 1.1.2 - resolution: "get-intrinsic@npm:1.1.2" +"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.0, get-intrinsic@npm:^1.2.1": + version: 1.2.1 + resolution: "get-intrinsic@npm:1.2.1" dependencies: function-bind: ^1.1.1 has: ^1.0.3 + has-proto: ^1.0.1 has-symbols: ^1.0.3 - checksum: 252f45491f2ba88ebf5b38018020c7cc3279de54b1d67ffb70c0cdf1dfa8ab31cd56467b5d117a8b4275b7a4dde91f86766b163a17a850f036528a7b2faafb2b + checksum: 5b61d88552c24b0cf6fa2d1b3bc5459d7306f699de060d76442cce49a4721f52b8c560a33ab392cf5575b7810277d54ded9d4d39a1ea61855619ebc005aa7e5f languageName: node linkType: hard @@ -5533,6 +5660,21 @@ fsevents@~2.1.1: languageName: node linkType: hard +"glob@npm:^10.2.2": + version: 10.3.3 + resolution: "glob@npm:10.3.3" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^2.0.3 + minimatch: ^9.0.1 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + path-scurry: ^1.10.1 + bin: + glob: dist/cjs/src/bin.js + checksum: 29190d3291f422da0cb40b77a72fc8d2c51a36524e99b8bf412548b7676a6627489528b57250429612b6eec2e6fe7826d328451d3e694a9d15e575389308ec53 + languageName: node + linkType: hard + "glob@npm:^5.0.15": version: 5.0.15 resolution: "glob@npm:5.0.15" @@ -5560,19 +5702,6 @@ fsevents@~2.1.1: languageName: node linkType: hard -"glob@npm:^8.0.1": - version: 8.0.3 - resolution: "glob@npm:8.0.3" - dependencies: - fs.realpath: ^1.0.0 - inflight: ^1.0.4 - inherits: 2 - minimatch: ^5.0.1 - once: ^1.3.0 - checksum: 50bcdea19d8e79d8de5f460b1939ffc2b3299eac28deb502093fdca22a78efebc03e66bf54f0abc3d3d07d8134d19a32850288b7440d77e072aa55f9d33b18c5 - languageName: node - linkType: hard - "global-modules@npm:^2.0.0": version: 2.0.0 resolution: "global-modules@npm:2.0.0" @@ -5600,12 +5729,21 @@ fsevents@~2.1.1: languageName: node linkType: hard -"globals@npm:^13.15.0": - version: 13.16.0 - resolution: "globals@npm:13.16.0" +"globals@npm:^13.19.0, globals@npm:^13.6.0": + version: 13.20.0 + resolution: "globals@npm:13.20.0" dependencies: type-fest: ^0.20.2 - checksum: e571b28462b8922a29ac78c8df89848cfd5dc9bdd5d8077440c022864f512a4aae82e7561a2f366337daa86fd4b366aec16fd3f08686de387e4089b01be6cb14 + checksum: ad1ecf914bd051325faad281d02ea2c0b1df5d01bd94d368dcc5513340eac41d14b3c61af325768e3c7f8d44576e72780ec0b6f2d366121f8eec6e03c3a3b97a + languageName: node + linkType: hard + +"globalthis@npm:^1.0.3": + version: 1.0.3 + resolution: "globalthis@npm:1.0.3" + dependencies: + define-properties: ^1.1.3 + checksum: fbd7d760dc464c886d0196166d92e5ffb4c84d0730846d6621a39fbbc068aeeb9c8d1421ad330e94b7bca4bb4ea092f5f21f3d36077812af5d098b4dc006c998 languageName: node linkType: hard @@ -5625,7 +5763,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"globby@npm:^11.1.0": +"globby@npm:^11.0.4": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -5639,10 +5777,19 @@ fsevents@~2.1.1: languageName: node linkType: hard +"gopd@npm:^1.0.1": + version: 1.0.1 + resolution: "gopd@npm:1.0.1" + dependencies: + get-intrinsic: ^1.1.3 + checksum: a5ccfb8806e0917a94e0b3de2af2ea4979c1da920bc381667c260e00e7cafdbe844e2cb9c5bcfef4e5412e8bf73bab837285bc35c7ba73aaaf0134d4583393a6 + languageName: node + linkType: hard + "graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": - version: 4.2.10 - resolution: "graceful-fs@npm:4.2.10" - checksum: 3f109d70ae123951905d85032ebeae3c2a5a7a997430df00ea30df0e3a6c60cf6689b109654d6fdacd28810a053348c4d14642da1d075049e6be1ba5216218da + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 languageName: node linkType: hard @@ -5661,9 +5808,9 @@ fsevents@~2.1.1: linkType: hard "graphql@npm:^16.6.0": - version: 16.6.0 - resolution: "graphql@npm:16.6.0" - checksum: bf1d9e3c1938ce3c1a81e909bd3ead1ae4707c577f91cff1ca2eca474bfbc7873d5d7b942e1e9777ff5a8304421dba57a4b76d7a29eb19de8711cb70e3c2415e + version: 16.7.1 + resolution: "graphql@npm:16.7.1" + checksum: c924d8428daf0e96a5ea43e9bc3cd1b6802899907d284478ac8f705c8fd233a0a51eef915f7569fb5de8acb2e85b802ccc6c85c2b157ad805c1e9adba5a299bd languageName: node linkType: hard @@ -5710,20 +5857,21 @@ fsevents@~2.1.1: linkType: hard "hardhat-contract-sizer@npm:^2.4.0": - version: 2.6.1 - resolution: "hardhat-contract-sizer@npm:2.6.1" + version: 2.10.0 + resolution: "hardhat-contract-sizer@npm:2.10.0" dependencies: chalk: ^4.0.0 cli-table3: ^0.6.0 + strip-ansi: ^6.0.0 peerDependencies: hardhat: ^2.0.0 - checksum: a82ae2405a8571e8b0cd0a21dea9a10946b342f1ada04c72c9cbe28fca955f9a2b1394c70400003f388182298dc1de00e80bf56dbfa5e36833d3c93ab1f50c0c + checksum: 870e7cad5d96ad7288b64da0faec7962a9a18e1eaaa02ed474e4f9285cd4b1a0fc6f66326e6a7476f7063fdf99aee57f227084519b1fb3723700a2d65fc65cfa languageName: node linkType: hard "hardhat-deploy@npm:^0.11.14": - version: 0.11.30 - resolution: "hardhat-deploy@npm:0.11.30" + version: 0.11.34 + resolution: "hardhat-deploy@npm:0.11.34" dependencies: "@ethersproject/abi": ^5.7.0 "@ethersproject/abstract-signer": ^5.7.0 @@ -5749,26 +5897,26 @@ fsevents@~2.1.1: murmur-128: ^0.2.1 qs: ^6.9.4 zksync-web3: ^0.14.3 - checksum: 7b9ac9d856097be1df88ed86cbec88e5bdeb6258c7167c097d6ad4e80a1131b9288fc7704ff6457253f293f57c9992d83383f15ce8f22190d94966b8bb05d832 + checksum: 3c4bcd657a80e4f22c1f8bcee021e5277060849ce4180cbc721e0c2d625f2f98de8ebbfad23875d32eeaf06d88bdba06cb43ab29359cb9531e8bb7851a98ead1 languageName: node linkType: hard "hardhat-gas-reporter@npm:^1.0.8": - version: 1.0.8 - resolution: "hardhat-gas-reporter@npm:1.0.8" + version: 1.0.9 + resolution: "hardhat-gas-reporter@npm:1.0.9" dependencies: array-uniq: 1.0.3 - eth-gas-reporter: ^0.2.24 + eth-gas-reporter: ^0.2.25 sha1: ^1.1.1 peerDependencies: hardhat: ^2.0.2 - checksum: bf18aacd08e0bdef81b180f3c97f76fcab885de3e92ed2dc014712e671c83ee7f77755c0e6c0f923a95f8372714cfcb7cdaa019afc42984c159603f8a8d724cf + checksum: 77f8f8d085ff3d9d7787f0227e5355e1800f7d6707bc70171e0567bf69706703ae7f6f53dce1be1d409e7e71e3629a434c94b546bdbbc1e4c1af47cd5d0c6776 languageName: node linkType: hard "hardhat@npm:^2.12.3": - version: 2.14.0 - resolution: "hardhat@npm:2.14.0" + version: 2.17.0 + resolution: "hardhat@npm:2.17.0" dependencies: "@ethersproject/abi": ^5.1.2 "@metamask/eth-sig-util": ^4.0.0 @@ -5809,7 +5957,6 @@ fsevents@~2.1.1: mnemonist: ^0.38.0 mocha: ^10.0.0 p-map: ^4.0.0 - qs: ^6.7.0 raw-body: ^2.4.1 resolve: 1.17.0 semver: ^6.3.0 @@ -5830,7 +5977,7 @@ fsevents@~2.1.1: optional: true bin: hardhat: internal/cli/bootstrap.js - checksum: 7a11ad4650759851306d65c30252ccffa2aca9cb461c66f3fcef5f29d38fe66402d6bc295d293670fa0a72bf3572cc95029c5cd0b0fd45f45edd99d6eb5b7586 + checksum: fcbbee245069a9c3fd0b7f015bc7b99529d3b2be6b8c0c9a61d6a68e5eb2bece8d4f7a9a206808af8fa46a3f3f05df51571caa4e88d3ebbc977c0f16fdb0aafe languageName: node linkType: hard @@ -5871,7 +6018,14 @@ fsevents@~2.1.1: languageName: node linkType: hard -"has-symbols@npm:^1.0.0, has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": +"has-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "has-proto@npm:1.0.1" + checksum: febc5b5b531de8022806ad7407935e2135f1cc9e64636c3916c6842bd7995994ca3b29871ecd7954bd35f9e2986c17b3b227880484d22259e2f8e6ce63fd383e + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.0, has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": version: 1.0.3 resolution: "has-symbols@npm:1.0.3" checksum: a054c40c631c0d5741a8285010a0777ea0c068f99ed43e5d6eb12972da223f8af553a455132fdb0801bdcfa0e0f443c0c03a68d8555aa529b3144b446c3f2410 @@ -5973,10 +6127,10 @@ fsevents@~2.1.1: languageName: node linkType: hard -"http-cache-semantics@npm:^4.1.0": - version: 4.1.0 - resolution: "http-cache-semantics@npm:4.1.0" - checksum: 974de94a81c5474be07f269f9fd8383e92ebb5a448208223bfb39e172a9dbc26feff250192ecc23b9593b3f92098e010406b0f24bd4d588d631f80214648ed42 +"http-cache-semantics@npm:^4.1.1": + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 languageName: node linkType: hard @@ -6091,17 +6245,17 @@ fsevents@~2.1.1: languageName: node linkType: hard -"ignore@npm:^5.1.1, ignore@npm:^5.2.0": - version: 5.2.0 - resolution: "ignore@npm:5.2.0" - checksum: 6b1f926792d614f64c6c83da3a1f9c83f6196c2839aa41e1e32dd7b8d174cef2e329d75caabb62cb61ce9dc432f75e67d07d122a037312db7caa73166a1bdb77 +"ignore@npm:^5.1.1, ignore@npm:^5.1.8, ignore@npm:^5.2.0": + version: 5.2.4 + resolution: "ignore@npm:5.2.4" + checksum: 3d4c309c6006e2621659311783eaea7ebcd41fe4ca1d78c91c473157ad6666a57a2df790fe0d07a12300d9aac2888204d7be8d59f9aaf665b1c7fcdb432517ef languageName: node linkType: hard "immutable@npm:^4.0.0-rc.12": - version: 4.1.0 - resolution: "immutable@npm:4.1.0" - checksum: b9bc1f14fb18eb382d48339c064b24a1f97ae4cf43102e0906c0a6e186a27afcd18b55ca4a0b63c98eefb58143e2b5ebc7755a5fb4da4a7ad84b7a6096ac5b13 + version: 4.3.1 + resolution: "immutable@npm:4.3.1" + checksum: a3a5ba29bd43f3f9a2e4d599763d7455d11a0ea57e50bf43f2836672fc80003e90d69f2a4f5b589f1f3d6986faf97f08ce1e253583740dd33c00adebab88b217 languageName: node linkType: hard @@ -6146,13 +6300,6 @@ fsevents@~2.1.1: languageName: node linkType: hard -"infer-owner@npm:^1.0.4": - version: 1.0.4 - resolution: "infer-owner@npm:1.0.4" - checksum: 181e732764e4a0611576466b4b87dac338972b839920b2a8cde43642e4ed6bd54dc1fb0b40874728f2a2df9a1b097b8ff83b56d5f8f8e3927f837fdcb47d8a89 - languageName: node - linkType: hard - "inflight@npm:^1.0.4": version: 1.0.6 resolution: "inflight@npm:1.0.6" @@ -6198,14 +6345,14 @@ fsevents@~2.1.1: languageName: node linkType: hard -"internal-slot@npm:^1.0.3": - version: 1.0.3 - resolution: "internal-slot@npm:1.0.3" +"internal-slot@npm:^1.0.5": + version: 1.0.5 + resolution: "internal-slot@npm:1.0.5" dependencies: - get-intrinsic: ^1.1.0 + get-intrinsic: ^1.2.0 has: ^1.0.3 side-channel: ^1.0.4 - checksum: 1944f92e981e47aebc98a88ff0db579fd90543d937806104d0b96557b10c1f170c51fb777b97740a8b6ddeec585fca8c39ae99fd08a8e058dfc8ab70937238bf + checksum: 97e84046bf9e7574d0956bd98d7162313ce7057883b6db6c5c7b5e5f05688864b0978ba07610c726d15d66544ffe4b1050107d93f8a39ebc59b15d8b429b497a languageName: node linkType: hard @@ -6225,10 +6372,10 @@ fsevents@~2.1.1: languageName: node linkType: hard -"ip@npm:^1.1.5": - version: 1.1.8 - resolution: "ip@npm:1.1.8" - checksum: a2ade53eb339fb0cbe9e69a44caab10d6e3784662285eb5d2677117ee4facc33a64679051c35e0dfdb1a3983a51ce2f5d2cb36446d52e10d01881789b76e28fb +"ip@npm:^2.0.0": + version: 2.0.0 + resolution: "ip@npm:2.0.0" + checksum: cfcfac6b873b701996d71ec82a7dd27ba92450afdb421e356f44044ed688df04567344c36cbacea7d01b1c39a4c732dc012570ebe9bebfb06f27314bca625349 languageName: node linkType: hard @@ -6239,6 +6386,17 @@ fsevents@~2.1.1: languageName: node linkType: hard +"is-array-buffer@npm:^3.0.1, is-array-buffer@npm:^3.0.2": + version: 3.0.2 + resolution: "is-array-buffer@npm:3.0.2" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.2.0 + is-typed-array: ^1.1.10 + checksum: dcac9dda66ff17df9cabdc58214172bf41082f956eab30bb0d86bc0fab1e44b690fc8e1f855cf2481245caf4e8a5a006a982a71ddccec84032ed41f9d8da8c14 + languageName: node + linkType: hard + "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -6281,19 +6439,19 @@ fsevents@~2.1.1: languageName: node linkType: hard -"is-callable@npm:^1.1.4, is-callable@npm:^1.2.4": - version: 1.2.4 - resolution: "is-callable@npm:1.2.4" - checksum: 1a28d57dc435797dae04b173b65d6d1e77d4f16276e9eff973f994eadcfdc30a017e6a597f092752a083c1103cceb56c91e3dadc6692fedb9898dfaba701575f +"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 61fd57d03b0d984e2ed3720fb1c7a897827ea174bd44402878e059542ea8c4aeedee0ea0985998aa5cc2736b2fa6e271c08587addb5b3959ac52cf665173d1ac languageName: node linkType: hard -"is-core-module@npm:^2.8.1, is-core-module@npm:^2.9.0": - version: 2.9.0 - resolution: "is-core-module@npm:2.9.0" +"is-core-module@npm:^2.11.0, is-core-module@npm:^2.12.0, is-core-module@npm:^2.8.0": + version: 2.12.1 + resolution: "is-core-module@npm:2.12.1" dependencies: has: ^1.0.3 - checksum: b27034318b4b462f1c8f1dfb1b32baecd651d891a4e2d1922135daeff4141dfced2b82b07aef83ef54275c4a3526aa38da859223664d0868ca24182badb784ce + checksum: f04ea30533b5e62764e7b2e049d3157dc0abd95ef44275b32489ea2081176ac9746ffb1cdb107445cf1ff0e0dfcad522726ca27c27ece64dadf3795428b8e468 languageName: node linkType: hard @@ -6428,8 +6586,17 @@ fsevents@~2.1.1: version: 1.0.4 resolution: "is-symbol@npm:1.0.4" dependencies: - has-symbols: ^1.0.2 - checksum: 92805812ef590738d9de49d677cd17dfd486794773fb6fa0032d16452af46e9b91bb43ffe82c983570f015b37136f4b53b28b8523bfb10b0ece7a66c31a54510 + has-symbols: ^1.0.2 + checksum: 92805812ef590738d9de49d677cd17dfd486794773fb6fa0032d16452af46e9b91bb43ffe82c983570f015b37136f4b53b28b8523bfb10b0ece7a66c31a54510 + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.9": + version: 1.1.12 + resolution: "is-typed-array@npm:1.1.12" + dependencies: + which-typed-array: ^1.1.11 + checksum: 4c89c4a3be07186caddadf92197b17fda663a9d259ea0d44a85f171558270d36059d1c386d34a12cba22dfade5aba497ce22778e866adc9406098c8fc4771796 languageName: node linkType: hard @@ -6472,6 +6639,13 @@ fsevents@~2.1.1: languageName: node linkType: hard +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: bd5bbe4104438c4196ba58a54650116007fa0262eccef13a4c55b2e09a5b36b59f1e75b9fcc49883dd9d4953892e6fc007eef9e9155648ceea036e184b0f930a + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -6514,27 +6688,40 @@ fsevents@~2.1.1: linkType: hard "istanbul-lib-instrument@npm:^5.0.4": - version: 5.2.0 - resolution: "istanbul-lib-instrument@npm:5.2.0" + version: 5.2.1 + resolution: "istanbul-lib-instrument@npm:5.2.1" dependencies: "@babel/core": ^7.12.3 "@babel/parser": ^7.14.7 "@istanbuljs/schema": ^0.1.2 istanbul-lib-coverage: ^3.2.0 semver: ^6.3.0 - checksum: 7c242ed782b6bf7b655656576afae8b6bd23dcc020e5fdc1472cca3dfb6ddb196a478385206d0df5219b9babf46ac4f21fea5d8ea9a431848b6cca6007012353 + checksum: bf16f1803ba5e51b28bbd49ed955a736488381e09375d830e42ddeb403855b2006f850711d95ad726f2ba3f1ae8e7366de7e51d2b9ac67dc4d80191ef7ddf272 languageName: node linkType: hard -"jest-diff@npm:^28.1.1": - version: 28.1.1 - resolution: "jest-diff@npm:28.1.1" +"jackspeak@npm:^2.0.3": + version: 2.2.2 + resolution: "jackspeak@npm:2.2.2" + dependencies: + "@isaacs/cliui": ^8.0.2 + "@pkgjs/parseargs": ^0.11.0 + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 7b1468dd910afc00642db87448f24b062346570b8b47531409aa9012bcb95fdf7ec2b1c48edbb8b57a938c08391f8cc01b5034fc335aa3a2e74dbcc0ee5c555a + languageName: node + linkType: hard + +"jest-diff@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-diff@npm:28.1.3" dependencies: chalk: ^4.0.0 diff-sequences: ^28.1.1 jest-get-type: ^28.0.2 - pretty-format: ^28.1.1 - checksum: d9e0355880bee8728f7615ac0f03c66dcd4e93113935cca056a5f5a2f20ac2c7812aca6ad68e79bd1b11f2428748bd9123e6b1c7e51c93b4da3dfa5a875339f7 + pretty-format: ^28.1.3 + checksum: fa8583e0ccbe775714ce850b009be1b0f6b17a4b6759f33ff47adef27942ebc610dbbcc8a5f7cfb7f12b3b3b05afc9fb41d5f766674616025032ff1e4f9866e0 languageName: node linkType: hard @@ -6545,11 +6732,11 @@ fsevents@~2.1.1: languageName: node linkType: hard -"jest-haste-map@npm:^28.1.1": - version: 28.1.1 - resolution: "jest-haste-map@npm:28.1.1" +"jest-haste-map@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-haste-map@npm:28.1.3" dependencies: - "@jest/types": ^28.1.1 + "@jest/types": ^28.1.3 "@types/graceful-fs": ^4.1.3 "@types/node": "*" anymatch: ^3.0.3 @@ -6557,43 +6744,43 @@ fsevents@~2.1.1: fsevents: ^2.3.2 graceful-fs: ^4.2.9 jest-regex-util: ^28.0.2 - jest-util: ^28.1.1 - jest-worker: ^28.1.1 + jest-util: ^28.1.3 + jest-worker: ^28.1.3 micromatch: ^4.0.4 walker: ^1.0.8 dependenciesMeta: fsevents: optional: true - checksum: db31a2a83906277d96b79017742c433c1573b322d061632a011fb1e184cf6f151f94134da09da7366e4477e8716f280efa676b4cc04a8544c13ce466a44102e8 + checksum: d05fdc108645fc2b39fcd4001952cc7a8cb550e93494e98c1e9ab1fc542686f6ac67177c132e564cf94fe8f81503f3f8db8b825b9b713dc8c5748aec63ba4688 languageName: node linkType: hard -"jest-matcher-utils@npm:^28.1.1": - version: 28.1.1 - resolution: "jest-matcher-utils@npm:28.1.1" +"jest-matcher-utils@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-matcher-utils@npm:28.1.3" dependencies: chalk: ^4.0.0 - jest-diff: ^28.1.1 + jest-diff: ^28.1.3 jest-get-type: ^28.0.2 - pretty-format: ^28.1.1 - checksum: cb73ccd347638cd761ef7e0b606fbd71c115bd8febe29413f7b105fff6855d4356b8094c6b72393c5457db253b9c163498f188f25f9b6308c39c510e4c2886ee + pretty-format: ^28.1.3 + checksum: 6b34f0cf66f6781e92e3bec97bf27796bd2ba31121e5c5997218d9adba6deea38a30df5203937d6785b68023ed95cbad73663cc9aad6fb0cb59aeb5813a58daf languageName: node linkType: hard -"jest-message-util@npm:^28.1.1": - version: 28.1.1 - resolution: "jest-message-util@npm:28.1.1" +"jest-message-util@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-message-util@npm:28.1.3" dependencies: "@babel/code-frame": ^7.12.13 - "@jest/types": ^28.1.1 + "@jest/types": ^28.1.3 "@types/stack-utils": ^2.0.0 chalk: ^4.0.0 graceful-fs: ^4.2.9 micromatch: ^4.0.4 - pretty-format: ^28.1.1 + pretty-format: ^28.1.3 slash: ^3.0.0 stack-utils: ^2.0.3 - checksum: cca23b9a0103c8fb7006a6d21e67a204fcac4289e1a3961450a4a1ad62eb37087c2a19a26337d3c0ea9f82c030a80dda79ac8ec34a18bf3fd5eca3fd55bef957 + checksum: 1f266854166dcc6900d75a88b54a25225a2f3710d463063ff1c99021569045c35c7d58557b25447a17eb3a65ce763b2f9b25550248b468a9d4657db365f39e96 languageName: node linkType: hard @@ -6605,58 +6792,58 @@ fsevents@~2.1.1: linkType: hard "jest-snapshot@npm:^28.1.1": - version: 28.1.2 - resolution: "jest-snapshot@npm:28.1.2" + version: 28.1.3 + resolution: "jest-snapshot@npm:28.1.3" dependencies: "@babel/core": ^7.11.6 "@babel/generator": ^7.7.2 "@babel/plugin-syntax-typescript": ^7.7.2 "@babel/traverse": ^7.7.2 "@babel/types": ^7.3.3 - "@jest/expect-utils": ^28.1.1 - "@jest/transform": ^28.1.2 - "@jest/types": ^28.1.1 + "@jest/expect-utils": ^28.1.3 + "@jest/transform": ^28.1.3 + "@jest/types": ^28.1.3 "@types/babel__traverse": ^7.0.6 "@types/prettier": ^2.1.5 babel-preset-current-node-syntax: ^1.0.0 chalk: ^4.0.0 - expect: ^28.1.1 + expect: ^28.1.3 graceful-fs: ^4.2.9 - jest-diff: ^28.1.1 + jest-diff: ^28.1.3 jest-get-type: ^28.0.2 - jest-haste-map: ^28.1.1 - jest-matcher-utils: ^28.1.1 - jest-message-util: ^28.1.1 - jest-util: ^28.1.1 + jest-haste-map: ^28.1.3 + jest-matcher-utils: ^28.1.3 + jest-message-util: ^28.1.3 + jest-util: ^28.1.3 natural-compare: ^1.4.0 - pretty-format: ^28.1.1 + pretty-format: ^28.1.3 semver: ^7.3.5 - checksum: 5c33c8b05d387d4fa4516556dc6fdeca4d7c0a1d48bfb31d05d5bf182988713800a35b0f7d4d9e40e3646edbde095aba36bb1b64a8d9bac40e34f76e90ddb482 + checksum: 2a46a5493f1fb50b0a236a21f25045e7f46a244f9f3ae37ef4fbcd40249d0d68bb20c950ce77439e4e2cac985b05c3061c90b34739bf6069913a1199c8c716e1 languageName: node linkType: hard -"jest-util@npm:^28.1.1": - version: 28.1.1 - resolution: "jest-util@npm:28.1.1" +"jest-util@npm:^28.1.1, jest-util@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-util@npm:28.1.3" dependencies: - "@jest/types": ^28.1.1 + "@jest/types": ^28.1.3 "@types/node": "*" chalk: ^4.0.0 ci-info: ^3.2.0 graceful-fs: ^4.2.9 picomatch: ^2.2.3 - checksum: bca1601099d6a4c3c4ba997b8c035a698f23b9b04a0a284a427113f7d0399f7402ba9f4d73812328e6777bf952bf93dfe3d3edda6380a6ca27cdc02768d601e0 + checksum: fd6459742c941f070223f25e38a2ac0719aad92561591e9fb2a50d602a5d19d754750b79b4074327a42b00055662b95da3b006542ceb8b54309da44d4a62e721 languageName: node linkType: hard -"jest-worker@npm:^28.1.1": - version: 28.1.1 - resolution: "jest-worker@npm:28.1.1" +"jest-worker@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-worker@npm:28.1.3" dependencies: "@types/node": "*" merge-stream: ^2.0.0 supports-color: ^8.0.0 - checksum: 28519c43b4007e60a3756d27f1e7884192ee9161b6a9587383a64b6535f820cc4868e351a67775e0feada41465f48ccf323a8db34ae87e15a512ddac5d1424b2 + checksum: e921c9a1b8f0909da9ea07dbf3592f95b653aef3a8bb0cbcd20fc7f9a795a1304adecac31eecb308992c167e8d7e75c522061fec38a5928ace0f9571c90169ca languageName: node linkType: hard @@ -6668,9 +6855,9 @@ fsevents@~2.1.1: linkType: hard "js-sdsl@npm:^4.1.4": - version: 4.4.0 - resolution: "js-sdsl@npm:4.4.0" - checksum: 7bb08a2d746ab7ff742720339aa006c631afe05e77d11eda988c1c35fae8e03e492e4e347e883e786e3ce6170685d4780c125619111f0730c11fdb41b04059c7 + version: 4.4.2 + resolution: "js-sdsl@npm:4.4.2" + checksum: ba705adc1788bf3c6f6c8e5077824f2bb4f0acab5a984420ce5cc492c7fff3daddc26335ad2c9a67d4f5e3241ec790f9e5b72a625adcf20cf321d2fd85e62b8b languageName: node linkType: hard @@ -6788,23 +6975,23 @@ fsevents@~2.1.1: languageName: node linkType: hard -"json5@npm:^1.0.1": - version: 1.0.1 - resolution: "json5@npm:1.0.1" +"json5@npm:^1.0.2": + version: 1.0.2 + resolution: "json5@npm:1.0.2" dependencies: minimist: ^1.2.0 bin: json5: lib/cli.js - checksum: e76ea23dbb8fc1348c143da628134a98adf4c5a4e8ea2adaa74a80c455fc2cdf0e2e13e6398ef819bfe92306b610ebb2002668ed9fc1af386d593691ef346fc3 + checksum: 866458a8c58a95a49bef3adba929c625e82532bcff1fe93f01d29cb02cac7c3fe1f4b79951b7792c2da9de0b32871a8401a6e3c5b36778ad852bf5b8a61165d7 languageName: node linkType: hard -"json5@npm:^2.2.1": - version: 2.2.1 - resolution: "json5@npm:2.2.1" +"json5@npm:^2.2.2": + version: 2.2.3 + resolution: "json5@npm:2.2.3" bin: json5: lib/cli.js - checksum: 74b8a23b102a6f2bf2d224797ae553a75488b5adbaee9c9b6e5ab8b510a2fc6e38f876d4c77dea672d4014a44b2399e15f2051ac2b37b87f74c0c7602003543b + checksum: 2a7436a93393830bce797d4626275152e37e877b265e94ca69c99e3d20c2b9dab021279146a39cdb700e71b2dd32a4cebd1514cd57cee102b1af906ce5040349 languageName: node linkType: hard @@ -6865,14 +7052,14 @@ fsevents@~2.1.1: linkType: hard "keccak@npm:^3.0.0, keccak@npm:^3.0.2": - version: 3.0.2 - resolution: "keccak@npm:3.0.2" + version: 3.0.3 + resolution: "keccak@npm:3.0.3" dependencies: node-addon-api: ^2.0.0 node-gyp: latest node-gyp-build: ^4.2.0 readable-stream: ^3.6.0 - checksum: 39a7d6128b8ee4cb7dcd186fc7e20c6087cc39f573a0f81b147c323f688f1f7c2b34f62c4ae189fe9b81c6730b2d1228d8a399cdc1f3d8a4c8f030cdc4f20272 + checksum: f08f04f5cc87013a3fc9e87262f761daff38945c86dd09c01a7f7930a15ae3e14f93b310ef821dcc83675a7b814eb1c983222399a2f263ad980251201d1b9a99 languageName: node linkType: hard @@ -7035,11 +7222,11 @@ fsevents@~2.1.1: linkType: hard "loupe@npm:^2.3.1": - version: 2.3.4 - resolution: "loupe@npm:2.3.4" + version: 2.3.6 + resolution: "loupe@npm:2.3.6" dependencies: get-func-name: ^2.0.0 - checksum: 5af91db61aa18530f1749a64735ee194ac263e65e9f4d1562bf3036c591f1baa948289c193e0e34c7b5e2c1b75d3c1dc4fce87f5edb3cee10b0c0df46bc9ffb3 + checksum: cc83f1b124a1df7384601d72d8d1f5fe95fd7a8185469fec48bb2e4027e45243949e7a013e8d91051a138451ff0552310c32aa9786e60b6a30d1e801bdc2163f languageName: node linkType: hard @@ -7062,9 +7249,16 @@ fsevents@~2.1.1: linkType: hard "lru-cache@npm:^7.7.1": - version: 7.12.0 - resolution: "lru-cache@npm:7.12.0" - checksum: fdb62262978393df7a4bd46a072bc5c3808c50ca5a347a82bb9459410efd841b7bae50655c3cf9004c70d12c756cf6d018f6bff155a16cdde9eba9a82899b5eb + version: 7.18.3 + resolution: "lru-cache@npm:7.18.3" + checksum: e550d772384709deea3f141af34b6d4fa392e2e418c1498c078de0ee63670f1f46f5eee746e8ef7e69e1c895af0d4224e62ee33e66a543a14763b0f2e74c1356 + languageName: node + linkType: hard + +"lru-cache@npm:^9.1.1 || ^10.0.0": + version: 10.0.0 + resolution: "lru-cache@npm:10.0.0" + checksum: 18f101675fe283bc09cda0ef1e3cc83781aeb8373b439f086f758d1d91b28730950db785999cd060d3c825a8571c03073e8c14512b6655af2188d623031baf50 languageName: node linkType: hard @@ -7082,27 +7276,26 @@ fsevents@~2.1.1: languageName: node linkType: hard -"make-fetch-happen@npm:^10.0.3": - version: 10.1.8 - resolution: "make-fetch-happen@npm:10.1.8" +"make-fetch-happen@npm:^11.0.3": + version: 11.1.1 + resolution: "make-fetch-happen@npm:11.1.1" dependencies: agentkeepalive: ^4.2.1 - cacache: ^16.1.0 - http-cache-semantics: ^4.1.0 + cacache: ^17.0.0 + http-cache-semantics: ^4.1.1 http-proxy-agent: ^5.0.0 https-proxy-agent: ^5.0.0 is-lambda: ^1.0.1 lru-cache: ^7.7.1 - minipass: ^3.1.6 - minipass-collect: ^1.0.2 - minipass-fetch: ^2.0.3 + minipass: ^5.0.0 + minipass-fetch: ^3.0.0 minipass-flush: ^1.0.5 minipass-pipeline: ^1.2.4 negotiator: ^0.6.3 promise-retry: ^2.0.1 socks-proxy-agent: ^7.0.0 - ssri: ^9.0.0 - checksum: 5fe9fd9da5368a8a4fe9a3ea5b9aa15f1e91c9ab703cd9027a6b33840ecc8a57c182fbe1c767c139330a88c46a448b1f00da5e32065cec373aff2450b3da54ee + ssri: ^10.0.0 + checksum: 7268bf274a0f6dcf0343829489a4506603ff34bd0649c12058753900b0eb29191dce5dba12680719a5d0a983d3e57810f594a12f3c18494e93a1fbc6348a4540 languageName: node linkType: hard @@ -7256,7 +7449,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"minimatch@npm:2 || 3, minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:2 || 3, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -7283,19 +7476,19 @@ fsevents@~2.1.1: languageName: node linkType: hard -"minimatch@npm:^5.0.1": - version: 5.1.0 - resolution: "minimatch@npm:5.1.0" +"minimatch@npm:^9.0.1": + version: 9.0.3 + resolution: "minimatch@npm:9.0.3" dependencies: brace-expansion: ^2.0.1 - checksum: 15ce53d31a06361e8b7a629501b5c75491bc2b59712d53e802b1987121d91b433d73fcc5be92974fde66b2b51d8fb28d75a9ae900d249feb792bb1ba2a4f0a90 + checksum: 253487976bf485b612f16bf57463520a14f512662e592e95c571afdab1442a6a6864b6c88f248ce6fc4ff0b6de04ac7aa6c8bb51e868e99d1d65eb0658a708b5 languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6": - version: 1.2.6 - resolution: "minimist@npm:1.2.6" - checksum: d15428cd1e11eb14e1233bcfb88ae07ed7a147de251441d61158619dfb32c4d7e9061d09cab4825fdee18ecd6fce323228c8c47b5ba7cd20af378ca4048fb3fb +"minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.7": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0 languageName: node linkType: hard @@ -7308,18 +7501,18 @@ fsevents@~2.1.1: languageName: node linkType: hard -"minipass-fetch@npm:^2.0.3": - version: 2.1.0 - resolution: "minipass-fetch@npm:2.1.0" +"minipass-fetch@npm:^3.0.0": + version: 3.0.3 + resolution: "minipass-fetch@npm:3.0.3" dependencies: encoding: ^0.1.13 - minipass: ^3.1.6 + minipass: ^5.0.0 minipass-sized: ^1.0.3 minizlib: ^2.1.2 dependenciesMeta: encoding: optional: true - checksum: 1334732859a3f7959ed22589bafd9c40384b885aebb5932328071c33f86b3eb181d54c86919675d1825ab5f1c8e4f328878c863873258d113c29d79a4b0c9c9f + checksum: af5ab2552a16fcf505d35fd7ffb84b57f4a0eeb269e6e1d9a2a75824dda48b36e527083250b7cca4a4def21d9544e2ade441e4730e233c0bc2133f6abda31e18 languageName: node linkType: hard @@ -7350,12 +7543,26 @@ fsevents@~2.1.1: languageName: node linkType: hard -"minipass@npm:^3.0.0, minipass@npm:^3.1.1, minipass@npm:^3.1.6": - version: 3.3.4 - resolution: "minipass@npm:3.3.4" +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" dependencies: yallist: ^4.0.0 - checksum: 5d95a7738c54852ba78d484141e850c792e062666a2d0c681a5ac1021275beb7e1acb077e59f9523ff1defb80901aea4e30fac10ded9a20a25d819a42916ef1b + checksum: a30d083c8054cee83cdcdc97f97e4641a3f58ae743970457b1489ce38ee1167b3aaf7d815cd39ec7a99b9c40397fd4f686e83750e73e652b21cb516f6d845e48 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 425dab288738853fded43da3314a0b5c035844d6f3097a8e3b5b29b328da8f3c1af6fc70618b32c29ff906284cf6406b6841376f21caaadd0793c1d5a6a620ea + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0": + version: 7.0.2 + resolution: "minipass@npm:7.0.2" + checksum: 46776de732eb7cef2c7404a15fb28c41f5c54a22be50d47b03c605bf21f5c18d61a173c0a20b49a97e7a65f78d887245066410642551e45fffe04e9ac9e325bc languageName: node linkType: hard @@ -7462,8 +7669,8 @@ fsevents@~2.1.1: linkType: hard "mocha@npm:^10.0.0": - version: 10.1.0 - resolution: "mocha@npm:10.1.0" + version: 10.2.0 + resolution: "mocha@npm:10.2.0" dependencies: ansi-colors: 4.1.1 browser-stdout: 1.3.1 @@ -7489,7 +7696,7 @@ fsevents@~2.1.1: bin: _mocha: bin/_mocha mocha: bin/mocha.js - checksum: c64c7305769e09ae5559c1cd31eae8b4c7c0e19e328cf54d1374e5555a0f01e3d5dced99882911d927e0a9d0c613d0644a1750b848a2848fb7dcf4684f97f65f + checksum: 406c45eab122ffd6ea2003c2f108b2bc35ba036225eee78e0c784b6fa2c7f34e2b13f1dbacef55a4fdf523255d76e4f22d1b5aacda2394bd11666febec17c719 languageName: node linkType: hard @@ -7590,10 +7797,10 @@ fsevents@~2.1.1: languageName: node linkType: hard -"napi-macros@npm:~2.0.0": - version: 2.0.0 - resolution: "napi-macros@npm:2.0.0" - checksum: 30384819386977c1f82034757014163fa60ab3c5a538094f778d38788bebb52534966279956f796a92ea771c7f8ae072b975df65de910d051ffbdc927f62320c +"napi-macros@npm:^2.2.2": + version: 2.2.2 + resolution: "napi-macros@npm:2.2.2" + checksum: c6f9bd71cdbbc37ddc3535aa5be481238641d89585b8a3f4d301cb89abf459e2d294810432bb7d12056d1f9350b1a0899a5afcf460237a3da6c398cf0fec7629 languageName: node linkType: hard @@ -7653,21 +7860,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"node-fetch@npm:2.6.7": - version: 2.6.7 - resolution: "node-fetch@npm:2.6.7" - dependencies: - whatwg-url: ^5.0.0 - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - checksum: 8d816ffd1ee22cab8301c7756ef04f3437f18dace86a1dae22cf81db8ef29c0bf6655f3215cb0cdb22b420b6fe141e64b26905e7f33f9377a7fa59135ea3e10b - languageName: node - linkType: hard - -"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1": +"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12": version: 2.6.12 resolution: "node-fetch@npm:2.6.12" dependencies: @@ -7682,25 +7875,26 @@ fsevents@~2.1.1: linkType: hard "node-gyp-build@npm:^4.2.0, node-gyp-build@npm:^4.3.0": - version: 4.5.0 - resolution: "node-gyp-build@npm:4.5.0" + version: 4.6.0 + resolution: "node-gyp-build@npm:4.6.0" bin: node-gyp-build: bin.js node-gyp-build-optional: optional.js node-gyp-build-test: build-test.js - checksum: d888bae0fb88335f69af1b57a2294a931c5042f36e413d8d364c992c9ebfa0b96ffe773179a5a2c8f04b73856e8634e09cce108dbb9804396d3cc8c5455ff2db + checksum: 25d78c5ef1f8c24291f4a370c47ba52fcea14f39272041a90a7894cd50d766f7c8cb8fb06c0f42bf6f69b204b49d9be3c8fc344aac09714d5bdb95965499eb15 languageName: node linkType: hard "node-gyp@npm:latest": - version: 9.0.0 - resolution: "node-gyp@npm:9.0.0" + version: 9.4.0 + resolution: "node-gyp@npm:9.4.0" dependencies: env-paths: ^2.2.0 + exponential-backoff: ^3.1.1 glob: ^7.1.4 graceful-fs: ^4.2.6 - make-fetch-happen: ^10.0.3 - nopt: ^5.0.0 + make-fetch-happen: ^11.0.3 + nopt: ^6.0.0 npmlog: ^6.0.0 rimraf: ^3.0.2 semver: ^7.3.5 @@ -7708,7 +7902,7 @@ fsevents@~2.1.1: which: ^2.0.2 bin: node-gyp: bin/node-gyp.js - checksum: 4d8ef8860f7e4f4d86c91db3f519d26ed5cc23b48fe54543e2afd86162b4acbd14f21de42a5db344525efb69a991e021b96a68c70c6e2d5f4a5cb770793da6d3 + checksum: 78b404e2e0639d64e145845f7f5a3cb20c0520cdaf6dda2f6e025e9b644077202ea7de1232396ba5bde3fee84cdc79604feebe6ba3ec84d464c85d407bb5da99 languageName: node linkType: hard @@ -7719,17 +7913,10 @@ fsevents@~2.1.1: languageName: node linkType: hard -"node-releases@npm:^2.0.5": - version: 2.0.5 - resolution: "node-releases@npm:2.0.5" - checksum: e85d949addd19f8827f32569d2be5751e7812ccf6cc47879d49f79b5234ff4982225e39a3929315f96370823b070640fb04d79fc0ddec8b515a969a03493a42f - languageName: node - linkType: hard - -"nofilter@npm:^1.0.4": - version: 1.0.4 - resolution: "nofilter@npm:1.0.4" - checksum: 54d864f745de5c3312994e880cf2d4f55e34830d6adc8275dce3731507ca380d21040336e4a277a4901551c07f04c452fbeffd57fad1dc8f68a2943eaf894a04 +"node-releases@npm:^2.0.12": + version: 2.0.13 + resolution: "node-releases@npm:2.0.13" + checksum: 17ec8f315dba62710cae71a8dad3cd0288ba943d2ece43504b3b1aa8625bf138637798ab470b1d9035b0545996f63000a8a926e0f6d35d0996424f8b6d36dda3 languageName: node linkType: hard @@ -7751,14 +7938,14 @@ fsevents@~2.1.1: languageName: node linkType: hard -"nopt@npm:^5.0.0": - version: 5.0.0 - resolution: "nopt@npm:5.0.0" +"nopt@npm:^6.0.0": + version: 6.0.0 + resolution: "nopt@npm:6.0.0" dependencies: - abbrev: 1 + abbrev: ^1.0.0 bin: nopt: bin/nopt.js - checksum: d35fdec187269503843924e0114c0c6533fb54bbf1620d0f28b4b60ba01712d6687f62565c55cc20a504eff0fbe5c63e22340c3fad549ad40469ffb611b04f2f + checksum: 82149371f8be0c4b9ec2f863cc6509a7fd0fa729929c009f3a58e4eb0c9e4cae9920e8f1f8eb46e7d032fec8fb01bede7f0f41a67eb3553b7b8e14fa53de1dac languageName: node linkType: hard @@ -7805,10 +7992,10 @@ fsevents@~2.1.1: languageName: node linkType: hard -"object-inspect@npm:^1.12.0, object-inspect@npm:^1.9.0": - version: 1.12.2 - resolution: "object-inspect@npm:1.12.2" - checksum: a534fc1b8534284ed71f25ce3a496013b7ea030f3d1b77118f6b7b1713829262be9e6243acbcb3ef8c626e2b64186112cb7f6db74e37b2789b9c789ca23048b2 +"object-inspect@npm:^1.12.3, object-inspect@npm:^1.9.0": + version: 1.12.3 + resolution: "object-inspect@npm:1.12.3" + checksum: dabfd824d97a5f407e6d5d24810d888859f6be394d8b733a77442b277e0808860555176719c5905e765e3743a7cada6b8b0a3b85e5331c530fd418cc8ae991db languageName: node linkType: hard @@ -7831,38 +8018,39 @@ fsevents@~2.1.1: languageName: node linkType: hard -"object.assign@npm:^4.1.2": - version: 4.1.2 - resolution: "object.assign@npm:4.1.2" +"object.assign@npm:^4.1.4": + version: 4.1.4 + resolution: "object.assign@npm:4.1.4" dependencies: - call-bind: ^1.0.0 - define-properties: ^1.1.3 - has-symbols: ^1.0.1 + call-bind: ^1.0.2 + define-properties: ^1.1.4 + has-symbols: ^1.0.3 object-keys: ^1.1.1 - checksum: d621d832ed7b16ac74027adb87196804a500d80d9aca536fccb7ba48d33a7e9306a75f94c1d29cbfa324bc091bfc530bc24789568efdaee6a47fcfa298993814 + checksum: 76cab513a5999acbfe0ff355f15a6a125e71805fcf53de4e9d4e082e1989bdb81d1e329291e1e4e0ae7719f0e4ef80e88fb2d367ae60500d79d25a6224ac8864 languageName: node linkType: hard "object.getownpropertydescriptors@npm:^2.0.3": - version: 2.1.4 - resolution: "object.getownpropertydescriptors@npm:2.1.4" + version: 2.1.6 + resolution: "object.getownpropertydescriptors@npm:2.1.6" dependencies: - array.prototype.reduce: ^1.0.4 + array.prototype.reduce: ^1.0.5 call-bind: ^1.0.2 - define-properties: ^1.1.4 - es-abstract: ^1.20.1 - checksum: 988c466fe49fc4f19a28d2d1d894c95c6abfe33c94674ec0b14d96eed71f453c7ad16873d430dc2acbb1760de6d3d2affac4b81237a306012cc4dc49f7539e7f + define-properties: ^1.2.0 + es-abstract: ^1.21.2 + safe-array-concat: ^1.0.0 + checksum: 7757ce0ef61c8bee7f8043f8980fd3d46fc1ab3faf0795bd1f9f836781143b4afc91f7219a3eed4675fbd0b562f3708f7e736d679ebfd43ea37ab6077d9f5004 languageName: node linkType: hard "object.values@npm:^1.1.5": - version: 1.1.5 - resolution: "object.values@npm:1.1.5" + version: 1.1.6 + resolution: "object.values@npm:1.1.6" dependencies: call-bind: ^1.0.2 - define-properties: ^1.1.3 - es-abstract: ^1.19.1 - checksum: 0f17e99741ebfbd0fa55ce942f6184743d3070c61bd39221afc929c8422c4907618c8da694c6915bc04a83ab3224260c779ba37fc07bb668bdc5f33b66a902a4 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: f6fff9fd817c24cfd8107f50fb33061d81cd11bacc4e3dbb3852e9ff7692fde4dbce823d4333ea27cd9637ef1b6690df5fbb61f1ed314fa2959598dc3ae23d8e languageName: node linkType: hard @@ -7926,16 +8114,16 @@ fsevents@~2.1.1: linkType: hard "optionator@npm:^0.9.1": - version: 0.9.1 - resolution: "optionator@npm:0.9.1" + version: 0.9.3 + resolution: "optionator@npm:0.9.3" dependencies: + "@aashutoshrathi/word-wrap": ^1.2.3 deep-is: ^0.1.3 fast-levenshtein: ^2.0.6 levn: ^0.4.1 prelude-ls: ^1.2.1 type-check: ^0.4.0 - word-wrap: ^1.2.3 - checksum: dbc6fa065604b24ea57d734261914e697bd73b69eff7f18e967e8912aa2a40a19a9f599a507fa805be6c13c24c4eae8c71306c239d517d42d4c041c942f508a0 + checksum: 09281999441f2fe9c33a5eeab76700795365a061563d66b098923eb719251a42bdbe432790d35064d0816ead9296dbeb1ad51a733edf4167c96bd5d0882e428a languageName: node linkType: hard @@ -8121,6 +8309,16 @@ fsevents@~2.1.1: languageName: node linkType: hard +"path-scurry@npm:^1.10.1": + version: 1.10.1 + resolution: "path-scurry@npm:1.10.1" + dependencies: + lru-cache: ^9.1.1 || ^10.0.0 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + checksum: e2557cff3a8fb8bc07afdd6ab163a92587884f9969b05bbbaf6fe7379348bfb09af9ed292af12ed32398b15fb443e81692047b786d1eeb6d898a51eb17ed7d90 + languageName: node + linkType: hard + "path-to-regexp@npm:0.1.7": version: 0.1.7 resolution: "path-to-regexp@npm:0.1.7" @@ -8184,22 +8382,9 @@ fsevents@~2.1.1: linkType: hard "pirates@npm:^4.0.4": - version: 4.0.5 - resolution: "pirates@npm:4.0.5" - checksum: c9994e61b85260bec6c4fc0307016340d9b0c4f4b6550a957afaaff0c9b1ad58fbbea5cfcf083860a25cb27a375442e2b0edf52e2e1e40e69934e08dcc52d227 - languageName: node - linkType: hard - -"platform-deploy-client@npm:^0.3.2": - version: 0.3.3 - resolution: "platform-deploy-client@npm:0.3.3" - dependencies: - "@ethersproject/abi": ^5.6.3 - axios: ^0.21.2 - defender-base-client: ^1.40.0 - lodash: ^4.17.19 - node-fetch: ^2.6.0 - checksum: 2e3385497e009e74633eac9755d492d1372ed4cf54141949959812c6c3c793bab2dbdb494524c520d3886d201107c4227f0aa10300fa5db2914e3ea1425b3121 + version: 4.0.6 + resolution: "pirates@npm:4.0.6" + checksum: 46a65fefaf19c6f57460388a5af9ab81e3d7fd0e7bc44ca59d753cb5c4d0df97c6c6e583674869762101836d68675f027d60f841c105d72734df9dfca97cbcc6 languageName: node linkType: hard @@ -8226,19 +8411,19 @@ fsevents@~2.1.1: languageName: node linkType: hard -"prettier-plugin-solidity@npm:^1.0.0-beta.13": - version: 1.0.0-dev.21 - resolution: "prettier-plugin-solidity@npm:1.0.0-dev.21" +"prettier-plugin-solidity@npm:1.0.0-beta.13": + version: 1.0.0-beta.13 + resolution: "prettier-plugin-solidity@npm:1.0.0-beta.13" dependencies: - "@solidity-parser/parser": ^0.14.1 - emoji-regex: ^10.0.0 + "@solidity-parser/parser": ^0.13.2 + emoji-regex: ^9.2.2 escape-string-regexp: ^4.0.0 semver: ^7.3.5 solidity-comments-extractor: ^0.0.7 - string-width: ^4.2.3 + string-width: ^4.2.2 peerDependencies: prettier: ^2.3.0 - checksum: 3c43bb7404c380091310e59be718ec0161d268e4674e5e658723f7a7bc9b0f541df9816a8e7bd93b9e73a289f1e13ea1eab58027d4ee51d25460ea6e63c0c99e + checksum: 253de4255f3e9f64b88dbc2a2d8de595090ff10eb9dca845b58632d9ce71d23f5e1954864d0e5542a34d103aa0f4d1a1a9267a7a7dea3e5dfbe41f1f7bf9cbcf languageName: node linkType: hard @@ -8261,23 +8446,23 @@ fsevents@~2.1.1: linkType: hard "prettier@npm:^2.1.2": - version: 2.7.1 - resolution: "prettier@npm:2.7.1" + version: 2.8.8 + resolution: "prettier@npm:2.8.8" bin: prettier: bin-prettier.js - checksum: 55a4409182260866ab31284d929b3cb961e5fdb91fe0d2e099dac92eaecec890f36e524b4c19e6ceae839c99c6d7195817579cdffc8e2c80da0cb794463a748b + checksum: b49e409431bf129dd89238d64299ba80717b57ff5a6d1c1a8b1a28b590d998a34e083fa13573bc732bb8d2305becb4c9a4407f8486c81fa7d55100eb08263cf8 languageName: node linkType: hard -"pretty-format@npm:^28.1.1": - version: 28.1.1 - resolution: "pretty-format@npm:28.1.1" +"pretty-format@npm:^28.1.3": + version: 28.1.3 + resolution: "pretty-format@npm:28.1.3" dependencies: - "@jest/schemas": ^28.0.2 + "@jest/schemas": ^28.1.3 ansi-regex: ^5.0.1 ansi-styles: ^5.0.0 react-is: ^18.0.0 - checksum: 7fde4e2d6fd57cef8cf2fa9d5560cc62126de481f09c65dccfe89a3e6158a04355cff278853ace07fdf7f2f48c3d77877c00c47d7d3c1c028dcff5c322300d79 + checksum: e69f857358a3e03d271252d7524bec758c35e44680287f36c1cb905187fbc82da9981a6eb07edfd8a03bc3cbeebfa6f5234c13a3d5b59f2bbdf9b4c4053e0a7f languageName: node linkType: hard @@ -8295,13 +8480,6 @@ fsevents@~2.1.1: languageName: node linkType: hard -"promise-inflight@npm:^1.0.1": - version: 1.0.1 - resolution: "promise-inflight@npm:1.0.1" - checksum: 22749483091d2c594261517f4f80e05226d4d5ecc1fc917e1886929da56e22b5718b7f2a75f3807e7a7d471bc3be2907fe92e6e8f373ddf5c64bae35b5af3981 - languageName: node - linkType: hard - "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -8313,11 +8491,11 @@ fsevents@~2.1.1: linkType: hard "promise@npm:^8.0.0": - version: 8.1.0 - resolution: "promise@npm:8.1.0" + version: 8.3.0 + resolution: "promise@npm:8.3.0" dependencies: asap: ~2.0.6 - checksum: 89b71a56154ed7d66a73236d8e8351a9c59adddba3929ecc845f75421ff37fc08ea0c67ad76cd5c0b0d81812c7d07a32bed27e7df5fcc960c6d68b0c1cd771f7 + checksum: a69f0ddbddf78ffc529cffee7ad950d307347615970564b17988ce43fbe767af5c738a9439660b24a9a8cbea106c0dcbb6c2b20e23b7e96a8e89e5c2679e94d5 languageName: node linkType: hard @@ -8352,6 +8530,13 @@ fsevents@~2.1.1: languageName: node linkType: hard +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4 + languageName: node + linkType: hard + "psl@npm:^1.1.28": version: 1.9.0 resolution: "psl@npm:1.9.0" @@ -8360,16 +8545,16 @@ fsevents@~2.1.1: linkType: hard "punycode@npm:^2.1.0, punycode@npm:^2.1.1": - version: 2.1.1 - resolution: "punycode@npm:2.1.1" - checksum: 823bf443c6dd14f669984dea25757b37993f67e8d94698996064035edd43bed8a5a17a9f12e439c2b35df1078c6bec05a6c86e336209eb1061e8025c481168e8 + version: 2.3.0 + resolution: "punycode@npm:2.3.0" + checksum: 39f760e09a2a3bbfe8f5287cf733ecdad69d6af2fe6f97ca95f24b8921858b91e9ea3c9eeec6e08cede96181b3bb33f95c6ffd8c77e63986508aa2e8159fa200 languageName: node linkType: hard "pure-rand@npm:^5.0.1": - version: 5.0.1 - resolution: "pure-rand@npm:5.0.1" - checksum: 2b05a6d80163308583a013fab8d7f7f2958a6f77895680c99d8c3ea1f3e49ac273716a59cb1777cfc370540df53e6dc017e46c70a869da81fe490b2e6703d77d + version: 5.0.5 + resolution: "pure-rand@npm:5.0.5" + checksum: 824b906f7f66695c15ed9a898ff650e925723515e999de0360b0726ebad924ce41a74cc2ac60409dc6c55f5781008855f32ecd0fe0a1f40fbce293d48bd11dd1 languageName: node linkType: hard @@ -8382,7 +8567,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"qs@npm:^6.4.0, qs@npm:^6.7.0, qs@npm:^6.9.4": +"qs@npm:^6.4.0, qs@npm:^6.9.4": version: 6.11.2 resolution: "qs@npm:6.11.2" dependencies: @@ -8421,7 +8606,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"raw-body@npm:2.5.1, raw-body@npm:^2.4.1": +"raw-body@npm:2.5.1": version: 2.5.1 resolution: "raw-body@npm:2.5.1" dependencies: @@ -8433,6 +8618,18 @@ fsevents@~2.1.1: languageName: node linkType: hard +"raw-body@npm:^2.4.1": + version: 2.5.2 + resolution: "raw-body@npm:2.5.2" + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + checksum: ba1583c8d8a48e8fbb7a873fdbb2df66ea4ff83775421bfe21ee120140949ab048200668c47d9ae3880012f6e217052690628cf679ddfbd82c9fc9358d574676 + languageName: node + linkType: hard + "react-is@npm:^18.0.0": version: 18.2.0 resolution: "react-is@npm:18.2.0" @@ -8441,8 +8638,8 @@ fsevents@~2.1.1: linkType: hard "readable-stream@npm:^2.2.2": - version: 2.3.7 - resolution: "readable-stream@npm:2.3.7" + version: 2.3.8 + resolution: "readable-stream@npm:2.3.8" dependencies: core-util-is: ~1.0.0 inherits: ~2.0.3 @@ -8451,18 +8648,18 @@ fsevents@~2.1.1: safe-buffer: ~5.1.1 string_decoder: ~1.1.1 util-deprecate: ~1.0.1 - checksum: e4920cf7549a60f8aaf694d483a0e61b2a878b969d224f89b3bc788b8d920075132c4b55a7494ee944c7b6a9a0eada28a7f6220d80b0312ece70bbf08eeca755 + checksum: 65645467038704f0c8aaf026a72fbb588a9e2ef7a75cd57a01702ee9db1c4a1e4b03aaad36861a6a0926546a74d174149c8c207527963e0c2d3eee2f37678a42 languageName: node linkType: hard "readable-stream@npm:^3.6.0": - version: 3.6.0 - resolution: "readable-stream@npm:3.6.0" + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" dependencies: inherits: ^2.0.3 string_decoder: ^1.1.1 util-deprecate: ^1.0.1 - checksum: d4ea81502d3799439bb955a3a5d1d808592cf3133350ed352aeaa499647858b27b1c4013984900238b0873ec8d0d8defce72469fb7a83e61d53f5ad61cb80dc8 + checksum: bdcbe6c22e846b6af075e32cf8f4751c2576238c5043169a1c221c92ee2878458a816a4ea33f4c67623c0b6827c8a400409bfb3cf0bf3381392d0b1dfb52ac8d languageName: node linkType: hard @@ -8494,22 +8691,22 @@ fsevents@~2.1.1: linkType: hard "recursive-readdir@npm:^2.2.2": - version: 2.2.2 - resolution: "recursive-readdir@npm:2.2.2" + version: 2.2.3 + resolution: "recursive-readdir@npm:2.2.3" dependencies: - minimatch: 3.0.4 - checksum: a6b22994d76458443d4a27f5fd7147ac63ad31bba972666a291d511d4d819ee40ff71ba7524c14f6a565b8cfaf7f48b318f971804b913cf538d58f04e25d1fee + minimatch: ^3.0.5 + checksum: 88ec96e276237290607edc0872b4f9842837b95cfde0cdbb1e00ba9623dfdf3514d44cdd14496ab60a0c2dd180a6ef8a3f1c34599e6cf2273afac9b72a6fb2b5 languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.4.3": - version: 1.4.3 - resolution: "regexp.prototype.flags@npm:1.4.3" +"regexp.prototype.flags@npm:^1.5.0": + version: 1.5.0 + resolution: "regexp.prototype.flags@npm:1.5.0" dependencies: call-bind: ^1.0.2 - define-properties: ^1.1.3 - functions-have-names: ^1.2.2 - checksum: 51228bae732592adb3ededd5e15426be25f289e9c4ef15212f4da73f4ec3919b6140806374b8894036a86020d054a8d2657d3fee6bb9b4d35d8939c20030b7a6 + define-properties: ^1.2.0 + functions-have-names: ^1.2.3 + checksum: c541687cdbdfff1b9a07f6e44879f82c66bbf07665f9a7544c5fd16acdb3ec8d1436caab01662d2fbcad403f3499d49ab0b77fbc7ef29ef961d98cc4bc9755b4 languageName: node linkType: hard @@ -8641,21 +8838,21 @@ fsevents@~2.1.1: "@types/lodash": ^4.14.177 "@types/mocha": ^9.0.0 "@types/node": ^12.20.37 - "@typescript-eslint/eslint-plugin": ^5.17.0 - "@typescript-eslint/parser": ^5.17.0 + "@typescript-eslint/eslint-plugin": 5.17.0 + "@typescript-eslint/parser": 5.17.0 axios: ^0.24.0 bignumber.js: ^9.1.1 caip: ^1.1.0 chai: ^4.3.4 decimal.js: ^10.4.3 dotenv: ^16.0.0 - eslint: ^8.14.0 - eslint-config-prettier: ^8.5.0 - eslint-config-standard: ^16.0.3 - eslint-plugin-import: ^2.25.4 - eslint-plugin-node: ^11.1.0 - eslint-plugin-prettier: ^4.0.0 - eslint-plugin-promise: ^6.0.0 + eslint: 8.14.0 + eslint-config-prettier: 8.5.0 + eslint-config-standard: 16.0.3 + eslint-plugin-import: 2.25.4 + eslint-plugin-node: 11.1.0 + eslint-plugin-prettier: 4.0.0 + eslint-plugin-promise: 6.0.0 eth-permit: ^0.2.1 ethers: ^5.7.2 fast-check: ^2.24.0 @@ -8670,9 +8867,9 @@ fsevents@~2.1.1: lodash.get: ^4.4.2 mocha-chai-jest-snapshot: ^1.1.3 prettier: 2.5.1 - prettier-plugin-solidity: ^1.0.0-beta.13 - solhint: ^3.3.6 - solhint-plugin-prettier: ^0.0.5 + prettier-plugin-solidity: 1.0.0-beta.13 + solhint: 3.3.6 + solhint-plugin-prettier: 0.0.5 solidity-coverage: ^0.8.2 ts-node: ^10.4.0 tsconfig-paths: ^4.1.0 @@ -8703,14 +8900,14 @@ fsevents@~2.1.1: languageName: node linkType: hard -resolve@1.1.x: +"resolve@npm:1.1.x": version: 1.1.7 resolution: "resolve@npm:1.1.7" checksum: afd20873fbde7641c9125efe3f940c2a99f6b1f90f1b7b743e744bdaac1cb105b2e4e0317bcc052ed7e31d57afa86b394a4dc9a1b33a297977be134fdf0250ab languageName: node linkType: hard -resolve@1.17.0: +"resolve@npm:1.17.0": version: 1.17.0 resolution: "resolve@npm:1.17.0" dependencies: @@ -8719,16 +8916,16 @@ resolve@1.17.0: languageName: node linkType: hard -"resolve@^1.1.6, resolve@^1.10.1, resolve@^1.20.0, resolve@^1.22.0": - version: 1.22.1 - resolution: "resolve@npm:1.22.1" +"resolve@npm:^1.1.6, resolve@npm:^1.10.1, resolve@npm:^1.20.0, resolve@npm:^1.22.1": + version: 1.22.3 + resolution: "resolve@npm:1.22.3" dependencies: - is-core-module: ^2.9.0 + is-core-module: ^2.12.0 path-parse: ^1.0.7 supports-preserve-symlinks-flag: ^1.0.0 bin: resolve: bin/resolve - checksum: 07af5fc1e81aa1d866cbc9e9460fbb67318a10fa3c4deadc35c3ad8a898ee9a71a86a65e4755ac3195e0ea0cfbe201eb323ebe655ce90526fd61917313a34e4e + checksum: fb834b81348428cb545ff1b828a72ea28feb5a97c026a1cf40aa1008352c72811ff4d4e71f2035273dc536dcfcae20c13604ba6283c612d70fa0b6e44519c374 languageName: node linkType: hard @@ -8748,16 +8945,16 @@ resolve@1.17.0: languageName: node linkType: hard -"resolve@patch:resolve@^1.1.6#~builtin, resolve@patch:resolve@^1.10.1#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.0#~builtin": - version: 1.22.1 - resolution: "resolve@patch:resolve@npm%3A1.22.1#~builtin::version=1.22.1&hash=c3c19d" +"resolve@patch:resolve@^1.1.6#~builtin, resolve@patch:resolve@^1.10.1#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin": + version: 1.22.3 + resolution: "resolve@patch:resolve@npm%3A1.22.3#~builtin::version=1.22.3&hash=c3c19d" dependencies: - is-core-module: ^2.9.0 + is-core-module: ^2.12.0 path-parse: ^1.0.7 supports-preserve-symlinks-flag: ^1.0.0 bin: resolve: bin/resolve - checksum: 5656f4d0bedcf8eb52685c1abdf8fbe73a1603bb1160a24d716e27a57f6cecbe2432ff9c89c2bd57542c3a7b9d14b1882b73bfe2e9d7849c9a4c0b8b39f02b8b + checksum: ad59734723b596d0891321c951592ed9015a77ce84907f89c9d9307dd0c06e11a67906a3e628c4cae143d3e44898603478af0ddeb2bba3f229a9373efe342665 languageName: node linkType: hard @@ -8887,6 +9084,18 @@ resolve@1.17.0: languageName: node linkType: hard +"safe-array-concat@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-array-concat@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.2.0 + has-symbols: ^1.0.3 + isarray: ^2.0.5 + checksum: f43cb98fe3b566327d0c09284de2b15fb85ae964a89495c1b1a5d50c7c8ed484190f4e5e71aacc167e16231940079b326f2c0807aea633d47cc7322f40a6b57f + languageName: node + linkType: hard + "safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" @@ -8901,6 +9110,17 @@ resolve@1.17.0: languageName: node linkType: hard +"safe-regex-test@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-regex-test@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.3 + is-regex: ^1.1.4 + checksum: bc566d8beb8b43c01b94e67de3f070fd2781685e835959bbbaaec91cc53381145ca91f69bd837ce6ec244817afa0a5e974fc4e40a2957f0aca68ac3add1ddd34 + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:^2.1.0, safer-buffer@npm:~2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -8959,31 +9179,31 @@ resolve@1.17.0: linkType: hard "semver@npm:^5.5.0, semver@npm:^5.5.1, semver@npm:^5.7.0": - version: 5.7.1 - resolution: "semver@npm:5.7.1" + version: 5.7.2 + resolution: "semver@npm:5.7.2" bin: - semver: ./bin/semver - checksum: 57fd0acfd0bac382ee87cd52cd0aaa5af086a7dc8d60379dfe65fea491fb2489b6016400813930ecd61fd0952dae75c115287a1b16c234b1550887117744dfaf + semver: bin/semver + checksum: fb4ab5e0dd1c22ce0c937ea390b4a822147a9c53dbd2a9a0132f12fe382902beef4fbf12cf51bb955248d8d15874ce8cd89532569756384f994309825f10b686 languageName: node linkType: hard -"semver@npm:^6.1.0, semver@npm:^6.3.0": - version: 6.3.0 - resolution: "semver@npm:6.3.0" +"semver@npm:^6.1.0, semver@npm:^6.3.0, semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" bin: - semver: ./bin/semver.js - checksum: 1b26ecf6db9e8292dd90df4e781d91875c0dcc1b1909e70f5d12959a23c7eebb8f01ea581c00783bbee72ceeaad9505797c381756326073850dc36ed284b21b9 + semver: bin/semver.js + checksum: ae47d06de28836adb9d3e25f22a92943477371292d9b665fb023fae278d345d508ca1958232af086d85e0155aee22e313e100971898bbb8d5d89b8b1d4054ca2 languageName: node linkType: hard -"semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7": - version: 7.3.7 - resolution: "semver@npm:7.3.7" +"semver@npm:^7.3.4, semver@npm:^7.3.5": + version: 7.5.4 + resolution: "semver@npm:7.5.4" dependencies: lru-cache: ^6.0.0 bin: semver: bin/semver.js - checksum: 2fa3e877568cd6ce769c75c211beaed1f9fce80b28338cadd9d0b6c40f2e2862bafd62c19a6cff42f3d54292b7c623277bcab8816a2b5521cf15210d43e75232 + checksum: 12d8ad952fa353b0995bf180cdac205a4068b759a140e5d3c608317098b3575ac2f1e09182206bf2eb26120e1c0ed8fb92c48c592f6099680de56bb071423ca3 languageName: node linkType: hard @@ -9142,6 +9362,13 @@ resolve@1.17.0: languageName: node linkType: hard +"signal-exit@npm:^4.0.1": + version: 4.0.2 + resolution: "signal-exit@npm:4.0.2" + checksum: 41f5928431cc6e91087bf0343db786a6313dd7c6fd7e551dbc141c95bb5fb26663444fd9df8ea47c5d7fc202f60aa7468c3162a9365cbb0615fc5e1b1328fe31 + languageName: node + linkType: hard + "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -9197,12 +9424,12 @@ resolve@1.17.0: linkType: hard "socks@npm:^2.6.2": - version: 2.6.2 - resolution: "socks@npm:2.6.2" + version: 2.7.1 + resolution: "socks@npm:2.7.1" dependencies: - ip: ^1.1.5 + ip: ^2.0.0 smart-buffer: ^4.2.0 - checksum: dd9194293059d737759d5c69273850ad4149f448426249325c4bea0e340d1cf3d266c3b022694b0dcf5d31f759de23657244c481fc1e8322add80b7985c36b5e + checksum: 259d9e3e8e1c9809a7f5c32238c3d4d2a36b39b83851d0f573bfde5f21c4b1288417ce1af06af1452569cd1eb0841169afd4998f0e04ba04656f6b7f0e46d748 languageName: node linkType: hard @@ -9225,7 +9452,7 @@ resolve@1.17.0: languageName: node linkType: hard -"solhint-plugin-prettier@npm:^0.0.5": +"solhint-plugin-prettier@npm:0.0.5": version: 0.0.5 resolution: "solhint-plugin-prettier@npm:0.0.5" dependencies: @@ -9237,11 +9464,11 @@ resolve@1.17.0: languageName: node linkType: hard -"solhint@npm:^3.3.6": - version: 3.3.7 - resolution: "solhint@npm:3.3.7" +"solhint@npm:3.3.6": + version: 3.3.6 + resolution: "solhint@npm:3.3.6" dependencies: - "@solidity-parser/parser": ^0.14.1 + "@solidity-parser/parser": ^0.13.2 ajv: ^6.6.1 antlr4: 4.7.1 ast-parents: 0.0.1 @@ -9261,14 +9488,14 @@ resolve@1.17.0: optional: true bin: solhint: solhint.js - checksum: 140a4660b691ea78aa7de19aca2123991fb4f9bc7be574e1573ae428b356e12919805df56c2892ddbdd031a4a4db477a81425ad85aac6672f3fb73f4887c2abb + checksum: 0ea5c96540adbc33e3c0305dacf270bcdfdbfb6f64652eb3584de2770b98dc1383bd65faf8506fbee95d773e359fe7f3b0a15c492a0596fcc14de0d609a0a335 languageName: node linkType: hard "solidity-ast@npm:^0.4.15": - version: 0.4.35 - resolution: "solidity-ast@npm:0.4.35" - checksum: 6cde9e656dee814fa3d7ce9ef42f1cd0344162515d0d215dbd7d18bf931ed9cd6ce4093aed0a8abbcfb5a4a6faf6638f615aaad479e4657054c6a4ae2cb5092e + version: 0.4.49 + resolution: "solidity-ast@npm:0.4.49" + checksum: f5b0354ddfa882346cf12d33f79c6123796a07637b248ceb9cfeec9f81540e270407f6fca660cf75666e1ba1866270319ab3fbe54b01491dbd35adffd1405243 languageName: node linkType: hard @@ -9280,11 +9507,11 @@ resolve@1.17.0: linkType: hard "solidity-coverage@npm:^0.8.2": - version: 0.8.2 - resolution: "solidity-coverage@npm:0.8.2" + version: 0.8.4 + resolution: "solidity-coverage@npm:0.8.4" dependencies: "@ethersproject/abi": ^5.0.9 - "@solidity-parser/parser": ^0.14.1 + "@solidity-parser/parser": ^0.16.0 chalk: ^2.4.2 death: ^1.1.0 detect-port: ^1.3.0 @@ -9307,7 +9534,7 @@ resolve@1.17.0: hardhat: ^2.11.0 bin: solidity-coverage: plugins/bin.js - checksum: 489f73d56a1279f2394b7a14db315532884895baa00a4016e68a4e5be0eddca90a95cb3322e6a0b15e67f2d9003b9413ee24c1c61d78f558f5a2e1e233840825 + checksum: 263089376d05f572350a2e47b61b2c604b3b5deedf4547cb0334342ecf6b732f823c069790e21063a56502a0d1fb9051a6f7bae1b990e2917af56fc94ac96759 languageName: node linkType: hard @@ -9365,21 +9592,21 @@ resolve@1.17.0: languageName: node linkType: hard -"ssri@npm:^9.0.0": - version: 9.0.1 - resolution: "ssri@npm:9.0.1" +"ssri@npm:^10.0.0": + version: 10.0.4 + resolution: "ssri@npm:10.0.4" dependencies: - minipass: ^3.1.1 - checksum: fb58f5e46b6923ae67b87ad5ef1c5ab6d427a17db0bead84570c2df3cd50b4ceb880ebdba2d60726588272890bae842a744e1ecce5bd2a2a582fccd5068309eb + minipass: ^5.0.0 + checksum: fb14da9f8a72b04eab163eb13a9dda11d5962cd2317f85457c4e0b575e9a6e0e3a6a87b5bf122c75cb36565830cd5f263fb457571bf6f1587eb5f95d095d6165 languageName: node linkType: hard "stack-utils@npm:^2.0.3": - version: 2.0.5 - resolution: "stack-utils@npm:2.0.5" + version: 2.0.6 + resolution: "stack-utils@npm:2.0.6" dependencies: escape-string-regexp: ^2.0.0 - checksum: 76b69da0f5b48a34a0f93c98ee2a96544d2c4ca2557f7eef5ddb961d3bdc33870b46f498a84a7c4f4ffb781df639840e7ebf6639164ed4da5e1aeb659615b9c7 + checksum: 052bf4d25bbf5f78e06c1d5e67de2e088b06871fa04107ca8d3f0e9d9263326e2942c8bedee3545795fc77d787d443a538345eef74db2f8e35db3558c6f91ff7 languageName: node linkType: hard @@ -9413,7 +9640,7 @@ resolve@1.17.0: languageName: node linkType: hard -"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.2, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -9445,25 +9672,47 @@ resolve@1.17.0: languageName: node linkType: hard -"string.prototype.trimend@npm:^1.0.5": - version: 1.0.5 - resolution: "string.prototype.trimend@npm:1.0.5" +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: ^0.2.0 + emoji-regex: ^9.2.2 + strip-ansi: ^7.0.1 + checksum: 7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193 + languageName: node + linkType: hard + +"string.prototype.trim@npm:^1.2.7": + version: 1.2.7 + resolution: "string.prototype.trim@npm:1.2.7" dependencies: call-bind: ^1.0.2 define-properties: ^1.1.4 - es-abstract: ^1.19.5 - checksum: d44f543833112f57224e79182debadc9f4f3bf9d48a0414d6f0cbd2a86f2b3e8c0ca1f95c3f8e5b32ae83e91554d79d932fc746b411895f03f93d89ed3dfb6bc + es-abstract: ^1.20.4 + checksum: 05b7b2d6af63648e70e44c4a8d10d8cc457536df78b55b9d6230918bde75c5987f6b8604438c4c8652eb55e4fc9725d2912789eb4ec457d6995f3495af190c09 languageName: node linkType: hard -"string.prototype.trimstart@npm:^1.0.5": - version: 1.0.5 - resolution: "string.prototype.trimstart@npm:1.0.5" +"string.prototype.trimend@npm:^1.0.6": + version: 1.0.6 + resolution: "string.prototype.trimend@npm:1.0.6" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: 0fdc34645a639bd35179b5a08227a353b88dc089adf438f46be8a7c197fc3f22f8514c1c9be4629b3cd29c281582730a8cbbad6466c60f76b5f99cf2addb132e + languageName: node + linkType: hard + +"string.prototype.trimstart@npm:^1.0.6": + version: 1.0.6 + resolution: "string.prototype.trimstart@npm:1.0.6" dependencies: call-bind: ^1.0.2 define-properties: ^1.1.4 - es-abstract: ^1.19.5 - checksum: a4857c5399ad709d159a77371eeaa8f9cc284469a0b5e1bfe405de16f1fd4166a8ea6f4180e55032f348d1b679b1599fd4301fbc7a8b72bdb3e795e43f7b1048 + es-abstract: ^1.20.4 + checksum: 89080feef416621e6ef1279588994305477a7a91648d9436490d56010a1f7adc39167cddac7ce0b9884b8cdbef086987c4dcb2960209f2af8bac0d23ceff4f41 languageName: node linkType: hard @@ -9485,6 +9734,15 @@ resolve@1.17.0: languageName: node linkType: hard +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: ^5.0.1 + checksum: f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c + languageName: node + linkType: hard + "strip-ansi@npm:^4.0.0": version: 4.0.0 resolution: "strip-ansi@npm:4.0.0" @@ -9503,12 +9761,12 @@ resolve@1.17.0: languageName: node linkType: hard -"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" +"strip-ansi@npm:^7.0.1": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" dependencies: - ansi-regex: ^5.0.1 - checksum: f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c + ansi-regex: ^6.0.1 + checksum: 859c73fcf27869c22a4e4d8c6acfe690064659e84bef9458aa6d13719d09ca88dcfd40cbf31fd0be63518ea1a643fe070b4827d353e09533a5b0b9fd4553d64d languageName: node linkType: hard @@ -9627,29 +9885,29 @@ resolve@1.17.0: linkType: hard "table@npm:^6.8.0": - version: 6.8.0 - resolution: "table@npm:6.8.0" + version: 6.8.1 + resolution: "table@npm:6.8.1" dependencies: ajv: ^8.0.1 lodash.truncate: ^4.4.2 slice-ansi: ^4.0.0 string-width: ^4.2.3 strip-ansi: ^6.0.1 - checksum: 5b07fe462ee03d2e1fac02cbb578efd2e0b55ac07e3d3db2e950aa9570ade5a4a2b8d3c15e9f25c89e4e50b646bc4269934601ee1eef4ca7968ad31960977690 + checksum: 08249c7046125d9d0a944a6e96cfe9ec66908d6b8a9db125531be6eb05fa0de047fd5542e9d43b4f987057f00a093b276b8d3e19af162a9c40db2681058fd306 languageName: node linkType: hard "tar@npm:^6.1.11, tar@npm:^6.1.2": - version: 6.1.11 - resolution: "tar@npm:6.1.11" + version: 6.1.15 + resolution: "tar@npm:6.1.15" dependencies: chownr: ^2.0.0 fs-minipass: ^2.0.0 - minipass: ^3.0.0 + minipass: ^5.0.0 minizlib: ^2.1.1 mkdirp: ^1.0.3 yallist: ^4.0.0 - checksum: a04c07bb9e2d8f46776517d4618f2406fb977a74d914ad98b264fc3db0fe8224da5bec11e5f8902c5b9bcb8ace22d95fbe3c7b36b8593b7dfc8391a25898f32f + checksum: f23832fceeba7578bf31907aac744ae21e74a66f4a17a9e94507acf460e48f6db598c7023882db33bab75b80e027c21f276d405e4a0322d58f51c7088d428268 languageName: node linkType: hard @@ -9816,8 +10074,8 @@ resolve@1.17.0: linkType: hard "ts-node@npm:^10.4.0": - version: 10.8.2 - resolution: "ts-node@npm:10.8.2" + version: 10.9.1 + resolution: "ts-node@npm:10.9.1" dependencies: "@cspotcode/source-map-support": ^0.8.0 "@tsconfig/node10": ^1.0.7 @@ -9849,30 +10107,30 @@ resolve@1.17.0: ts-node-script: dist/bin-script.js ts-node-transpile-only: dist/bin-transpile.js ts-script: dist/bin-script-deprecated.js - checksum: 1eede939beed9f4db35bcc88d78ef803815b99dcdbed1ecac728d861d74dc694918a7f0f437aa08d026193743a31e7e00e2ee34f875f909b5879981c1808e2a7 + checksum: 090adff1302ab20bd3486e6b4799e90f97726ed39e02b39e566f8ab674fd5bd5f727f43615debbfc580d33c6d9d1c6b1b3ce7d8e3cca3e20530a145ffa232c35 languageName: node linkType: hard -"tsconfig-paths@npm:^3.14.1": - version: 3.14.1 - resolution: "tsconfig-paths@npm:3.14.1" +"tsconfig-paths@npm:^3.12.0": + version: 3.14.2 + resolution: "tsconfig-paths@npm:3.14.2" dependencies: "@types/json5": ^0.0.29 - json5: ^1.0.1 + json5: ^1.0.2 minimist: ^1.2.6 strip-bom: ^3.0.0 - checksum: 8afa01c673ebb4782ba53d3a12df97fa837ce524f8ad38ee4e2b2fd57f5ac79abc21c574e9e9eb014d93efe7fe8214001b96233b5c6ea75bd1ea82afe17a4c6d + checksum: a6162eaa1aed680537f93621b82399c7856afd10ec299867b13a0675e981acac4e0ec00896860480efc59fc10fd0b16fdc928c0b885865b52be62cadac692447 languageName: node linkType: hard "tsconfig-paths@npm:^4.1.0": - version: 4.1.0 - resolution: "tsconfig-paths@npm:4.1.0" + version: 4.2.0 + resolution: "tsconfig-paths@npm:4.2.0" dependencies: - json5: ^2.2.1 + json5: ^2.2.2 minimist: ^1.2.6 strip-bom: ^3.0.0 - checksum: e4b101f81b2abd95499d8145e0aa73144e857c2c359191058486cef101b7accae22a69114e5d5814a13d5ab3b0bae70dd0c85bcdb7e829bbe1bfda5c9067c9b1 + checksum: 28c5f7bbbcabc9dabd4117e8fdc61483f6872a1c6b02a4b1c4d68c5b79d06896c3cc9547610c4c3ba64658531caa2de13ead1ea1bf321c7b53e969c4752b98c7 languageName: node linkType: hard @@ -9884,9 +10142,9 @@ resolve@1.17.0: linkType: hard "tslib@npm:^2.3.1, tslib@npm:^2.5.0": - version: 2.5.0 - resolution: "tslib@npm:2.5.0" - checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1 + version: 2.6.1 + resolution: "tslib@npm:2.6.1" + checksum: b0d176d176487905b66ae4d5856647df50e37beea7571c53b8d10ba9222c074b81f1410fb91da13debaf2cbc970663609068bdebafa844ea9d69b146527c38fe languageName: node linkType: hard @@ -10023,6 +10281,53 @@ resolve@1.17.0: languageName: node linkType: hard +"typed-array-buffer@npm:^1.0.0": + version: 1.0.0 + resolution: "typed-array-buffer@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.2.1 + is-typed-array: ^1.1.10 + checksum: 3e0281c79b2a40cd97fe715db803884301993f4e8c18e8d79d75fd18f796e8cd203310fec8c7fdb5e6c09bedf0af4f6ab8b75eb3d3a85da69328f28a80456bd3 + languageName: node + linkType: hard + +"typed-array-byte-length@npm:^1.0.0": + version: 1.0.0 + resolution: "typed-array-byte-length@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + for-each: ^0.3.3 + has-proto: ^1.0.1 + is-typed-array: ^1.1.10 + checksum: b03db16458322b263d87a702ff25388293f1356326c8a678d7515767ef563ef80e1e67ce648b821ec13178dd628eb2afdc19f97001ceae7a31acf674c849af94 + languageName: node + linkType: hard + +"typed-array-byte-offset@npm:^1.0.0": + version: 1.0.0 + resolution: "typed-array-byte-offset@npm:1.0.0" + dependencies: + available-typed-arrays: ^1.0.5 + call-bind: ^1.0.2 + for-each: ^0.3.3 + has-proto: ^1.0.1 + is-typed-array: ^1.1.10 + checksum: 04f6f02d0e9a948a95fbfe0d5a70b002191fae0b8fe0fe3130a9b2336f043daf7a3dda56a31333c35a067a97e13f539949ab261ca0f3692c41603a46a94e960b + languageName: node + linkType: hard + +"typed-array-length@npm:^1.0.4": + version: 1.0.4 + resolution: "typed-array-length@npm:1.0.4" + dependencies: + call-bind: ^1.0.2 + for-each: ^0.3.3 + is-typed-array: ^1.1.9 + checksum: 2228febc93c7feff142b8c96a58d4a0d7623ecde6c7a24b2b98eb3170e99f7c7eff8c114f9b283085cd59dcd2bd43aadf20e25bba4b034a53c5bb292f71f8956 + languageName: node + linkType: hard + "typedarray@npm:^0.0.6": version: 0.0.6 resolution: "typedarray@npm:0.0.6" @@ -10030,23 +10335,23 @@ resolve@1.17.0: languageName: node linkType: hard -typescript@^4.4.2: - version: 4.7.4 - resolution: "typescript@npm:4.7.4" +"typescript@npm:^4.4.2": + version: 4.9.5 + resolution: "typescript@npm:4.9.5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 5750181b1cd7e6482c4195825547e70f944114fb47e58e4aa7553e62f11b3f3173766aef9c281783edfd881f7b8299cf35e3ca8caebe73d8464528c907a164df + checksum: ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db languageName: node linkType: hard "typescript@patch:typescript@^4.4.2#~builtin": - version: 4.7.4 - resolution: "typescript@patch:typescript@npm%3A4.7.4#~builtin::version=4.7.4&hash=65a307" + version: 4.9.5 + resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=ad5954" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 9096d8f6c16cb80ef3bf96fcbbd055bf1c4a43bd14f3b7be45a9fbe7ada46ec977f604d5feed3263b4f2aa7d4c7477ce5f9cd87de0d6feedec69a983f3a4f93e + checksum: 8f6260acc86b56bfdda6004bc53f32ea548f543e8baef7071c8e34d29d292f3e375c8416556c8de10b24deef6933cd1c16a8233dc84a3dd43a13a13265d0faab languageName: node linkType: hard @@ -10058,11 +10363,11 @@ typescript@^4.4.2: linkType: hard "uglify-js@npm:^3.1.4": - version: 3.16.2 - resolution: "uglify-js@npm:3.16.2" + version: 3.17.4 + resolution: "uglify-js@npm:3.17.4" bin: uglifyjs: bin/uglifyjs - checksum: 5b62e748b7fa1d982f0949ed1876b9367dcde4782f74159f4ea0b3d130835336eb0245e090456ec057468d937eb016114677bb38a7a4fdc7f68c3d002ca760ee + checksum: 7b3897df38b6fc7d7d9f4dcd658599d81aa2b1fb0d074829dd4e5290f7318dbca1f4af2f45acb833b95b1fe0ed4698662ab61b87e94328eb4c0a0d3435baf924 languageName: node linkType: hard @@ -10078,12 +10383,12 @@ typescript@^4.4.2: languageName: node linkType: hard -"undici@npm:^5.14.0, undici@npm:^5.4.0": - version: 5.22.0 - resolution: "undici@npm:5.22.0" +"undici@npm:^5.14.0": + version: 5.22.1 + resolution: "undici@npm:5.22.1" dependencies: busboy: ^1.6.0 - checksum: 8dc55240a60ae7680798df344e8f46ad0f872ed0fa434fb94cc4fd2b5b2f8053bdf11994d15902999d3880f9bf7cd875a2e90883d2702bf0f366dacd9cbf3fc6 + checksum: 048a3365f622be44fb319316cedfaa241c59cf7f3368ae7667a12323447e1822e8cc3d00f6956c852d1478a6fde1cbbe753f49e05f2fdaed229693e716ebaf35 languageName: node linkType: hard @@ -10094,21 +10399,21 @@ typescript@^4.4.2: languageName: node linkType: hard -"unique-filename@npm:^1.1.1": - version: 1.1.1 - resolution: "unique-filename@npm:1.1.1" +"unique-filename@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-filename@npm:3.0.0" dependencies: - unique-slug: ^2.0.0 - checksum: cf4998c9228cc7647ba7814e255dec51be43673903897b1786eff2ac2d670f54d4d733357eb08dea969aa5e6875d0e1bd391d668fbdb5a179744e7c7551a6f80 + unique-slug: ^4.0.0 + checksum: 8e2f59b356cb2e54aab14ff98a51ac6c45781d15ceaab6d4f1c2228b780193dc70fae4463ce9e1df4479cb9d3304d7c2043a3fb905bdeca71cc7e8ce27e063df languageName: node linkType: hard -"unique-slug@npm:^2.0.0": - version: 2.0.2 - resolution: "unique-slug@npm:2.0.2" +"unique-slug@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-slug@npm:4.0.0" dependencies: imurmurhash: ^0.1.4 - checksum: 5b6876a645da08d505dedb970d1571f6cebdf87044cb6b740c8dbb24f0d6e1dc8bdbf46825fd09f994d7cf50760e6f6e063cfa197d51c5902c00a861702eb75a + checksum: 0884b58365af59f89739e6f71e3feacb5b1b41f2df2d842d0757933620e6de08eff347d27e9d499b43c40476cbaf7988638d3acb2ffbcb9d35fd035591adfd15 languageName: node linkType: hard @@ -10133,17 +10438,17 @@ typescript@^4.4.2: languageName: node linkType: hard -"update-browserslist-db@npm:^1.0.4": - version: 1.0.4 - resolution: "update-browserslist-db@npm:1.0.4" +"update-browserslist-db@npm:^1.0.11": + version: 1.0.11 + resolution: "update-browserslist-db@npm:1.0.11" dependencies: escalade: ^3.1.1 picocolors: ^1.0.0 peerDependencies: browserslist: ">= 4.21.0" bin: - browserslist-lint: cli.js - checksum: 7c7da28d0fc733b17e01c8fa9385ab909eadce64b8ea644e9603867dc368c2e2a6611af8247e72612b23f9e7cb87ac7c7585a05ff94e1759e9d646cbe9bf49a7 + update-browserslist-db: cli.js + checksum: b98327518f9a345c7cad5437afae4d2ae7d865f9779554baf2a200fdf4bac4969076b679b1115434bd6557376bdd37ca7583d0f9b8f8e302d7d4cc1e91b5f231 languageName: node linkType: hard @@ -10244,8 +10549,8 @@ typescript@^4.4.2: linkType: hard "web3-utils@npm:^1.3.6": - version: 1.8.1 - resolution: "web3-utils@npm:1.8.1" + version: 1.10.0 + resolution: "web3-utils@npm:1.10.0" dependencies: bn.js: ^5.2.1 ethereum-bloom-filters: ^1.0.6 @@ -10254,7 +10559,7 @@ typescript@^4.4.2: number-to-bn: 1.7.0 randombytes: ^2.1.0 utf8: 3.0.0 - checksum: 08bb2df9cd19672f034bb82a27b857e0571b836a620f83de2214377457c6e52446e8dedcf916f8f10a13c86b5a02674dd4f45c60c45698b388368601cce9cf5e + checksum: c6b7662359c0513b5cbfe02cdcb312ce9152778bb19d94d413d44f74cfaa93b7de97190ab6ba11af25a40855c949d2427dcb751929c6d0f257da268c55a3ba2a languageName: node linkType: hard @@ -10266,9 +10571,9 @@ typescript@^4.4.2: linkType: hard "whatwg-fetch@npm:^3.4.1": - version: 3.6.2 - resolution: "whatwg-fetch@npm:3.6.2" - checksum: ee976b7249e7791edb0d0a62cd806b29006ad7ec3a3d89145921ad8c00a3a67e4be8f3fb3ec6bc7b58498724fd568d11aeeeea1f7827e7e1e5eae6c8a275afed + version: 3.6.17 + resolution: "whatwg-fetch@npm:3.6.17" + checksum: 0a8785dc2d1515c17ee9365d3f6438cf8fd281567426652fc6c55fc99e58cc6287ae5d1add5b8b1dd665f149e38d3de4ebe3812fd7170438ba0681d03b88b4dd languageName: node linkType: hard @@ -10296,9 +10601,22 @@ typescript@^4.4.2: linkType: hard "which-module@npm:^2.0.0": - version: 2.0.0 - resolution: "which-module@npm:2.0.0" - checksum: 809f7fd3dfcb2cdbe0180b60d68100c88785084f8f9492b0998c051d7a8efe56784492609d3f09ac161635b78ea29219eb1418a98c15ce87d085bce905705c9c + version: 2.0.1 + resolution: "which-module@npm:2.0.1" + checksum: 1967b7ce17a2485544a4fdd9063599f0f773959cca24176dbe8f405e55472d748b7c549cd7920ff6abb8f1ab7db0b0f1b36de1a21c57a8ff741f4f1e792c52be + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.10, which-typed-array@npm:^1.1.11": + version: 1.1.11 + resolution: "which-typed-array@npm:1.1.11" + dependencies: + available-typed-arrays: ^1.0.5 + call-bind: ^1.0.2 + for-each: ^0.3.3 + gopd: ^1.0.1 + has-tostringtag: ^1.0.0 + checksum: 711ffc8ef891ca6597b19539075ec3e08bb9b4c2ca1f78887e3c07a977ab91ac1421940505a197758fb5939aa9524976d0a5bbcac34d07ed6faa75cedbb17206 languageName: node linkType: hard @@ -10342,10 +10660,10 @@ typescript@^4.4.2: languageName: node linkType: hard -"word-wrap@npm:^1.2.3, word-wrap@npm:~1.2.3": - version: 1.2.3 - resolution: "word-wrap@npm:1.2.3" - checksum: 30b48f91fcf12106ed3186ae4fa86a6a1842416df425be7b60485de14bec665a54a68e4b5156647dec3a70f25e84d270ca8bc8cd23182ed095f5c7206a938c1f +"word-wrap@npm:~1.2.3": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: f93ba3586fc181f94afdaff3a6fef27920b4b6d9eaefed0f428f8e07adea2a7f54a5f2830ce59406c8416f033f86902b91eb824072354645eea687dff3691ccb languageName: node linkType: hard @@ -10363,6 +10681,17 @@ typescript@^4.4.2: languageName: node linkType: hard +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + languageName: node + linkType: hard + "wrap-ansi@npm:^5.1.0": version: 5.1.0 resolution: "wrap-ansi@npm:5.1.0" @@ -10374,14 +10703,14 @@ typescript@^4.4.2: languageName: node linkType: hard -"wrap-ansi@npm:^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" dependencies: - ansi-styles: ^4.0.0 - string-width: ^4.1.0 - strip-ansi: ^6.0.0 - checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + ansi-styles: ^6.1.0 + string-width: ^5.0.1 + strip-ansi: ^7.0.1 + checksum: 371733296dc2d616900ce15a0049dca0ef67597d6394c57347ba334393599e800bab03c41d4d45221b6bc967b8c453ec3ae4749eff3894202d16800fdfe0e238 languageName: node linkType: hard @@ -10393,19 +10722,19 @@ typescript@^4.4.2: linkType: hard "wretch@npm:^2.0.4": - version: 2.0.4 - resolution: "wretch@npm:2.0.4" - checksum: 27278ca4f239dc26af2fb8da0221ba1a6373005343ed0e66d4f8c3227710d39e5e4f4323ed8f81996f5fe62a7343722bf0076f5f6e4930c18815e27a6680917e + version: 2.6.0 + resolution: "wretch@npm:2.6.0" + checksum: 90f583c6673628f248f5517c25b40e0a2d459ea3bb5e7b952fc76a6d61f5c6c1785c9e943ed3e954ef99a476b610c02cca18189db3d4328a00fa17ed376c0da5 languageName: node linkType: hard "write-file-atomic@npm:^4.0.1": - version: 4.0.1 - resolution: "write-file-atomic@npm:4.0.1" + version: 4.0.2 + resolution: "write-file-atomic@npm:4.0.2" dependencies: imurmurhash: ^0.1.4 signal-exit: ^3.0.7 - checksum: 8f780232533ca6223c63c9b9c01c4386ca8c625ebe5017a9ed17d037aec19462ae17109e0aa155bff5966ee4ae7a27b67a99f55caf3f32ffd84155e9da3929fc + checksum: 5da60bd4eeeb935eec97ead3df6e28e5917a6bd317478e4a85a5285e8480b8ed96032bbcc6ecd07b236142a24f3ca871c924ec4a6575e623ec1b11bf8c1c253c languageName: node linkType: hard @@ -10434,8 +10763,8 @@ typescript@^4.4.2: linkType: hard "ws@npm:^7.4.6": - version: 7.5.8 - resolution: "ws@npm:7.5.8" + version: 7.5.9 + resolution: "ws@npm:7.5.9" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ^5.0.2 @@ -10444,7 +10773,7 @@ typescript@^4.4.2: optional: true utf-8-validate: optional: true - checksum: 49479ccf3ddab6500c5906fbcc316e9c8cd44b0ffb3903a6c1caf9b38cb9e06691685722a4c642cfa7d4c6eb390424fc3142cd4f8b940cfc7a9ce9761b1cd65b + checksum: c3c100a181b731f40b7f2fddf004aa023f79d64f489706a28bc23ff88e87f6a64b3c6651fbec3a84a53960b75159574d7a7385709847a62ddb7ad6af76f49138 languageName: node linkType: hard @@ -10507,10 +10836,10 @@ typescript@^4.4.2: languageName: node linkType: hard -"yargs-parser@npm:^21.0.0": - version: 21.0.1 - resolution: "yargs-parser@npm:21.0.1" - checksum: c3ea2ed12cad0377ce3096b3f138df8267edf7b1aa7d710cd502fe16af417bafe4443dd71b28158c22fcd1be5dfd0e86319597e47badf42ff83815485887323a +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c languageName: node linkType: hard @@ -10571,17 +10900,17 @@ typescript@^4.4.2: linkType: hard "yargs@npm:^17.5.1": - version: 17.5.1 - resolution: "yargs@npm:17.5.1" + version: 17.7.2 + resolution: "yargs@npm:17.7.2" dependencies: - cliui: ^7.0.2 + cliui: ^8.0.1 escalade: ^3.1.1 get-caller-file: ^2.0.5 require-directory: ^2.1.1 string-width: ^4.2.3 y18n: ^5.0.5 - yargs-parser: ^21.0.0 - checksum: 00d58a2c052937fa044834313f07910fd0a115dec5ee35919e857eeee3736b21a4eafa8264535800ba8bac312991ce785ecb8a51f4d2cc8c4676d865af1cfbde + yargs-parser: ^21.1.1 + checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a languageName: node linkType: hard From e85ed27d8cae87119d6746fe9c1cd8ce44254552 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 2 Aug 2023 18:28:43 +0200 Subject: [PATCH 352/462] Zero distribution (#878) --- contracts/interfaces/IRevenueTrader.sol | 4 + contracts/p0/RevenueTrader.sol | 20 ++++ contracts/p1/RevenueTrader.sol | 25 ++++- .../plugins/mocks/InvalidRevTraderP1Mock.sol | 24 +++- test/Revenues.test.ts | 106 ++++++++++++++++++ 5 files changed, 177 insertions(+), 2 deletions(-) diff --git a/contracts/interfaces/IRevenueTrader.sol b/contracts/interfaces/IRevenueTrader.sol index 4b07ff70bc..8ab78078e1 100644 --- a/contracts/interfaces/IRevenueTrader.sol +++ b/contracts/interfaces/IRevenueTrader.sol @@ -25,6 +25,10 @@ interface IRevenueTrader is IComponent, ITrading { /// @custom:interaction function distributeTokenToBuy() external; + /// Return registered ERC20s to the BackingManager if distribution for tokenToBuy is 0 + /// @custom:interaction + function returnTokens(IERC20[] memory erc20s) external; + /// Process some number of tokens /// If the tokenToBuy is included in erc20s, RevenueTrader will distribute it at end of the tx /// @param erc20s The ERC20s to manage; can be tokenToBuy or anything registered diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index 4196cc7916..2f380927fe 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -50,6 +50,26 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { _distributeTokenToBuy(); } + /// Return registered ERC20s to the BackingManager if distribution for tokenToBuy is 0 + /// @custom:interaction + function returnTokens(IERC20[] memory erc20s) external notTradingPausedOrFrozen { + RevenueTotals memory revTotals = main.distributor().totals(); + if (tokenToBuy == main.rsr()) { + require(revTotals.rsrTotal == 0, "rsrTotal > 0"); + } else if (address(tokenToBuy) == address(main.rToken())) { + require(revTotals.rTokenTotal == 0, "rTokenTotal > 0"); + } else { + revert("invalid tokenToBuy"); + } + + // Return ERC20s to the BackingManager + for (uint256 i = 0; i < erc20s.length; i++) { + require(main.assetRegistry().isRegistered(erc20s[i]), "unregistered erc20"); + address backingManager = address(main.backingManager()); + erc20s[i].safeTransfer(backingManager, erc20s[i].balanceOf(address(this))); + } + } + /// Process some number of tokens /// @param erc20s The ERC20s to manage; can be tokenToBuy or anything registered /// @param kinds The kinds of auctions to launch: DUTCH_AUCTION | BATCH_AUCTION diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 47123b02e2..fe7b409b50 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -22,6 +22,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { IBackingManager private backingManager; IFurnace private furnace; IRToken private rToken; + IERC20 private rsr; function init( IMain main_, @@ -43,6 +44,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { backingManager = main.backingManager(); furnace = main.furnace(); rToken = main.rToken(); + rsr = main.rsr(); } /// Settle a single trade + distribute revenue @@ -62,6 +64,27 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { _distributeTokenToBuy(); } + /// Return registered ERC20s to the BackingManager if distribution for tokenToBuy is 0 + /// @custom:interaction + function returnTokens(IERC20[] memory erc20s) external notTradingPausedOrFrozen { + RevenueTotals memory revTotals = distributor.totals(); + if (tokenToBuy == rsr) { + require(revTotals.rsrTotal == 0, "rsrTotal > 0"); + } else if (address(tokenToBuy) == address(rToken)) { + require(revTotals.rTokenTotal == 0, "rTokenTotal > 0"); + } else { + // untestable: tokenToBuy is always the RSR or RToken + revert("invalid tokenToBuy"); + } + + // Return ERC20s to the BackingManager + uint256 len = erc20s.length; + for (uint256 i = 0; i < len; ++i) { + require(assetRegistry.isRegistered(erc20s[i]), "unregistered erc20"); + erc20s[i].safeTransfer(address(backingManager), erc20s[i].balanceOf(address(this))); + } + } + /// Process some number of tokens /// If the tokenToBuy is included in erc20s, RevenueTrader will distribute it at end of the tx /// @param erc20s The ERC20s to manage; can be tokenToBuy or anything registered @@ -164,5 +187,5 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[44] private __gap; + uint256[43] private __gap; } diff --git a/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol b/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol index 64b3904230..ccb2b0f31e 100644 --- a/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol +++ b/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol @@ -20,6 +20,7 @@ contract RevenueTraderP1InvalidReverts is TradingP1, IRevenueTrader { IBackingManager private backingManager; IFurnace private furnace; IRToken private rToken; + IERC20 private rsr; function init( IMain main_, @@ -35,13 +36,33 @@ contract RevenueTraderP1InvalidReverts is TradingP1, IRevenueTrader { } /// Distribute tokenToBuy to its destinations - function distributeTokenToBuy() public { + function distributeTokenToBuy() public notTradingPausedOrFrozen { uint256 bal = tokenToBuy.balanceOf(address(this)); tokenToBuy.safeApprove(address(main.distributor()), 0); tokenToBuy.safeApprove(address(main.distributor()), bal); main.distributor().distribute(tokenToBuy, bal); } + /// Return registered ERC20s to the BackingManager if distribution for tokenToBuy is 0 + /// @custom:interaction + function returnTokens(IERC20[] memory erc20s) external notTradingPausedOrFrozen { + RevenueTotals memory revTotals = distributor.totals(); + if (tokenToBuy == rsr) { + require(revTotals.rsrTotal == 0, "rsrTotal > 0"); + } else if (address(tokenToBuy) == address(rToken)) { + require(revTotals.rTokenTotal == 0, "rTokenTotal > 0"); + } else { + revert("invalid tokenToBuy"); + } + + // Return ERC20s to the BackingManager + uint256 len = erc20s.length; + for (uint256 i = 0; i < len; ++i) { + require(assetRegistry.isRegistered(erc20s[i]), "erc20 unregistered"); + erc20s[i].safeTransfer(address(backingManager), erc20s[i].balanceOf(address(this))); + } + } + /// Processes a single token; unpermissioned /// Reverts for testing purposes function manageTokens(IERC20[] memory, TradeKind[] memory) external notTradingPausedOrFrozen { @@ -55,5 +76,6 @@ contract RevenueTraderP1InvalidReverts is TradingP1, IRevenueTrader { backingManager = main.backingManager(); furnace = main.furnace(); rToken = main.rToken(); + rsr = main.rsr(); } } diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index ecf2a581a1..f95ee4a894 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -636,6 +636,112 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rsr.balanceOf(stRSR.address)).to.be.closeTo(expectedAmount, 100) }) + it('Should return tokens to BackingManager correctly - rsrTrader.returnTokens()', async () => { + // Mint tokens + await rsr.connect(owner).mint(rsrTrader.address, issueAmount) + await token0.connect(owner).mint(rsrTrader.address, issueAmount.add(1)) + await token1.connect(owner).mint(rsrTrader.address, issueAmount.add(2)) + + // Should fail when trading paused or frozen + await main.connect(owner).pauseIssuance() + await main.connect(owner).pauseTrading() + await main.connect(owner).freezeForever() + await expect( + rsrTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('frozen or trading paused') + await main.connect(owner).unfreeze() + await expect( + rsrTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('frozen or trading paused') + await main.connect(owner).unpauseTrading() + + // Should fail when distribution is nonzero + await expect( + rsrTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('rsrTotal > 0') + await distributor.setDistribution(STRSR_DEST, { rTokenDist: bn('0'), rsrDist: bn('0') }) + + // Should fail for unregistered token + await assetRegistry.connect(owner).unregister(collateral1.address) + await expect( + rsrTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('unregistered erc20') + + // Succeed on just token0 + rsr + await expectEvents(rsrTrader.returnTokens([rsr.address, token0.address]), [ + { + contract: rsr, + name: 'Transfer', + args: [rsrTrader.address, backingManager.address, issueAmount], + emitted: true, + }, + { + contract: token0, + name: 'Transfer', + args: [rsrTrader.address, backingManager.address, issueAmount.add(1)], + emitted: true, + }, + { + contract: token1, + name: 'Transfer', + emitted: false, + }, + ]) + }) + + it('Should return tokens to BackingManager correctly - rTokenTrader.returnTokens()', async () => { + // Mint tokens + await rsr.connect(owner).mint(rTokenTrader.address, issueAmount) + await token0.connect(owner).mint(rTokenTrader.address, issueAmount.add(1)) + await token1.connect(owner).mint(rTokenTrader.address, issueAmount.add(2)) + + // Should fail when trading paused or frozen + await main.connect(owner).pauseIssuance() + await main.connect(owner).pauseTrading() + await main.connect(owner).freezeForever() + await expect( + rTokenTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('frozen or trading paused') + await main.connect(owner).unfreeze() + await expect( + rTokenTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('frozen or trading paused') + await main.connect(owner).unpauseTrading() + + // Should fail when distribution is nonzero + await expect( + rTokenTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('rTokenTotal > 0') + await distributor.setDistribution(FURNACE_DEST, { rTokenDist: bn('0'), rsrDist: bn('0') }) + + // Should fail for unregistered token + await assetRegistry.connect(owner).unregister(collateral1.address) + await expect( + rTokenTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('unregistered erc20') + + // Succeed on just token0 + rsr + await expectEvents(rTokenTrader.returnTokens([rsr.address, token0.address]), [ + { + contract: rsr, + name: 'Transfer', + args: [rTokenTrader.address, backingManager.address, issueAmount], + emitted: true, + }, + { + contract: token0, + name: 'Transfer', + args: [rTokenTrader.address, backingManager.address, issueAmount.add(1)], + emitted: true, + }, + { + contract: token1, + name: 'Transfer', + emitted: false, + }, + ]) + }) + it('Should launch multiple auctions -- has tokenToBuy', async () => { // Mint AAVE, token0, and RSR to the RSRTrader await aaveToken.connect(owner).mint(rsrTrader.address, issueAmount) From 12c81a0e03f74384fffcd010e5de93c43f3f1455 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Thu, 3 Aug 2023 02:08:40 +0530 Subject: [PATCH 353/462] Bunch of small changes (#877) --- contracts/p0/AssetRegistry.sol | 7 +++++++ contracts/p0/Broker.sol | 5 +++-- contracts/p0/Deployer.sol | 6 ++++-- contracts/p1/AssetRegistry.sol | 9 +++++++++ contracts/p1/BackingManager.sol | 4 ++++ contracts/p1/mixins/Trading.sol | 1 - .../plugins/mocks/InvalidRefPerTokCollateral.sol | 12 +++++++++--- contracts/plugins/trading/DutchTrade.sol | 2 ++ contracts/plugins/trading/GnosisTrade.sol | 4 ++++ docs/system-design.md | 5 ++--- test/Broker.test.ts | 6 +++++- test/Main.test.ts | 15 +++++++++++++++ test/integration/EasyAuction.test.ts | 8 ++++++-- 13 files changed, 70 insertions(+), 14 deletions(-) diff --git a/contracts/p0/AssetRegistry.sol b/contracts/p0/AssetRegistry.sol index 223c691154..ebdf5fd8b1 100644 --- a/contracts/p0/AssetRegistry.sol +++ b/contracts/p0/AssetRegistry.sol @@ -144,6 +144,13 @@ contract AssetRegistryP0 is ComponentP0, IAssetRegistry { /// Register an asset, unregistering any previous asset with the same ERC20. function _registerIgnoringCollisions(IAsset asset) private returns (bool swapped) { + if (asset.isCollateral()) { + require( + ICollateral(address(asset)).status() == CollateralStatus.SOUND, + "collateral not sound" + ); + } + if (_erc20s.contains(address(asset.erc20())) && assets[asset.erc20()] == asset) return false; diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index c1c7da1452..9cf00da1f0 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/proxy/Clones.sol"; import "../plugins/trading/DutchTrade.sol"; import "../plugins/trading/GnosisTrade.sol"; import "../interfaces/IBroker.sol"; @@ -174,7 +175,7 @@ contract BrokerP0 is ComponentP0, IBroker { function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { require(batchAuctionLength > 0, "batch auctions not enabled"); - GnosisTrade trade = new GnosisTrade(); + GnosisTrade trade = GnosisTrade(Clones.clone(address(batchTradeImplementation))); trades[address(trade)] = true; // Apply Gnosis EasyAuction-specific resizing of req, if needed: Ensure that @@ -202,7 +203,7 @@ contract BrokerP0 is ComponentP0, IBroker { ITrading caller ) private returns (ITrade) { require(dutchAuctionLength > 0, "dutch auctions not enabled"); - DutchTrade trade = new DutchTrade(); + DutchTrade trade = DutchTrade(Clones.clone(address(dutchTradeImplementation))); trades[address(trade)] = true; IERC20Metadata(address(req.sell.erc20())).safeTransferFrom( diff --git a/contracts/p0/Deployer.sol b/contracts/p0/Deployer.sol index 8c8d124d2e..0b1ff0324a 100644 --- a/contracts/p0/Deployer.sol +++ b/contracts/p0/Deployer.sol @@ -4,6 +4,8 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../plugins/assets/Asset.sol"; import "../plugins/assets/RTokenAsset.sol"; +import "../plugins/trading/DutchTrade.sol"; +import "../plugins/trading/GnosisTrade.sol"; import "./AssetRegistry.sol"; import "./BackingManager.sol"; import "./BasketHandler.sol"; @@ -113,9 +115,9 @@ contract DeployerP0 is IDeployer, Versioned { main.broker().init( main, gnosis, - ITrade(address(1)), + ITrade(address(new GnosisTrade())), params.batchAuctionLength, - ITrade(address(1)), + ITrade(address(new DutchTrade())), params.dutchAuctionLength ); diff --git a/contracts/p1/AssetRegistry.sol b/contracts/p1/AssetRegistry.sol index 3474d171f8..098e47f93a 100644 --- a/contracts/p1/AssetRegistry.sol +++ b/contracts/p1/AssetRegistry.sol @@ -98,6 +98,8 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { } /// Unregister an asset, requiring that it is already registered + /// Rewards are NOT claimed by default when unregistering due to security concerns. + /// If the collateral is secure, governance should claim rewards before unregistering. /// @custom:governance // checks: assets[asset.erc20()] == asset // effects: assets' = assets - {asset.erc20():_} + {asset.erc20(), asset} @@ -186,6 +188,13 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { // effects: assets' = assets.set(asset.erc20(), asset) // returns: assets[asset.erc20()] != asset function _registerIgnoringCollisions(IAsset asset) private returns (bool swapped) { + if (asset.isCollateral()) { + require( + ICollateral(address(asset)).status() == CollateralStatus.SOUND, + "collateral not sound" + ); + } + IERC20Metadata erc20 = asset.erc20(); if (_erc20s.contains(address(erc20))) { if (assets[erc20] == asset) return false; diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index 6dde9dae27..a64ff3f412 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -82,6 +82,10 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// Settle a single trade. If the caller is the trade, try chaining into rebalance() /// While this function is not nonReentrant, its two subsets each individually are + /// If the caller is a trade contract, initiate the next trade. + /// This is done in order to better align incentives, + /// and have the last bidder be the one to start the next auction. + /// This behaviour currently only happens for Dutch Trade. /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 1cfa49c191..863f7ab113 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -117,7 +117,6 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl TradeRequest memory req, TradePrices memory prices ) internal returns (ITrade trade) { - /* */ IERC20 sell = req.sell.erc20(); assert(address(trades[sell]) == address(0)); diff --git a/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol b/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol index 3ad165f9ab..3ce9386c9e 100644 --- a/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol +++ b/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol @@ -18,9 +18,10 @@ contract InvalidRefPerTokCollateralMock is AppreciatingFiatCollateral { // solhint-disable no-empty-blocks - constructor(CollateralConfig memory config, uint192 revenueHiding) - AppreciatingFiatCollateral(config, revenueHiding) - {} + constructor( + CollateralConfig memory config, + uint192 revenueHiding + ) AppreciatingFiatCollateral(config, revenueHiding) {} // solhint-enable no-empty-blocks @@ -68,6 +69,11 @@ contract InvalidRefPerTokCollateralMock is AppreciatingFiatCollateral { refPerTokRevert = on; } + // Setter for status + function setStatus(CollateralStatus _status) external { + markStatus(_status); + } + function refPerTok() public view virtual override returns (uint192) { if (refPerTokRevert) revert(); // Revert with no reason return rateMock; diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 86160bcedc..d73777e094 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -98,6 +98,8 @@ contract DutchTrade is ITrade { constructor() { ONE_BLOCK = NetworkConfigLib.blocktime(); + + status = TradeStatus.CLOSED; } // === External === diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index ab954dbe8a..d3256be6ce 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -58,6 +58,10 @@ contract GnosisTrade is ITrade { status = end; } + constructor() { + status = TradeStatus.CLOSED; + } + /// Constructor function, can only be called once /// @dev Expects sell tokens to already be present /// @custom:interaction reentrancy-safe b/c state-locking diff --git a/docs/system-design.md b/docs/system-design.md index 23d3c0491b..8e8ba41292 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -33,9 +33,8 @@ Some of the core contracts in our system regularly own ERC20 tokens. In each cas ### RToken Lifecycle -1. During SlowIssuance, the `RToken` transfers collateral tokens from the caller's address into itself. -2. At vesting time, the `RToken` contract mints new RToken to the recipient and transfers the held collateral to the `BackingManager`. If the `BasketHandler` has updated the basket since issuance began, then the collateral is instead returned to the recipient and no RToken is minted. -3. During redemption, RToken is burnt from the redeemer's account and they are transferred a prorata share of backing collateral from the `BackingManager`. +1. During minting, the `RToken` transfers collateral tokens from the caller's address into itself and mints new RToken to the caller's address. Minting amount must be less than the current throttle limit, or the transaction will revert. +2. During redemption, RToken is burnt from the redeemer's account and they are transferred a prorata share of backing collateral from the `BackingManager`. ## Protocol Assumptions diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 4c47b54176..d920ec3d4f 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -1,4 +1,4 @@ -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { loadFixture, setStorageAt } from '@nomicfoundation/hardhat-network-helpers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' import { expect } from 'chai' @@ -505,6 +505,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') trade = await TradeFactory.deploy() + await setStorageAt(trade.address, 0, 0) + // Check state expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) expect(await trade.canSettle()).to.equal(false) @@ -948,6 +950,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { const TradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') trade = await TradeFactory.deploy() + await setStorageAt(trade.address, 0, 0) + // Check state expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) expect(await trade.canSettle()).to.equal(false) diff --git a/test/Main.test.ts b/test/Main.test.ts index 591fe6d095..b8613d40a0 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1668,6 +1668,21 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { // Basket is now disabled expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) }) + + it('Recognizes Sound Collateral', async () => { + expect(await collateral1.status()).to.equal(CollateralStatus.SOUND) + await expect(assetRegistry.register(collateral1.address)).not.be.reverted + + await revertCollateral.setStatus(CollateralStatus.DISABLED) + expect(await revertCollateral.status()).to.equal(CollateralStatus.DISABLED) + + await expect( + assetRegistry.connect(owner).register(revertCollateral.address) + ).be.revertedWith('collateral not sound') + await expect( + assetRegistry.connect(owner).swapRegistered(revertCollateral.address) + ).be.revertedWith('collateral not sound') + }) }) }) diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 808a21eca9..877effc053 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -715,10 +715,14 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function const CollFactory = await ethers.getContractFactory('FiatCollateral') const MainFactory = await ethers.getContractFactory('MainP0') const BrokerFactory = await ethers.getContractFactory('BrokerP0') + const GnosisTradeFactory = await ethers.getContractFactory('GnosisTrade') + const DutchTradeFactory = await ethers.getContractFactory('DutchTrade') // Deployments const main = await MainFactory.deploy() const broker = await BrokerFactory.deploy() + const gnosisTradeImpl = await GnosisTradeFactory.deploy() + const dutchTradeImpl = await DutchTradeFactory.deploy() await main.init( { rToken: ONE_ADDRESS, @@ -743,9 +747,9 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await broker.init( main.address, easyAuction.address, - ONE_ADDRESS, + gnosisTradeImpl.address, config.batchAuctionLength, - ONE_ADDRESS, + dutchTradeImpl.address, config.dutchAuctionLength ) const sellTok = await ERC20Factory.deploy('Sell Token', 'SELL', sellTokDecimals) From 809a8d2cbcd8c787591659eea52e2bc50e6996d3 Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Thu, 3 Aug 2023 09:39:17 -0400 Subject: [PATCH 354/462] useAvailable when setting throttles. (#872) --- contracts/p0/RToken.sol | 2 ++ contracts/p1/RToken.sol | 2 ++ test/RToken.test.ts | 52 +++++++++++++++++++++++++++++++++++ test/ZTradingExtremes.test.ts | 6 ++++ 4 files changed, 62 insertions(+) diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index 2ba631e3a2..e7ce86a0b2 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -337,6 +337,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { require(params.amtRate >= MIN_THROTTLE_RATE_AMT, "issuance amtRate too small"); require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "issuance amtRate too big"); require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "issuance pctRate too big"); + issuanceThrottle.useAvailable(totalSupply(), 0); emit IssuanceThrottleSet(issuanceThrottle.params, params); issuanceThrottle.params = params; } @@ -346,6 +347,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { require(params.amtRate >= MIN_THROTTLE_RATE_AMT, "redemption amtRate too small"); require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "redemption amtRate too big"); require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "redemption pctRate too big"); + redemptionThrottle.useAvailable(totalSupply(), 0); emit RedemptionThrottleSet(redemptionThrottle.params, params); redemptionThrottle.params = params; } diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 942e2d2501..f68a43a53e 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -442,6 +442,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { require(params.amtRate >= MIN_THROTTLE_RATE_AMT, "issuance amtRate too small"); require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "issuance amtRate too big"); require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "issuance pctRate too big"); + issuanceThrottle.useAvailable(totalSupply(), 0); emit IssuanceThrottleSet(issuanceThrottle.params, params); issuanceThrottle.params = params; } @@ -451,6 +452,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { require(params.amtRate >= MIN_THROTTLE_RATE_AMT, "redemption amtRate too small"); require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "redemption amtRate too big"); require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "redemption pctRate too big"); + redemptionThrottle.useAvailable(totalSupply(), 0); emit RedemptionThrottleSet(redemptionThrottle.params, params); redemptionThrottle.params = params; } diff --git a/test/RToken.test.ts b/test/RToken.test.ts index c13c235db7..4652dec1c9 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -224,6 +224,26 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ).to.be.revertedWith('issuance pctRate too big') }) + it('Should account for accrued value when updating issuance throttle parameters', async () => { + await advanceTime(12 * 5 * 60) // 60 minutes, charge fully + const issuanceThrottleParams = { amtRate: fp('60'), pctRate: fp('0.1') } + + await rToken.connect(owner).setIssuanceThrottleParams(issuanceThrottleParams) + const params = await rToken.issuanceThrottleParams() + expect(params[0]).to.equal(issuanceThrottleParams.amtRate) + expect(params[1]).to.equal(issuanceThrottleParams.pctRate) + + await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, initialBal))) + await rToken.connect(addr1).issue(fp('20')) + expect(await rToken.issuanceAvailable()).to.equal(fp('40')) + + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12 * 5 * 10) // 10 minutes + + issuanceThrottleParams.amtRate = fp('100') + await rToken.connect(owner).setIssuanceThrottleParams(issuanceThrottleParams) + expect(await rToken.issuanceAvailable()).to.equal(fp('50')) + }) + it('Should allow to update redemption throttle if Owner and perform validations', async () => { const redemptionThrottleParams = { amtRate: fp('1'), pctRate: fp('0.1') } await expect( @@ -262,6 +282,30 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ).to.be.revertedWith('redemption pctRate too big') }) + it('Should account for accrued value when updating redemption throttle parameters', async () => { + await advanceTime(12 * 5 * 60) // 60 minutes, charge fully + const issuanceThrottleParams = { amtRate: fp('100'), pctRate: fp('0.1') } + const redemptionThrottleParams = { amtRate: fp('60'), pctRate: fp('0.1') } + + await rToken.connect(owner).setIssuanceThrottleParams(issuanceThrottleParams) + await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + const params = await rToken.redemptionThrottleParams() + expect(params[0]).to.equal(redemptionThrottleParams.amtRate) + expect(params[1]).to.equal(redemptionThrottleParams.pctRate) + + await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, initialBal))) + await rToken.connect(addr1).issue(fp('100')) + expect(await rToken.redemptionAvailable()).to.equal(fp('60')) + await rToken.connect(addr1).redeem(fp('30')) + expect(await rToken.redemptionAvailable()).to.equal(fp('30')) + + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12 * 5 * 10) // 10 minutes + + redemptionThrottleParams.amtRate = fp('100') + await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + expect(await rToken.redemptionAvailable()).to.equal(fp('40')) + }) + it('Should return a price of 0 if the assets become unregistered', async () => { const startPrice = await basketHandler.price() @@ -367,6 +411,8 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await Promise.all( tokens.map((t) => t.connect(addr1).approve(rToken.address, MAX_THROTTLE_AMT_RATE)) ) + // advance time + await advanceTime(12 * 5 * 60) // 60 minutes, charge fully await rToken.connect(addr1).issue(MAX_THROTTLE_AMT_RATE) expect(await rToken.totalSupply()).to.equal(MAX_THROTTLE_AMT_RATE) expect(await rToken.basketsNeeded()).to.equal(MAX_THROTTLE_AMT_RATE) @@ -1370,6 +1416,9 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { redemptionThrottleParams.pctRate = bn(0) await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + // advance time + await advanceTime(12 * 5 * 60) // 60 minutes, charge fully + // Check redemption throttle expect(await rToken.redemptionAvailable()).to.equal(redemptionThrottleParams.amtRate) @@ -2183,6 +2232,9 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { redemptionThrottleParams.pctRate = bn(0) await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + // advance time + await advanceTime(12 * 5 * 60) // 60 minutes, charge fully + // Check redemption throttle expect(await rToken.redemptionAvailable()).to.equal(redemptionThrottleParams.amtRate) diff --git a/test/ZTradingExtremes.test.ts b/test/ZTradingExtremes.test.ts index 537ba81761..77ca05227d 100644 --- a/test/ZTradingExtremes.test.ts +++ b/test/ZTradingExtremes.test.ts @@ -480,6 +480,9 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, const noThrottle = { amtRate: MAX_THROTTLE_AMT_RATE, pctRate: 0 } await rToken.setIssuanceThrottleParams(noThrottle) await rToken.setRedemptionThrottleParams(noThrottle) + + await advanceTime(12 * 5 * 60) // 60 minutes, charge fully + await rToken.connect(addr1).issue(rTokenSupply) expect(await rToken.balanceOf(addr1.address)).to.equal(rTokenSupply) @@ -651,6 +654,9 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, const noThrottle = { amtRate: MAX_THROTTLE_AMT_RATE, pctRate: 0 } await rToken.setIssuanceThrottleParams(noThrottle) await rToken.setRedemptionThrottleParams(noThrottle) + + await advanceTime(12 * 5 * 60) // 60 minutes, charge fully + await rToken.connect(addr1).issue(rTokenSupply) expect(await rToken.balanceOf(addr1.address)).to.equal(rTokenSupply) From 64ed2a9e82fb33031fb3c53bf87c658ed869e529 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 4 Aug 2023 01:10:42 +0200 Subject: [PATCH 355/462] Disable dutch auctions per-collateral (#873) Co-authored-by: Akshat Mittal --- contracts/facade/FacadeTest.sol | 24 +- contracts/interfaces/IBroker.sol | 18 +- contracts/p0/Broker.sol | 45 ++- contracts/p0/mixins/Trading.sol | 1 - contracts/p1/Broker.sol | 55 ++- contracts/plugins/mocks/InvalidBrokerMock.sol | 12 +- contracts/plugins/trading/DutchTrade.sol | 194 ++++++---- test/Broker.test.ts | 211 ++++++----- test/Facade.test.ts | 12 +- test/Recollateralization.test.ts | 47 +-- test/Revenues.test.ts | 332 ++++++++++++++---- test/Upgradeability.test.ts | 8 +- test/integration/EasyAuction.test.ts | 8 +- test/utils/trades.ts | 32 +- 14 files changed, 690 insertions(+), 309 deletions(-) diff --git a/contracts/facade/FacadeTest.sol b/contracts/facade/FacadeTest.sol index e0683265fe..d16c6ab6d3 100644 --- a/contracts/facade/FacadeTest.sol +++ b/contracts/facade/FacadeTest.sol @@ -24,6 +24,11 @@ contract FacadeTest is IFacadeTest { /// Prompt all traders to run auctions /// Relatively gas-inefficient, shouldn't be used in production. Use multicall instead function runAuctionsForAllTraders(IRToken rToken) external { + runAuctionsForAllTradersForKind(rToken, TradeKind.BATCH_AUCTION); + } + + // Prompt all traders to run auctions of a specific kind + function runAuctionsForAllTradersForKind(IRToken rToken, TradeKind kind) public { IMain main = rToken.main(); IBackingManager backingManager = main.backingManager(); IRevenueTrader rsrTrader = main.rsrTrader(); @@ -55,12 +60,17 @@ contract FacadeTest is IFacadeTest { try main.backingManager().forwardRevenue(erc20s) {} catch {} // Start exact RSR auctions - (IERC20[] memory rsrERC20s, TradeKind[] memory rsrKinds) = traderERC20s(rsrTrader, erc20s); + (IERC20[] memory rsrERC20s, TradeKind[] memory rsrKinds) = traderERC20s( + rsrTrader, + kind, + erc20s + ); try main.rsrTrader().manageTokens(rsrERC20s, rsrKinds) {} catch {} // Start exact RToken auctions (IERC20[] memory rTokenERC20s, TradeKind[] memory rTokenKinds) = traderERC20s( rTokenTrader, + kind, erc20s ); try main.rTokenTrader().manageTokens(rTokenERC20s, rTokenKinds) {} catch {} @@ -115,11 +125,11 @@ contract FacadeTest is IFacadeTest { // === Private === - function traderERC20s(IRevenueTrader trader, IERC20[] memory erc20sAll) - private - view - returns (IERC20[] memory erc20s, TradeKind[] memory kinds) - { + function traderERC20s( + IRevenueTrader trader, + TradeKind kind, + IERC20[] memory erc20sAll + ) private view returns (IERC20[] memory erc20s, TradeKind[] memory kinds) { uint256 len; IERC20[] memory traderERC20sAll = new IERC20[](erc20sAll.length); for (uint256 i = 0; i < erc20sAll.length; ++i) { @@ -136,7 +146,7 @@ contract FacadeTest is IFacadeTest { kinds = new TradeKind[](len); for (uint256 i = 0; i < len; ++i) { erc20s[i] = traderERC20sAll[i]; - kinds[i] = TradeKind.BATCH_AUCTION; + kinds[i] = kind; } } } diff --git a/contracts/interfaces/IBroker.sol b/contracts/interfaces/IBroker.sol index cbb2f9cbdf..c0496801a0 100644 --- a/contracts/interfaces/IBroker.sol +++ b/contracts/interfaces/IBroker.sol @@ -38,7 +38,12 @@ interface IBroker is IComponent { event DutchTradeImplementationSet(ITrade indexed oldVal, ITrade indexed newVal); event BatchAuctionLengthSet(uint48 indexed oldVal, uint48 indexed newVal); event DutchAuctionLengthSet(uint48 indexed oldVal, uint48 indexed newVal); - event DisabledSet(bool indexed prevVal, bool indexed newVal); + event BatchTradeDisabledSet(bool indexed prevVal, bool indexed newVal); + event DutchTradeDisabledSet( + IERC20Metadata indexed erc20, + bool indexed prevVal, + bool indexed newVal + ); // Initialization function init( @@ -62,7 +67,9 @@ interface IBroker is IComponent { /// Only callable by one of the trading contracts the broker deploys function reportViolation() external; - function disabled() external view returns (bool); + function batchTradeDisabled() external view returns (bool); + + function dutchTradeDisabled(IERC20Metadata erc20) external view returns (bool); } interface TestIBroker is IBroker { @@ -86,5 +93,10 @@ interface TestIBroker is IBroker { function setDutchAuctionLength(uint48 newAuctionLength) external; - function setDisabled(bool disabled_) external; + function setBatchTradeDisabled(bool disabled) external; + + function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external; + + // only present on pre-3.0.0 Brokers; used by EasyAuction regression test + function disabled() external view returns (bool); } diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 9cf00da1f0..41584907d2 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -25,7 +25,7 @@ contract BrokerP0 is ComponentP0, IBroker { uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration -1 week // solhint-disable-next-line var-name-mixedcase - uint48 public immutable MIN_AUCTION_LENGTH; // {s} 2 blocks based on network + uint48 public immutable MIN_AUCTION_LENGTH; // {s} 20 blocks, based on network // Added for interface compatibility with P1 ITrade public batchTradeImplementation; @@ -38,10 +38,12 @@ contract BrokerP0 is ComponentP0, IBroker { uint48 public batchAuctionLength; // {s} the length of a Gnosis EasyAuction uint48 public dutchAuctionLength; // {s} the length of a Dutch Auction - bool public disabled; + bool public batchTradeDisabled; + + mapping(IERC20Metadata => bool) public dutchTradeDisabled; constructor() { - MIN_AUCTION_LENGTH = NetworkConfigLib.blocktime() * 2; + MIN_AUCTION_LENGTH = NetworkConfigLib.blocktime() * 20; } function init( @@ -69,7 +71,6 @@ contract BrokerP0 is ComponentP0, IBroker { TradeRequest memory req, TradePrices memory prices ) external returns (ITrade) { - require(!disabled, "broker disabled"); assert(req.sellAmount > 0); address caller = _msgSender(); @@ -93,8 +94,23 @@ contract BrokerP0 is ComponentP0, IBroker { /// @custom:protected function reportViolation() external notTradingPausedOrFrozen { require(trades[_msgSender()], "unrecognized trade contract"); - emit DisabledSet(disabled, true); - disabled = true; + ITrade trade = ITrade(_msgSender()); + TradeKind kind = trade.KIND(); + + if (kind == TradeKind.BATCH_AUCTION) { + emit BatchTradeDisabledSet(batchTradeDisabled, true); + batchTradeDisabled = true; + } else if (kind == TradeKind.DUTCH_AUCTION) { + IERC20Metadata sell = trade.sell(); + emit DutchTradeDisabledSet(sell, dutchTradeDisabled[sell], true); + dutchTradeDisabled[sell] = true; + + IERC20Metadata buy = trade.buy(); + emit DutchTradeDisabledSet(buy, dutchTradeDisabled[buy], true); + dutchTradeDisabled[buy] = true; + } else { + revert("unrecognized trade kind"); + } } /// @param maxTokensAllowed {qTok} The max number of sell tokens allowed by the trading platform @@ -174,6 +190,7 @@ contract BrokerP0 is ComponentP0, IBroker { // === Private === function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { + require(!batchTradeDisabled, "batch auctions disabled"); require(batchAuctionLength > 0, "batch auctions not enabled"); GnosisTrade trade = GnosisTrade(Clones.clone(address(batchTradeImplementation))); trades[address(trade)] = true; @@ -202,6 +219,10 @@ contract BrokerP0 is ComponentP0, IBroker { TradePrices memory prices, ITrading caller ) private returns (ITrade) { + require( + !dutchTradeDisabled[req.sell.erc20()] && !dutchTradeDisabled[req.buy.erc20()], + "dutch auctions disabled for token pair" + ); require(dutchAuctionLength > 0, "dutch auctions not enabled"); DutchTrade trade = DutchTrade(Clones.clone(address(dutchTradeImplementation))); trades[address(trade)] = true; @@ -217,8 +238,14 @@ contract BrokerP0 is ComponentP0, IBroker { } /// @custom:governance - function setDisabled(bool disabled_) external governance { - emit DisabledSet(disabled, disabled_); - disabled = disabled_; + function setBatchTradeDisabled(bool disabled) external governance { + emit BatchTradeDisabledSet(batchTradeDisabled, disabled); + batchTradeDisabled = disabled; + } + + /// @custom:governance + function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external governance { + emit DutchTradeDisabledSet(erc20, dutchTradeDisabled[erc20], disabled); + dutchTradeDisabled[erc20] = disabled; } } diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index 4df64445c8..b49aee3f11 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -67,7 +67,6 @@ abstract contract TradingP0 is RewardableP0, ITrading { ) internal returns (ITrade trade) { IBroker broker = main.broker(); assert(address(trades[req.sell.erc20()]) == address(0)); - require(!broker.disabled(), "broker disabled"); req.sell.erc20().safeApprove(address(broker), 0); req.sell.erc20().safeApprove(address(broker), req.sellAmount); diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index f248cb3fcd..98e52120e4 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -26,7 +26,7 @@ contract BrokerP1 is ComponentP1, IBroker { uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration - 1 week /// @custom:oz-upgrades-unsafe-allow state-variable-immutable // solhint-disable-next-line var-name-mixedcase - uint48 public immutable MIN_AUCTION_LENGTH; // {s} 2 blocks based on network + uint48 public immutable MIN_AUCTION_LENGTH; // {s} 20 blocks, based on network IBackingManager private backingManager; IRevenueTrader private rsrTrader; @@ -43,9 +43,10 @@ contract BrokerP1 is ComponentP1, IBroker { // {s} the length of a Gnosis EasyAuction. Governance parameter. uint48 public batchAuctionLength; - // Whether trading is disabled. - // Initially false. Settable by OWNER. A trade clone can set it to true via reportViolation() - bool public disabled; + // Whether Batch Auctions are disabled. + // Initially false. Settable by OWNER. + // A GnosisTrade clone can set it to true via reportViolation() + bool public batchTradeDisabled; // The set of ITrade (clone) addresses this contract has created mapping(address => bool) private trades; @@ -58,12 +59,15 @@ contract BrokerP1 is ComponentP1, IBroker { // {s} the length of a Dutch Auction. Governance parameter. uint48 public dutchAuctionLength; + // Whether Dutch Auctions are currently disabled, per ERC20 + mapping(IERC20Metadata => bool) public dutchTradeDisabled; + // ==== Invariant ==== // (trades[addr] == true) iff this contract has created an ITrade clone at addr /// @custom:oz-upgrades-unsafe-allow constructor constructor() { - MIN_AUCTION_LENGTH = NetworkConfigLib.blocktime() * 2; + MIN_AUCTION_LENGTH = NetworkConfigLib.blocktime() * 20; } // effects: initial parameters are set @@ -93,7 +97,6 @@ contract BrokerP1 is ComponentP1, IBroker { /// @dev Requires setting an allowance in advance /// @custom:protected and @custom:interaction CEI // checks: - // not disabled, paused (trading), or frozen // caller is a system Trader // effects: // Deploys a new trade clone, `trade` @@ -106,8 +109,6 @@ contract BrokerP1 is ComponentP1, IBroker { TradeRequest memory req, TradePrices memory prices ) external returns (ITrade) { - require(!disabled, "broker disabled"); - address caller = _msgSender(); require( caller == address(backingManager) || @@ -129,8 +130,23 @@ contract BrokerP1 is ComponentP1, IBroker { // effects: disabled' = true function reportViolation() external notTradingPausedOrFrozen { require(trades[_msgSender()], "unrecognized trade contract"); - emit DisabledSet(disabled, true); - disabled = true; + ITrade trade = ITrade(_msgSender()); + TradeKind kind = trade.KIND(); + + if (kind == TradeKind.BATCH_AUCTION) { + emit BatchTradeDisabledSet(batchTradeDisabled, true); + batchTradeDisabled = true; + } else if (kind == TradeKind.DUTCH_AUCTION) { + IERC20Metadata sell = trade.sell(); + emit DutchTradeDisabledSet(sell, dutchTradeDisabled[sell], true); + dutchTradeDisabled[sell] = true; + + IERC20Metadata buy = trade.buy(); + emit DutchTradeDisabledSet(buy, dutchTradeDisabled[buy], true); + dutchTradeDisabled[buy] = true; + } else { + revert("unrecognized trade kind"); + } } // === Setters === @@ -188,14 +204,21 @@ contract BrokerP1 is ComponentP1, IBroker { } /// @custom:governance - function setDisabled(bool disabled_) external governance { - emit DisabledSet(disabled, disabled_); - disabled = disabled_; + function setBatchTradeDisabled(bool disabled) external governance { + emit BatchTradeDisabledSet(batchTradeDisabled, disabled); + batchTradeDisabled = disabled; + } + + /// @custom:governance + function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external governance { + emit DutchTradeDisabledSet(erc20, dutchTradeDisabled[erc20], disabled); + dutchTradeDisabled[erc20] = disabled; } // === Private === function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { + require(!batchTradeDisabled, "batch auctions disabled"); require(batchAuctionLength > 0, "batch auctions not enabled"); GnosisTrade trade = GnosisTrade(address(batchTradeImplementation).clone()); trades[address(trade)] = true; @@ -224,6 +247,10 @@ contract BrokerP1 is ComponentP1, IBroker { TradePrices memory prices, ITrading caller ) private returns (ITrade) { + require( + !dutchTradeDisabled[req.sell.erc20()] && !dutchTradeDisabled[req.buy.erc20()], + "dutch auctions disabled for token pair" + ); require(dutchAuctionLength > 0, "dutch auctions not enabled"); DutchTrade trade = DutchTrade(address(dutchTradeImplementation).clone()); trades[address(trade)] = true; @@ -244,5 +271,5 @@ contract BrokerP1 is ComponentP1, IBroker { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[43] private __gap; + uint256[42] private __gap; } diff --git a/contracts/plugins/mocks/InvalidBrokerMock.sol b/contracts/plugins/mocks/InvalidBrokerMock.sol index 525d1f7559..7b19993c94 100644 --- a/contracts/plugins/mocks/InvalidBrokerMock.sol +++ b/contracts/plugins/mocks/InvalidBrokerMock.sol @@ -22,7 +22,9 @@ contract InvalidBrokerMock is ComponentP0, IBroker { uint48 public batchAuctionLength; // {s} the length of a batch auction uint48 public dutchAuctionLength; // {s} the length of a dutch auction - bool public disabled = false; + bool public batchTradeDisabled = false; + + mapping(IERC20Metadata => bool) public dutchTradeDisabled; function init( IMain main_, @@ -44,8 +46,6 @@ contract InvalidBrokerMock is ComponentP0, IBroker { TradeRequest memory, TradePrices memory ) external view notTradingPausedOrFrozen returns (ITrade) { - require(!disabled, "broker disabled"); - // Revert when opening trades revert("Failure opening trade"); } @@ -64,5 +64,9 @@ contract InvalidBrokerMock is ComponentP0, IBroker { /// Dummy implementation /* solhint-disable no-empty-blocks */ - function setDisabled(bool disabled_) external governance {} + function setBatchTradeDisabled(bool disabled) external governance {} + + /// Dummy implementation + /* solhint-disable no-empty-blocks */ + function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external governance {} } diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index d73777e094..98eecfeb96 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -9,36 +9,65 @@ import "../../interfaces/IAsset.sol"; import "../../interfaces/IBroker.sol"; import "../../interfaces/ITrade.sol"; -uint192 constant FORTY_PERCENT = 4e17; // {1} 0.4 -uint192 constant SIXTY_PERCENT = 6e17; // {1} 0.6 - -// Exponential price decay with base (999999/1000000). Price starts at 1000x and decays to <1x -// A 30-minute auction on a chain with a 12-second blocktime has a ~10.87% price drop per block -// during the geometric/exponential period and a 0.05% drop per block during the linear period. -// 30-minutes is the recommended length of auction for a chain with 12-second blocktimes, but -// longer and shorter times can be used as well. The pricing method does not degrade -// beyond the degree to which less overall blocktime means necessarily larger price drops. -uint192 constant MAX_EXP = 6907752 * FIX_ONE; // {1} (1000000/999999)^6907752 = ~1000x +// A dutch auction in 4 parts: +// 1. 0% - 20%: Geometric decay from 1000x the bestPrice to ~1.5x the bestPrice +// 2. 20% - 45%: Linear decay from ~1.5x the bestPrice to the bestPrice +// 3. 45% - 95%: Linear decay from the bestPrice to the worstPrice +// 4. 95% - 100%: Constant at the worstPrice +// +// For a trade between 2 assets with 1% oracleError: +// A 30-minute auction on a chain with a 12-second blocktime has a ~20% price drop per block +// during the 1st period, ~0.8% during the 2nd period, and ~0.065% during the 3rd period. +// +// 30-minutes is the recommended length of auction for a chain with 12-second blocktimes. +// 6 minutes, 7.5 minutes, 15 minutes, 1.5 minutes for each pariod respectively. +// +// Longer and shorter times can be used as well. The pricing method does not degrade +// beyond the degree to which less overall blocktime means less overall precision. + +uint192 constant FIVE_PERCENT = 5e16; // {1} 0.05 +uint192 constant TWENTY_PERCENT = 20e16; // {1} 0.2 +uint192 constant TWENTY_FIVE_PERCENT = 25e16; // {1} 0.25 +uint192 constant FORTY_FIVE_PERCENT = 45e16; // {1} 0.45 +uint192 constant FIFTY_PERCENT = 50e16; // {1} 0.5 +uint192 constant NINETY_FIVE_PERCENT = 95e16; // {1} 0.95 + +uint192 constant MAX_EXP = 6502287e18; // {1} (1000000/999999)^6502287 = ~666.6667 uint192 constant BASE = 999999e12; // {1} (999999/1000000) +uint192 constant ONE_POINT_FIVE = 150e16; // {1} 1.5 /** * @title DutchTrade - * @notice Implements a wholesale dutch auction via a piecewise falling-price mechansim. - * Over the first 40% of the auction the price falls from ~1000x the best plausible price - * down to the best plausible price in a geometric series. The price decreases by the same % - * each time. At 30 minutes the decreases are 10.87% per block. Longer auctions have - * smaller price decreases, and shorter auctions have larger price decreases. + * @notice Implements a wholesale dutch auction via a 4-piecewise falling-price mechansim. + * The overall idea is to handle 4 cases: + * 1. Price manipulation of the exchange rate up to 1000x (eg: via a read-only reentrancy) + * 2. Price movement of up to 50% during the auction + * 3. Typical case: no significant price movement; clearing price within expected range + * 4. No bots online; manual human doing bidding; additional time for tx clearing + * + * Case 1: Over the first 20% of the auction the price falls from ~1000x the best plausible + * price down to 1.5x the best plausible price in a geometric series. * This period DOES NOT expect to receive a bid; it just defends against manipulated prices. + * If a bid occurs during this period, a violation is reported to the Broker. + * This is still safe for the protocol since other trades, with price discovery, can occur. + * + * Case 2: Over the next 20% of the auction the price falls from 1.5x the best plausible price + * to the best plausible price, linearly. No violation is reported if a bid occurs. This case + * exists to handle cases where prices change after the auction is started, naturally. * - * Over the last 60% of the auction the price falls from the best plausible price to the worst - * price, linearly. The worst price is further discounted by the maxTradeSlippage as a fraction - * of how far from minTradeVolume to maxTradeVolume the trade lies. + * Case 3: Over the next 50% of the auction the price falls from the best plausible price to the + * worst price, linearly. The worst price is further discounted by the maxTradeSlippage as a + * fraction of how far from minTradeVolume to maxTradeVolume the trade lies. * At maxTradeVolume, no additonal discount beyond the oracle errors is applied. + * This is the phase of the auction where bids will typically occur. + * + * Case 4: Lastly the price stays at the worst price for the final 5% of the auction to allow + * a bid to occur if no bots are online and the only bidders are humans. * * To bid: - * 1. Call `bidAmount()` view to check prices at various timestamps + * 1. Call `bidAmount()` view to check prices at various blocks. * 2. Provide approval of sell tokens for precisely the `bidAmount()` desired - * 3. Wait until a desirable block is reached (hopefully not in the first 40% of the auction) + * 3. Wait until the desired block is reached (hopefully not in the first 20% of the auction) * 4. Call bid() */ contract DutchTrade is ITrade { @@ -52,6 +81,7 @@ contract DutchTrade is ITrade { TradeStatus public status; // reentrancy protection + IBroker public broker; // The Broker that cloned this contract into existence ITrading public origin; // the address that initialized the contract // === Auction === @@ -59,13 +89,14 @@ contract DutchTrade is ITrade { IERC20Metadata public buy; uint192 public sellAmount; // {sellTok} - // The auction runs from [startTime, endTime], inclusive - uint48 public startTime; // {s} when the dutch auction begins (one block after init()) - uint48 public endTime; // {s} when the dutch auction ends if no bids are received + // The auction runs from [startBlock, endTime], inclusive + uint256 public startBlock; // {block} when the dutch auction begins (one block after init()) + uint256 public endBlock; // {block} when the dutch auction ends if no bids are received + uint48 public endTime; // {s} not used in this contract; needed on interface - // highPrice is always 1000x the middlePrice, so we don't need to track it explicitly - uint192 public middlePrice; // {buyTok/sellTok} The price at which the function is piecewise - uint192 public lowPrice; // {buyTok/sellTok} The price the auction ends at + uint192 public bestPrice; // {buyTok/sellTok} The best plausible price based on oracle data + uint192 public worstPrice; // {buyTok/sellTok} The worst plausible price based on oracle data + // and further discounted by a fraction of maxTradeSlippage based on auction volume. // === Bid === address public bidder; @@ -80,20 +111,13 @@ contract DutchTrade is ITrade { status = end; } - // === Public Bid Helper === + // === External Bid Helper === - /// Calculates how much buy token is needed to purchase the lot, at a particular timestamp - /// @param timestamp {s} The block timestamp to get price for + /// Calculates how much buy token is needed to purchase the lot at a particular block + /// @param blockNumber {block} The block number of the bid /// @return {qBuyTok} The amount of buy tokens required to purchase the lot - function bidAmount(uint48 timestamp) public view returns (uint256) { - require(timestamp >= startTime, "auction not started"); - require(timestamp <= endTime, "auction over"); - - // {buyTok/sellTok} - uint192 price = _price(timestamp); - - // {qBuyTok} = {sellTok} * {buyTok/sellTok} * {qBuyTok/buyTok} - return sellAmount.mul(price, CEIL).shiftl_toUint(int8(buy.decimals()), CEIL); + function bidAmount(uint256 blockNumber) external view returns (uint256) { + return _bidAmount(_price(blockNumber)); } constructor() { @@ -120,21 +144,23 @@ contract DutchTrade is ITrade { assert( address(sell_) != address(0) && address(buy_) != address(0) && - auctionLength >= 2 * ONE_BLOCK + auctionLength >= 20 * ONE_BLOCK ); // misuse by caller // Only start dutch auctions under well-defined prices - require(prices.sellLow > 0 && prices.sellHigh < FIX_MAX, "bad sell pricing"); - require(prices.buyLow > 0 && prices.buyHigh < FIX_MAX, "bad buy pricing"); + require(prices.sellLow > 0 && prices.sellHigh < FIX_MAX / 1000, "bad sell pricing"); + require(prices.buyLow > 0 && prices.buyHigh < FIX_MAX / 1000, "bad buy pricing"); + broker = IBroker(msg.sender); origin = origin_; sell = sell_.erc20(); buy = buy_.erc20(); require(sellAmount_ <= sell.balanceOf(address(this)), "unfunded trade"); sellAmount = shiftl_toFix(sellAmount_, -int8(sell.decimals())); // {sellTok} - startTime = uint48(block.timestamp) + ONE_BLOCK; // start in the next block - endTime = startTime + auctionLength; + startBlock = block.number + 1; // start in the next block + endBlock = startBlock + auctionLength / ONE_BLOCK; // FLOOR, since endBlock is inclusive + endTime = uint48(block.timestamp + ONE_BLOCK * (endBlock - startBlock)); // {1} uint192 slippage = _slippage( @@ -144,11 +170,9 @@ contract DutchTrade is ITrade { ); // {buyTok/sellTok} = {UoA/sellTok} * {1} / {UoA/buyTok} - lowPrice = prices.sellLow.mulDiv(FIX_ONE - slippage, prices.buyHigh, FLOOR); - middlePrice = prices.sellHigh.div(prices.buyLow, CEIL); // no additional slippage - // highPrice = 1000 * middlePrice - - assert(lowPrice <= middlePrice); + worstPrice = prices.sellLow.mulDiv(FIX_ONE - slippage, prices.buyHigh, FLOOR); + bestPrice = prices.sellHigh.div(prices.buyLow, CEIL); // no additional slippage + assert(worstPrice <= bestPrice); } /// Bid for the auction lot at the current price; settling atomically via a callback @@ -157,8 +181,11 @@ contract DutchTrade is ITrade { function bid() external returns (uint256 amountIn) { require(bidder == address(0), "bid already received"); + // {buyTok/sellTok} + uint192 price = _price(block.number); // enforces auction ongoing + // {qBuyTok} - amountIn = bidAmount(uint48(block.timestamp)); // enforces auction ongoing + amountIn = _bidAmount(price); // Transfer in buy tokens bidder = msg.sender; @@ -167,6 +194,11 @@ contract DutchTrade is ITrade { // status must begin OPEN assert(status == TradeStatus.OPEN); + // reportViolation if auction cleared in geometric phase + if (price > bestPrice.mul(ONE_POINT_FIVE, CEIL)) { + broker.reportViolation(); + } + // settle() via callback origin.settleTrade(sell); @@ -188,7 +220,7 @@ contract DutchTrade is ITrade { if (bidder != address(0)) { sell.safeTransfer(bidder, sellAmount); } else { - require(block.timestamp >= endTime, "auction not over"); + require(block.number > endBlock, "auction not over"); } uint256 sellBal = sell.balanceOf(address(this)); @@ -211,7 +243,7 @@ contract DutchTrade is ITrade { /// @return true iff the trade can be settled. // Guaranteed to be true some time after init(), until settle() is called function canSettle() external view returns (bool) { - return status == TradeStatus.OPEN && (bidder != address(0) || block.timestamp > endTime); + return status == TradeStatus.OPEN && (bidder != address(0) || block.number > endBlock); } /// @return {qSellTok} The size of the lot being sold, in token quanta @@ -246,29 +278,57 @@ contract DutchTrade is ITrade { } /// Return the price of the auction at a particular timestamp - /// @param timestamp {s} The block timestamp + /// @param blockNumber {block} The block number to get price for /// @return {buyTok/sellTok} - function _price(uint48 timestamp) private view returns (uint192) { + function _price(uint256 blockNumber) private view returns (uint192) { + require(blockNumber >= startBlock, "auction not started"); + require(blockNumber <= endBlock, "auction over"); + /// Price Curve: - /// - first 40%: geometrically decrease the price from 1000x the middlePrice to 1x - /// - last 60: decrease linearly from middlePrice to lowPrice + /// - first 20%: geometrically decrease the price from 1000x the bestPrice to 1.5x it + /// - next 25%: linearly decrease the price from 1.5x the bestPrice to 1x it + /// - next 50%: linearly decrease the price from bestPrice to worstPrice + /// - last 5%: constant at worstPrice - uint192 progression = divuu(timestamp - startTime, endTime - startTime); // {1} + uint192 progression = divuu(blockNumber - startBlock, endBlock - startBlock); // {1} - // Fast geometric decay -- 0%-40% of auction - if (progression < FORTY_PERCENT) { - uint192 exp = MAX_EXP.mulDiv(FORTY_PERCENT - progression, FORTY_PERCENT, ROUND); + // Fast geometric decay -- 0%-20% of auction + if (progression < TWENTY_PERCENT) { + uint192 exp = MAX_EXP.mulDiv(TWENTY_PERCENT - progression, TWENTY_PERCENT, ROUND); - // middlePrice * ((1000000/999999) ^ exp) = middlePrice / ((999999/1000000) ^ exp) - // safe uint48 downcast: exp is at-most 6907752 + // bestPrice * ((1000000/999999) ^ exp) = bestPrice / ((999999/1000000) ^ exp) + // safe uint48 downcast: exp is at-most 6502287 // {buyTok/sellTok} = {buyTok/sellTok} / {1} ^ {1} - return middlePrice.div(BASE.powu(uint48(exp.toUint(ROUND))), CEIL); - // this reverts for middlePrice >= 6.21654046e36 * FIX_ONE + return bestPrice.mulDiv(ONE_POINT_FIVE, BASE.powu(uint48(exp.toUint(ROUND))), CEIL); + // this reverts for bestPrice >= 6.21654046e36 * FIX_ONE + } else if (progression < FORTY_FIVE_PERCENT) { + // First linear decay -- 20%-45% of auction + // 1.5x -> 1x the bestPrice + + // {buyTok/sellTok} = {buyTok/sellTok} * {1} + uint192 highPrice = bestPrice.mul(ONE_POINT_FIVE, CEIL); + return + highPrice - + (highPrice - bestPrice).mulDiv(progression - TWENTY_PERCENT, TWENTY_FIVE_PERCENT); + } else if (progression < NINETY_FIVE_PERCENT) { + // Second linear decay -- 45%-95% of auction + // bestPrice -> worstPrice + + // {buyTok/sellTok} = {buyTok/sellTok} * {1} + return + bestPrice - + (bestPrice - worstPrice).mulDiv(progression - FORTY_FIVE_PERCENT, FIFTY_PERCENT); } - // Slow linear decay -- 40%-100% of auction - return - middlePrice - - (middlePrice - lowPrice).mulDiv(progression - FORTY_PERCENT, SIXTY_PERCENT); + // Constant price -- 95%-100% of auction + return worstPrice; + } + + /// Calculates how much buy token is needed to purchase the lot at a particular price + /// @param price {buyTok/sellTok} + /// @return {qBuyTok} The amount of buy tokens required to purchase the lot + function _bidAmount(uint192 price) public view returns (uint256) { + // {qBuyTok} = {sellTok} * {buyTok/sellTok} * {qBuyTok/buyTok} + return sellAmount.mul(price, CEIL).shiftl_toUint(int8(buy.decimals()), CEIL); } } diff --git a/test/Broker.test.ts b/test/Broker.test.ts index d920ec3d4f..a484b2d5e6 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -41,7 +41,13 @@ import { PRICE_TIMEOUT, } from './fixtures' import snapshotGasCost from './utils/snapshotGasCost' -import { advanceTime, advanceToTimestamp, getLatestBlockTimestamp } from './utils/time' +import { + advanceBlocks, + advanceTime, + advanceToTimestamp, + getLatestBlockTimestamp, + getLatestBlockNumber, +} from './utils/time' import { ITradeRequest } from './utils/trades' import { useEnv } from '#/utils/env' @@ -116,7 +122,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { it('Should setup Broker correctly', async () => { expect(await broker.gnosis()).to.equal(gnosis.address) expect(await broker.batchAuctionLength()).to.equal(config.batchAuctionLength) - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) expect(await broker.main()).to.equal(main.address) }) @@ -142,9 +149,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { main.address, gnosis.address, ZERO_ADDRESS, - bn('100'), + bn('1000'), ZERO_ADDRESS, - bn('100') + bn('1000') ) ).to.be.revertedWith('invalid batchTradeImplementation address') await expect( @@ -152,9 +159,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { main.address, gnosis.address, ONE_ADDRESS, - bn('100'), + bn('1000'), ZERO_ADDRESS, - bn('100') + bn('1000') ) ).to.be.revertedWith('invalid dutchTradeImplementation address') }) @@ -351,42 +358,70 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await broker.dutchAuctionLength()).to.equal(bn(0)) }) - it('Should allow to update disabled if Owner', async () => { + it('Should allow to update batchTradeDisabled/dutchTradeDisabled if Owner', async () => { // Check existing value - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) // If not owner cannot update - await expect(broker.connect(other).setDisabled(true)).to.be.revertedWith('governance only') + await expect(broker.connect(other).setBatchTradeDisabled(true)).to.be.revertedWith( + 'governance only' + ) + await expect( + broker.connect(other).setDutchTradeDisabled(token0.address, true) + ).to.be.revertedWith('governance only') // Check value did not change - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) - // Update with owner - await expect(broker.connect(owner).setDisabled(true)) - .to.emit(broker, 'DisabledSet') + // Update batchTradeDisabled with owner + await expect(broker.connect(owner).setBatchTradeDisabled(true)) + .to.emit(broker, 'BatchTradeDisabledSet') .withArgs(false, true) // Check value was updated - expect(await broker.disabled()).to.equal(true) + expect(await broker.batchTradeDisabled()).to.equal(true) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) // Update back to false - await expect(broker.connect(owner).setDisabled(false)) - .to.emit(broker, 'DisabledSet') + await expect(broker.connect(owner).setBatchTradeDisabled(false)) + .to.emit(broker, 'BatchTradeDisabledSet') .withArgs(true, false) // Check value was updated - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) + + // Update dutchTradeDisabled with owner + await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, true)) + .to.emit(broker, 'DutchTradeDisabledSet') + .withArgs(token0.address, false, true) + + // Check value was updated + expect(await broker.batchTradeDisabled()).to.equal(false) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(true) + expect(await broker.dutchTradeDisabled(token1.address)).to.equal(false) + + // Update back to false + await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, false)) + .to.emit(broker, 'DutchTradeDisabledSet') + .withArgs(token0.address, true, false) + + // Check value was updated + expect(await broker.batchTradeDisabled()).to.equal(false) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(token1.address)).to.equal(false) }) }) describe('Trade Management', () => { - it('Should not allow to open trade if Disabled', async () => { - // Disable Broker - await expect(broker.connect(owner).setDisabled(true)) - .to.emit(broker, 'DisabledSet') + it('Should not allow to open Batch trade if Disabled', async () => { + // Disable Broker Batch Auctions + await expect(broker.connect(owner).setBatchTradeDisabled(true)) + .to.emit(broker, 'BatchTradeDisabledSet') .withArgs(false, true) - // Attempt to open trade const tradeRequest: ITradeRequest = { sell: collateral0.address, buy: collateral1.address, @@ -394,13 +429,59 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { minBuyAmount: bn('0'), } + // Batch Auction openTrade should fail await whileImpersonating(backingManager.address, async (bmSigner) => { await expect( broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest, prices) - ).to.be.revertedWith('broker disabled') + ).to.be.revertedWith('batch auctions disabled') + }) + }) + + it('Should not allow to open Dutch trade if Disabled for either token', async () => { + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: bn('100e18'), + minBuyAmount: bn('0'), + } + await whileImpersonating(backingManager.address, async (bmSigner) => { + await token0.mint(backingManager.address, tradeRequest.sellAmount) + await token0.connect(bmSigner).approve(broker.address, tradeRequest.sellAmount) + + // Should succeed in callStatic + await broker + .connect(bmSigner) + .callStatic.openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) + + // Disable Broker Dutch Auctions for token0 + await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, true)) + .to.emit(broker, 'DutchTradeDisabledSet') + .withArgs(token0.address, false, true) + + // Dutch Auction openTrade should fail now + await expect( + broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) + ).to.be.revertedWith('dutch auctions disabled for token pair') + + // Re-enable Dutch Auctions for token0 + await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, false)) + .to.emit(broker, 'DutchTradeDisabledSet') + .withArgs(token0.address, true, false) + + // Should succeed in callStatic + await broker + .connect(bmSigner) + .callStatic.openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) + + // Disable Broker Dutch Auctions for token1 + await expect(broker.connect(owner).setDutchTradeDisabled(token1.address, true)) + .to.emit(broker, 'DutchTradeDisabledSet') + .withArgs(token1.address, false, true) + + // Dutch Auction openTrade should fail now await expect( broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) - ).to.be.revertedWith('broker disabled') + ).to.be.revertedWith('dutch auctions disabled for token pair') }) }) @@ -454,7 +535,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { it('Should not allow to report violation if not trade contract', async () => { // Check not disabled - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) // Should not allow to report violation from any address await expect(broker.connect(addr1).reportViolation()).to.be.revertedWith( @@ -469,12 +550,12 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) // Check nothing changed - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) }) it('Should not allow to report violation if paused or frozen', async () => { // Check not disabled - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) await main.connect(owner).pauseTrading() @@ -491,7 +572,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ) // Check nothing changed - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) }) }) @@ -937,6 +1018,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await token0.balanceOf(trade.address)).to.equal(0) expect(await token0.balanceOf(backingManager.address)).to.equal(amount.add(newFunds)) }) + + // There is no test here for the reportViolation case; that is in Revenues.test.ts }) context('DutchTrade', () => { @@ -978,14 +1061,15 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(token1.address) expect(await trade.sellAmount()).to.equal(amount) - expect(await trade.startTime()).to.equal((await getLatestBlockTimestamp()) + 12) + expect(await trade.startBlock()).to.equal((await getLatestBlockNumber()) + 1) + const tradeLen = (await trade.endBlock()).sub(await trade.startBlock()) expect(await trade.endTime()).to.equal( - (await trade.startTime()) + config.dutchAuctionLength.toNumber() + tradeLen.mul(12).add(await getLatestBlockTimestamp()) ) - expect(await trade.middlePrice()).to.equal( + expect(await trade.bestPrice()).to.equal( divCeil(prices.sellHigh.mul(fp('1')), prices.buyLow) ) - expect(await trade.lowPrice()).to.equal(prices.sellLow.mul(fp('1')).div(prices.buyHigh)) + expect(await trade.worstPrice()).to.equal(prices.sellLow.mul(fp('1')).div(prices.buyHigh)) expect(await trade.canSettle()).to.equal(false) // Attempt to initialize again @@ -1050,14 +1134,14 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ).to.not.be.reverted // Check trade values - expect(await trade.middlePrice()).to.equal( + expect(await trade.bestPrice()).to.equal( divCeil(prices.sellHigh.mul(fp('1')), prices.buyLow) ) const withoutSlippage = prices.sellLow.mul(fp('1')).div(prices.buyHigh) const withSlippage = withoutSlippage.sub( withoutSlippage.mul(config.maxTradeSlippage).div(fp('1')) ) - expect(await trade.lowPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) + expect(await trade.worstPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) }) it('Should apply full maxTradeSlippage with low maxTradeVolume', async () => { @@ -1093,57 +1177,14 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ).to.not.be.reverted // Check trade values - expect(await trade.middlePrice()).to.equal( + expect(await trade.bestPrice()).to.equal( divCeil(prices.sellHigh.mul(fp('1')), prices.buyLow) ) const withoutSlippage = prices.sellLow.mul(fp('1')).div(prices.buyHigh) const withSlippage = withoutSlippage.sub( withoutSlippage.mul(config.maxTradeSlippage).div(fp('1')) ) - expect(await trade.lowPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) - }) - - it('Should apply full maxTradeSlippage with low maxTradeVolume', async () => { - // Set low maxTradeVolume for collateral - const FiatCollateralFactory = await ethers.getContractFactory('FiatCollateral') - const newCollateral0: FiatCollateral = await FiatCollateralFactory.deploy({ - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: await collateral0.chainlinkFeed(), - oracleError: ORACLE_ERROR, - erc20: token0.address, - maxTradeVolume: bn(500), - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - }) - - // Refresh and swap collateral - await newCollateral0.refresh() - await assetRegistry.connect(owner).swapRegistered(newCollateral0.address) - - // Fund trade and initialize - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - backingManager.address, - newCollateral0.address, - collateral1.address, - amount, - config.dutchAuctionLength, - prices - ) - ).to.not.be.reverted - - // Check trade values - expect(await trade.middlePrice()).to.equal( - divCeil(prices.sellHigh.mul(fp('1')), prices.buyLow) - ) - const withoutSlippage = prices.sellLow.mul(fp('1')).div(prices.buyHigh) - const withSlippage = withoutSlippage.sub( - withoutSlippage.mul(config.maxTradeSlippage).div(fp('1')) - ) - expect(await trade.lowPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) + expect(await trade.worstPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) }) it('Should not allow to initialize an unfunded trade', async () => { @@ -1180,8 +1221,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await expect(trade.connect(bmSigner).settle()).to.be.revertedWith('auction not over') }) - // Advance time till trade can be settled - await advanceTime(config.dutchAuctionLength.add(100).toString()) + // Advance blocks til trade can be settled + const tradeLen = (await trade.endBlock()).sub(await getLatestBlockNumber()) + await advanceBlocks(tradeLen.add(1)) // Settle trade expect(await trade.canSettle()).to.equal(true) @@ -1218,8 +1260,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { 'only after trade is closed' ) - // Advance time till trade can be settled - await advanceTime(config.dutchAuctionLength.add(100).toString()) + // Advance blocks til trade can be settled + const tradeLen = (await trade.endBlock()).sub(await getLatestBlockNumber()) + await advanceBlocks(tradeLen.add(1)) // Settle trade await whileImpersonating(backingManager.address, async (bmSigner) => { @@ -1250,6 +1293,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await token0.balanceOf(trade.address)).to.equal(0) expect(await token0.balanceOf(backingManager.address)).to.equal(amount.add(newFunds)) }) + + // There is no test here for the reportViolation case; that is in Revenues.test.ts }) }) diff --git a/test/Facade.test.ts b/test/Facade.test.ts index 1ebc9145e4..5415dd39d6 100644 --- a/test/Facade.test.ts +++ b/test/Facade.test.ts @@ -46,7 +46,7 @@ import { defaultFixture, ORACLE_ERROR, } from './fixtures' -import { getLatestBlockTimestamp, setNextBlockTimestamp } from './utils/time' +import { advanceBlocks, getLatestBlockTimestamp, setNextBlockTimestamp } from './utils/time' import { CollateralStatus, TradeKind, MAX_UINT256, ZERO_ADDRESS } from '#/common/constants' import { expectTrade } from './utils/trades' import { mintCollaterals } from './utils/tokens' @@ -605,8 +605,8 @@ describe('FacadeRead + FacadeAct contracts', () => { // Nothing should be settleable expect((await facade.auctionsSettleable(trader.address)).length).to.equal(0) - // Advance time till auction ended - await advanceTime(auctionLength + 13) + // Advance time till auction is over + await advanceBlocks(2 + auctionLength / 12) // Now should be settleable const settleable = await facade.auctionsSettleable(trader.address) @@ -1097,7 +1097,7 @@ describe('FacadeRead + FacadeAct contracts', () => { expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(0) // Advance time till auction ended - await advanceTime(auctionLength + 13) + await advanceBlocks(1 + auctionLength / 12) // Settle and start new auction - Will retry await expectEvents( @@ -1161,7 +1161,7 @@ describe('FacadeRead + FacadeAct contracts', () => { expect((await facade.auctionsSettleable(rTokenTrader.address)).length).to.equal(0) // Advance time till auction ended - await advanceTime(auctionLength + 13) + await advanceBlocks(1 + auctionLength / 12) // Upgrade components to V2 await backingManager.connect(owner).upgradeTo(backingManagerV2.address) @@ -1196,7 +1196,7 @@ describe('FacadeRead + FacadeAct contracts', () => { await rTokenTrader.connect(owner).upgradeTo(revTraderV1.address) // Advance time till auction ended - await advanceTime(auctionLength + 13) + await advanceBlocks(1 + auctionLength / 12) // Settle and start new auction - Will retry again await expectEvents( diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 16ffc14f07..c6f62c1cc9 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -37,7 +37,13 @@ import { TestIStRSR, USDCMock, } from '../typechain' -import { advanceTime, advanceToTimestamp, getLatestBlockTimestamp } from './utils/time' +import { + advanceTime, + advanceBlocks, + advanceToTimestamp, + getLatestBlockTimestamp, + getLatestBlockNumber, +} from './utils/time' import { Collateral, defaultFixture, @@ -3279,7 +3285,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ) // Check the empty buffer block as well - await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength + 12) + await advanceBlocks(1) await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.be.revertedWith( 'already rebalancing' ) @@ -3296,29 +3302,30 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ) await token1.connect(addr1).approve(trade.address, initialBal) - const start = await trade.startTime() - const end = await trade.endTime() - await advanceToTimestamp(start) + const start = await trade.startBlock() + const end = await trade.endBlock() // Simulate 30 minutes of blocks, should swap at right price each time - for (let now = await getLatestBlockTimestamp(); now <= end; now += 12) { + let now = bn(await getLatestBlockNumber()) + while (now.lt(end)) { const actual = await trade.connect(addr1).bidAmount(now) const expected = divCeil( await dutchBuyAmount( - fp(now - start).div(end - start), - rTokenAsset.address, + fp(now.sub(start)).div(end.sub(start)), + collateral1.address, collateral0.address, issueAmount, config.minTradeVolume, config.maxTradeSlippage ), - bn('1e12') // fix for decimals + bn('1e12') ) expect(actual).to.be.closeTo(expected, expected.div(bn('1e15'))) const staticResult = await trade.connect(addr1).callStatic.bid() - expect(staticResult).to.equal(expected) - await advanceToTimestamp((await getLatestBlockTimestamp()) + 12) + expect(staticResult).to.equal(actual) + await advanceBlocks(1) + now = bn(await getLatestBlockNumber()) } }) @@ -3329,9 +3336,10 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await backingManager.trades(token0.address) ) await token1.connect(addr1).approve(trade.address, initialBal) - await advanceToTimestamp((await trade.endTime()) + 1) + + await advanceBlocks((await trade.endBlock()).sub(await getLatestBlockNumber()).add(1)) await expect( - trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + trade.connect(addr1).bidAmount(await getLatestBlockNumber()) ).to.be.revertedWith('auction over') await expect(trade.connect(addr1).bid()).be.revertedWith('auction over') @@ -3358,7 +3366,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await token1.connect(addr1).approve(trade1.address, initialBal) // Snipe auction at 0s left - await advanceToTimestamp((await trade1.endTime()) - 1) + await advanceBlocks((await trade1.endBlock()).sub(await getLatestBlockNumber()).sub(1)) await trade1.connect(addr1).bid() expect(await trade1.canSettle()).to.equal(false) expect(await trade1.status()).to.equal(2) // Status.CLOSED @@ -3367,7 +3375,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { const expected = divCeil( await dutchBuyAmount( - fp(auctionLength).div(auctionLength), // last possible second + fp('1'), // last block collateral0.address, collateral1.address, issueAmount, @@ -3397,8 +3405,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { it('even under worst-possible bids', async () => { await token1.connect(addr1).approve(trade2.address, initialBal) - // Advance to final second of auction - await advanceToTimestamp((await trade2.endTime()) - 1) + // Advance to final block of auction + await advanceBlocks((await trade2.endBlock()).sub(await getLatestBlockNumber()).sub(1)) expect(await trade2.status()).to.equal(1) // TradeStatus.OPEN expect(await trade2.canSettle()).to.equal(false) @@ -3407,8 +3415,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) it('via fallback to Batch Auction', async () => { - // Advance past auction timeout - await advanceToTimestamp((await trade2.endTime()) + 1) + // Advance past auction end block + await advanceBlocks((await trade2.endBlock()).sub(await getLatestBlockNumber()).add(1)) expect(await trade2.status()).to.equal(1) // TradeStatus.OPEN expect(await trade2.canSettle()).to.equal(true) @@ -5193,7 +5201,6 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { tradeAddr = await backingManager.trades(rsr.address) trade = await ethers.getContractAt('DutchTrade', tradeAddr) await backupToken1.connect(addr1).approve(trade.address, initialBal) - await advanceToTimestamp((await trade.startTime()) - 1) await snapshotGasCost(trade.connect(addr1).bid()) // No new trade diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index f95ee4a894..6b4dad02c8 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -46,9 +46,9 @@ import { whileImpersonating } from './utils/impersonation' import snapshotGasCost from './utils/snapshotGasCost' import { advanceTime, - advanceToTimestamp, + advanceBlocks, + getLatestBlockNumber, getLatestBlockTimestamp, - setNextBlockTimestamp, } from './utils/time' import { withinQuad } from './utils/matchers' import { @@ -2087,7 +2087,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader) }) - it('Should report violation when auction behaves incorrectly', async () => { + it('Should report violation when Batch Auction behaves incorrectly', async () => { + // This test needs to be in this file and not Broker.test.ts because settleTrade() + // requires the BackingManager _actually_ started the trade + rewardAmountAAVE = bn('0.5e18') // AAVE Rewards @@ -2186,7 +2189,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ { contract: broker, - name: 'DisabledSet', + name: 'BatchTradeDisabledSet', args: [false, true], emitted: true, }, @@ -2225,7 +2228,233 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(furnace.address)).to.be.closeTo(minBuyAmtRToken.sub(10), 50) }) - it('Should not perform auction if Broker is disabled', async () => { + it('Should report violation when Dutch Auction clears in geometric phase', async () => { + // This test needs to be in this file and not Broker.test.ts because settleTrade() + // requires the BackingManager _actually_ started the trade + + rewardAmountAAVE = bn('0.5e18') + + // AAVE Rewards + await token2.setRewards(backingManager.address, rewardAmountAAVE) + + // Collect revenue + // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) + const sellAmt: BigNumber = rewardAmountAAVE.mul(60).div(100) // due to f = 60% + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + const sellAmtRToken: BigNumber = rewardAmountAAVE.sub(sellAmt) // Remainder + const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) + + // Claim rewards + + await expectEvents(facadeTest.claimRewards(rToken.address), [ + { + contract: token3, + name: 'RewardsClaimed', + args: [compToken.address, bn(0)], + emitted: true, + }, + { + contract: token2, + name: 'RewardsClaimed', + args: [aaveToken.address, rewardAmountAAVE], + emitted: true, + }, + ]) + + // Check status of destinations at this point + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + + // Run auctions + await expectEvents( + facadeTest.runAuctionsForAllTradersForKind(rToken.address, TradeKind.DUTCH_AUCTION), + [ + { + contract: rsrTrader, + name: 'TradeStarted', + args: [anyValue, aaveToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + args: [ + anyValue, + aaveToken.address, + rToken.address, + sellAmtRToken, + withinQuad(minBuyAmtRToken), + ], + emitted: true, + }, + ] + ) + + // Check auctions registered + // AAVE -> RSR Auction + const rsrTrade = await ethers.getContractAt( + 'DutchTrade', + ( + await getTrade(rsrTrader, aaveToken.address) + ).address + ) + expect(await rsrTrade.sell()).to.equal(aaveToken.address) + expect(await rsrTrade.buy()).to.equal(rsr.address) + expect(await rsrTrade.sellAmount()).to.equal(sellAmt) + + // AAVE -> RToken Auction + const rTokenTrade = await ethers.getContractAt( + 'DutchTrade', + ( + await getTrade(rTokenTrader, aaveToken.address) + ).address + ) + expect(await rTokenTrade.sell()).to.equal(aaveToken.address) + expect(await rTokenTrade.buy()).to.equal(rToken.address) + expect(await rTokenTrade.sellAmount()).to.equal(sellAmtRToken) + + // Should not be disabled to start + expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rToken.address)).to.equal(false) + + // Advance time near end of geometric phase + await advanceBlocks(config.dutchAuctionLength.div(12).div(5).sub(5)) + + // Should settle RSR auction + await rsr.connect(addr1).approve(rsrTrade.address, sellAmt.mul(10)) + await expect(rsrTrade.connect(addr1).bid()) + .to.emit(rsrTrader, 'TradeSettled') + .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, anyValue) + expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(true) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(true) + + // Should still be able to settle RToken auction, even though aaveToken is now disabled + await rToken.connect(addr1).approve(rTokenTrade.address, sellAmtRToken.mul(10)) + await expect(rTokenTrade.connect(addr1).bid()) + .to.emit(rTokenTrader, 'TradeSettled') + .withArgs(anyValue, aaveToken.address, rToken.address, sellAmtRToken, anyValue) + + // Check all 3 tokens are disabled for dutch auctions + expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(true) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(true) + expect(await broker.dutchTradeDisabled(rToken.address)).to.equal(true) + }) + + it('Should not report violation when Dutch Auction clears in first linear phase', async () => { + // This test needs to be in this file and not Broker.test.ts because settleTrade() + // requires the BackingManager _actually_ started the trade + + rewardAmountAAVE = bn('0.5e18') + + // AAVE Rewards + await token2.setRewards(backingManager.address, rewardAmountAAVE) + + // Collect revenue + // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) + const sellAmt: BigNumber = rewardAmountAAVE.mul(60).div(100) // due to f = 60% + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + const sellAmtRToken: BigNumber = rewardAmountAAVE.sub(sellAmt) // Remainder + const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) + + // Claim rewards + + await expectEvents(facadeTest.claimRewards(rToken.address), [ + { + contract: token3, + name: 'RewardsClaimed', + args: [compToken.address, bn(0)], + emitted: true, + }, + { + contract: token2, + name: 'RewardsClaimed', + args: [aaveToken.address, rewardAmountAAVE], + emitted: true, + }, + ]) + + // Check status of destinations at this point + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + + // Run auctions + await expectEvents( + facadeTest.runAuctionsForAllTradersForKind(rToken.address, TradeKind.DUTCH_AUCTION), + [ + { + contract: rsrTrader, + name: 'TradeStarted', + args: [anyValue, aaveToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + args: [ + anyValue, + aaveToken.address, + rToken.address, + sellAmtRToken, + withinQuad(minBuyAmtRToken), + ], + emitted: true, + }, + ] + ) + + // Check auctions registered + // AAVE -> RSR Auction + const rsrTrade = await ethers.getContractAt( + 'DutchTrade', + ( + await getTrade(rsrTrader, aaveToken.address) + ).address + ) + expect(await rsrTrade.sell()).to.equal(aaveToken.address) + expect(await rsrTrade.buy()).to.equal(rsr.address) + expect(await rsrTrade.sellAmount()).to.equal(sellAmt) + + // AAVE -> RToken Auction + const rTokenTrade = await ethers.getContractAt( + 'DutchTrade', + ( + await getTrade(rTokenTrader, aaveToken.address) + ).address + ) + expect(await rTokenTrade.sell()).to.equal(aaveToken.address) + expect(await rTokenTrade.buy()).to.equal(rToken.address) + expect(await rTokenTrade.sellAmount()).to.equal(sellAmtRToken) + + // Should not be disabled to start + expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rToken.address)).to.equal(false) + + // Advance time to middle of first linear phase + await advanceBlocks(config.dutchAuctionLength.div(12).div(3)) + + // Should settle RSR auction + await rsr.connect(addr1).approve(rsrTrade.address, sellAmt.mul(10)) + await expect(rsrTrade.connect(addr1).bid()) + .to.emit(rsrTrader, 'TradeSettled') + .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, anyValue) + + // Should settle RToken auction + await rToken.connect(addr1).approve(rTokenTrade.address, sellAmtRToken.mul(10)) + await expect(rTokenTrade.connect(addr1).bid()) + .to.emit(rTokenTrader, 'TradeSettled') + .withArgs(anyValue, aaveToken.address, rToken.address, sellAmtRToken, anyValue) + + // Should not have disabled anything + expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rToken.address)).to.equal(false) + }) + + it('Should not perform auction if Batch Trades are disabled', async () => { rewardAmountAAVE = bn('0.5e18') // AAVE Rewards @@ -2255,7 +2484,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) // Disable broker - await broker.connect(owner).setDisabled(true) + await broker.connect(owner).setBatchTradeDisabled(true) // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) const sellAmt: BigNumber = rewardAmountAAVE.mul(60).div(100) // due to f = 60% @@ -2265,10 +2494,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await backingManager.forwardRevenue([aaveToken.address]) await expect( rsrTrader.manageTokens([aaveToken.address], [TradeKind.BATCH_AUCTION]) - ).to.be.revertedWith('broker disabled') + ).to.be.revertedWith('batch auctions disabled') await expect( rTokenTrader.manageTokens([aaveToken.address], [TradeKind.BATCH_AUCTION]) - ).to.be.revertedWith('broker disabled') + ).to.be.revertedWith('batch auctions disabled') // Check funds - remain in traders expect(await rsr.balanceOf(stRSR.address)).to.equal(0) @@ -2655,64 +2884,17 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Cannot get bid amount yet await expect( - trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + trade.connect(addr1).bidAmount(await getLatestBlockNumber()) ).to.be.revertedWith('auction not started') - // Advance to start time - const start = await trade.startTime() - await advanceToTimestamp(start) - - // Now we can get bid amount - const actual = await trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) - expect(actual).to.be.gt(bn(0)) - }) - - it('Should allow one bidder', async () => { - await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount.div(2)) - await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) - - const trade = await ethers.getContractAt( - 'DutchTrade', - await rTokenTrader.trades(token0.address) - ) - - // Advance to auction on-going - await advanceToTimestamp((await trade.endTime()) - 1000) - - // Bid - await rToken.connect(addr1).approve(trade.address, initialBal) - await trade.connect(addr1).bid() - expect(await trade.bidder()).to.equal(addr1.address) - - // Cannot bid once is settled - await expect(trade.connect(addr1).bid()).to.be.revertedWith('bid already received') - }) - - it('Should not return bid amount before auction starts', async () => { - await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) - - const trade = await ethers.getContractAt( - 'DutchTrade', - await rTokenTrader.trades(token0.address) - ) - - // Cannot get bid amount yet - await expect( - trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) - ).to.be.revertedWith('auction not started') - - // Advance to start time - const start = await trade.startTime() - await advanceToTimestamp(start) - - // Now we can get bid amount - const actual = await trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + // Can get bid amount in following block + await advanceBlocks(1) + const actual = await trade.connect(addr1).bidAmount(await getLatestBlockNumber()) expect(actual).to.be.gt(bn(0)) }) it('Should allow one bidder', async () => { - await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount.div(2)) + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount.div(2000)) await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) const trade = await ethers.getContractAt( @@ -2720,11 +2902,8 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await rTokenTrader.trades(token0.address) ) - // Advance to auction on-going - await advanceToTimestamp((await trade.endTime()) - 1000) - // Bid - await rToken.connect(addr1).approve(trade.address, initialBal) + await rToken.connect(addr1).approve(trade.address, issueAmount) await trade.connect(addr1).bid() expect(await trade.bidder()).to.equal(addr1.address) @@ -2742,15 +2921,15 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) await rToken.connect(addr1).approve(trade.address, initialBal) - const start = await trade.startTime() - const end = await trade.endTime() - await advanceToTimestamp(start) + const start = await trade.startBlock() + const end = await trade.endBlock() // Simulate 30 minutes of blocks, should swap at right price each time - for (let now = await getLatestBlockTimestamp(); now <= end; now += 12) { + let now = bn(await getLatestBlockNumber()) + while (now.lt(end)) { const actual = await trade.connect(addr1).bidAmount(now) const expected = await dutchBuyAmount( - fp(now - start).div(end - start), + fp(now.sub(start)).div(end.sub(start)), rTokenAsset.address, collateral0.address, issueAmount, @@ -2761,7 +2940,8 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const staticResult = await trade.connect(addr1).callStatic.bid() expect(staticResult).to.equal(actual) - await advanceToTimestamp((await getLatestBlockTimestamp()) + 12) + await advanceBlocks(1) + now = bn(await getLatestBlockNumber()) } }) @@ -2774,9 +2954,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) await rToken.connect(addr1).approve(trade.address, initialBal) - await advanceToTimestamp((await trade.endTime()) + 1) + await advanceBlocks((await trade.endBlock()).sub(await getLatestBlockNumber()).add(1)) await expect( - trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + trade.connect(addr1).bidAmount(await getLatestBlockNumber()) ).to.be.revertedWith('auction over') await expect(trade.connect(addr1).bid()).be.revertedWith('auction over') @@ -2790,7 +2970,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await backingManager.tradesOpen()).to.equal(0) }) - it('Should bid at exactly endTime() and not launch another auction', async () => { + it('Should bid at exactly endBlock() and not launch another auction', async () => { await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) const trade = await ethers.getContractAt( @@ -2798,12 +2978,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await rTokenTrader.trades(token0.address) ) await rToken.connect(addr1).approve(trade.address, initialBal) + await expect(trade.bidAmount(await trade.endBlock())).to.not.be.reverted // Snipe auction at 0s left - await advanceToTimestamp((await trade.endTime()) - 1) - await expect(trade.bidAmount(await trade.endTime())).to.not.be.reverted - // Set timestamp to be exactly endTime() - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 1) + await advanceBlocks((await trade.endBlock()).sub(await getLatestBlockNumber()).sub(1)) await trade.connect(addr1).bid() expect(await trade.canSettle()).to.equal(false) expect(await trade.status()).to.equal(2) // Status.CLOSED diff --git a/test/Upgradeability.test.ts b/test/Upgradeability.test.ts index 3a6e62135c..0c94eec20c 100644 --- a/test/Upgradeability.test.ts +++ b/test/Upgradeability.test.ts @@ -284,7 +284,9 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { expect(await newBroker.gnosis()).to.equal(gnosis.address) expect(await newBroker.batchAuctionLength()).to.equal(config.batchAuctionLength) expect(await newBroker.dutchAuctionLength()).to.equal(config.dutchAuctionLength) - expect(await newBroker.disabled()).to.equal(false) + expect(await newBroker.batchTradeDisabled()).to.equal(false) + expect(await newBroker.dutchTradeDisabled(rToken.address)).to.equal(false) + expect(await newBroker.dutchTradeDisabled(rsr.address)).to.equal(false) expect(await newBroker.main()).to.equal(main.address) }) @@ -553,7 +555,9 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { // Check state is preserved expect(await brokerV2.gnosis()).to.equal(gnosis.address) expect(await brokerV2.batchAuctionLength()).to.equal(config.batchAuctionLength) - expect(await brokerV2.disabled()).to.equal(false) + expect(await brokerV2.batchTradeDisabled()).to.equal(false) + expect(await brokerV2.dutchTradeDisabled(rToken.address)).to.equal(false) + expect(await brokerV2.dutchTradeDisabled(rsr.address)).to.equal(false) expect(await brokerV2.main()).to.equal(main.address) // Check new version is implemented diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 877effc053..16798523be 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -188,7 +188,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function afterEach(async () => { // Should not trigger a de-listing of the auction platform - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) // Should not be able to re-bid in auction await token0.connect(addr2).approve(easyAuction.address, buyAmt) @@ -840,8 +840,8 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await advanceTime(config.batchAuctionLength.add(100).toString()) // End Auction - await expect(trade.connect(addr1).settle()).to.not.emit(broker, 'DisabledSet') - expect(await broker.disabled()).to.equal(false) + await expect(trade.connect(addr1).settle()).to.not.emit(broker, 'BatchTradeDisabledSet') + expect(await broker.batchTradeDisabled()).to.equal(false) } // ==== Generate the tests ==== @@ -887,7 +887,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function }) describe('Regression Tests', () => { - it('Passes Test: 12/03/2023 - Broker Disabled on Trade Settlement with one less token', async () => { + it('Passes Test: 12/03/2023 - Batch Auctions on Trade Settlement with one less token', async () => { // TX: 0xb5fc3d61d46e41b79bd333583448e6d4c186ca49206f8a0e7dde05f2700e0965 // This set the broker to false since it was one token short. // This test is to make sure that the broker is not disabled in this case. diff --git a/test/utils/trades.ts b/test/utils/trades.ts index 7302e0489f..7c7e75d394 100644 --- a/test/utils/trades.ts +++ b/test/utils/trades.ts @@ -104,21 +104,29 @@ export const dutchBuyAmount = async ( const leftover = slippage1e18.mod(fp('1')) const slippage = slippage1e18.div(fp('1')).add(leftover.gte(fp('0.5')) ? 1 : 0) - const lowPrice = sellLow.mul(fp('1').sub(slippage)).div(buyHigh) - const middlePrice = divCeil(sellHigh.mul(fp('1')), buyLow) - - const FORTY_PERCENT = fp('0.4') // 40% - const SIXTY_PERCENT = fp('0.6') // 60% + const worstPrice = sellLow.mul(fp('1').sub(slippage)).div(buyHigh) + const bestPrice = divCeil(sellHigh.mul(fp('1')), buyLow) + const highPrice = divCeil(sellHigh.mul(fp('1.5')), buyLow) let price: BigNumber - if (progression.lt(FORTY_PERCENT)) { - const exp = divRound(bn('6907752').mul(FORTY_PERCENT.sub(progression)), FORTY_PERCENT) + if (progression.lt(fp('0.2'))) { + const exp = divRound(bn('6502287').mul(fp('0.2').sub(progression)), fp('0.2')) const divisor = new Decimal('999999').div('1000000').pow(exp.toString()) - price = divCeil(middlePrice.mul(fp('1')), fp(divisor.toString())) - } else { - price = middlePrice.sub( - middlePrice.sub(lowPrice).mul(progression.sub(FORTY_PERCENT)).div(SIXTY_PERCENT) + price = divCeil(highPrice.mul(fp('1')), fp(divisor.toString())) + } else if (progression.lt(fp('0.45'))) { + price = highPrice.sub( + highPrice + .sub(bestPrice) + .mul(progression.sub(fp('0.2'))) + .div(fp('0.25')) + ) + } else if (progression.lt(fp('0.95'))) { + price = bestPrice.sub( + bestPrice + .sub(worstPrice) + .mul(progression.sub(fp('0.45'))) + .div(fp('0.5')) ) - } + } else price = worstPrice return divCeil(outAmount.mul(price), fp('1')) } From ef66344390994daae2c222bda1ce83a6b1b6dfbc Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Thu, 3 Aug 2023 19:11:01 -0400 Subject: [PATCH 356/462] c4 #51 (#871) --- contracts/facade/FacadeRead.sol | 2 ++ contracts/p1/BasketHandler.sol | 1 + contracts/p1/RToken.sol | 4 ++++ 3 files changed, 7 insertions(+) diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 9c0d0ffa5f..694ae839c6 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -49,6 +49,8 @@ contract FacadeRead is IFacadeRead { return basketsHeld.bottom.mulDiv(totalSupply, needed).shiftl_toUint(decimals); } + /// Do no use inifite approvals. Instead, use BasketHandler.quote() to determine the amount + /// of backing tokens to approve. /// @return tokens The erc20 needed for the issuance /// @return deposits {qTok} The deposits necessary to issue `amount` RToken /// @return depositsUoA {UoA} The UoA value of the deposits necessary to issue `amount` RToken diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 7bcd48028d..441007d763 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -356,6 +356,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } /// Return the current issuance/redemption value of `amount` BUs + /// Any approvals needed to issue RTokens should be set to the values returned by this function /// @dev Subset of logic of quoteCustomRedemption; more gas efficient for current nonce /// @param amount {BU} /// @return erc20s The backing collateral erc20s diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index f68a43a53e..dd5b1790a9 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -87,6 +87,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { } /// Issue an RToken on the current basket + /// Do no use inifite approvals. Instead, use BasketHandler.quote() to determine the amount + /// of backing tokens to approve. /// @param amount {qTok} The quantity of RToken to issue /// @custom:interaction nearly CEI, but see comments around handling of refunds function issue(uint256 amount) public { @@ -94,6 +96,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { } /// Issue an RToken on the current basket, to a particular recipient + /// Do no use inifite approvals. Instead, use BasketHandler.quote() to determine the amount + /// of backing tokens to approve. /// @param recipient The address to receive the issued RTokens /// @param amount {qRTok} The quantity of RToken to issue /// @custom:interaction RCEI From 117f63eba95ade0df342dc629c276917e3248db8 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 4 Aug 2023 23:26:22 +0200 Subject: [PATCH 357/462] gas test asset.refresh() for all tests (#880) --- test/plugins/Asset.test.ts | 40 +++- test/plugins/Collateral.test.ts | 92 +++++--- .../aave/ATokenFiatCollateral.test.ts | 211 ++++++++++++++++-- .../ATokenFiatCollateral.test.ts.snap | 25 +++ .../AnkrEthCollateralTestSuite.test.ts.snap | 17 ++ .../CBETHCollateral.test.ts.snap | 17 ++ .../individual-collateral/collateralTests.ts | 49 ++++ .../compoundv2/CTokenFiatCollateral.test.ts | 175 ++++++++++++++- .../CTokenFiatCollateral.test.ts.snap | 25 +++ .../__snapshots__/CometTestSuite.test.ts.snap | 25 +++ .../curve/collateralTests.ts | 54 +++++ .../CrvStableMetapoolSuite.test.ts.snap | 25 +++ ...StableRTokenMetapoolTestSuite.test.ts.snap | 25 +++ .../CrvStableTestSuite.test.ts.snap | 25 +++ .../CrvVolatileTestSuite.test.ts.snap | 25 +++ .../CvxStableMetapoolSuite.test.ts.snap | 25 +++ ...StableRTokenMetapoolTestSuite.test.ts.snap | 25 +++ .../CvxStableTestSuite.test.ts.snap | 25 +++ .../CvxVolatileTestSuite.test.ts.snap | 25 +++ .../SDaiCollateralTestSuite.test.ts.snap | 17 ++ .../FTokenFiatCollateral.test.ts.snap | 49 ++++ .../SFrxEthTestSuite.test.ts.snap | 13 ++ .../LidoStakedEthTestSuite.test.ts.snap | 25 +++ .../MorphoAAVEFiatCollateral.test.ts.snap | 49 ++++ .../MorphoAAVENonFiatCollateral.test.ts.snap | 33 +++ ...AAVESelfReferentialCollateral.test.ts.snap | 17 ++ .../RethCollateralTestSuite.test.ts.snap | 17 ++ .../StargateETHTestSuite.test.ts.snap | 49 ++++ 28 files changed, 1140 insertions(+), 59 deletions(-) create mode 100644 test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index 2a6f946314..3d9661263f 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -3,7 +3,12 @@ import { expect } from 'chai' import { Wallet, ContractFactory } from 'ethers' import { ethers } from 'hardhat' import { IConfig } from '../../common/configuration' -import { advanceBlocks, advanceTime, getLatestBlockTimestamp } from '../utils/time' +import { + advanceBlocks, + advanceTime, + getLatestBlockTimestamp, + setNextBlockTimestamp, +} from '../utils/time' import { ZERO_ADDRESS, ONE_ADDRESS, MAX_UINT192 } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { @@ -34,11 +39,18 @@ import { import { Collateral, defaultFixture, + IMPLEMENTATION, + Implementation, ORACLE_TIMEOUT, ORACLE_ERROR, PRICE_TIMEOUT, VERSION, } from '../fixtures' +import { useEnv } from '#/utils/env' +import snapshotGasCost from '../utils/snapshotGasCost' + +const describeGas = + IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip const DEFAULT_THRESHOLD = fp('0.01') // 1% const DELAY_UNTIL_DEFAULT = bn('86400') // 24h @@ -664,4 +676,30 @@ describe('Assets contracts #fast', () => { ) }) }) + + describeGas('Gas Reporting', () => { + context('refresh()', () => { + afterEach(async () => { + await snapshotGasCost(rsrAsset.refresh()) + await snapshotGasCost(rsrAsset.refresh()) // 2nd refresh can be different than 1st + }) + + it('refresh() during SOUND', async () => { + // pass + }) + + it('refresh() after oracle timeout', async () => { + const oracleTimeout = await rsrAsset.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(bn(oracleTimeout).div(12)) + }) + + it('refresh() after full price timeout', async () => { + await advanceTime((await rsrAsset.priceTimeout()) + (await rsrAsset.oracleTimeout())) + const lotP = await rsrAsset.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) + }) + }) + }) }) diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index d2fdbc0177..69225d900b 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -29,9 +29,13 @@ import { UnpricedAppreciatingFiatCollateralMock, USDCMock, WETH9, - UnpricedAppreciatingFiatCollateralMock, } from '../../typechain' -import { advanceTime, getLatestBlockTimestamp, setNextBlockTimestamp } from '../utils/time' +import { + advanceBlocks, + advanceTime, + getLatestBlockTimestamp, + setNextBlockTimestamp, +} from '../utils/time' import snapshotGasCost from '../utils/snapshotGasCost' import { expectPrice, @@ -2164,45 +2168,69 @@ describe('Collateral contracts', () => { }) describeGas('Gas Reporting', () => { - it('Force Updates - Soft Default', async function () { - const delayUntilDefault: BigNumber = bn(await tokenCollateral.delayUntilDefault()) + context('refresh()', () => { + it('during SOUND', async () => { + await snapshotGasCost(tokenCollateral.refresh()) + await snapshotGasCost(usdcCollateral.refresh()) + }) - // Depeg one of the underlying tokens - Reducing price 20% - // Should also impact on the aToken and cToken - await setOraclePrice(tokenCollateral.address, bn('7e7')) + it('during + after soft default', async function () { + const delayUntilDefault: BigNumber = bn(await tokenCollateral.delayUntilDefault()) - // Force updates - Should update whenDefault and status - await snapshotGasCost(tokenCollateral.refresh()) - expect(await tokenCollateral.status()).to.equal(CollateralStatus.IFFY) + // Depeg one of the underlying tokens - Reducing price 20% + // Should also impact on the aToken and cToken + await setOraclePrice(tokenCollateral.address, bn('7e7')) - // Adance half the delay - await advanceTime(Number(delayUntilDefault.div(2)) + 1) + // Force updates - Should update whenDefault and status + await snapshotGasCost(tokenCollateral.refresh()) + expect(await tokenCollateral.status()).to.equal(CollateralStatus.IFFY) - // Force updates - Nothing occurs - await snapshotGasCost(tokenCollateral.refresh()) - await snapshotGasCost(usdcCollateral.refresh()) - expect(await usdcCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await tokenCollateral.status()).to.equal(CollateralStatus.IFFY) + // Adance half the delay + await advanceTime(Number(delayUntilDefault.div(2)) + 1) - // Adance the other half - await advanceTime(Number(delayUntilDefault.div(2)) + 1) + // Force updates - Nothing occurs + await snapshotGasCost(tokenCollateral.refresh()) + await snapshotGasCost(usdcCollateral.refresh()) + expect(await usdcCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await tokenCollateral.status()).to.equal(CollateralStatus.IFFY) - // Move time forward past delayUntilDefault - expect(await tokenCollateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await usdcCollateral.status()).to.equal(CollateralStatus.SOUND) - }) + // Adance the other half + await advanceTime(Number(delayUntilDefault.div(2)) + 1) - it('Force Updates - Hard Default - ATokens/CTokens', async function () { - // Decrease rate for AToken and CToken, will disable collateral immediately - await aToken.setExchangeRate(fp('0.99')) - await cToken.setExchangeRate(fp('0.95')) + // Move time forward past delayUntilDefault + expect(await tokenCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await usdcCollateral.status()).to.equal(CollateralStatus.SOUND) + await snapshotGasCost(tokenCollateral.refresh()) + await snapshotGasCost(usdcCollateral.refresh()) + }) - // Force updates - Should update whenDefault and status for Atokens/CTokens - await snapshotGasCost(aTokenCollateral.refresh()) - expect(await aTokenCollateral.status()).to.equal(CollateralStatus.DISABLED) + it('after hard default', async function () { + // Decrease rate for AToken and CToken, will disable collateral immediately + await aToken.setExchangeRate(fp('0.99')) + await cToken.setExchangeRate(fp('0.95')) - await snapshotGasCost(cTokenCollateral.refresh()) - expect(await cTokenCollateral.status()).to.equal(CollateralStatus.DISABLED) + // Force updates - Should update whenDefault and status for Atokens/CTokens + await snapshotGasCost(aTokenCollateral.refresh()) + expect(await aTokenCollateral.status()).to.equal(CollateralStatus.DISABLED) + + await snapshotGasCost(cTokenCollateral.refresh()) + expect(await cTokenCollateral.status()).to.equal(CollateralStatus.DISABLED) + }) + + it('after oracle timeout', async () => { + const oracleTimeout = await tokenCollateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(oracleTimeout / 12) + }) + + it('after full price timeout', async () => { + await advanceTime( + (await tokenCollateral.priceTimeout()) + (await tokenCollateral.oracleTimeout()) + ) + const lotP = await tokenCollateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) + }) }) }) }) diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index 38853a79c9..b74fda69de 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -3,7 +3,13 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { BigNumber, ContractFactory } from 'ethers' import hre, { ethers } from 'hardhat' -import { IMPLEMENTATION, ORACLE_ERROR, PRICE_TIMEOUT, REVENUE_HIDING } from '../../../fixtures' +import { + IMPLEMENTATION, + Implementation, + ORACLE_ERROR, + PRICE_TIMEOUT, + REVENUE_HIDING, +} from '../../../fixtures' import { DefaultFixture, Fixture, getDefaultFixture, ORACLE_TIMEOUT } from '../fixtures' import { getChainId } from '../../../../common/blockchain-utils' import forkBlockNumber from '../../../integration/fork-block-numbers' @@ -26,7 +32,12 @@ import { expectUnpriced, setOraclePrice, } from '../../../utils/oracles' -import { advanceBlocks, advanceTime, getLatestBlockTimestamp } from '../../../utils/time' +import { + advanceBlocks, + advanceTime, + getLatestBlockTimestamp, + setNextBlockTimestamp, +} from '../../../utils/time' import { Asset, ATokenFiatCollateral, @@ -50,6 +61,10 @@ import { } from '../../../../typechain' import { useEnv } from '#/utils/env' import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' +import snapshotGasCost from '../../../utils/snapshotGasCost' + +const describeGas = + IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip // Setup test environment const setup = async (blockNumber: number) => { @@ -709,7 +724,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Test for soft default it('Updates status in case of soft default', async () => { // Redeploy plugin using a Chainlink mock feed where we can change the price - const newCDaiCollateral: ATokenFiatCollateral = await ( + const newADaiCollateral: ATokenFiatCollateral = await ( await ethers.getContractFactory('ATokenFiatCollateral') ).deploy( { @@ -727,36 +742,36 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi ) // Check initial state - expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await newCDaiCollateral.whenDefault()).to.equal(MAX_UINT48) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newADaiCollateral.whenDefault()).to.equal(MAX_UINT48) // Depeg one of the underlying tokens - Reducing price 20% - await setOraclePrice(newCDaiCollateral.address, bn('8e7')) // -20% + await setOraclePrice(newADaiCollateral.address, bn('8e7')) // -20% // Force updates - Should update whenDefault and status - await expect(newCDaiCollateral.refresh()) - .to.emit(newCDaiCollateral, 'CollateralStatusChanged') + await expect(newADaiCollateral.refresh()) + .to.emit(newADaiCollateral, 'CollateralStatusChanged') .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) - expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.IFFY) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.IFFY) const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()).add( delayUntilDefault ) - expect(await newCDaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + expect(await newADaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) // Move time forward past delayUntilDefault await advanceTime(Number(delayUntilDefault)) - expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.DISABLED) // Nothing changes if attempt to refresh after default // CToken - const prevWhenDefault: BigNumber = await newCDaiCollateral.whenDefault() - await expect(newCDaiCollateral.refresh()).to.not.emit( - newCDaiCollateral, + const prevWhenDefault: BigNumber = await newADaiCollateral.whenDefault() + await expect(newADaiCollateral.refresh()).to.not.emit( + newADaiCollateral, 'CollateralStatusChanged' ) - expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await newCDaiCollateral.whenDefault()).to.equal(prevWhenDefault) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await newADaiCollateral.whenDefault()).to.equal(prevWhenDefault) }) // Test for hard default @@ -774,7 +789,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await saDaiMock.setExchangeRate(fp('0.02')) // Redeploy plugin using the new aDai mock - const newCDaiCollateral: ATokenFiatCollateral = await ( + const newADaiCollateral: ATokenFiatCollateral = await ( await ethers.getContractFactory('ATokenFiatCollateral') ).deploy( { @@ -790,23 +805,23 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi }, REVENUE_HIDING ) - await newCDaiCollateral.refresh() + await newADaiCollateral.refresh() // Check initial state - expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await newCDaiCollateral.whenDefault()).to.equal(MAX_UINT48) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newADaiCollateral.whenDefault()).to.equal(MAX_UINT48) // Decrease rate for aDAI, will disable collateral immediately await saDaiMock.setExchangeRate(fp('0.019')) // Force updates - Should update whenDefault and status for Atokens/aTokens - await expect(newCDaiCollateral.refresh()) - .to.emit(newCDaiCollateral, 'CollateralStatusChanged') + await expect(newADaiCollateral.refresh()) + .to.emit(newADaiCollateral, 'CollateralStatusChanged') .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) - expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.DISABLED) const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) - expect(await newCDaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + expect(await newADaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) }) it('Reverts if oracle reverts or runs out of gas, maintains status', async () => { @@ -845,4 +860,152 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(await invalidCTokenCollateral.status()).to.equal(CollateralStatus.SOUND) }) }) + + describeGas('Gas Reporting', () => { + context('refresh()', () => { + it('during SOUND', async () => { + await snapshotGasCost(aDaiCollateral.refresh()) + await snapshotGasCost(aDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after hard default', async () => { + const sATokenMockFactory: ContractFactory = await ethers.getContractFactory( + 'StaticATokenMock' + ) + const aDaiErc20 = await ethers.getContractAt('ERC20Mock', aDai.address) + const symbol = await aDaiErc20.symbol() + const saDaiMock: StaticATokenMock = ( + await sATokenMockFactory.deploy(symbol + ' Token', symbol, dai.address) + ) + // Set initial exchange rate to the new aDai Mock + await saDaiMock.setExchangeRate(fp('0.02')) + + // Redeploy plugin using the new aDai mock + const newADaiCollateral: ATokenFiatCollateral = await ( + await ethers.getContractFactory('ATokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await aDaiCollateral.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: saDaiMock.address, + maxTradeVolume: await aDaiCollateral.maxTradeVolume(), + oracleTimeout: await aDaiCollateral.oracleTimeout(), + targetName: await aDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await aDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + await newADaiCollateral.refresh() + + // Decrease rate for aDAI, will disable collateral immediately + await saDaiMock.setExchangeRate(fp('0.019')) + await snapshotGasCost(newADaiCollateral.refresh()) + await snapshotGasCost(newADaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('during soft default', async () => { + // Redeploy plugin using a Chainlink mock feed where we can change the price + const newADaiCollateral: ATokenFiatCollateral = await ( + await ethers.getContractFactory('ATokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: await aDaiCollateral.erc20(), + maxTradeVolume: await aDaiCollateral.maxTradeVolume(), + oracleTimeout: await aDaiCollateral.oracleTimeout(), + targetName: await aDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await aDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + + // Check initial state + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newADaiCollateral.whenDefault()).to.equal(MAX_UINT48) + + // Depeg one of the underlying tokens - Reducing price 20% + await setOraclePrice(newADaiCollateral.address, bn('8e7')) // -20% + await snapshotGasCost(newADaiCollateral.refresh()) + await snapshotGasCost(newADaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after soft default', async () => { + // Redeploy plugin using a Chainlink mock feed where we can change the price + const newADaiCollateral: ATokenFiatCollateral = await ( + await ethers.getContractFactory('ATokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: await aDaiCollateral.erc20(), + maxTradeVolume: await aDaiCollateral.maxTradeVolume(), + oracleTimeout: await aDaiCollateral.oracleTimeout(), + targetName: await aDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await aDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + + // Check initial state + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newADaiCollateral.whenDefault()).to.equal(MAX_UINT48) + + // Depeg one of the underlying tokens - Reducing price 20% + await setOraclePrice(newADaiCollateral.address, bn('8e7')) // -20% + + // Force updates - Should update whenDefault and status + await expect(newADaiCollateral.refresh()) + .to.emit(newADaiCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.IFFY) + + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()).add( + delayUntilDefault + ) + expect(await newADaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + + // Move time forward past delayUntilDefault + await advanceTime(Number(delayUntilDefault)) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + + // Nothing changes if attempt to refresh after default + // CToken + const prevWhenDefault: BigNumber = await newADaiCollateral.whenDefault() + await expect(newADaiCollateral.refresh()).to.not.emit( + newADaiCollateral, + 'CollateralStatusChanged' + ) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await newADaiCollateral.whenDefault()).to.equal(prevWhenDefault) + await snapshotGasCost(newADaiCollateral.refresh()) + await snapshotGasCost(newADaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after oracle timeout', async () => { + const oracleTimeout = await aDaiCollateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(bn(oracleTimeout).div(12)) + await snapshotGasCost(aDaiCollateral.refresh()) + await snapshotGasCost(aDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after full price timeout', async () => { + await advanceTime( + (await aDaiCollateral.priceTimeout()) + (await aDaiCollateral.oracleTimeout()) + ) + const lotP = await aDaiCollateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) + await snapshotGasCost(aDaiCollateral.refresh()) + await snapshotGasCost(aDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + }) + }) }) diff --git a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap new file mode 100644 index 0000000000..e505e04797 --- /dev/null +++ b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `74518`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `72850`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `73073`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `30918`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `74518`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `72850`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `51728`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `51728`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `92420`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92346`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `125132`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `89264`; diff --git a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap new file mode 100644 index 0000000000..b2a094843f --- /dev/null +++ b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `58262`; + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `53793`; + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `76400`; + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `37508`; + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `58262`; + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `53793`; + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `68714`; + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `68714`; diff --git a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap new file mode 100644 index 0000000000..3551302c16 --- /dev/null +++ b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `57750`; + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `53281`; + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `91861`; + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `36971`; + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `75731`; + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `71262`; + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `84175`; + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `84175`; diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 55052779fc..653af6dc07 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -26,9 +26,14 @@ import { CollateralStatus, } from './pluginTestTypes' import { expectPrice } from '../../utils/oracles' +import snapshotGasCost from '../../utils/snapshotGasCost' +import { IMPLEMENTATION, Implementation } from '../../fixtures' const describeFork = useEnv('FORK') ? describe : describe.skip +const describeGas = + IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip + export default function fn( fixtures: CollateralTestSuiteFixtures ) { @@ -490,6 +495,50 @@ export default function fn( }) describe('collateral-specific tests', collateralSpecificStatusTests) + + describeGas('Gas Reporting', () => { + context('refresh()', () => { + afterEach(async () => { + await snapshotGasCost(collateral.refresh()) + await snapshotGasCost(collateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('during SOUND', async () => { + // pass + }) + + itChecksRefPerTokDefault('after hard default', async () => { + await reduceRefPerTok(ctx, 5) + }) + + itChecksTargetPerRefDefault('during soft default', async () => { + await reduceTargetPerRef(ctx, 20) + }) + + itChecksTargetPerRefDefault('after soft default', async () => { + await reduceTargetPerRef(ctx, 20) + await expect(collateral.refresh()) + .to.emit(collateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) + await advanceTime(await collateral.delayUntilDefault()) + }) + + it('after oracle timeout', async () => { + const oracleTimeout = await collateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(oracleTimeout / 12) + }) + + it('after full price timeout', async () => { + await advanceTime( + (await collateral.priceTimeout()) + (await collateral.oracleTimeout()) + ) + const lotP = await collateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) + }) + }) + }) }) }) } diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 2170b61b45..2a16daceb9 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -3,7 +3,13 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { BigNumber, ContractFactory } from 'ethers' import hre, { ethers } from 'hardhat' -import { IMPLEMENTATION, ORACLE_ERROR, PRICE_TIMEOUT, REVENUE_HIDING } from '../../../fixtures' +import { + IMPLEMENTATION, + Implementation, + ORACLE_ERROR, + PRICE_TIMEOUT, + REVENUE_HIDING, +} from '../../../fixtures' import { DefaultFixture, Fixture, getDefaultFixture, ORACLE_TIMEOUT } from '../fixtures' import { getChainId } from '../../../../common/blockchain-utils' import forkBlockNumber from '../../../integration/fork-block-numbers' @@ -26,7 +32,12 @@ import { expectUnpriced, setOraclePrice, } from '../../../utils/oracles' -import { advanceBlocks, advanceTime, getLatestBlockTimestamp } from '../../../utils/time' +import { + advanceBlocks, + advanceTime, + getLatestBlockTimestamp, + setNextBlockTimestamp, +} from '../../../utils/time' import { Asset, BadERC20, @@ -51,6 +62,10 @@ import { } from '../../../../typechain' import { useEnv } from '#/utils/env' import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' +import snapshotGasCost from '../../../utils/snapshotGasCost' + +const describeGas = + IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip // Setup test environment const setup = async (blockNumber: number) => { @@ -865,4 +880,160 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(await invalidCTokenCollateral.status()).to.equal(CollateralStatus.SOUND) }) }) + + describeGas('Gas Reporting', () => { + context('refresh()', () => { + it('during SOUND', async () => { + await snapshotGasCost(cDaiCollateral.refresh()) + await snapshotGasCost(cDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after hard default', async () => { + // Note: In this case requires to use a CToken mock to be able to change the rate + const CTokenMockFactory: ContractFactory = await ethers.getContractFactory('CTokenMock') + const symbol = await cDai.symbol() + const cDaiMock: CTokenMock = ( + await CTokenMockFactory.deploy(symbol + ' Token', symbol, dai.address) + ) + // Set initial exchange rate to the new cDai Mock + await cDaiMock.setExchangeRate(fp('0.02')) + + const cDaiVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenWrapper') + cDaiVault = ( + await cDaiVaultFactory.deploy( + cDaiMock.address, + 'cDAI RToken Vault', + 'rv_cDAI', + comptroller.address + ) + ) + + // Redeploy plugin using the new cDai mock + const newCDaiCollateral: CTokenFiatCollateral = await ( + await ethers.getContractFactory('CTokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await cDaiCollateral.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: cDaiVault.address, + maxTradeVolume: await cDaiCollateral.maxTradeVolume(), + oracleTimeout: await cDaiCollateral.oracleTimeout(), + targetName: await cDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await cDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + await newCDaiCollateral.refresh() + + // Decrease rate for aDAI, will disable collateral immediately + await cDaiMock.setExchangeRate(fp('0.019')) + await snapshotGasCost(newCDaiCollateral.refresh()) + await snapshotGasCost(newCDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('during soft default', async () => { + // Redeploy plugin using a Chainlink mock feed where we can change the price + const newCDaiCollateral: CTokenFiatCollateral = await ( + await ethers.getContractFactory('CTokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: await cDaiCollateral.erc20(), + maxTradeVolume: await cDaiCollateral.maxTradeVolume(), + oracleTimeout: await cDaiCollateral.oracleTimeout(), + targetName: await cDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await cDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + + // Check initial state + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newCDaiCollateral.whenDefault()).to.equal(MAX_UINT48) + + // Depeg one of the underlying tokens - Reducing price 20% + await setOraclePrice(newCDaiCollateral.address, bn('8e7')) // -20% + await snapshotGasCost(newCDaiCollateral.refresh()) + await snapshotGasCost(newCDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after soft default', async () => { + // Redeploy plugin using a Chainlink mock feed where we can change the price + const newCDaiCollateral: CTokenFiatCollateral = await ( + await ethers.getContractFactory('CTokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: await cDaiCollateral.erc20(), + maxTradeVolume: await cDaiCollateral.maxTradeVolume(), + oracleTimeout: await cDaiCollateral.oracleTimeout(), + targetName: await cDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await cDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + + // Check initial state + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newCDaiCollateral.whenDefault()).to.equal(MAX_UINT48) + + // Depeg one of the underlying tokens - Reducing price 20% + await setOraclePrice(newCDaiCollateral.address, bn('8e7')) // -20% + + // Force updates - Should update whenDefault and status + await expect(newCDaiCollateral.refresh()) + .to.emit(newCDaiCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.IFFY) + + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()).add( + delayUntilDefault + ) + expect(await newCDaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + + // Move time forward past delayUntilDefault + await advanceTime(Number(delayUntilDefault)) + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + + // Nothing changes if attempt to refresh after default + // CToken + const prevWhenDefault: BigNumber = await newCDaiCollateral.whenDefault() + await expect(newCDaiCollateral.refresh()).to.not.emit( + newCDaiCollateral, + 'CollateralStatusChanged' + ) + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await newCDaiCollateral.whenDefault()).to.equal(prevWhenDefault) + await snapshotGasCost(newCDaiCollateral.refresh()) + await snapshotGasCost(newCDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after oracle timeout', async () => { + const oracleTimeout = await cDaiCollateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(bn(oracleTimeout).div(12)) + await snapshotGasCost(cDaiCollateral.refresh()) + await snapshotGasCost(cDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after full price timeout', async () => { + await advanceTime( + (await cDaiCollateral.priceTimeout()) + (await cDaiCollateral.oracleTimeout()) + ) + const lotP = await cDaiCollateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) + await snapshotGasCost(cDaiCollateral.refresh()) + await snapshotGasCost(cDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + }) + }) }) diff --git a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap new file mode 100644 index 0000000000..b1bf29c3a9 --- /dev/null +++ b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `119481`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `117813`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `74209`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `31842`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `119481`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `117813`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `96669`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `96669`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `139939`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `139939`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `172725`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `136857`; diff --git a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap new file mode 100644 index 0000000000..57d6205291 --- /dev/null +++ b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `107007`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `102270`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `132320`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `70661`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `107007`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `102270`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `73461`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `70661`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `124634`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `124634`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `132185`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `124916`; diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index 17cb3f6881..55af313bd8 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -19,6 +19,11 @@ import { getLatestBlockTimestamp, setNextBlockTimestamp, } from '#/test/utils/time' +import snapshotGasCost from '../../../utils/snapshotGasCost' +import { IMPLEMENTATION, Implementation } from '../../../fixtures' + +const describeGas = + IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip const describeFork = useEnv('FORK') ? describe : describe.skip @@ -636,6 +641,55 @@ export default function fn( describe('collateral-specific tests', collateralSpecificStatusTests) }) + + describeGas('Gas Reporting', () => { + context('refresh()', () => { + afterEach(async () => { + await snapshotGasCost(ctx.collateral.refresh()) + await snapshotGasCost(ctx.collateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('during SOUND', async () => { + // pass + }) + + it('after hard default', async () => { + const currentExchangeRate = await ctx.curvePool.get_virtual_price() + await ctx.curvePool.setVirtualPrice(currentExchangeRate.sub(1e3)).then((e) => e.wait()) + }) + + it('during soft default', async () => { + // Depeg first feed - Reducing price by 20% from 1 to 0.8 + const updateAnswerTx = await ctx.feeds[0].updateAnswer(bn('8e7')) + await updateAnswerTx.wait() + }) + + it('after soft default', async () => { + // Depeg first feed - Reducing price by 20% from 1 to 0.8 + const updateAnswerTx = await ctx.feeds[0].updateAnswer(bn('8e7')) + await updateAnswerTx.wait() + await expect(ctx.collateral.refresh()) + .to.emit(ctx.collateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) + await advanceTime(await ctx.collateral.delayUntilDefault()) + }) + + it('after oracle timeout', async () => { + const oracleTimeout = await ctx.collateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(oracleTimeout / 12) + }) + + it('after full price timeout', async () => { + await advanceTime( + (await ctx.collateral.priceTimeout()) + (await ctx.collateral.oracleTimeout()) + ) + const lotP = await ctx.collateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) + }) + }) + }) }) }) } diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap new file mode 100644 index 0000000000..a4b3141f3a --- /dev/null +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `75961`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `71493`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `242158`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `75961`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `71493`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `237273`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `237273`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `244851`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `237583`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap new file mode 100644 index 0000000000..82de3742f2 --- /dev/null +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `92858`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `88390`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `220114`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `97863`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `93395`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `215229`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `215229`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `203246`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `195978`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap new file mode 100644 index 0000000000..28185554e3 --- /dev/null +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59818`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55350`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `192331`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59818`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55350`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `187446`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `187446`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `175071`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `167803`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap new file mode 100644 index 0000000000..de32617273 --- /dev/null +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62266`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57798`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `224290`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62266`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57798`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `219405`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `219405`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `225659`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `218391`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap new file mode 100644 index 0000000000..8e747784bc --- /dev/null +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `75961`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `71493`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `242158`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `75961`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `71493`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `237273`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `237273`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `244851`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `237583`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap new file mode 100644 index 0000000000..c5192687d0 --- /dev/null +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `92858`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `88390`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `220114`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `97863`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `93395`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `215229`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `215229`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `203246`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `195978`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap new file mode 100644 index 0000000000..18f069f0e4 --- /dev/null +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59818`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55350`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `192331`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59818`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55350`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `187446`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `187446`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `175071`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `167803`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap new file mode 100644 index 0000000000..71f287f75d --- /dev/null +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62266`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57798`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `224290`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62266`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57798`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `219405`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `219405`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `225659`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `218391`; diff --git a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap new file mode 100644 index 0000000000..4fb7c27a86 --- /dev/null +++ b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `114677`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `106365`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `129111`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `90061`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `114496`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `106365`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `121148`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `121148`; diff --git a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap new file mode 100644 index 0000000000..74ec91a258 --- /dev/null +++ b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `115251`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `113583`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `115251`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `113583`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `136973`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `136973`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `115443`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `113775`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `115443`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `113775`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `137229`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `137229`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `123733`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `122065`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `123733`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `122065`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `146011`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `146011`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118381`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116713`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118381`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `116713`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `140375`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `140305`; diff --git a/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap new file mode 100644 index 0000000000..e0a213d4e2 --- /dev/null +++ b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `56918`; + +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `52181`; + +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `57618`; + +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55949`; + +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `71659`; + +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `71659`; diff --git a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap new file mode 100644 index 0000000000..759ebc8430 --- /dev/null +++ b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `85967`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `81498`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `128420`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `65149`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `85967`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `81498`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `65149`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `65149`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `120734`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `120734`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `125485`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `121016`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap new file mode 100644 index 0000000000..7bb8adbe2a --- /dev/null +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `131942`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 2`] = `127473`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 1`] = `177256`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 2`] = `111194`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `131942`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `127473`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `169570`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 2`] = `169570`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `132145`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `127676`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `177662`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `111397`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `132145`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `127676`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `169976`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `169976`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `131298`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `126829`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 1`] = `175968`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 2`] = `110550`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `131298`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `126829`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `168282`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 2`] = `168282`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap new file mode 100644 index 0000000000..7d3645000a --- /dev/null +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `131365`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `126896`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 1`] = `194216`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 2`] = `110550`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `178094`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `173625`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `186530`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 2`] = `186530`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `164997`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `160528`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 1`] = `233480`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 2`] = `144182`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `217358`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `212889`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `225794`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `225794`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap new file mode 100644 index 0000000000..0bb7fec1de --- /dev/null +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `199080`; + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `194611`; + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 1`] = `215207`; + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 2`] = `144160`; + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `199080`; + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `194611`; + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `207521`; + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `207521`; diff --git a/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap new file mode 100644 index 0000000000..54fc867521 --- /dev/null +++ b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `68835`; + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `64366`; + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 1`] = `102946`; + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 2`] = `48056`; + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `68835`; + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `64366`; + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `95260`; + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `95260`; diff --git a/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap b/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap new file mode 100644 index 0000000000..5757607e08 --- /dev/null +++ b/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `53187`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48719`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `66829`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `23429`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `53187`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48719`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `23429`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `23429`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `64090`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `64090`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `71640`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `64372`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `53187`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48719`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `66829`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `23429`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `53187`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48719`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `23429`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `23429`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `64090`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `64090`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `71640`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `64372`; From a6871ac57c0a68eb31249006c0491f165b1d5de4 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Mon, 7 Aug 2023 21:01:20 +0530 Subject: [PATCH 358/462] Fix 4byte Integration (#879) --- .github/workflows/{4bytes.yml => 4byte.yml} | 11 +- package.json | 4 +- scripts/4byte.ts | 51 + scripts/4bytes-syncced.json | 1126 ----------------- scripts/4bytes.ts | 105 -- .../upgrade-checker-utils/constants.ts | 2 +- 6 files changed, 57 insertions(+), 1242 deletions(-) rename .github/workflows/{4bytes.yml => 4byte.yml} (59%) create mode 100644 scripts/4byte.ts delete mode 100644 scripts/4bytes-syncced.json delete mode 100644 scripts/4bytes.ts diff --git a/.github/workflows/4bytes.yml b/.github/workflows/4byte.yml similarity index 59% rename from .github/workflows/4bytes.yml rename to .github/workflows/4byte.yml index 72ce1fb622..3e456ddbae 100644 --- a/.github/workflows/4bytes.yml +++ b/.github/workflows/4byte.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - main pull_request: types: - closed @@ -13,7 +14,7 @@ jobs: name: '4byte Sync' runs-on: ubuntu-latest permissions: - contents: write + contents: read steps: - uses: actions/checkout@v3 with: @@ -23,10 +24,4 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - - run: yarn run:4bytes - - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: 4bytes-syncced.json - commit_options: '--no-verify --signoff' - file_pattern: 'scripts/4bytes-syncced.json' + - run: yarn run:4byte diff --git a/package.json b/package.json index a9e2497e3e..616de80010 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ "size": "hardhat size-contracts", "slither": "python3 tools/slither.py", "prepare": "husky install", - "run:backtests": "npx hardhat run scripts/ci_backtest_plugin.ts", - "run:4bytes": "npx hardhat run scripts/4bytes.ts" + "run:backtests": "hardhat run scripts/ci_backtest_plugin.ts", + "run:4byte": "hardhat run scripts/4byte.ts" }, "repository": { "type": "git", diff --git a/scripts/4byte.ts b/scripts/4byte.ts new file mode 100644 index 0000000000..aef7bee418 --- /dev/null +++ b/scripts/4byte.ts @@ -0,0 +1,51 @@ +import axios from 'axios' +import hre from 'hardhat' + +async function main() { + await hre.run('compile') + + const allArtifactNames = await hre.artifacts.getAllFullyQualifiedNames() + const fullComposite = await Promise.all( + allArtifactNames.map((fullName) => hre.artifacts.readArtifact(fullName).then((e) => e.abi)) + ) + .then((e) => e.flat()) + .then((e) => e.map((v) => [JSON.stringify(v), v] as const)) + .then((e) => [...new Map(e).values()]) + + const parsedComposite = fullComposite + .filter((e) => ['function', 'event', 'error'].includes(e.type)) + .map((e) => { + if (e.type === 'error') { + // errors are same as functions + e.type = 'function' + e.outputs = [] + } + + return e + }) + + if (parsedComposite.length === 0) { + return console.log('Nothing to sync!') + } + + await axios + .post('https://www.4byte.directory/api/v1/import-abi/', { + contract_abi: JSON.stringify(parsedComposite), + }) + .then(({ data }) => { + console.log( + `Processed ${data.num_processed} unique items from ${allArtifactNames.length} individual ABIs adding ${data.num_imported} new selectors to database with ${data.num_duplicates} duplicates and ${data.num_ignored} ignored items.` + ) + }) + .catch((error) => { + throw Error(`Sync failed with code ${error.response.status}!`) + }) + + console.log('Done!') +} + +main().catch((error) => { + console.error(error) + + process.exitCode = 1 +}) diff --git a/scripts/4bytes-syncced.json b/scripts/4bytes-syncced.json deleted file mode 100644 index ccb75c42d6..0000000000 --- a/scripts/4bytes-syncced.json +++ /dev/null @@ -1,1126 +0,0 @@ -{ - "functions": [ - "allowance(address,address)", - "approve(address,uint256)", - "balanceOf(address)", - "totalSupply()", - "transfer(address,uint256)", - "transferFrom(address,address,uint256)", - "decimals()", - "name()", - "symbol()", - "burn(address,address,uint256,uint256)", - "getScaledUserBalanceAndSupply(address)", - "mint(address,uint256,uint256)", - "mintToTreasury(uint256,uint256)", - "scaledBalanceOf(address)", - "scaledTotalSupply()", - "transferOnLiquidation(address,address,uint256)", - "transferUnderlyingTo(address,uint256)", - "handleAction(address,uint256,uint256)", - "borrow(address,uint256,uint256,uint16,address)", - "deposit(address,uint256,address,uint16)", - "finalizeTransfer(address,address,address,uint256,uint256,uint256)", - "flashLoan(address,address[],uint256[],uint256[],address,bytes,uint16)", - "getAddressesProvider()", - "getConfiguration(address)", - "getReserveData(address)", - "getReserveNormalizedIncome(address)", - "getReserveNormalizedVariableDebt(address)", - "getReservesList()", - "getUserAccountData(address)", - "getUserConfiguration(address)", - "initReserve(address,address,address,address,address)", - "liquidationCall(address,address,address,uint256,bool)", - "paused()", - "rebalanceStableBorrowRate(address,address)", - "repay(address,uint256,uint256,address)", - "setConfiguration(address,uint256)", - "setPause(bool)", - "setReserveInterestRateStrategyAddress(address,address)", - "setUserUseReserveAsCollateral(address,bool)", - "swapBorrowRateMode(address,uint256)", - "withdraw(address,uint256,address)", - "getAddress(bytes32)", - "getEmergencyAdmin()", - "getLendingPool()", - "getLendingPoolCollateralManager()", - "getLendingPoolConfigurator()", - "getLendingRateOracle()", - "getMarketId()", - "getPoolAdmin()", - "getPriceOracle()", - "setAddress(bytes32,address)", - "setAddressAsProxy(bytes32,address)", - "setEmergencyAdmin(address)", - "setLendingPoolCollateralManager(address)", - "setLendingPoolConfiguratorImpl(address)", - "setLendingPoolImpl(address)", - "setLendingRateOracle(address)", - "setMarketId(string)", - "setPoolAdmin(address)", - "setPriceOracle(address)", - "BORROW_ALLOWANCE_NOT_ENOUGH()", - "CALLER_NOT_POOL_ADMIN()", - "CT_CALLER_MUST_BE_LENDING_POOL()", - "CT_CANNOT_GIVE_ALLOWANCE_TO_HIMSELF()", - "CT_INVALID_BURN_AMOUNT()", - "CT_INVALID_MINT_AMOUNT()", - "CT_TRANSFER_AMOUNT_NOT_GT_0()", - "LPAPR_INVALID_ADDRESSES_PROVIDER_ID()", - "LPAPR_PROVIDER_NOT_REGISTERED()", - "LPCM_COLLATERAL_CANNOT_BE_LIQUIDATED()", - "LPCM_HEALTH_FACTOR_NOT_BELOW_THRESHOLD()", - "LPCM_NOT_ENOUGH_LIQUIDITY_TO_LIQUIDATE()", - "LPCM_NO_ERRORS()", - "LPCM_SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER()", - "LPC_CALLER_NOT_EMERGENCY_ADMIN()", - "LPC_INVALID_ADDRESSES_PROVIDER_ID()", - "LPC_INVALID_ATOKEN_POOL_ADDRESS()", - "LPC_INVALID_CONFIGURATION()", - "LPC_INVALID_STABLE_DEBT_TOKEN_POOL_ADDRESS()", - "LPC_INVALID_STABLE_DEBT_TOKEN_UNDERLYING_ADDRESS()", - "LPC_INVALID_VARIABLE_DEBT_TOKEN_POOL_ADDRESS()", - "LPC_INVALID_VARIABLE_DEBT_TOKEN_UNDERLYING_ADDRESS()", - "LPC_RESERVE_LIQUIDITY_NOT_0()", - "LP_CALLER_MUST_BE_AN_ATOKEN()", - "LP_CALLER_NOT_LENDING_POOL_CONFIGURATOR()", - "LP_FAILED_COLLATERAL_SWAP()", - "LP_FAILED_REPAY_WITH_COLLATERAL()", - "LP_INCONSISTENT_FLASHLOAN_PARAMS()", - "LP_INCONSISTENT_PARAMS_LENGTH()", - "LP_INCONSISTENT_PROTOCOL_ACTUAL_BALANCE()", - "LP_INTEREST_RATE_REBALANCE_CONDITIONS_NOT_MET()", - "LP_INVALID_EQUAL_ASSETS_TO_SWAP()", - "LP_INVALID_FLASHLOAN_MODE()", - "LP_INVALID_FLASH_LOAN_EXECUTOR_RETURN()", - "LP_IS_PAUSED()", - "LP_LIQUIDATION_CALL_FAILED()", - "LP_NOT_CONTRACT()", - "LP_NOT_ENOUGH_LIQUIDITY_TO_BORROW()", - "LP_NOT_ENOUGH_STABLE_BORROW_BALANCE()", - "LP_NO_MORE_RESERVES_ALLOWED()", - "LP_REENTRANCY_NOT_ALLOWED()", - "LP_REQUESTED_AMOUNT_TOO_SMALL()", - "MATH_ADDITION_OVERFLOW()", - "MATH_DIVISION_BY_ZERO()", - "MATH_MULTIPLICATION_OVERFLOW()", - "RC_INVALID_DECIMALS()", - "RC_INVALID_LIQ_BONUS()", - "RC_INVALID_LIQ_THRESHOLD()", - "RC_INVALID_LTV()", - "RC_INVALID_RESERVE_FACTOR()", - "RL_LIQUIDITY_INDEX_OVERFLOW()", - "RL_LIQUIDITY_RATE_OVERFLOW()", - "RL_RESERVE_ALREADY_INITIALIZED()", - "RL_STABLE_BORROW_RATE_OVERFLOW()", - "RL_VARIABLE_BORROW_INDEX_OVERFLOW()", - "RL_VARIABLE_BORROW_RATE_OVERFLOW()", - "SDT_BURN_EXCEEDS_BALANCE()", - "SDT_STABLE_DEBT_OVERFLOW()", - "UL_INVALID_INDEX()", - "VL_AMOUNT_BIGGER_THAN_MAX_LOAN_SIZE_STABLE()", - "VL_BORROWING_NOT_ENABLED()", - "VL_COLLATERAL_BALANCE_IS_0()", - "VL_COLLATERAL_CANNOT_COVER_NEW_BORROW()", - "VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY()", - "VL_CURRENT_AVAILABLE_LIQUIDITY_NOT_ENOUGH()", - "VL_DEPOSIT_ALREADY_IN_USE()", - "VL_HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD()", - "VL_INCONSISTENT_FLASHLOAN_PARAMS()", - "VL_INVALID_AMOUNT()", - "VL_INVALID_INTEREST_RATE_MODE_SELECTED()", - "VL_NOT_ENOUGH_AVAILABLE_USER_BALANCE()", - "VL_NO_ACTIVE_RESERVE()", - "VL_NO_DEBT_OF_SELECTED_TYPE()", - "VL_NO_EXPLICIT_AMOUNT_TO_REPAY_ON_BEHALF()", - "VL_NO_STABLE_RATE_LOAN_IN_RESERVE()", - "VL_NO_VARIABLE_RATE_LOAN_IN_RESERVE()", - "VL_RESERVE_FROZEN()", - "VL_STABLE_BORROWING_NOT_ENABLED()", - "VL_TRANSFER_NOT_ALLOWED()", - "VL_UNDERLYING_BALANCE_NOT_GREATER_THAN_0()", - "ATOKEN_REVISION()", - "DOMAIN_SEPARATOR()", - "EIP712_REVISION()", - "PERMIT_TYPEHASH()", - "POOL()", - "RESERVE_TREASURY_ADDRESS()", - "UINT_MAX_VALUE()", - "UNDERLYING_ASSET_ADDRESS()", - "_nonces(address)", - "decreaseAllowance(address,uint256)", - "increaseAllowance(address,uint256)", - "initialize(uint8,string,string)", - "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)", - "description()", - "getRoundData(uint80)", - "latestRoundData()", - "version()", - "DEFAULT_ADMIN_ROLE()", - "getRoleAdmin(bytes32)", - "grantRole(bytes32,address)", - "hasRole(bytes32,address)", - "renounceRole(bytes32,address)", - "revokeRole(bytes32,address)", - "supportsInterface(bytes4)", - "owner()", - "renounceOwnership()", - "transferOwnership(address)", - "delegate(address)", - "delegateBySig(address,uint256,uint256,uint8,bytes32,bytes32)", - "delegates(address)", - "getPastTotalSupply(uint256)", - "getPastVotes(address,uint256)", - "getVotes(address)", - "isValidSignature(bytes32,bytes)", - "proxiableUUID()", - "implementation()", - "upgradeTo(address)", - "upgradeToAndCall(address,bytes)", - "nonces(address)", - "BALLOT_TYPEHASH()", - "COUNTING_MODE()", - "EXTENDED_BALLOT_TYPEHASH()", - "castVote(uint256,uint8)", - "castVoteBySig(uint256,uint8,uint8,bytes32,bytes32)", - "castVoteWithReason(uint256,uint8,string)", - "castVoteWithReasonAndParams(uint256,uint8,string,bytes)", - "castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32)", - "execute(address[],uint256[],bytes[],bytes32)", - "getVotes(address,uint256)", - "getVotesWithParams(address,uint256,bytes)", - "hasVoted(uint256,address)", - "hashProposal(address[],uint256[],bytes[],bytes32)", - "onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)", - "onERC1155Received(address,address,uint256,uint256,bytes)", - "onERC721Received(address,address,uint256,bytes)", - "proposalDeadline(uint256)", - "proposalSnapshot(uint256)", - "proposalThreshold()", - "propose(address[],uint256[],bytes[],string)", - "quorum(uint256)", - "relay(address,uint256,bytes)", - "state(uint256)", - "votingDelay()", - "votingPeriod()", - "CANCELLER_ROLE()", - "EXECUTOR_ROLE()", - "PROPOSER_ROLE()", - "TIMELOCK_ADMIN_ROLE()", - "cancel(bytes32)", - "execute(address,uint256,bytes,bytes32,bytes32)", - "executeBatch(address[],uint256[],bytes[],bytes32,bytes32)", - "getMinDelay()", - "getTimestamp(bytes32)", - "hashOperation(address,uint256,bytes,bytes32,bytes32)", - "hashOperationBatch(address[],uint256[],bytes[],bytes32,bytes32)", - "isOperation(bytes32)", - "isOperationDone(bytes32)", - "isOperationPending(bytes32)", - "isOperationReady(bytes32)", - "schedule(address,uint256,bytes,bytes32,bytes32,uint256)", - "scheduleBatch(address[],uint256[],bytes[],bytes32,bytes32,uint256)", - "updateDelay(uint256)", - "proposalVotes(uint256)", - "setProposalThreshold(uint256)", - "setVotingDelay(uint256)", - "setVotingPeriod(uint256)", - "proposalEta(uint256)", - "queue(address[],uint256[],bytes[],bytes32)", - "timelock()", - "updateTimelock(address)", - "token()", - "quorumDenominator()", - "quorumNumerator(uint256)", - "quorumNumerator()", - "updateQuorumNumerator(uint256)", - "admin()", - "changeAdmin(address)", - "multicall(bytes[])", - "ENS()", - "deployments(string)", - "latestDeployment()", - "register(string,address,bool)", - "unregister(string)", - "canRunRecollateralizationAuctions(address)", - "claimRewards(address)", - "getActCalldata(address)", - "getRevenueAuctionERC20s(address)", - "runRevenueAuctions(address,address[],address[])", - "getTradesForBackingManager(address)", - "getTradesForRevenueTraders(address)", - "auctionsSettleable(address)", - "backingOverview(address)", - "backupConfig(address,bytes32)", - "basketBreakdown(address)", - "basketTokens(address)", - "issue(address,uint256)", - "maxIssuable(address,address)", - "pendingUnstakings(address,address)", - "price(address)", - "primeBasket(address)", - "redeem(address,uint256,uint48)", - "stToken(address)", - "traderBalances(address,address)", - "runAuctionsForAllTraders(address)", - "totalAssetValue(address)", - "wholeBasketsHeldBy(address,address)", - "deployRToken((string,string,string,((uint16,uint16),uint192,uint192,uint48,uint48,uint192,uint48,uint48,uint48,uint192,uint192,(uint256,uint192),(uint256,uint192))),(address[],address[],uint192[],(bytes32,uint256,address[])[],(address,(uint16,uint16))[]))", - "deployer()", - "setupGovernance(address,bool,bool,(uint256,uint256,uint256,uint256,uint256),address,address,address)", - "bal(address)", - "claimRewards()", - "erc20()", - "erc20Decimals()", - "isCollateral()", - "lotPrice()", - "maxTradeVolume()", - "price()", - "refresh()", - "refPerTok()", - "status()", - "targetName()", - "targetPerRef()", - "chainlinkFeed()", - "oracleError()", - "oracleTimeout()", - "priceTimeout()", - "delayUntilDefault()", - "whenDefault()", - "erc20s()", - "getRegistry()", - "init(address,address[])", - "isRegistered(address)", - "main()", - "register(address)", - "swapRegistered(address)", - "toAsset(address)", - "toColl(address)", - "unregister(address)", - "claimRewardsSingle(address)", - "grantRTokenAllowance(address)", - "init(address,uint48,uint192,uint192,uint192)", - "manageTokens(address[])", - "manageTokensSortedOrder(address[])", - "maxTradeSlippage()", - "minTradeVolume()", - "mulDivCeil(uint192,uint192,uint192)", - "settleTrade(address)", - "trades(address)", - "tradesOpen()", - "backingBuffer()", - "setBackingBuffer(uint192)", - "setMaxTradeSlippage(uint192)", - "setMinTradeVolume(uint192)", - "setTradingDelay(uint48)", - "tradingDelay()", - "basketsHeldBy(address)", - "disableBasket()", - "fullyCollateralized()", - "init(address)", - "nonce()", - "quantity(address)", - "quantityUnsafe(address,address)", - "quote(uint192,uint8)", - "refreshBasket()", - "setBackupConfig(bytes32,uint256,address[])", - "setPrimeBasket(address[],uint192[])", - "timestamp()", - "disabled()", - "init(address,address,address,uint48)", - "openTrade((address,address,uint256,uint256))", - "reportViolation()", - "auctionLength()", - "gnosis()", - "setAuctionLength(uint48)", - "setDisabled(bool)", - "setGnosis(address)", - "setTradeImplementation(address)", - "tradeImplementation()", - "deploy(string,string,string,address,((uint16,uint16),uint192,uint192,uint48,uint48,uint192,uint48,uint48,uint48,uint192,uint192,(uint256,uint192),(uint256,uint192)))", - "rsr()", - "rsrAsset()", - "distribute(address,uint256)", - "init(address,(uint16,uint16))", - "setDistribution(address,(uint16,uint16))", - "totals()", - "FURNACE()", - "ST_RSR()", - "distribution(address)", - "init(address,uint192)", - "melt()", - "ratio()", - "setRatio(uint192)", - "lastPayout()", - "lastPayoutBal()", - "auctionData(uint256)", - "feeNumerator()", - "initiateAuction(address,address,uint256,uint256,uint96,uint96,uint256,uint256,bool,address,bytes)", - "settleAuction(uint256)", - "freezeForever()", - "freezeLong()", - "freezeShort()", - "frozen()", - "longFreeze()", - "pause()", - "pausedOrFrozen()", - "shortFreeze()", - "unfreeze()", - "unpause()", - "assetRegistry()", - "backingManager()", - "basketHandler()", - "broker()", - "distributor()", - "furnace()", - "rToken()", - "rTokenTrader()", - "rsrTrader()", - "stRSR()", - "init((address,address,address,address,address,address,address,address,address,address),address,uint48,uint48)", - "poke()", - "longFreezes(address)", - "setLongFreeze(uint48)", - "setShortFreeze(uint48)", - "basketsNeeded()", - "init(address,string,string,string,(uint256,uint192),(uint256,uint192))", - "issuanceAvailable()", - "issue(uint256)", - "issueTo(address,uint256)", - "melt(uint256)", - "mint(address,uint256)", - "redeem(uint256,uint48)", - "redeemTo(address,uint256,uint48)", - "redemptionAvailable()", - "setBasketsNeeded(uint192)", - "issuanceThrottleParams()", - "monetizeDonations(address)", - "redemptionThrottleParams()", - "setIssuanceThrottleParams((uint256,uint192))", - "setRedemptionThrottleParams((uint256,uint192))", - "init(address,address,uint192,uint192)", - "manageToken(address)", - "tokenToBuy()", - "endIdForWithdraw(address)", - "exchangeRate()", - "init(address,string,string,uint48,uint192)", - "payoutRewards()", - "seizeRSR(uint256)", - "stake(uint256)", - "unstake(uint256)", - "withdraw(address,uint256)", - "rewardRatio()", - "setRewardRatio(uint192)", - "setUnstakingDelay(uint48)", - "unstakingDelay()", - "currentEra()", - "getPastEra(uint256)", - "buy()", - "canSettle()", - "endTime()", - "sell()", - "settle()", - "allUnique(address[])", - "sortedAndAllUnique(address[])", - "abs_(int256)", - "div(uint192,uint192)", - "divFix_(uint256,uint192)", - "divRnd(uint192,uint192,uint8)", - "divrnd_(uint256,uint256,uint8)", - "divu(uint192,uint256)", - "divuRnd(uint192,uint256,uint8)", - "divuu_(uint256,uint256)", - "eq(uint192,uint192)", - "fixMax_(uint192,uint192)", - "fixMin_(uint192,uint192)", - "fullMul_(uint256,uint256)", - "gt(uint192,uint192)", - "gte(uint192,uint192)", - "lt(uint192,uint192)", - "lte(uint192,uint192)", - "minus(uint192,uint192)", - "minusu(uint192,uint256)", - "mul(uint192,uint192)", - "mulDiv(uint192,uint192,uint192)", - "mulDiv256Rnd_(uint256,uint256,uint256,uint8)", - "mulDiv256_(uint256,uint256,uint256)", - "mulDivRnd(uint192,uint192,uint192,uint8)", - "mulRnd(uint192,uint192,uint8)", - "mul_toUint(uint192,uint192)", - "mul_toUintRnd(uint192,uint192,uint8)", - "mulu(uint192,uint256)", - "muluDivu(uint192,uint256,uint256)", - "muluDivuRnd(uint192,uint256,uint256,uint8)", - "mulu_toUint(uint192,uint256)", - "mulu_toUintRnd(uint192,uint256,uint8)", - "near(uint192,uint192,uint192)", - "neq(uint192,uint192)", - "plus(uint192,uint192)", - "plusu(uint192,uint256)", - "powu(uint192,uint48)", - "safeMul_(uint192,uint192,uint8)", - "shiftl(uint192,int8)", - "shiftlRnd(uint192,int8,uint8)", - "shiftl_toFix_(uint256,int8)", - "shiftl_toFix_Rnd(uint256,int8,uint8)", - "shiftl_toUint(uint192,int8)", - "shiftl_toUintRnd(uint192,int8,uint8)", - "toFix_(uint256)", - "toUint(uint192)", - "toUintRnd(uint192,uint8)", - "toLower(string)", - "LONG_FREEZER_ROLE()", - "OWNER_ROLE()", - "PAUSER_ROLE()", - "SHORT_FREEZER_ROLE()", - "unfreezeAt()", - "GAS_TO_RESERVE()", - "MAX_BACKING_BUFFER()", - "MAX_TRADE_SLIPPAGE()", - "MAX_TRADE_VOLUME()", - "MAX_TRADING_DELAY()", - "MAX_TARGET_AMT()", - "MAX_AUCTION_LENGTH()", - "MIN_BID_SHARE_OF_TOTAL_SUPPLY()", - "MAX_DESTINATIONS_ALLOWED()", - "MAX_RATIO()", - "PERIOD()", - "MAX_EXCHANGE_RATE()", - "MAX_THROTTLE_PCT_AMT()", - "MAX_THROTTLE_RATE_AMT()", - "MIN_EXCHANGE_RATE()", - "MIN_THROTTLE_RATE_AMT()", - "mandate()", - "MAX_REWARD_RATIO()", - "MAX_UNSTAKING_DELAY()", - "MIN_UNSTAKING_DELAY()", - "withdrawals(address,uint256)", - "prepareRecollateralizationTrade(IBackingManager,(uint192,uint192))", - "getBackupConfig(bytes32)", - "getPrimeBasket()", - "implementations()", - "delegationNonces(address)", - "draftQueueLen(uint256,address)", - "draftQueues(uint256,address,uint256)", - "draftRate()", - "firstRemainingDraft(uint256,address)", - "getDraftRSR()", - "getStakeRSR()", - "getTotalDrafts()", - "payoutLastPaid()", - "stakeRate()", - "checkpoints(address,uint48)", - "numCheckpoints(address)", - "exposedReferencePrice()", - "lastSave()", - "pegBottom()", - "pegTop()", - "revenueShowing()", - "savedHighPrice()", - "savedLowPrice()", - "tryPrice()", - "targetUnitChainlinkFeed()", - "targetUnitOracleTimeout()", - "REWARD_TOKEN()", - "claimRewardsToSelf(bool)", - "rate()", - "getIncentivesController()", - "handleRepayment(address,uint256)", - "PRECISION()", - "claimRewards(address[],uint256,address)", - "claimRewardsOnBehalf(address[],uint256,address,address)", - "configureAssets(address[],uint256[])", - "getAssetData(address)", - "getClaimer(address)", - "getRewardsBalance(address[],address)", - "getUserAssetData(address,address)", - "getUserUnclaimedRewards(address)", - "setClaimer(address,address)", - "ASSET()", - "ATOKEN()", - "INCENTIVES_CONTROLLER()", - "LENDING_POOL()", - "claimRewards(address,bool)", - "claimRewardsOnBehalf(address,address,bool)", - "collectAndUpdateRewards()", - "deposit(address,uint256,uint16,bool)", - "dynamicBalanceOf(address)", - "dynamicToStaticAmount(uint256)", - "getAccRewardsPerToken()", - "getClaimableRewards(address)", - "getDomainSeparator()", - "getLastRewardBlock()", - "getLifetimeRewards()", - "getLifetimeRewardsClaimed()", - "getTotalClaimableRewards()", - "getUnclaimedRewards(address)", - "metaDeposit(address,address,uint256,uint16,bool,uint256,(uint8,bytes32,bytes32))", - "metaWithdraw(address,address,uint256,uint256,bool,uint256,(uint8,bytes32,bytes32))", - "staticToDynamicAmount(uint256)", - "withdraw(address,uint256,bool)", - "withdrawDynamicAmount(address,uint256,bool)", - "INVALID_CLAIMER()", - "INVALID_DEPOSITOR()", - "INVALID_EXPIRATION()", - "INVALID_OWNER()", - "INVALID_RECIPIENT()", - "INVALID_SIGNATURE()", - "ONLY_ONE_AMOUNT_FORMAT_ALLOWED()", - "METADEPOSIT_TYPEHASH()", - "METAWITHDRAWAL_TYPEHASH()", - "STATIC_ATOKEN_LM_REVISION()", - "updateRatio(uint256)", - "comptroller()", - "referenceERC20Decimals()", - "exchangeRateCurrent()", - "exchangeRateStored()", - "mint(uint256)", - "redeem(uint256)", - "underlying()", - "claimComp(address)", - "getCompAddress()", - "comet()", - "reservesThresholdIffy()", - "rewardERC20()", - "BASE_SCALE()", - "EXP_SCALE()", - "RESCALE_FACTOR()", - "TRACKING_INDEX_SCALE()", - "accrue()", - "accrueAccount(address)", - "allow(address,bool)", - "baseTrackingAccrued(address)", - "baseTrackingIndex(address)", - "claimTo(address,address)", - "convertDynamicToStatic(uint256)", - "convertStaticToDynamic(uint104)", - "deposit(uint256)", - "depositFrom(address,address,uint256)", - "depositTo(address,uint256)", - "getRewardOwed(address)", - "hasPermission(address,address)", - "isAllowed(address,address)", - "rewardsAddr()", - "rewardsClaimed(address)", - "underlyingBalanceOf(address)", - "underlyingComet()", - "withdraw(uint256)", - "withdrawFrom(address,address,uint256)", - "withdrawTo(address,uint256)", - "allowBySig(address,address,bool,uint256,uint256,uint8,bytes32,bytes32)", - "baseAccrualScale()", - "baseIndexScale()", - "collateralBalanceOf(address,address)", - "factorScale()", - "maxAssets()", - "priceScale()", - "totalsBasic()", - "absorb(address,address[])", - "approveThis(address,address,uint256)", - "baseBorrowMin()", - "baseMinForRewards()", - "baseScale()", - "baseToken()", - "baseTokenPriceFeed()", - "baseTrackingBorrowSpeed()", - "baseTrackingSupplySpeed()", - "borrowBalanceOf(address)", - "borrowPerSecondInterestRateBase()", - "borrowPerSecondInterestRateSlopeHigh()", - "borrowPerSecondInterestRateSlopeLow()", - "buyCollateral(address,uint256,uint256,address)", - "extensionDelegate()", - "getAssetInfo(uint8)", - "getAssetInfoByAddress(address)", - "getBorrowRate(uint256)", - "getPrice(address)", - "getReserves()", - "getSupplyRate(uint256)", - "getUtilization()", - "governor()", - "initializeStorage()", - "isAbsorbPaused()", - "isBorrowCollateralized(address)", - "isBuyPaused()", - "isLiquidatable(address)", - "isSupplyPaused()", - "isTransferPaused()", - "isWithdrawPaused()", - "numAssets()", - "pause(bool,bool,bool,bool,bool)", - "pauseGuardian()", - "quoteCollateral(address,uint256)", - "storeFrontPriceFactor()", - "supply(address,uint256)", - "supplyFrom(address,address,address,uint256)", - "supplyKink()", - "supplyPerSecondInterestRateBase()", - "supplyPerSecondInterestRateSlopeHigh()", - "supplyPerSecondInterestRateSlopeLow()", - "supplyTo(address,address,uint256)", - "targetReserves()", - "totalBorrow()", - "trackingIndexScale()", - "transferAsset(address,address,uint256)", - "transferAssetFrom(address,address,address,uint256)", - "userBasic(address)", - "withdrawFrom(address,address,address,uint256)", - "withdrawReserves(address,uint256)", - "withdrawTo(address,address,uint256)", - "setBaseTrackingSupplySpeed(address,uint64)", - "deploy(address)", - "deployAndUpgradeTo(address,address)", - "claim(address,address,bool)", - "claimTo(address,address,address,bool)", - "getRewardOwed(address,address)", - "rewardConfig(address)", - "curvePool()", - "lpToken()", - "tokenPrice(uint8)", - "metapoolToken()", - "pairedToken()", - "pairedTokenPegBottom()", - "pairedTokenPegTop()", - "tryPairedPrice()", - "balances(uint256)", - "base_coins(uint256)", - "coins(uint256)", - "exchange(int128,int128,uint256,uint256)", - "get_virtual_price()", - "underlying_coins(uint256)", - "deposit(uint256,bool)", - "lockIncentive()", - "claim_rewards()", - "lp_token()", - "reward_tokens(uint256)", - "rewarded_token()", - "create_lock(uint256,uint256)", - "increase_amount(uint256)", - "increase_unlock_time(uint256)", - "smart_wallet_checker()", - "withdraw()", - "claimRewards(uint256,address)", - "isShutdown()", - "poolInfo(uint256)", - "rewardArbitrator()", - "rewardClaimed(uint256,address,uint256)", - "setGaugeRedirect(uint256)", - "withdrawTo(uint256,uint256,address)", - "claim()", - "mint(address)", - "addPool(address,address,uint256)", - "forceAddPool(address,address,uint256)", - "gaugeMap(address)", - "poolLength()", - "setPoolManager(address)", - "shutdownPool(uint256)", - "gauge_controller()", - "get_address(uint256)", - "get_gauges(address)", - "get_lp_token(address)", - "get_registry()", - "CreateCrvRewards(uint256,address)", - "CreateTokenRewards(address,address,address)", - "activeRewardCount(address)", - "addActiveReward(address,uint256)", - "removeActiveReward(address,uint256)", - "setAccess(address,bool)", - "addExtraReward(address)", - "earned(address)", - "exit(address)", - "getReward(address)", - "notifyRewardAmount(uint256)", - "queueNewRewards(uint256)", - "rewardToken()", - "stake(address,uint256)", - "stakeFor(address,uint256)", - "stakingToken()", - "balanceOfPool(address)", - "claimCrv(address)", - "claimFees(address,address)", - "createLock(uint256,uint256)", - "deposit(address,address)", - "execute(address,uint256,bytes)", - "increaseAmount(uint256)", - "increaseTime(uint256)", - "operator()", - "release()", - "setStashAccess(address,bool)", - "vote(uint256,address,bool)", - "voteGaugeWeight(address,uint256)", - "withdraw(address)", - "withdraw(address,address,uint256)", - "withdrawAll(address,address)", - "initialize(uint256,address,address,address,address)", - "processStash()", - "stashRewards()", - "CreateStash(uint256,address,address,uint256)", - "CreateDepositToken(address)", - "burn(address,uint256)", - "fund(address[],uint256[])", - "getVote(uint256)", - "vote(uint256,bool,bool)", - "vote_for_gauge_weights(address,uint256)", - "check(address)", - "addRewards()", - "collateralVault()", - "convexBooster()", - "convexPool()", - "convexPoolId()", - "convexToken()", - "crv()", - "curveToken()", - "cvx()", - "deposit(uint256,address)", - "earnedView(address)", - "getReward(address,address)", - "initialize(uint256)", - "isInit()", - "registeredRewards(address)", - "rewardLength()", - "rewards(uint256)", - "setApprovals()", - "shutdown()", - "stake(uint256,address)", - "totalBalanceOf(address)", - "user_checkpoint(address)", - "withdrawAndUnwrap(uint256)", - "deposit(uint256,uint256,bool)", - "deposit(uint256,bool,address)", - "ConvertCrvToCvx(uint256)", - "maxSupply()", - "reductionPerCliff()", - "totalCliffs()", - "extraRewards(uint256)", - "extraRewardsLength()", - "getReward()", - "getReward(address,bool)", - "withdrawAndUnwrap(uint256,bool)", - "submit()", - "submitAndDeposit(address)", - "convertToAssets(uint256)", - "pricePerShare()", - "rewardsCycleEnd()", - "syncRewards()", - "getBeaconStat()", - "handleOracleReport(uint256,uint256)", - "stEthPerToken()", - "targetPerRefChainlinkFeed()", - "targetPerRefChainlinkTimeout()", - "getExchangeRate()", - "getTotalETHBalance()", - "setUint(bytes32,uint256)", - "refPerTokChainlinkFeed()", - "refPerTokChainlinkTimeout()", - "ONE_HUNDRED_PERCENT()", - "cancel(address[],uint256[],bytes[],bytes32)", - "adminApprove(address,address,uint256)", - "aaveBalances(address)", - "aaveToken()", - "setAaveToken(address)", - "setExchangeRate(uint192)", - "setRewards(address,uint256)", - "setNormalizedIncome(address,uint256)", - "WETH()", - "getAssetPrice(address)", - "checkHardDefault()", - "checkSoftDefault()", - "setHardDefaultCheck(bool)", - "setSoftDefaultCheck(bool)", - "censored(address)", - "revertDecimals()", - "setCensored(address,bool)", - "borrowKink()", - "withdraw(uint256,bool)", - "setRevertDecimals(bool)", - "setTransferFee(uint192)", - "transferFee()", - "getAnswer(uint256)", - "getTimestamp(uint256)", - "latestAnswer()", - "latestAnsweredRound()", - "latestRound()", - "latestTimestamp()", - "setInvalidAnsweredRound()", - "setInvalidTimestamp()", - "updateAnswer(int256)", - "updateRoundData(uint80,int256,uint256,uint256)", - "externalDelegate()", - "setReserves(int256)", - "compBalances(address)", - "compToken()", - "setCompToken(address)", - "setBalances(uint256[])", - "setVirtualPrice(uint256)", - "setMockExchangeRate(bool,uint256)", - "hasAccess(address,bytes)", - "acceptOwnership()", - "aggregator()", - "confirmAggregator(address)", - "phaseAggregators(uint16)", - "phaseId()", - "proposeAggregator(address)", - "proposedAggregator()", - "proposedGetRoundData(uint80)", - "proposedLatestRoundData()", - "accessController()", - "setController(address)", - "__getAnswer(uint256)", - "__getTimestamp(uint256)", - "__latestAnswer()", - "__latestAnsweredRound()", - "__latestRound()", - "__latestTimestamp()", - "counter()", - "approvalsOn()", - "disableApprovals()", - "enableApprovals()", - "FEE_DENOMINATOR()", - "auctionAccessData(uint256)", - "auctionAccessManager(uint256)", - "auctionCounter()", - "cancelSellOrders(uint256,bytes32[])", - "claimFromParticipantOrder(uint256,bytes32[])", - "containsOrder(uint256,bytes32)", - "feeReceiverUserId()", - "getSecondsRemainingInBatch(uint256)", - "getUserId(address)", - "numUsers()", - "placeSellOrders(uint256,uint96[],uint96[],bytes32[],bytes)", - "placeSellOrdersOnBehalf(uint256,uint96[],uint96[],bytes32[],bytes,address)", - "precalculateSellAmountSum(uint256,uint256)", - "registerUser(address)", - "setFeeParameters(uint256,address)", - "settleAuctionAtomically(uint256,uint96[],uint96[],bytes32[],bytes)", - "infiniteLoop()", - "revertRefPerTok()", - "setRevertRefPerTok(bool)", - "auctions(uint256)", - "bids(uint256)", - "numAuctions()", - "placeBid(uint256,(address,uint256,uint256))", - "reenterOnInit()", - "reenterOnSettle()", - "setReenterOnInit(bool)", - "setReenterOnSettle(bool)", - "setSimplyRevert(bool)", - "simplyRevert()", - "rateMock()", - "refPerTokRevert()", - "setRate(uint192)", - "setRefPerTokRevert(bool)", - "setTargetPerRef(uint192)", - "priceable()", - "destroyAndTransfer(address)", - "setPricePerShare(uint256)", - "mockRefPerTok()", - "setUnpriced(bool)", - "unpriced()", - "deposit()", - "newValue()", - "setNewValue(uint256)", - "isAllowed(address,uint256,bytes)", - "contains(bytes32)", - "decodeOrder(bytes32)", - "encodeOrder(uint64,uint96,uint96)", - "first()", - "initializeEmptyList()", - "insert(bytes32)", - "insertAt(bytes32,bytes32)", - "isEmpty()", - "next(bytes32)", - "nextMap(bytes32)", - "prevMap(bytes32)", - "remove(bytes32)", - "removeKeepHistory(bytes32)", - "smallerThan(bytes32,bytes32)", - "DEFAULT_MIN_BID()", - "MAX_ORDERS()", - "auctionId()", - "init(address,address,address,uint48,(address,address,uint256,uint256))", - "initBal()", - "origin()", - "transferToOriginAfterTradeComplete(address)", - "worstCasePrice()" - ], - "events": [ - "Approval(address,address,uint256)", - "Transfer(address,address,uint256)", - "BalanceTransfer(address,address,uint256,uint256)", - "Burn(address,address,uint256,uint256)", - "Mint(address,uint256,uint256)", - "Borrow(address,address,address,uint256,uint256,uint256,uint16)", - "Deposit(address,address,address,uint256,uint16)", - "FlashLoan(address,address,address,uint256,uint256,uint16)", - "LiquidationCall(address,address,address,uint256,uint256,address,bool)", - "Paused()", - "RebalanceStableBorrowRate(address,address)", - "Repay(address,address,address,uint256)", - "ReserveDataUpdated(address,uint256,uint256,uint256,uint256,uint256)", - "ReserveUsedAsCollateralDisabled(address,address)", - "ReserveUsedAsCollateralEnabled(address,address)", - "Swap(address,address,uint256)", - "Unpaused()", - "Withdraw(address,address,address,uint256)", - "AddressSet(bytes32,address,bool)", - "ConfigurationAdminUpdated(address)", - "EmergencyAdminUpdated(address)", - "LendingPoolCollateralManagerUpdated(address)", - "LendingPoolConfiguratorUpdated(address)", - "LendingPoolUpdated(address)", - "LendingRateOracleUpdated(address)", - "MarketIdSet(string)", - "PriceOracleUpdated(address)", - "ProxyCreated(bytes32,address)", - "Initialized(uint8)", - "RoleAdminChanged(bytes32,bytes32,bytes32)", - "RoleGranted(bytes32,address,address)", - "RoleRevoked(bytes32,address,address)", - "OwnershipTransferred(address,address)", - "DelegateChanged(address,address,address)", - "DelegateVotesChanged(address,uint256,uint256)", - "AdminChanged(address,address)", - "BeaconUpgraded(address)", - "Upgraded(address)", - "ProposalCanceled(uint256)", - "ProposalCreated(uint256,address,address[],uint256[],string[],bytes[],uint256,uint256,string)", - "ProposalExecuted(uint256)", - "VoteCast(address,uint256,uint8,uint256,string)", - "VoteCastWithParams(address,uint256,uint8,uint256,string,bytes)", - "CallExecuted(bytes32,uint256,address,uint256,bytes)", - "CallScheduled(bytes32,uint256,address,uint256,bytes,bytes32,uint256)", - "Cancelled(bytes32)", - "MinDelayChange(uint256,uint256)", - "ProposalThresholdSet(uint256,uint256)", - "VotingDelaySet(uint256,uint256)", - "VotingPeriodSet(uint256,uint256)", - "ProposalQueued(uint256,uint256)", - "TimelockChange(address,address)", - "QuorumNumeratorUpdated(uint256,uint256)", - "DeploymentRegistered(string,address)", - "DeploymentUnregistered(string,address)", - "LatestChanged(string,address)", - "GovernanceCreated(address,address,address)", - "RewardsClaimed(address,uint256)", - "CollateralStatusChanged(uint8,uint8)", - "AssetRegistered(address,address)", - "AssetUnregistered(address,address)", - "BackingBufferSet(uint192,uint192)", - "MaxTradeSlippageSet(uint192,uint192)", - "MinTradeVolumeSet(uint192,uint192)", - "TradeSettled(address,address,address,uint256,uint256)", - "TradeStarted(address,address,address,uint256,uint256)", - "TradingDelaySet(uint48,uint48)", - "BackupConfigSet(bytes32,uint256,address[])", - "BasketSet(uint256,address[],uint192[],bool)", - "PrimeBasketSet(address[],uint192[],bytes32[])", - "AuctionLengthSet(uint48,uint48)", - "DisabledSet(bool,bool)", - "GnosisSet(address,address)", - "TradeImplementationSet(address,address)", - "RTokenCreated(address,address,address,address,string)", - "DistributionSet(address,uint16,uint16)", - "RevenueDistributed(address,address,uint256)", - "RatioSet(uint192,uint192)", - "LongFreezeDurationSet(uint48,uint48)", - "PausedSet(bool,bool)", - "ShortFreezeDurationSet(uint48,uint48)", - "UnfreezeAtSet(uint48,uint48)", - "AssetRegistrySet(address,address)", - "BackingManagerSet(address,address)", - "BasketHandlerSet(address,address)", - "BrokerSet(address,address)", - "DistributorSet(address,address)", - "FurnaceSet(address,address)", - "RSRTraderSet(address,address)", - "RTokenSet(address,address)", - "RTokenTraderSet(address,address)", - "StRSRSet(address,address)", - "MainInitialized()", - "BasketsNeededChanged(uint192,uint192)", - "Issuance(address,address,uint256,uint192)", - "IssuanceThrottleSet((uint256,uint192),(uint256,uint192))", - "Melted(uint256)", - "Redemption(address,address,uint256,uint192)", - "RedemptionThrottleSet((uint256,uint192),(uint256,uint192))", - "AllBalancesReset(uint256)", - "AllUnstakingReset(uint256)", - "ExchangeRateSet(uint192,uint192)", - "RewardRatioSet(uint192,uint192)", - "RewardsPaid(uint256)", - "Staked(uint256,address,uint256,uint256)", - "UnstakingCompleted(uint256,uint256,uint256,address,uint256)", - "UnstakingDelaySet(uint48,uint48)", - "UnstakingStarted(uint256,uint256,address,uint256,uint256,uint256)", - "ClaimerSet(address,address)", - "RewardsAccrued(address,uint256)", - "RewardsClaimed(address,address,uint256)", - "RewardsClaimed(address,address,address,uint256)", - "RewardClaimed(address,address,address,uint256)", - "AbsorbCollateral(address,address,address,uint256,uint256)", - "AbsorbDebt(address,address,uint256,uint256)", - "BuyCollateral(address,address,uint256,uint256)", - "PauseAction(bool,bool,bool,bool,bool)", - "Supply(address,address,uint256)", - "SupplyCollateral(address,address,address,uint256)", - "TransferCollateral(address,address,address,uint256)", - "Withdraw(address,address,uint256)", - "WithdrawCollateral(address,address,address,uint256)", - "WithdrawReserves(address,uint256)", - "Deposited(address,address,uint256,bool)", - "Withdrawn(address,uint256,bool)", - "AnswerUpdated(int256,uint256,uint256)", - "NewRound(uint256,address,uint256)", - "OwnershipTransferRequested(address,address)", - "AuctionCleared(uint256,uint96,uint96,bytes32)", - "CancellationSellOrder(uint256,uint64,uint96,uint96)", - "ClaimedFromOrder(uint256,uint64,uint96,uint96)", - "NewAuction(uint256,address,address,uint256,uint256,uint64,uint96,uint96,uint256,uint256,address,bytes)", - "NewSellOrder(uint256,uint64,uint96,uint96)", - "NewUser(uint64,address)", - "UserRegistration(address,uint64)", - "Deposit(address,uint256)", - "Withdrawal(address,uint256)", - "Empty()", - "OutOfBounds()", - "UIntOutOfBounds()", - "StalePrice()", - "InvalidInt256()", - "InvalidUInt104()", - "InvalidUInt64()", - "NegativeNumber()", - "BadAmount()", - "ExceedsBalance(uint256)", - "Unauthorized()", - "ZeroAddress()", - "BadNonce()", - "BadSignatory()", - "InvalidValueS()", - "InvalidValueV()", - "SignatureExpired()", - "Absurd()", - "AlreadyInitialized()", - "BadAsset()", - "BadDecimals()", - "BadDiscount()", - "BadMinimum()", - "BadPrice()", - "BorrowCFTooLarge()", - "BorrowTooSmall()", - "InsufficientReserves()", - "LiquidateCFTooLarge()", - "NoSelfTransfer()", - "NotCollateralized()", - "NotForSale()", - "NotLiquidatable()", - "SupplyCapExceeded()", - "TimestampTooLarge()", - "TooManyAssets()", - "TooMuchSlippage()", - "TransferInFailed()", - "TransferOutFailed()", - "NoToken(uint8)", - "WrongIndex(uint8)" - ] -} diff --git a/scripts/4bytes.ts b/scripts/4bytes.ts deleted file mode 100644 index e0bdc74421..0000000000 --- a/scripts/4bytes.ts +++ /dev/null @@ -1,105 +0,0 @@ -import hre from 'hardhat' -import fs from 'fs' -import fetch from 'isomorphic-fetch' -import previousSync from './4bytes-syncced.json' -/** - * This script will sync any event and function we have with www.4byte.directory - * The script saves all processed signatures with 4bytes-syncced.json as it succcesses - * this way we avoid syncing the same signature twice. - * */ - -const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) - -async function main() { - const artifacts = await hre.artifacts.getAllFullyQualifiedNames() - const artifactsWithAbi = ( - await Promise.all(artifacts.map((name) => hre.artifacts.readArtifact(name))) - ).filter((artifact) => artifact.abi.length !== 0) - const prevFunctions = new Set(previousSync.functions) - const prevEvents = new Set(previousSync.events) - const newErrorSignatures = new Set() - const newFunctionSignatures = new Set() - const newEventSignatures = new Set() - for (const { abi } of artifactsWithAbi) { - const abiInterface = new hre.ethers.utils.Interface(abi) - // Events and Errors seem to be the same thing for 4bytes - Object.keys(abiInterface.events) - .filter((e) => !prevEvents.has(e)) - .forEach((e) => newEventSignatures.add(e)) - Object.keys(abiInterface.errors) - .filter((e) => !prevEvents.has(e)) - .forEach((e) => newEventSignatures.add(e)) - - Object.keys(abiInterface.functions) - .filter((e) => !prevFunctions.has(e)) - .forEach((e) => newFunctionSignatures.add(e)) - } - const total = newErrorSignatures.size + newFunctionSignatures.size + newEventSignatures.size - if (total === 0) { - console.log('All up to date!') - return - } - - console.log('Will sync ' + total + ' signatures with 4bytes...') - - const save = () => { - fs.writeFileSync('./scripts/4bytes-syncced.json', JSON.stringify(previousSync, null, 2)) - } - console.log('----- Synccing functions ----- ') - for (const sig of newFunctionSignatures) { - for (let i = 0; i < 3; i++) { - const resp = await fetch('https://www.4byte.directory/api/v1/signatures/', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - text_signature: sig, - }), - }) - if (resp.status === 400 || resp.status === 201) { - console.log('function', sig, resp.status, await resp.text()) - previousSync.functions.push(sig) - save() - break - } - if (i === 2) { - console.log('Failed to sync function', sig, 'after 3 attempts') - } else { - await sleep(1000) - } - } - } - console.log('----- Synccing events ----- ') - for (const sig of newEventSignatures) { - for (let i = 0; i < 3; i++) { - const resp = await fetch('https://www.4byte.directory/api/v1/event-signatures/', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - text_signature: sig, - }), - }) - if (resp.status === 400 || resp.status === 201) { - console.log('event', sig, resp.status, await resp.text()) - previousSync.events.push(sig) - save() - break - } - - if (i === 2) { - console.log('Failed to sync event', sig, 'after 3 attempts') - } else { - await sleep(1000) - } - } - } - console.log('Done!') -} - -main().catch((error) => { - console.error(error) - process.exitCode = 1 -}) diff --git a/tasks/testing/upgrade-checker-utils/constants.ts b/tasks/testing/upgrade-checker-utils/constants.ts index fe5640245c..a58d60a7fe 100644 --- a/tasks/testing/upgrade-checker-utils/constants.ts +++ b/tasks/testing/upgrade-checker-utils/constants.ts @@ -17,7 +17,7 @@ export const whales: { [key: string]: string } = { [networkConfig['1'].tokens.stETH!.toLowerCase()]: '0x176F3DAb24a159341c0509bB36B833E7fdd0a132', [networkConfig['1'].tokens.WETH!.toLowerCase()]: '0x8EB8a3b98659Cce290402893d0123abb75E3ab28', [networkConfig['1'].tokens.DAI!.toLowerCase()]: '0x8EB8a3b98659Cce290402893d0123abb75E3ab28', - [networkConfig['1'].tokens.CRV!.toLowerCase()]: "0xf977814e90da44bfa03b6295a0616a897441acec" + [networkConfig['1'].tokens.CRV!.toLowerCase()]: '0xf977814e90da44bfa03b6295a0616a897441acec', } export const collateralToUnderlying: { [key: string]: string } = { From 949b0be9f492974e2603bc50bb0ed2fc5fcb8022 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 8 Aug 2023 15:23:55 +0200 Subject: [PATCH 359/462] Dutch trade decimals (#883) --- contracts/plugins/trading/DutchTrade.sol | 28 ++-- package.json | 2 +- test/Broker.test.ts | 155 ++++++++++++++++++++++- 3 files changed, 169 insertions(+), 16 deletions(-) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 98eecfeb96..b6373b32a6 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -111,7 +111,12 @@ contract DutchTrade is ITrade { status = end; } - // === External Bid Helper === + // === Auction Sizing Views === + + /// @return {qSellTok} The size of the lot being sold, in token quanta + function lot() public view returns (uint256) { + return sellAmount.shiftl_toUint(int8(sell.decimals())); + } /// Calculates how much buy token is needed to purchase the lot at a particular block /// @param blockNumber {block} The block number of the bid @@ -120,6 +125,8 @@ contract DutchTrade is ITrade { return _bidAmount(_price(blockNumber)); } + // ==== Constructor === + constructor() { ONE_BLOCK = NetworkConfigLib.blocktime(); @@ -218,18 +225,16 @@ contract DutchTrade is ITrade { // Received bid if (bidder != address(0)) { - sell.safeTransfer(bidder, sellAmount); + soldAmt = lot(); // {qSellTok} + sell.safeTransfer(bidder, soldAmt); // {qSellTok} } else { require(block.number > endBlock, "auction not over"); } - uint256 sellBal = sell.balanceOf(address(this)); - soldAmt = sellAmount > sellBal ? sellAmount - sellBal : 0; - boughtAmt = buy.balanceOf(address(this)); - - // Transfer balances back to origin - buy.safeTransfer(address(origin), boughtAmt); - sell.safeTransfer(address(origin), sellBal); + // Transfer remaining balances back to origin + boughtAmt = buy.balanceOf(address(this)); // {qBuyTok} + buy.safeTransfer(address(origin), boughtAmt); // {qBuyTok} + sell.safeTransfer(address(origin), sell.balanceOf(address(this))); // {qSellTok} } /// Anyone can transfer any ERC20 back to the origin after the trade has been closed @@ -246,11 +251,6 @@ contract DutchTrade is ITrade { return status == TradeStatus.OPEN && (bidder != address(0) || block.number > endBlock); } - /// @return {qSellTok} The size of the lot being sold, in token quanta - function lot() external view returns (uint256) { - return sellAmount.shiftl_toUint(int8(sell.decimals())); - } - // === Private === /// Return a sliding % from 0 (at maxTradeVolume) to maxTradeSlippage (at minTradeVolume) diff --git a/package.json b/package.json index 616de80010..9afb749c90 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "deploy:run": "hardhat run scripts/deploy.ts", "deploy:confirm": "hardhat run scripts/confirm.ts", "deploy:verify_etherscan": "hardhat run scripts/verify_etherscan.ts", - "test:extreme": "EXTREME=1 PROTO_IMPL=1 npx hardhat test test/{Furnace,RTokenExtremes,ZTradingExtremes,ZZStRSR}.test.ts", + "test:extreme": "EXTREME=1 PROTO_IMPL=1 npx hardhat test test/{Broker,Furnace,RTokenExtremes,ZTradingExtremes,ZZStRSR}.test.ts", "test:extreme:integration": "FORK=1 EXTREME=1 PROTO_IMPL=1 npx hardhat test test/integration/**/*.test.ts", "test:unit": "yarn test:plugins && yarn test:p0 && yarn test:p1", "test:fast": "bash tools/fast-test.sh", diff --git a/test/Broker.test.ts b/test/Broker.test.ts index a484b2d5e6..6f86dc61f4 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -6,6 +6,7 @@ import { BigNumber, ContractFactory } from 'ethers' import { ethers, upgrades } from 'hardhat' import { IConfig, MAX_AUCTION_LENGTH } from '../common/configuration' import { + MAX_UINT48, MAX_UINT96, MAX_UINT192, TradeKind, @@ -13,7 +14,7 @@ import { ZERO_ADDRESS, ONE_ADDRESS, } from '../common/constants' -import { bn, fp, divCeil, toBNDecimals } from '../common/numbers' +import { bn, fp, divCeil, shortString, toBNDecimals } from '../common/numbers' import { DutchTrade, ERC20Mock, @@ -23,13 +24,16 @@ import { GnosisTrade, IAssetRegistry, TestIBackingManager, + TestIBasketHandler, TestIBroker, TestIMain, TestIRevenueTrader, + TestIRToken, USDCMock, ZeroDecimalMock, } from '../typechain' import { whileImpersonating } from './utils/impersonation' +import { cartesianProduct } from './utils/cases' import { Collateral, DefaultFixture, @@ -39,6 +43,7 @@ import { ORACLE_ERROR, ORACLE_TIMEOUT, PRICE_TIMEOUT, + SLOW, } from './fixtures' import snapshotGasCost from './utils/snapshotGasCost' import { @@ -54,6 +59,9 @@ import { useEnv } from '#/utils/env' const DEFAULT_THRESHOLD = fp('0.01') // 1% const DELAY_UNTIL_DEFAULT = bn('86400') // 24h +const describeExtreme = + IMPLEMENTATION == Implementation.P1 && useEnv('EXTREME') ? describe.only : describe.skip + const describeGas = IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip @@ -82,8 +90,10 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { let main: TestIMain let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager + let basketHandler: TestIBasketHandler let rsrTrader: TestIRevenueTrader let rTokenTrader: TestIRevenueTrader + let rToken: TestIRToken let basket: Collateral[] let collateral: Collateral[] @@ -99,10 +109,12 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { main, assetRegistry, backingManager, + basketHandler, broker, gnosis, rsrTrader, rTokenTrader, + rToken, collateral, } = await loadFixture(defaultFixture)) @@ -1298,6 +1310,147 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) }) + describeExtreme(`Extreme Values ${SLOW ? 'slow mode' : 'fast mode'}`, () => { + if (!(Implementation.P1 && useEnv('EXTREME'))) return // prevents bunch of skipped tests + + async function runScenario([ + sellTokDecimals, + buyTokDecimals, + auctionSellAmt, + progression, + ]: BigNumber[]) { + // Factories + const ERC20Factory = await ethers.getContractFactory('ERC20MockDecimals') + const CollFactory = await ethers.getContractFactory('FiatCollateral') + const sellTok = await ERC20Factory.deploy('Sell Token', 'SELL', sellTokDecimals) + const buyTok = await ERC20Factory.deploy('Buy Token', 'BUY', buyTokDecimals) + const sellColl = await CollFactory.deploy({ + priceTimeout: MAX_UINT48, + chainlinkFeed: await collateral0.chainlinkFeed(), + oracleError: bn('1'), // minimize + erc20: sellTok.address, + maxTradeVolume: MAX_UINT192, + oracleTimeout: MAX_UINT48, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01'), // shouldn't matter + delayUntilDefault: bn('604800'), // shouldn't matter + }) + await assetRegistry.connect(owner).register(sellColl.address) + const buyColl = await CollFactory.deploy({ + priceTimeout: MAX_UINT48, + chainlinkFeed: await collateral0.chainlinkFeed(), + oracleError: bn('1'), // minimize + erc20: buyTok.address, + maxTradeVolume: MAX_UINT192, + oracleTimeout: MAX_UINT48, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01'), // shouldn't matter + delayUntilDefault: bn('604800'), // shouldn't matter + }) + await assetRegistry.connect(owner).register(buyColl.address) + + // Set basket + await basketHandler + .connect(owner) + .setPrimeBasket([sellTok.address, buyTok.address], [fp('0.5'), fp('0.5')]) + await basketHandler.connect(owner).refreshBasket() + + const MAX_ERC20_SUPPLY = bn('1e48') // from docs/solidity-style.md + + // Max out throttles + const issuanceThrottleParams = { amtRate: MAX_ERC20_SUPPLY, pctRate: 0 } + const redemptionThrottleParams = { amtRate: MAX_ERC20_SUPPLY, pctRate: 0 } + await rToken.connect(owner).setIssuanceThrottleParams(issuanceThrottleParams) + await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + await advanceTime(3600) + + // Mint coll tokens to addr1 + await buyTok.connect(owner).mint(addr1.address, MAX_ERC20_SUPPLY) + await sellTok.connect(owner).mint(addr1.address, MAX_ERC20_SUPPLY) + + // Issue RToken + await buyTok.connect(addr1).approve(rToken.address, MAX_ERC20_SUPPLY) + await sellTok.connect(addr1).approve(rToken.address, MAX_ERC20_SUPPLY) + await rToken.connect(addr1).issue(MAX_ERC20_SUPPLY.div(2)) + + // Burn buyTok from backingManager and send extra sellTok + const burnAmount = divCeil( + auctionSellAmt.mul(bn(10).pow(buyTokDecimals)), + bn(10).pow(sellTokDecimals) + ) + await buyTok.burn(backingManager.address, burnAmount) + await sellTok.connect(addr1).transfer(backingManager.address, auctionSellAmt.mul(10)) + + // Rebalance should cause backingManager to trade about auctionSellAmt, though not exactly + await backingManager.setMaxTradeSlippage(bn('0')) + await backingManager.setMinTradeVolume(bn('0')) + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)) + .to.emit(backingManager, 'TradeStarted') + .withArgs(anyValue, sellTok.address, buyTok.address, anyValue, anyValue) + + // Get Trade + const tradeAddr = await backingManager.trades(sellTok.address) + await buyTok.connect(addr1).approve(tradeAddr, MAX_ERC20_SUPPLY) + const trade = await ethers.getContractAt('DutchTrade', tradeAddr) + const currentBlock = bn(await getLatestBlockNumber()) + const toAdvance = progression + .mul((await trade.endBlock()).sub(currentBlock)) + .div(fp('1')) + .sub(1) + if (toAdvance.gt(0)) await advanceBlocks(toAdvance) + + // Bid + const sellAmt = await trade.lot() + const bidBlock = bn('1').add(await getLatestBlockNumber()) + const bidAmt = await trade.bidAmount(bidBlock) + expect(bidAmt).to.be.gt(0) + const buyBalBefore = await buyTok.balanceOf(backingManager.address) + const sellBalBefore = await sellTok.balanceOf(addr1.address) + await expect(trade.connect(addr1).bid()) + .to.emit(backingManager, 'TradeSettled') + .withArgs(anyValue, sellTok.address, buyTok.address, sellAmt, bidAmt) + + // Check balances + expect(await sellTok.balanceOf(addr1.address)).to.equal(sellBalBefore.add(sellAmt)) + expect(await buyTok.balanceOf(backingManager.address)).to.equal(buyBalBefore.add(bidAmt)) + expect(await sellTok.balanceOf(trade.address)).to.equal(0) + expect(await buyTok.balanceOf(trade.address)).to.equal(0) + + // Check disabled status + const shouldDisable = progression.lt(fp('0.2')) + expect(await broker.dutchTradeDisabled(sellTok.address)).to.equal(shouldDisable) + expect(await broker.dutchTradeDisabled(buyTok.address)).to.equal(shouldDisable) + } + + // ==== Generate the tests ==== + + // applied to both buy and sell tokens + const decimals = [bn('1'), bn('6'), bn('8'), bn('9'), bn('18')] + + // auction sell amount + const auctionSellAmts = [bn('2'), bn('1595439874635'), bn('987321984732198435645846513')] + + // auction progression %: these will get rounded to blocks later + const progression = [fp('0'), fp('0.321698432589749813'), fp('0.798138321987329646'), fp('1')] + + // total cases is 5 * 5 * 3 * 4 = 300 + + if (SLOW) { + progression.push(fp('0.176334768961354965'), fp('0.523449931646439834')) + + // total cases is 5 * 5 * 3 * 6 = 450 + } + + const paramList = cartesianProduct(decimals, decimals, auctionSellAmts, progression) + + const numCases = paramList.length.toString() + paramList.forEach((params, index) => { + it(`case ${index + 1} of ${numCases}: ${params.map(shortString).join(' ')}`, async () => { + await runScenario(params) + }) + }) + }) + describeGas('Gas Reporting', () => { context('GnosisTrade', () => { let amount: BigNumber From 9655b3494f32aba5937599c1c67a98823fce2e02 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 8 Aug 2023 15:31:59 -0400 Subject: [PATCH 360/462] add new tenderly fork with different chain id --- .openzeppelin/ropsten.json | 3155 ++++++++++++++++++++++ common/configuration.ts | 104 +- contracts/libraries/NetworkConfigLib.sol | 2 +- hardhat.config.ts | 6 +- 4 files changed, 3257 insertions(+), 10 deletions(-) create mode 100644 .openzeppelin/ropsten.json diff --git a/.openzeppelin/ropsten.json b/.openzeppelin/ropsten.json new file mode 100644 index 0000000000..8370c99c6f --- /dev/null +++ b/.openzeppelin/ropsten.json @@ -0,0 +1,3155 @@ +{ + "manifestVersion": "3.2", + "proxies": [], + "impls": { + "387f5c1d576149b40bc0e5061719ad027b6edbb10b7c3cc32ae96a49ca2ddd6d": { + "address": "0x773cf50adCF1730964D4A9b664BaEd4b9FFC2450", + "txHash": "0xfb5f15c28821dc69c191b1de79f54ac0eb95944c80d6cb601a3f944434d84218", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC165Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41" + }, + { + "label": "_roles", + "offset": 0, + "slot": "101", + "type": "t_mapping(t_bytes32,t_struct(RoleData)80_storage)", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:61" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:259" + }, + { + "label": "longFreezes", + "offset": 0, + "slot": "151", + "type": "t_mapping(t_address,t_uint256)", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:36" + }, + { + "label": "unfreezeAt", + "offset": 0, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:38" + }, + { + "label": "shortFreeze", + "offset": 6, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:39" + }, + { + "label": "longFreeze", + "offset": 12, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:40" + }, + { + "label": "tradingPaused", + "offset": 18, + "slot": "152", + "type": "t_bool", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:45", + "renamedFrom": "paused" + }, + { + "label": "issuancePaused", + "offset": 19, + "slot": "152", + "type": "t_bool", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:46" + }, + { + "label": "__gap", + "offset": 0, + "slot": "153", + "type": "t_array(t_uint256)48_storage", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:225" + }, + { + "label": "rToken", + "offset": 0, + "slot": "201", + "type": "t_contract(IRToken)9273", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:34" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "202", + "type": "t_contract(IStRSR)9610", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:42" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "203", + "type": "t_contract(IAssetRegistry)7781", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:50" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "204", + "type": "t_contract(IBasketHandler)8090", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:58" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "205", + "type": "t_contract(IBackingManager)7843", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:66" + }, + { + "label": "distributor", + "offset": 0, + "slot": "206", + "type": "t_contract(IDistributor)8548", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:74" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "207", + "type": "t_contract(IRevenueTrader)9402", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:82" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "208", + "type": "t_contract(IRevenueTrader)9402", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:90" + }, + { + "label": "furnace", + "offset": 0, + "slot": "209", + "type": "t_contract(IFurnace)8609", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:98" + }, + { + "label": "broker", + "offset": 0, + "slot": "210", + "type": "t_contract(IBroker)8239", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:106" + }, + { + "label": "__gap", + "offset": 0, + "slot": "211", + "type": "t_array(t_uint256)40_storage", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:119" + }, + { + "label": "__gap", + "offset": 0, + "slot": "251", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "301", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "rsr", + "offset": 0, + "slot": "351", + "type": "t_contract(IERC20)4390", + "contract": "MainP1", + "src": "contracts/p1/Main.sol:19" + }, + { + "label": "__gap", + "offset": 0, + "slot": "352", + "type": "t_array(t_uint256)49_storage", + "contract": "MainP1", + "src": "contracts/p1/Main.sol:71" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)40_storage": { + "label": "uint256[40]", + "numberOfBytes": "1280" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)7781": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)7843": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)8090": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IBroker)8239": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)8548": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)4390": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)8609": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IRToken)9273": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)9402": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)9610": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)80_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(RoleData)80_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "fe0f2dec194b882efa0d8220d324cba3ec32136c7c6322c221bd90103690d736": { + "address": "0x81b9Ae0740CcA7cDc5211b2737de735FBC4BeB3C", + "txHash": "0x8450c16d3aaf3b9ebcb9879d39c8bc62c0717a6ddc38530804ac13f59b95b70d", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22719", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "201", + "type": "t_contract(IBasketHandler)21243", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:19" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "202", + "type": "t_contract(IBackingManager)20996", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:20" + }, + { + "label": "_erc20s", + "offset": 0, + "slot": "203", + "type": "t_struct(AddressSet)17778_storage", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:23" + }, + { + "label": "assets", + "offset": 0, + "slot": "205", + "type": "t_mapping(t_contract(IERC20)11530,t_contract(IAsset)20691)", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:26" + }, + { + "label": "lastRefresh", + "offset": 0, + "slot": "206", + "type": "t_uint48", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:30" + }, + { + "label": "__gap", + "offset": 0, + "slot": "207", + "type": "t_array(t_uint256)46_storage", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:233" + } + ], + "types": { + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAsset)20691": { + "label": "contract IAsset", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)20996": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)21243": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IERC20)11530": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)22719": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)11530,t_contract(IAsset)20691)": { + "label": "mapping(contract IERC20 => contract IAsset)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)17778_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)17477_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Set)17477_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "75937c367e73897978ccbb283109f2c51fa3d603982f50cba839720fd5494c6f": { + "address": "0x1BD20253c49515D348dad1Af70ff2c0473FEa358", + "txHash": "0x6280f33e907da360d6ef84c181626cddcc4595ba685c3bf881ee9dc43e10bbc0", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)12158", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_status", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:74" + }, + { + "label": "broker", + "offset": 0, + "slot": "251", + "type": "t_contract(IBroker)11036", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:27" + }, + { + "label": "trades", + "offset": 0, + "slot": "252", + "type": "t_mapping(t_contract(IERC20)6264,t_contract(ITrade)12850)", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:30" + }, + { + "label": "tradesOpen", + "offset": 0, + "slot": "253", + "type": "t_uint48", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:31" + }, + { + "label": "maxTradeSlippage", + "offset": 6, + "slot": "253", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:34" + }, + { + "label": "minTradeVolume", + "offset": 0, + "slot": "254", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:35" + }, + { + "label": "tradesNonce", + "offset": 0, + "slot": "255", + "type": "t_uint256", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "256", + "type": "t_array(t_uint256)45_storage", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:155" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "301", + "type": "t_contract(IAssetRegistry)10578", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:30" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "302", + "type": "t_contract(IBasketHandler)10887", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:31" + }, + { + "label": "distributor", + "offset": 0, + "slot": "303", + "type": "t_contract(IDistributor)11345", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:32" + }, + { + "label": "rToken", + "offset": 0, + "slot": "304", + "type": "t_contract(IRToken)12378", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:33" + }, + { + "label": "rsr", + "offset": 0, + "slot": "305", + "type": "t_contract(IERC20)6264", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:34" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "306", + "type": "t_contract(IStRSR)12715", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:35" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "307", + "type": "t_contract(IRevenueTrader)12507", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:36" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "308", + "type": "t_contract(IRevenueTrader)12507", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:37" + }, + { + "label": "tradingDelay", + "offset": 20, + "slot": "308", + "type": "t_uint48", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:41" + }, + { + "label": "backingBuffer", + "offset": 0, + "slot": "309", + "type": "t_uint192", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:42" + }, + { + "label": "furnace", + "offset": 0, + "slot": "310", + "type": "t_contract(IFurnace)11714", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:45" + }, + { + "label": "tradeEnd", + "offset": 0, + "slot": "311", + "type": "t_mapping(t_enum(TradeKind)10909,t_uint48)", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:46" + }, + { + "label": "__gap", + "offset": 0, + "slot": "312", + "type": "t_array(t_uint256)39_storage", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:310" + } + ], + "types": { + "t_array(t_uint256)39_storage": { + "label": "uint256[39]", + "numberOfBytes": "1248" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IAssetRegistry)10578": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)10887": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IBroker)11036": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)11345": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)6264": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)11714": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)12158": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)12378": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)12507": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)12715": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_contract(ITrade)12850": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_enum(TradeKind)10909": { + "label": "enum TradeKind", + "members": [ + "DUTCH_AUCTION", + "BATCH_AUCTION" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_contract(IERC20)6264,t_contract(ITrade)12850)": { + "label": "mapping(contract IERC20 => contract ITrade)", + "numberOfBytes": "32" + }, + "t_mapping(t_enum(TradeKind)10909,t_uint48)": { + "label": "mapping(enum TradeKind => uint48)", + "numberOfBytes": "32" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "bbf9dd201f53ccf79786486dfa2cd67b1e7bd910977c30dfc96ef0ba62009025": { + "address": "0x0776Ad71Ae99D759354B3f06fe17454b94837B0D", + "txHash": "0xefc0dd100e291ebe15d63ad9d67ae1be0763329f4dcd1306074bca1a38c928fe", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22719", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "201", + "type": "t_contract(IAssetRegistry)20934", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:34" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "202", + "type": "t_contract(IBackingManager)20996", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:35" + }, + { + "label": "rsr", + "offset": 0, + "slot": "203", + "type": "t_contract(IERC20)11530", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:36" + }, + { + "label": "rToken", + "offset": 0, + "slot": "204", + "type": "t_contract(IRToken)22939", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:37" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "205", + "type": "t_contract(IStRSR)23276", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:38" + }, + { + "label": "config", + "offset": 0, + "slot": "206", + "type": "t_struct(BasketConfig)49596_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:42" + }, + { + "label": "basket", + "offset": 0, + "slot": "210", + "type": "t_struct(Basket)49606_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:46" + }, + { + "label": "nonce", + "offset": 0, + "slot": "212", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:48" + }, + { + "label": "timestamp", + "offset": 6, + "slot": "212", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:49" + }, + { + "label": "disabled", + "offset": 12, + "slot": "212", + "type": "t_bool", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:53" + }, + { + "label": "_targetNames", + "offset": 0, + "slot": "213", + "type": "t_struct(Bytes32Set)17671_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:59" + }, + { + "label": "_newBasket", + "offset": 0, + "slot": "215", + "type": "t_struct(Basket)49606_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:60" + }, + { + "label": "warmupPeriod", + "offset": 0, + "slot": "217", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:66" + }, + { + "label": "lastStatusTimestamp", + "offset": 6, + "slot": "217", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:70" + }, + { + "label": "lastStatus", + "offset": 12, + "slot": "217", + "type": "t_enum(CollateralStatus)20723", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:71" + }, + { + "label": "basketHistory", + "offset": 0, + "slot": "218", + "type": "t_mapping(t_uint48,t_struct(Basket)49606_storage)", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:77" + }, + { + "label": "_targetAmts", + "offset": 0, + "slot": "219", + "type": "t_struct(Bytes32ToUintMap)17283_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:80" + }, + { + "label": "__gap", + "offset": 0, + "slot": "222", + "type": "t_array(t_uint256)37_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:670" + } + ], + "types": { + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_contract(IERC20)11530)dyn_storage": { + "label": "contract IERC20[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)37_storage": { + "label": "uint256[37]", + "numberOfBytes": "1184" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)20934": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)20996": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IERC20)11530": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)22719": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)22939": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)23276": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_enum(CollateralStatus)20723": { + "label": "enum CollateralStatus", + "members": [ + "SOUND", + "IFFY", + "DISABLED" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_bytes32,t_bytes32)": { + "label": "mapping(bytes32 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(BackupConfig)49576_storage)": { + "label": "mapping(bytes32 => struct BackupConfig)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)11530,t_bytes32)": { + "label": "mapping(contract IERC20 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)11530,t_uint192)": { + "label": "mapping(contract IERC20 => uint192)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint48,t_struct(Basket)49606_storage)": { + "label": "mapping(uint48 => struct Basket)", + "numberOfBytes": "32" + }, + "t_struct(BackupConfig)49576_storage": { + "label": "struct BackupConfig", + "members": [ + { + "label": "max", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)11530)dyn_storage", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Basket)49606_storage": { + "label": "struct Basket", + "members": [ + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)11530)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "refAmts", + "type": "t_mapping(t_contract(IERC20)11530,t_uint192)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(BasketConfig)49596_storage": { + "label": "struct BasketConfig", + "members": [ + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)11530)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "targetAmts", + "type": "t_mapping(t_contract(IERC20)11530,t_uint192)", + "offset": 0, + "slot": "1" + }, + { + "label": "targetNames", + "type": "t_mapping(t_contract(IERC20)11530,t_bytes32)", + "offset": 0, + "slot": "2" + }, + { + "label": "backups", + "type": "t_mapping(t_bytes32,t_struct(BackupConfig)49576_storage)", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_struct(Bytes32Set)17671_storage": { + "label": "struct EnumerableSet.Bytes32Set", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)17477_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Bytes32ToBytes32Map)16360_storage": { + "label": "struct EnumerableMap.Bytes32ToBytes32Map", + "members": [ + { + "label": "_keys", + "type": "t_struct(Bytes32Set)17671_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_values", + "type": "t_mapping(t_bytes32,t_bytes32)", + "offset": 0, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(Bytes32ToUintMap)17283_storage": { + "label": "struct EnumerableMap.Bytes32ToUintMap", + "members": [ + { + "label": "_inner", + "type": "t_struct(Bytes32ToBytes32Map)16360_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "96" + }, + "t_struct(Set)17477_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "25acc9f9e0863580ed5e42b5b697f5c8de88f433a7369fd0b823c5535d615b73": { + "address": "0xDAacEE75C863a79f07699b094DB07793D3A52D6D", + "txHash": "0x226d1a61e9836ab01be911e23b0d1e6e34d9d20ecce6a5a0a1bddea14431b84f", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)12158", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "201", + "type": "t_contract(IBackingManager)10640", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:31" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "202", + "type": "t_contract(IRevenueTrader)12507", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:32" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "203", + "type": "t_contract(IRevenueTrader)12507", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:33" + }, + { + "label": "batchTradeImplementation", + "offset": 0, + "slot": "204", + "type": "t_contract(ITrade)12850", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:37", + "renamedFrom": "tradeImplementation" + }, + { + "label": "gnosis", + "offset": 0, + "slot": "205", + "type": "t_contract(IGnosis)11814", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:40" + }, + { + "label": "batchAuctionLength", + "offset": 20, + "slot": "205", + "type": "t_uint48", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:44", + "renamedFrom": "auctionLength" + }, + { + "label": "batchTradeDisabled", + "offset": 26, + "slot": "205", + "type": "t_bool", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:49" + }, + { + "label": "trades", + "offset": 0, + "slot": "206", + "type": "t_mapping(t_address,t_bool)", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:52" + }, + { + "label": "dutchTradeImplementation", + "offset": 0, + "slot": "207", + "type": "t_contract(ITrade)12850", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:57" + }, + { + "label": "dutchAuctionLength", + "offset": 20, + "slot": "207", + "type": "t_uint48", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:60" + }, + { + "label": "dutchTradeDisabled", + "offset": 0, + "slot": "208", + "type": "t_mapping(t_contract(IERC20Metadata)6289,t_bool)", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:63" + }, + { + "label": "__gap", + "offset": 0, + "slot": "209", + "type": "t_array(t_uint256)42_storage", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:274" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)42_storage": { + "label": "uint256[42]", + "numberOfBytes": "1344" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IBackingManager)10640": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IERC20Metadata)6289": { + "label": "contract IERC20Metadata", + "numberOfBytes": "20" + }, + "t_contract(IGnosis)11814": { + "label": "contract IGnosis", + "numberOfBytes": "20" + }, + "t_contract(IMain)12158": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)12507": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(ITrade)12850": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20Metadata)6289,t_bool)": { + "label": "mapping(contract IERC20Metadata => bool)", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "8bb3ef847ff4554ef7b76887a6e564f582e88926ce12f3a30993a52dd8e5417d": { + "address": "0xc3E9E42DE399F50C5Fc2BC971f0b8D10A631688D", + "txHash": "0x869f513695166f606c3a9aa21a5629345ede8e3b2d8b8bb33f6c8ee3b8243342", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22719", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "destinations", + "offset": 0, + "slot": "201", + "type": "t_struct(AddressSet)17778_storage", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:17" + }, + { + "label": "distribution", + "offset": 0, + "slot": "203", + "type": "t_mapping(t_address,t_struct(RevenueShare)21688_storage)", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:18" + }, + { + "label": "rsr", + "offset": 0, + "slot": "204", + "type": "t_contract(IERC20)11530", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:36" + }, + { + "label": "rToken", + "offset": 0, + "slot": "205", + "type": "t_contract(IERC20)11530", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:37" + }, + { + "label": "furnace", + "offset": 0, + "slot": "206", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:38" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "207", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:39" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "208", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:40" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "209", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:41" + }, + { + "label": "__gap", + "offset": 0, + "slot": "210", + "type": "t_array(t_uint256)44_storage", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:203" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)44_storage": { + "label": "uint256[44]", + "numberOfBytes": "1408" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IERC20)11530": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)22719": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_struct(RevenueShare)21688_storage)": { + "label": "mapping(address => struct RevenueShare)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)17778_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)17477_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(RevenueShare)21688_storage": { + "label": "struct RevenueShare", + "members": [ + { + "label": "rTokenDist", + "type": "t_uint16", + "offset": 0, + "slot": "0" + }, + { + "label": "rsrDist", + "type": "t_uint16", + "offset": 2, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Set)17477_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "4c00631ae462a60fb290aeb6dffd7b15c289fa8cd5f5821368d41a9ab59a3db1": { + "address": "0x6647c880Eb8F57948AF50aB45fca8FE86C154D24", + "txHash": "0xb0fb3a3ade3ccea4131e82db2c2af3d7f492e36d8fd96aeb51a9c7cb852694aa", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)12158", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "rToken", + "offset": 0, + "slot": "201", + "type": "t_contract(IRToken)12378", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:21" + }, + { + "label": "ratio", + "offset": 0, + "slot": "202", + "type": "t_uint192", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:24" + }, + { + "label": "lastPayout", + "offset": 24, + "slot": "202", + "type": "t_uint48", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:27" + }, + { + "label": "lastPayoutBal", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:28" + }, + { + "label": "__gap", + "offset": 0, + "slot": "204", + "type": "t_array(t_uint256)47_storage", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:106" + } + ], + "types": { + "t_array(t_uint256)47_storage": { + "label": "uint256[47]", + "numberOfBytes": "1504" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IMain)12158": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)12378": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "aeb2026b6b8a8117d4f6d9e7dd9ae207d11090a9c7cb204f34cf5d73ae6d6783": { + "address": "0x089848C5228ADe0DF18883197Cd82628b32E95BA", + "txHash": "0x9f9d9c08d2398ee71df5ee642dba3ef748159941bb3bc00b61d98b588378ff31", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)9053", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_status", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:74" + }, + { + "label": "broker", + "offset": 0, + "slot": "251", + "type": "t_contract(IBroker)8239", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:27" + }, + { + "label": "trades", + "offset": 0, + "slot": "252", + "type": "t_mapping(t_contract(IERC20)4390,t_contract(ITrade)9717)", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:30" + }, + { + "label": "tradesOpen", + "offset": 0, + "slot": "253", + "type": "t_uint48", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:31" + }, + { + "label": "maxTradeSlippage", + "offset": 6, + "slot": "253", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:34" + }, + { + "label": "minTradeVolume", + "offset": 0, + "slot": "254", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:35" + }, + { + "label": "tradesNonce", + "offset": 0, + "slot": "255", + "type": "t_uint256", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "256", + "type": "t_array(t_uint256)45_storage", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:155" + }, + { + "label": "tokenToBuy", + "offset": 0, + "slot": "301", + "type": "t_contract(IERC20)4390", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:19" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "302", + "type": "t_contract(IAssetRegistry)7781", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:20" + }, + { + "label": "distributor", + "offset": 0, + "slot": "303", + "type": "t_contract(IDistributor)8548", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:21" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "304", + "type": "t_contract(IBackingManager)7843", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:22" + }, + { + "label": "furnace", + "offset": 0, + "slot": "305", + "type": "t_contract(IFurnace)8609", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:23" + }, + { + "label": "rToken", + "offset": 0, + "slot": "306", + "type": "t_contract(IRToken)9273", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:24" + }, + { + "label": "rsr", + "offset": 0, + "slot": "307", + "type": "t_contract(IERC20)4390", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:25" + }, + { + "label": "__gap", + "offset": 0, + "slot": "308", + "type": "t_array(t_uint256)43_storage", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:190" + } + ], + "types": { + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IAssetRegistry)7781": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)7843": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBroker)8239": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)8548": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)4390": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)8609": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)9053": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)9273": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(ITrade)9717": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_mapping(t_contract(IERC20)4390,t_contract(ITrade)9717)": { + "label": "mapping(contract IERC20 => contract ITrade)", + "numberOfBytes": "32" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "38cc110882c73c48f9ef431eca3acd80c3851cbda1a1de240447152cf30f88f7": { + "address": "0xCFA67f42A0fDe4F0Fb612ea5e66170B0465B84c1", + "txHash": "0xf8fe997d4c24db6cb10f8a1931f741844fa5562ca56f97d9060642d3df81565a", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)12158", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_balances", + "offset": 0, + "slot": "201", + "type": "t_mapping(t_address,t_uint256)", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:37" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "202", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:39" + }, + { + "label": "_totalSupply", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:41" + }, + { + "label": "_name", + "offset": 0, + "slot": "204", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:43" + }, + { + "label": "_symbol", + "offset": 0, + "slot": "205", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:44" + }, + { + "label": "__gap", + "offset": 0, + "slot": "206", + "type": "t_array(t_uint256)45_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:394" + }, + { + "label": "_HASHED_NAME", + "offset": 0, + "slot": "251", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:32" + }, + { + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "252", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:33" + }, + { + "label": "__gap", + "offset": 0, + "slot": "253", + "type": "t_array(t_uint256)50_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:120" + }, + { + "label": "_nonces", + "offset": 0, + "slot": "303", + "type": "t_mapping(t_address,t_struct(Counter)2607_storage)", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:37" + }, + { + "label": "_PERMIT_TYPEHASH_DEPRECATED_SLOT", + "offset": 0, + "slot": "304", + "type": "t_bytes32", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:51", + "renamedFrom": "_PERMIT_TYPEHASH" + }, + { + "label": "__gap", + "offset": 0, + "slot": "305", + "type": "t_array(t_uint256)48_storage", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:129" + }, + { + "label": "mandate", + "offset": 0, + "slot": "353", + "type": "t_string_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:44" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "354", + "type": "t_contract(IAssetRegistry)10578", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:47" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "355", + "type": "t_contract(IBasketHandler)10887", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:48" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "356", + "type": "t_contract(IBackingManager)10640", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:49" + }, + { + "label": "furnace", + "offset": 0, + "slot": "357", + "type": "t_contract(IFurnace)11714", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:50" + }, + { + "label": "basketsNeeded", + "offset": 0, + "slot": "358", + "type": "t_uint192", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:55" + }, + { + "label": "issuanceThrottle", + "offset": 0, + "slot": "359", + "type": "t_struct(Throttle)15183_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:58" + }, + { + "label": "redemptionThrottle", + "offset": 0, + "slot": "363", + "type": "t_struct(Throttle)15183_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:59" + }, + { + "label": "__gap", + "offset": 0, + "slot": "367", + "type": "t_array(t_uint256)42_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:535" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)42_storage": { + "label": "uint256[42]", + "numberOfBytes": "1344" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)10578": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)10640": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)10887": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)11714": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)12158": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Counter)2607_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Counter)2607_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Params)15175_storage": { + "label": "struct ThrottleLib.Params", + "members": [ + { + "label": "amtRate", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "pctRate", + "type": "t_uint192", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Throttle)15183_storage": { + "label": "struct ThrottleLib.Throttle", + "members": [ + { + "label": "params", + "type": "t_struct(Params)15175_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "lastTimestamp", + "type": "t_uint48", + "offset": 0, + "slot": "2" + }, + { + "label": "lastAvailable", + "type": "t_uint256", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "397f5b1d44ab33f58c1c65d3016c7c7cfeb66dde054e9935b80d41f66f4f316a": { + "address": "0x5a5eb5d26871e26645bD6d006671ec0887aeca69", + "txHash": "0x724bba93e8c6297fd00ec195f10155802d5c06d81918cdf8b8cdf1c13c759278", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)12158", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_HASHED_NAME", + "offset": 0, + "slot": "201", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:32" + }, + { + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "202", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:33" + }, + { + "label": "__gap", + "offset": 0, + "slot": "203", + "type": "t_array(t_uint256)50_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:120" + }, + { + "label": "name", + "offset": 0, + "slot": "253", + "type": "t_string_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:48" + }, + { + "label": "symbol", + "offset": 0, + "slot": "254", + "type": "t_string_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:49" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "255", + "type": "t_contract(IAssetRegistry)10578", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:54" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "256", + "type": "t_contract(IBackingManager)10640", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:55" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "257", + "type": "t_contract(IBasketHandler)10887", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:56" + }, + { + "label": "rsr", + "offset": 0, + "slot": "258", + "type": "t_contract(IERC20)6264", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:57" + }, + { + "label": "era", + "offset": 0, + "slot": "259", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:62" + }, + { + "label": "stakes", + "offset": 0, + "slot": "260", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:66" + }, + { + "label": "totalStakes", + "offset": 0, + "slot": "261", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:67" + }, + { + "label": "stakeRSR", + "offset": 0, + "slot": "262", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:68" + }, + { + "label": "stakeRate", + "offset": 0, + "slot": "263", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:69" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "264", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_mapping(t_address,t_uint256)))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:74" + }, + { + "label": "draftEra", + "offset": 0, + "slot": "265", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:79" + }, + { + "label": "draftQueues", + "offset": 0, + "slot": "266", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(CumulativeDraft)31147_storage)dyn_storage))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:87" + }, + { + "label": "firstRemainingDraft", + "offset": 0, + "slot": "267", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:88" + }, + { + "label": "totalDrafts", + "offset": 0, + "slot": "268", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:89" + }, + { + "label": "draftRSR", + "offset": 0, + "slot": "269", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:90" + }, + { + "label": "draftRate", + "offset": 0, + "slot": "270", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:91" + }, + { + "label": "_nonces", + "offset": 0, + "slot": "271", + "type": "t_mapping(t_address,t_struct(Counter)2607_storage)", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:129" + }, + { + "label": "_delegationNonces", + "offset": 0, + "slot": "272", + "type": "t_mapping(t_address,t_struct(Counter)2607_storage)", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:131" + }, + { + "label": "unstakingDelay", + "offset": 0, + "slot": "273", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:141" + }, + { + "label": "rewardRatio", + "offset": 6, + "slot": "273", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:142" + }, + { + "label": "payoutLastPaid", + "offset": 0, + "slot": "274", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:153" + }, + { + "label": "rsrRewardsAtLastPayout", + "offset": 0, + "slot": "275", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:156" + }, + { + "label": "leaked", + "offset": 0, + "slot": "276", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:162" + }, + { + "label": "lastWithdrawRefresh", + "offset": 24, + "slot": "276", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:163" + }, + { + "label": "withdrawalLeak", + "offset": 0, + "slot": "277", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:164" + }, + { + "label": "__gap", + "offset": 0, + "slot": "278", + "type": "t_array(t_uint256)28_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:976" + }, + { + "label": "_delegates", + "offset": 0, + "slot": "306", + "type": "t_mapping(t_address,t_address)", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:31" + }, + { + "label": "_eras", + "offset": 0, + "slot": "307", + "type": "t_array(t_struct(Checkpoint)33345_storage)dyn_storage", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:34" + }, + { + "label": "_checkpoints", + "offset": 0, + "slot": "308", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(Checkpoint)33345_storage)dyn_storage))", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:38" + }, + { + "label": "_totalSupplyCheckpoints", + "offset": 0, + "slot": "309", + "type": "t_mapping(t_uint256,t_array(t_struct(Checkpoint)33345_storage)dyn_storage)", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:40" + }, + { + "label": "__gap", + "offset": 0, + "slot": "310", + "type": "t_array(t_uint256)46_storage", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:243" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_struct(Checkpoint)33345_storage)dyn_storage": { + "label": "struct StRSRP1Votes.Checkpoint[]", + "numberOfBytes": "32" + }, + "t_array(t_struct(CumulativeDraft)31147_storage)dyn_storage": { + "label": "struct StRSRP1.CumulativeDraft[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)28_storage": { + "label": "uint256[28]", + "numberOfBytes": "896" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)10578": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)10640": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)10887": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IERC20)6264": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)12158": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_address)": { + "label": "mapping(address => address)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_array(t_struct(Checkpoint)33345_storage)dyn_storage)": { + "label": "mapping(address => struct StRSRP1Votes.Checkpoint[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_array(t_struct(CumulativeDraft)31147_storage)dyn_storage)": { + "label": "mapping(address => struct StRSRP1.CumulativeDraft[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Counter)2607_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_array(t_struct(Checkpoint)33345_storage)dyn_storage)": { + "label": "mapping(uint256 => struct StRSRP1Votes.Checkpoint[])", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(Checkpoint)33345_storage)dyn_storage))": { + "label": "mapping(uint256 => mapping(address => struct StRSRP1Votes.Checkpoint[]))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(CumulativeDraft)31147_storage)dyn_storage))": { + "label": "mapping(uint256 => mapping(address => struct StRSRP1.CumulativeDraft[]))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_mapping(t_address,t_uint256)))": { + "label": "mapping(uint256 => mapping(address => mapping(address => uint256)))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_uint256))": { + "label": "mapping(uint256 => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Checkpoint)33345_storage": { + "label": "struct StRSRP1Votes.Checkpoint", + "members": [ + { + "label": "fromBlock", + "type": "t_uint48", + "offset": 0, + "slot": "0" + }, + { + "label": "val", + "type": "t_uint224", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Counter)2607_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(CumulativeDraft)31147_storage": { + "label": "struct StRSRP1.CumulativeDraft", + "members": [ + { + "label": "drafts", + "type": "t_uint176", + "offset": 0, + "slot": "0" + }, + { + "label": "availableAt", + "type": "t_uint64", + "offset": 22, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_uint176": { + "label": "uint176", + "numberOfBytes": "22" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint224": { + "label": "uint224", + "numberOfBytes": "28" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + } + } +} diff --git a/common/configuration.ts b/common/configuration.ts index 5edd69b99f..c12edfa2ce 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -166,7 +166,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { sUSDT: '0x38EA452219524Bb87e18dE1C24D3bB59510BD783', sETH: '0x101816545F6bd2b1076434B54383a1E633390A2E', MORPHO: '0x9994e35db50125e0df82e4c2dde62496ce330999', - astETH: "0x1982b2F5814301d4e9a8b0201555376e62F82428" + astETH: '0x1982b2F5814301d4e9a8b0201555376e62F82428', }, chainlinkFeeds: { RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', @@ -192,7 +192,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH - wBTCBTC: "0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23", // "WBTC/BTC" + wBTCBTC: '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', // "WBTC/BTC" }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5', @@ -204,7 +204,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { EASY_AUCTION_OWNER: '0x0da0c3e52c977ed3cbc641ff02dd271c3ed55afe', MORPHO_AAVE_LENS: '0x507fA343d0A90786d86C7cd885f5C49263A91FF4', MORPHO_AAVE_CONTROLLER: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', - MORPHO_REWARDS_DISTRIBUTOR: "0x3b14e5c73e0a56d607a8688098326fd4b4292135" + MORPHO_REWARDS_DISTRIBUTOR: '0x3b14e5c73e0a56d607a8688098326fd4b4292135', }, '3': { name: 'ropsten', @@ -269,7 +269,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { sUSDC: '0xdf0770dF86a8034b3EFEf0A1Bb3c889B8332FF56', sUSDT: '0x38EA452219524Bb87e18dE1C24D3bB59510BD783', sETH: '0x101816545F6bd2b1076434B54383a1E633390A2E', - astETH: "0x1982b2F5814301d4e9a8b0201555376e62F82428", + astETH: '0x1982b2F5814301d4e9a8b0201555376e62F82428', MORPHO: '0x9994e35db50125e0df82e4c2dde62496ce330999', }, chainlinkFeeds: { @@ -296,7 +296,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH - wBTCBTC: "0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23" // "WBTC/BTC" + wBTCBTC: '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', // "WBTC/BTC" }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', @@ -305,7 +305,99 @@ export const networkConfig: { [key: string]: INetworkConfig } = { GNOSIS_EASY_AUCTION: '0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101', MORPHO_AAVE_LENS: '0x507fA343d0A90786d86C7cd885f5C49263A91FF4', MORPHO_AAVE_CONTROLLER: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', - MORPHO_REWARDS_DISTRIBUTOR: "0x3b14e5c73e0a56d607a8688098326fd4b4292135" + MORPHO_REWARDS_DISTRIBUTOR: '0x3b14e5c73e0a56d607a8688098326fd4b4292135', + }, + '3': { + name: 'tenderly', + tokens: { + DAI: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + BUSD: '0x4Fabb145d64652a948d72533023f6E7A623C7C53', + USDP: '0x8E870D67F660D95d5be530380D0eC0bd388289E1', + TUSD: '0x0000000000085d4780B73119b644AE5ecd22b376', + sUSD: '0x57Ab1ec28D129707052df4dF418D58a2D46d5f51', + FRAX: '0x853d955aCEf822Db058eb8505911ED77F175b99e', + MIM: '0x99d8a9c45b2eca8864373a26d1459e3dff1e17f3', + eUSD: '0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F', + aDAI: '0x028171bCA77440897B824Ca71D1c56caC55b68A3', + aUSDC: '0xBcca60bB61934080951369a648Fb03DF4F96263C', + aUSDT: '0x3Ed3B47Dd13EC9a98b44e6204A523E766B225811', + aBUSD: '0xA361718326c15715591c299427c62086F69923D9', + aUSDP: '0x2e8F4bdbE3d47d7d7DE490437AeA9915D930F1A3', + aWETH: '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e', + cDAI: '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', + cUSDC: '0x39AA39c021dfbaE8faC545936693aC917d5E7563', + cUSDT: '0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9', + cUSDP: '0x041171993284df560249B57358F931D9eB7b925D', + cETH: '0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5', + cWBTC: '0xccF4429DB6322D5C611ee964527D42E5d685DD6a', + fUSDC: '0x465a5a630482f3abD6d3b84B39B29b07214d19e5', + fUSDT: '0x81994b9607e06ab3d5cF3AffF9a67374f05F27d7', + fFRAX: '0x1C9A2d6b33B4826757273D47ebEe0e2DddcD978B', + fDAI: '0xe2bA8693cE7474900A045757fe0efCa900F6530b', + AAVE: '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9', + stkAAVE: '0x4da27a545c0c5B758a6BA100e3a049001de870f5', + COMP: '0xc00e94Cb662C3520282E6f5717214004A7f26888', + WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + aWBTC: '0x9ff58f4ffb29fa2266ab25e75e2a8b3503311656', + aCRV: '0x8dae6cb04688c62d939ed9b68d32bc62e49970b1', + WBTC: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', + EURT: '0xC581b735A1688071A1746c968e0798D642EDE491', + RSR: '0x320623b8e4ff03373931769a31fc52a4e78b5d70', + CRV: '0xD533a949740bb3306d119CC777fa900bA034cd52', + CVX: '0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B', + ankrETH: '0xE95A203B1a91a908F9B9CE46459d101078c2c3cb', + frxETH: '0x5E8422345238F34275888049021821E8E08CAa1f', + sfrxETH: '0xac3E018457B222d93114458476f3E3416Abbe38F', + stETH: '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', + wstETH: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', + rETH: '0xae78736Cd615f374D3085123A210448E74Fc6393', + cUSDCv3: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', + ONDO: '0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3', + sDAI: '0x83f20f44975d03b1b09e64809b757c47f942beea', + cbETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', + STG: '0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6', + sUSDC: '0xdf0770dF86a8034b3EFEf0A1Bb3c889B8332FF56', + sUSDT: '0x38EA452219524Bb87e18dE1C24D3bB59510BD783', + sETH: '0x101816545F6bd2b1076434B54383a1E633390A2E', + astETH: '0x1982b2F5814301d4e9a8b0201555376e62F82428', + MORPHO: '0x9994e35db50125e0df82e4c2dde62496ce330999', + }, + chainlinkFeeds: { + RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', + AAVE: '0x547a514d5e3769680Ce22B2361c10Ea13619e8a9', + COMP: '0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5', + DAI: '0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9', + USDC: '0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6', + USDT: '0x3E7d1eAB13ad0104d2750B8863b489D65364e32D', + BUSD: '0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A', + USDP: '0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3', + TUSD: '0xec746eCF986E2927Abd291a2A1716c940100f8Ba', + sUSD: '0xad35Bd71b9aFE6e4bDc266B345c198eaDEf9Ad94', + FRAX: '0xB9E1E3A9feFf48998E45Fa90847ed4D467E8BcfD', + MIM: '0x7A364e8770418566e3eb2001A96116E6138Eb32F', + ETH: '0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419', + WBTC: '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', + BTC: '0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c', + EURT: '0x01D391A48f4F7339aC64CA2c83a07C22F95F587a', + EUR: '0xb49f677943BC038e9857d61E7d053CaA2C1734C1', + CVX: '0xd962fC30A72A84cE50161031391756Bf2876Af5D', + CRV: '0xCd627aA160A6fA45Eb793D19Ef54f5062F20f33f', + stETHETH: '0x86392dc19c0b719886221c78ab11eb8cf5c52812', // stETH/ETH + stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD + rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH + cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH + wBTCBTC: '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', // "WBTC/BTC" + }, + AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', + AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', + FLUX_FINANCE_COMPTROLLER: '0x95Af143a021DF745bc78e845b54591C53a8B3A51', + COMPTROLLER: '0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B', + GNOSIS_EASY_AUCTION: '0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101', + MORPHO_AAVE_LENS: '0x507fA343d0A90786d86C7cd885f5C49263A91FF4', + MORPHO_AAVE_CONTROLLER: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', + MORPHO_REWARDS_DISTRIBUTOR: '0x3b14e5c73e0a56d607a8688098326fd4b4292135', }, '5': { name: 'goerli', diff --git a/contracts/libraries/NetworkConfigLib.sol b/contracts/libraries/NetworkConfigLib.sol index b347bec484..c07aa392c0 100644 --- a/contracts/libraries/NetworkConfigLib.sol +++ b/contracts/libraries/NetworkConfigLib.sol @@ -15,7 +15,7 @@ library NetworkConfigLib { // untestable: // most of the branches will be shown as uncovered, because we only run coverage // on local Ethereum PoS network (31337). Manual testing was performed. - if (chainId == 1 || chainId == 5 || chainId == 31337) { + if (chainId == 1 || chainId == 3 || chainId == 5 || chainId == 31337) { return 12; // Ethereum PoS, Goerli, HH (tests) } else if (chainId == 8453 || chainId == 84531) { return 2; // Base, Base Goerli diff --git a/hardhat.config.ts b/hardhat.config.ts index 2fa2cc5eed..e9364399e1 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -79,13 +79,13 @@ const config: HardhatUserConfig = { gasMultiplier: 1.05, // 5% buffer; seen failures on RToken deployment and asset refreshes otherwise }, tenderly: { - chainId: 1, + chainId: 3, url: TENDERLY_RPC_URL, accounts: { mnemonic: MNEMONIC, }, // gasPrice: 10_000_000_000, - gasMultiplier: 1.015, // 1.5% buffer; seen failures on RToken deployment and asset refreshes + gasMultiplier: 1.05, // 5% buffer; seen failures on RToken deployment and asset refreshes otherwise }, }, solidity: { @@ -117,7 +117,7 @@ const config: HardhatUserConfig = { mocha: { timeout: TIMEOUT, slow: 1000, - retries: 3 + retries: 3, }, contractSizer: { alphaSort: false, From 9d8b7e26beb31a1257b73316a36b8a7c4927c6f4 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 8 Aug 2023 15:32:09 -0400 Subject: [PATCH 361/462] tenderly fork addresses --- .../addresses/3-tmp-assets-collateral.json | 85 +++++++++++++++++++ scripts/addresses/3-tmp-deployments.json | 35 ++++++++ 2 files changed, 120 insertions(+) create mode 100644 scripts/addresses/3-tmp-assets-collateral.json create mode 100644 scripts/addresses/3-tmp-deployments.json diff --git a/scripts/addresses/3-tmp-assets-collateral.json b/scripts/addresses/3-tmp-assets-collateral.json new file mode 100644 index 0000000000..ff5d1dac64 --- /dev/null +++ b/scripts/addresses/3-tmp-assets-collateral.json @@ -0,0 +1,85 @@ +{ + "assets": { + "stkAAVE": "0x72BA23683CBc1a3Fa5b3129b1335327A32c2CE8C", + "COMP": "0x70c635Bf4972259F2358Db5e431DB9592a2745a2", + "CRV": "0x66a3b432F77123E418cDbeD35fBaDdB0Eb9576B0", + "CVX": "0x0F53Aba2a7354C86B64dcaEe0ab9BF852846bAa5" + }, + "collateral": { + "DAI": "0x97C75046CE7Ea5253d20A35B3138699865E8813f", + "USDC": "0x256b89658bD831CC40283F42e85B1fa8973Db0c9", + "USDT": "0x743063E627d375f0A21bB92D07598Edc7D6F3a2d", + "USDP": "0x22d28452B506eFD909E9FC1d446a27061C4BDA7B", + "TUSD": "0xAd76B12aeEe90B745F0C62110cf1E261Fc5a06bb", + "BUSD": "0xe639d53Aa860757D7fe9cD4ebF9C8b92b8DedE7D", + "aDAI": "0x80A574cC2B369dc496af6655f57a16a4f180BfAF", + "aUSDC": "0x3043be171e846c33D5f06864Cc045d9Fc799aF52", + "aUSDT": "0xf579F9885f1AEa0d3F8bE0F18AfED28c92a43022", + "aBUSD": "0x4Be33630F92661afD646081BC29079A38b879aA0", + "aUSDP": "0xF69c995129CC16d0F577C303091a400cC1879fFa", + "cDAI": "0xF2A309bc36A504c772B416a4950d5d0021219745", + "cUSDC": "0xbF6E8F64547Bdec55bc3FBb0664722465FCC2F0F", + "cUSDT": "0x87A959e0377C68A50b08a91ae5ab3aFA7F41ACA4", + "cUSDP": "0x11C9ca7a43B76a5d9604e7441EB41a49e2084723", + "cWBTC": "0xC1E16AD7844Da1AEFFa6c3932AD02b823DE12d3F", + "cETH": "0x3700b22C742980be9D22740933d4a041A64f7314", + "WBTC": "0x1FFA5955D64Ee32cB1BF7104167b81bb085b0c8d", + "WETH": "0x2837f952c1FD773B3Ce02631A90f95E4b9ce2cF7", + "EURT": "0x5c83CA710E72D130E3B74aEC5b739676ef5737c2", + "wstETH": "0xE1fcCf8e23713Ed0497ED1a0E6Ae2b19ED443eCd", + "rETH": "0x55590a1Bf90fbf7352A46c4af652A231AA5CbF13", + "fUSDC": "0x7e1e077b289c0153b5ceAD9F264d66215341c9Ab", + "fUSDT": "0xaBd7E7a5C846eD497681a590feBED99e7157B6a3", + "fDAI": "0x3C8cD9FCa9925780598eB097D9718dF3da482C2F", + "fFRAX": "0x890FAa00C16EAD6AA76F18A1A7fe9C40838F9122", + "cUSDCv3": "0xc9291eF2f81dBc9B412381aBe83b28954220565E", + "cvx3Pool": "0xdEBe74dc2A415e00bE8B4b9d1e6e0007153D006a", + "cvxeUSDFRAXBP": "0x0240E29Be6cBbB178543fF27EA4AaC8F8b870b44", + "cvxMIM3Pool": "0xe8461dB45A7430AA7aB40346E68821284980FdFD", + "crveUSDFRAXBP": "0xa9F0eca90B5d4f213f8119834E0920785bb70F46", + "crvMIM3Pool": "0xaA91d24c2F7DBb6487f61869cD8cd8aFd5c5Cab2", + "sDAI": "0xE2b16e14dB6216e33082D5A8Be1Ef01DF7511bBb", + "cbETH": "0x291ed25eB61fcc074156eE79c5Da87e5DA94198F" + }, + "erc20s": { + "stkAAVE": "0x4da27a545c0c5B758a6BA100e3a049001de870f5", + "COMP": "0xc00e94Cb662C3520282E6f5717214004A7f26888", + "CRV": "0xD533a949740bb3306d119CC777fa900bA034cd52", + "CVX": "0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B", + "DAI": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "USDC": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "USDT": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "USDP": "0x8E870D67F660D95d5be530380D0eC0bd388289E1", + "TUSD": "0x0000000000085d4780B73119b644AE5ecd22b376", + "BUSD": "0x4Fabb145d64652a948d72533023f6E7A623C7C53", + "aDAI": "0x2a00A95Dc311EB96fEf922B8418c998f86D2c464", + "aUSDC": "0x9E8C96d86F1c85BC43200B8093159cf47e0CB921", + "aUSDT": "0x6356F6876D781795660cD2F6410b5a78636Df5C1", + "aBUSD": "0xfa21CD6EEde080Fdb1C79c1bdBC0C593c8CD08A5", + "aUSDP": "0x6446189FD250D96517C119DD9929c24bF825fb4e", + "cDAI": "0x22018D85BFdA9e2673FB4101e957562a1e952Cdf", + "cUSDC": "0x1142Ad5E5A082077A7d79d211726c1bd39b0D5FA", + "cUSDT": "0x35E6756B92daf6aE2CF2156d479e8a806898971B", + "cUSDP": "0x6B87142C7e6cA80aa3E6ead0351673C45c8990e3", + "cWBTC": "0xe352b0aE3114c57f56258F73277F825E643268d0", + "cETH": "0x0E6D6cBdA4629Fb2D82b4b4Af0D5c887f21F3BC7", + "WBTC": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "EURT": "0xC581b735A1688071A1746c968e0798D642EDE491", + "wstETH": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + "rETH": "0xae78736Cd615f374D3085123A210448E74Fc6393", + "fUSDC": "0xb3dCcEf35647A8821C76f796bE8B5426Cc953412", + "fUSDT": "0x7906238833Bb9e4Fec24a1735C94f47cb194f678", + "fDAI": "0x62C394620f674e85768a7618a6C202baE7fB8Dd1", + "fFRAX": "0x5A4f2FfC4aD066152B344Ceb2fc2275275b1a9C7", + "cUSDCv3": "0x9FF9c353136e86EFe02ADD177E7c9769f8a5A77F", + "cvx3Pool": "0xAdfB9BCdA981136c83076a52Ef8fE4D8B2b520e7", + "cvxeUSDFRAXBP": "0xFdb9F465C56933ab91f341C966DB517f975de5c1", + "cvxMIM3Pool": "0xC87CDFFD680D57BF50De4C364BF4277B8A90098E", + "crv3Pool": "0x1bc463270b8d3D797F59Fe639eDF5ae130f35FF3", + "crveUSDFRAXBP": "0xDe0e2F0c9792617D3908D92A024cAa846354CEa2", + "crvMIM3Pool": "0x9b2A9bAeB8F1930fC2AF9b7Fa473edF2B8c3B549", + "sDAI": "0x83f20f44975d03b1b09e64809b757c47f942beea", + "cbETH": "0xBe9895146f7AF43049ca1c1AE358B0541Ea49704" + } +} \ No newline at end of file diff --git a/scripts/addresses/3-tmp-deployments.json b/scripts/addresses/3-tmp-deployments.json new file mode 100644 index 0000000000..92393dde70 --- /dev/null +++ b/scripts/addresses/3-tmp-deployments.json @@ -0,0 +1,35 @@ +{ + "prerequisites": { + "RSR": "0x320623b8e4ff03373931769a31fc52a4e78b5d70", + "RSR_FEED": "0x759bBC1be8F90eE6457C44abc7d443842a976d02", + "GNOSIS_EASY_AUCTION": "0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101" + }, + "tradingLib": "0x1cCa3FBB11C4b734183f997679d52DeFA74b613A", + "cvxMiningLib": "0xC98eaFc9F249D90e3E35E729e3679DD75A899c10", + "facadeRead": "0x4024c00bBD0C420E719527D88781bc1543e63dd5", + "facadeAct": "0xBE9D23040fe22E8Bd8A88BF5101061557355cA04", + "facadeWriteLib": "0xf7d1C6eE4C0D84C6B530D53A897daa1E9eB56833", + "basketLib": "0xb6f01Aa21defA4a4DE33Bed16BcC06cfd23b6A6F", + "facadeWrite": "0xb3Be23A0cEFfd1814DC4F1FdcDc1200b39922bCc", + "deployer": "0xDeC1B73754449166cB270AC83F4b536e738b1351", + "rsrAsset": "0x45B950AF443281c5F67c2c7A1d9bBc325ECb8eEA", + "implementations": { + "main": "0x773cf50adCF1730964D4A9b664BaEd4b9FFC2450", + "trading": { + "gnosisTrade": "0x7edD40933DfdA0ecEe1ad3E61a5044962284e1A6", + "dutchTrade": "0x339c1509b980D80A0b50858518531eDbe2940dA1" + }, + "components": { + "assetRegistry": "0x81b9Ae0740CcA7cDc5211b2737de735FBC4BeB3C", + "backingManager": "0x1BD20253c49515D348dad1Af70ff2c0473FEa358", + "basketHandler": "0x0776Ad71Ae99D759354B3f06fe17454b94837B0D", + "broker": "0xDAacEE75C863a79f07699b094DB07793D3A52D6D", + "distributor": "0xc3E9E42DE399F50C5Fc2BC971f0b8D10A631688D", + "furnace": "0x6647c880Eb8F57948AF50aB45fca8FE86C154D24", + "rsrTrader": "0x089848C5228ADe0DF18883197Cd82628b32E95BA", + "rTokenTrader": "0x089848C5228ADe0DF18883197Cd82628b32E95BA", + "rToken": "0xCFA67f42A0fDe4F0Fb612ea5e66170B0465B84c1", + "stRSR": "0x5a5eb5d26871e26645bD6d006671ec0887aeca69" + } + } +} \ No newline at end of file From 7aa062800e65ef376f5e4a4309c05702bf44f31a Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 8 Aug 2023 17:06:52 -0400 Subject: [PATCH 362/462] init readme doc for building on top --- README.md | 1 + docs/build-on-top.md | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 docs/build-on-top.md diff --git a/README.md b/README.md index ae48bf253d..488763237e 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ For a much more detailed explanation of the economic design, including an hour-l - [System Design](docs/system-design.md): The overall architecture of our system, and some detailed descriptions about what our protocol is _intended_ to do. - [Our Solidity Style](docs/solidity-style.md): Common practices, details, and conventions relevant to reading and writing our Solidity source code, estpecially where those go beyond standard practice. - [Writing Collateral Plugins](docs/collateral.md): An overview of how to develop collateral plugins and the concepts / questions involved. +- [Building on Top](docs/build-on-top.md): How to build on top of Reserve, including information about long-lived fork environments. - [MEV](docs/mev.md): A resource for MEV searchers and others looking to interact with the deployed protocol programatically. - [Rebalancing Algorithm](docs/recollateralization.md): Description of our trading algorithm during the recollateralization process - [Changelog](CHANGELOG.md): Release changelog diff --git a/docs/build-on-top.md b/docs/build-on-top.md new file mode 100644 index 0000000000..8a777f5168 --- /dev/null +++ b/docs/build-on-top.md @@ -0,0 +1,17 @@ +# Building on Top of Reserve + +TODO -- this document is a work in progress. + +## Overview + +Reserve uses a long-lived Tenderly fork as the main testing environment. Since the Reserve Protocol is a meta-protocol it relies on functional building blocks that are not all present on testnets such as Goerli or Sepolia. For this reason it makes the most sense to use a fork of mainnet. + +Unfortunately it would be bad practice to share the RPC publicly. Please reach out at protocol.eng@reserve.org to request we share the RPC privately with you. + +## Chain + +We re-use the chain ID of 3 (previously: ropsten) for the Tenderly fork in order to separate book-keeping between mainnet and the fork environment. + +- [Core Contracts](../scripts/addresses/3-tmp-deployments.json) +- [Collateral Plugins](../scripts/addresses/3-tmp-assets-collateral.json) + - Note that oracles require special logic in order to be refreshed and for these plugins to function correctly. From fe5d38df8a1816d411ab81565fbef3ce210aea9a Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 8 Aug 2023 17:07:10 -0400 Subject: [PATCH 363/462] README: add responsible disclosure section while we're at it --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 488763237e..2ff52fa10b 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,10 @@ Usage: `https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}` To get setup with tenderly, install the [tenderly cli](https://github.com/Tenderly/tenderly-cli). and login with `tenderly login --authentication-method access-key --access-key {your_access_key} --force`. +## Responsible Disclosure + +[Immunifi](https://immunefi.com/bounty/reserve/) + ## External Documentation [Video overview](https://youtu.be/341MhkOWsJE) From e8f5568d35502bc8e0d7ebf5597976b5205be0c7 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 9 Aug 2023 16:56:59 -0400 Subject: [PATCH 364/462] C4 #29 (#885) --- contracts/p0/Furnace.sol | 10 ++++++++-- contracts/p1/Furnace.sol | 10 ++++++++-- test/Furnace.test.ts | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/contracts/p0/Furnace.sol b/contracts/p0/Furnace.sol index 869a3eff76..aa99a8140c 100644 --- a/contracts/p0/Furnace.sol +++ b/contracts/p0/Furnace.sol @@ -58,8 +58,14 @@ contract FurnaceP0 is ComponentP0, IFurnace { /// Ratio setting /// @custom:governance function setRatio(uint192 ratio_) public governance { - // solhint-disable-next-line no-empty-blocks - try this.melt() {} catch {} + if (lastPayout > 0) { + // solhint-disable-next-line no-empty-blocks + try this.melt() {} catch { + uint48 numPeriods = uint48((block.timestamp) - lastPayout) / PERIOD; + lastPayout += numPeriods * PERIOD; + lastPayoutBal = main.rToken().balanceOf(address(this)); + } + } require(ratio_ <= MAX_RATIO, "invalid ratio"); // The ratio can safely be set to 0, though it is not recommended emit RatioSet(ratio, ratio_); diff --git a/contracts/p1/Furnace.sol b/contracts/p1/Furnace.sol index 3259e48fd2..923ba33737 100644 --- a/contracts/p1/Furnace.sol +++ b/contracts/p1/Furnace.sol @@ -90,8 +90,14 @@ contract FurnaceP1 is ComponentP1, IFurnace { /// Ratio setting /// @custom:governance function setRatio(uint192 ratio_) public governance { - // solhint-disable-next-line no-empty-blocks - if (lastPayout > 0) try this.melt() {} catch {} + if (lastPayout > 0) { + // solhint-disable-next-line no-empty-blocks + try this.melt() {} catch { + uint48 numPeriods = uint48((block.timestamp) - lastPayout) / PERIOD; + lastPayout += numPeriods * PERIOD; + lastPayoutBal = rToken.balanceOf(address(this)); + } + } require(ratio_ <= MAX_RATIO, "invalid ratio"); // The ratio can safely be set to 0 to turn off payouts, though it is not recommended emit RatioSet(ratio, ratio_); diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index de3a602612..15776210be 100644 --- a/test/Furnace.test.ts +++ b/test/Furnace.test.ts @@ -446,6 +446,45 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { expect(diff).to.be.lte(expectedDiff) }) + + it('Regression test -- C4 June 2023 Issue #29', async () => { + // https://github.com/code-423n4/2023-06-reserve-findings/issues/29 + + // Transfer to Furnace and do first melt + await rToken.connect(addr1).transfer(furnace.address, bn('10e18')) + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) + await furnace.melt() + + // Should have updated lastPayout + lastPayoutBal + expect(await furnace.lastPayout()).to.be.closeTo(await getLatestBlockTimestamp(), 12) + expect(await furnace.lastPayout()).to.be.lte(await getLatestBlockTimestamp()) + expect(await furnace.lastPayoutBal()).to.equal(bn('10e18')) + + // Advance 99 periods -- should melt at old ratio + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 99 * Number(ONE_PERIOD)) + + // Freeze and change ratio + await main.connect(owner).freezeForever() + const maxRatio = bn('1e14') + await expect(furnace.connect(owner).setRatio(maxRatio)) + .to.emit(furnace, 'RatioSet') + .withArgs(config.rewardRatio, maxRatio) + + // Should have updated lastPayout + lastPayoutBal + expect(await furnace.lastPayout()).to.be.closeTo(await getLatestBlockTimestamp(), 12) + expect(await furnace.lastPayout()).to.be.lte(await getLatestBlockTimestamp()) + expect(await furnace.lastPayoutBal()).to.equal(bn('10e18')) // no change + + // Unfreeze and advance 1 period + await main.connect(owner).unfreeze() + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) + await expect(furnace.melt()).to.emit(rToken, 'Melted') + + // Should have updated lastPayout + lastPayoutBal + expect(await furnace.lastPayout()).to.be.closeTo(await getLatestBlockTimestamp(), 12) + expect(await furnace.lastPayout()).to.be.lte(await getLatestBlockTimestamp()) + expect(await furnace.lastPayoutBal()).to.equal(bn('9.999e18')) + }) }) describeExtreme('Extreme Bounds', () => { From 30d5bfc888ccfa7fbd23466c208996624cd76fe3 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Fri, 11 Aug 2023 00:43:31 +0530 Subject: [PATCH 365/462] [docs] Positive Token Redemption Value (#887) --- docs/recollateralization.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/recollateralization.md b/docs/recollateralization.md index 67c9067592..aecb345c94 100644 --- a/docs/recollateralization.md +++ b/docs/recollateralization.md @@ -8,6 +8,8 @@ Recollateralization takes place in the central loop of [`BackingManager.rebalanc The trading algorithm is isolated in [RecollateralizationLib.sol](../contracts/p1/mixins/RecollateralizationLib.sol). This document describes the algorithm implemented by the library at a high-level, as well as the concepts required to evaluate the correctness of the implementation. +Note: In case of an upwards default, as in a token is worth _more_ than what it is supposed to be, the token redemption is worth more than the peg during recollateralization process. This will continue to be the case until the rebalancing process is complete. This is a good thing, and the protocol should be able to take advantage of this. + ## High-level overview ```solidity From 4ce086ff0869a4e00d4a3ac3f68ff91f808922c3 Mon Sep 17 00:00:00 2001 From: brr Date: Thu, 10 Aug 2023 22:53:20 +0100 Subject: [PATCH 366/462] C4: handle Chainlink oracle deprecation (#886) --- contracts/plugins/assets/OracleLib.sol | 9 ++++ contracts/plugins/mocks/ChainlinkMock.sol | 18 +++++++ test/fixtures.ts | 32 ++++++++---- test/plugins/OracleDeprecation.test.ts | 64 +++++++++++++++++++++++ 4 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 test/plugins/OracleDeprecation.test.ts diff --git a/contracts/plugins/assets/OracleLib.sol b/contracts/plugins/assets/OracleLib.sol index b0e2538761..495186e36c 100644 --- a/contracts/plugins/assets/OracleLib.sol +++ b/contracts/plugins/assets/OracleLib.sol @@ -6,6 +6,10 @@ import "../../libraries/Fixed.sol"; error StalePrice(); +interface EACAggregatorProxy { + function aggregator() external view returns (address); +} + /// Used by asset plugins to price their collateral library OracleLib { /// @dev Use for on-the-fly calculations that should revert @@ -16,6 +20,11 @@ library OracleLib { view returns (uint192) { + // If the aggregator is not set, the chainlink feed has been deprecated + if (EACAggregatorProxy(address(chainlinkFeed)).aggregator() == address(0)) { + revert StalePrice(); + } + (uint80 roundId, int256 p, , uint256 updateTime, uint80 answeredInRound) = chainlinkFeed .latestRoundData(); diff --git a/contracts/plugins/mocks/ChainlinkMock.sol b/contracts/plugins/mocks/ChainlinkMock.sol index db794d7ec6..6e0fee6785 100644 --- a/contracts/plugins/mocks/ChainlinkMock.sol +++ b/contracts/plugins/mocks/ChainlinkMock.sol @@ -23,6 +23,7 @@ contract MockV3Aggregator is AggregatorV3Interface { // Additional variable to be able to test invalid behavior uint256 public latestAnsweredRound; + address public aggregator; mapping(uint256 => int256) public getAnswer; mapping(uint256 => uint256) public getTimestamp; @@ -30,9 +31,14 @@ contract MockV3Aggregator is AggregatorV3Interface { constructor(uint8 _decimals, int256 _initialAnswer) { decimals = _decimals; + aggregator = address(this); updateAnswer(_initialAnswer); } + function deprecate() external { + aggregator = address(0); + } + function updateAnswer(int256 _answer) public { latestAnswer = _answer; latestTimestamp = block.timestamp; @@ -80,6 +86,12 @@ contract MockV3Aggregator is AggregatorV3Interface { uint80 answeredInRound ) { + if (aggregator == address(0)) { + // solhint-disable-next-line no-inline-assembly + assembly { + revert(0, 0) + } + } return ( _roundId, getAnswer[_roundId], @@ -102,6 +114,12 @@ contract MockV3Aggregator is AggregatorV3Interface { uint80 answeredInRound ) { + if (aggregator == address(0)) { + // solhint-disable-next-line no-inline-assembly + assembly { + revert(0, 0) + } + } return ( uint80(latestRound), getAnswer[latestRound], diff --git a/test/fixtures.ts b/test/fixtures.ts index 2f20d2a669..15944a43b8 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -1,4 +1,4 @@ -import { BigNumber, ContractFactory } from 'ethers' +import { ContractFactory } from 'ethers' import { expect } from 'chai' import hre, { ethers } from 'hardhat' import { getChainId } from '../common/blockchain-utils' @@ -149,19 +149,12 @@ async function gnosisFixture(): Promise { } } -interface CollateralFixture { - erc20s: ERC20Mock[] // all erc20 addresses - collateral: Collateral[] // all collateral - basket: Collateral[] // only the collateral actively backing the RToken - basketsNeededAmts: BigNumber[] // reference amounts -} - async function collateralFixture( compToken: ERC20Mock, comptroller: ComptrollerMock, aaveToken: ERC20Mock, config: IConfig -): Promise { +) { const ERC20: ContractFactory = await ethers.getContractFactory('ERC20Mock') const USDC: ContractFactory = await ethers.getContractFactory('USDCMock') const ATokenMockFactory: ContractFactory = await ethers.getContractFactory('StaticATokenMock') @@ -349,7 +342,7 @@ async function collateralFixture( ausdt[0], abusd[0], zcoin[0], - ] + ] as ERC20Mock[] const collateral = [ dai[1], usdc[1], @@ -374,9 +367,25 @@ async function collateralFixture( collateral, basket, basketsNeededAmts, + bySymbol: { + dai, + usdc, + usdt, + busd, + cdai, + cusdc, + cusdt, + adai, + ausdc, + ausdt, + abusd, + zcoin, + }, } } +type CollateralFixture = Awaited> + type RSRAndCompAaveAndCollateralAndModuleFixture = RSRFixture & COMPAAVEFixture & CollateralFixture & @@ -663,7 +672,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = const stRSR: TestIStRSR = await ethers.getContractAt('TestIStRSR', await main.stRSR()) // Deploy collateral for Main - const { erc20s, collateral, basket, basketsNeededAmts } = await collateralFixture( + const { erc20s, collateral, basket, basketsNeededAmts, bySymbol } = await collateralFixture( compToken, compoundMock, aaveToken, @@ -742,5 +751,6 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = facadeTest, rsrTrader, rTokenTrader, + bySymbol, } } diff --git a/test/plugins/OracleDeprecation.test.ts b/test/plugins/OracleDeprecation.test.ts new file mode 100644 index 0000000000..d76d16df0e --- /dev/null +++ b/test/plugins/OracleDeprecation.test.ts @@ -0,0 +1,64 @@ +import { Wallet } from 'ethers' +import { ethers } from 'hardhat' +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { fp } from '../../common/numbers' +import { ERC20Mock, TestIRToken } from '../../typechain' +import { Collateral, DefaultFixture, defaultFixture } from '../fixtures' +import { expect } from 'chai' + +describe('Chainlink Oracle', () => { + // Tokens + let rsr: ERC20Mock + let compToken: ERC20Mock + let aaveToken: ERC20Mock + let rToken: TestIRToken + + // Assets + let basket: Collateral[] + + let wallet: Wallet + + const amt = fp('1e4') + let fixture: DefaultFixture + + before('create fixture loader', async () => { + ;[wallet] = (await ethers.getSigners()) as unknown as Wallet[] + }) + + beforeEach(async () => { + // Deploy fixture + fixture = await loadFixture(defaultFixture) + ;({ rsr, compToken, aaveToken, basket, rToken } = fixture) + + // Get collateral tokens + await rsr.connect(wallet).mint(wallet.address, amt) + await compToken.connect(wallet).mint(wallet.address, amt) + await aaveToken.connect(wallet).mint(wallet.address, amt) + + // Issue RToken to enable RToken.price + for (let i = 0; i < basket.length; i++) { + const tok = await ethers.getContractAt('ERC20Mock', await basket[i].erc20()) + await tok.connect(wallet).mint(wallet.address, amt) + await tok.connect(wallet).approve(rToken.address, amt) + } + await rToken.connect(wallet).issue(amt) + }) + + describe('Chainlink deprecates an asset', () => { + it('Refresh should mark the asset as IFFY', async () => { + const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') + const [, aUSDCCollateral] = fixture.bySymbol.ausdc + const chainLinkOracle = MockV3AggregatorFactory.attach(await aUSDCCollateral.chainlinkFeed()) + await aUSDCCollateral.refresh() + await aUSDCCollateral.tryPrice() + expect(await aUSDCCollateral.status()).to.equal(0) + await chainLinkOracle.deprecate() + await aUSDCCollateral.refresh() + expect(await aUSDCCollateral.status()).to.equal(1) + await expect(aUSDCCollateral.tryPrice()).to.be.revertedWithCustomError( + aUSDCCollateral, + 'StalePrice' + ) + }) + }) +}) From 20bedef7fd2173d814e2b87f6592aa0bb1f3d34d Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 10 Aug 2023 19:13:01 -0300 Subject: [PATCH 367/462] C4: StRSR reset stakes functionality (#888) --- contracts/interfaces/IStRSR.sol | 4 ++ contracts/p0/StRSR.sol | 18 +++++ contracts/p1/StRSR.sol | 17 +++++ test/ZZStRSR.test.ts | 120 ++++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+) diff --git a/contracts/interfaces/IStRSR.sol b/contracts/interfaces/IStRSR.sol index 45b7db9227..efdb937f32 100644 --- a/contracts/interfaces/IStRSR.sol +++ b/contracts/interfaces/IStRSR.sol @@ -134,6 +134,10 @@ interface IStRSR is IERC20MetadataUpgradeable, IERC20PermitUpgradeable, ICompone /// @custom:protected function seizeRSR(uint256 amount) external; + /// Reset all stakes and advance era + /// @custom:governance + function resetStakes() external; + /// Return the maximum valid value of endId such that withdraw(endId) should immediately work function endIdForWithdraw(address account) external view returns (uint256 endId); diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index aca576a471..fe3676dcef 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -98,6 +98,10 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { // Min exchange rate {qRSR/qStRSR} (compile-time constant) uint192 private constant MIN_EXCHANGE_RATE = uint192(1e9); // 1e-9 + // stake rate under/over which governance can reset all stakes + uint192 private constant MAX_SAFE_STAKE_RATE = 1e6 * FIX_ONE; // 1e6 + uint192 private constant MIN_SAFE_STAKE_RATE = uint192(1e12); // 1e-6 + // Withdrawal Leak uint192 private leaked; // {1} stake fraction that has withdrawn without a refresh uint48 private lastWithdrawRefresh; // {s} timestamp of last refresh() during withdraw() @@ -378,6 +382,20 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { address account = accounts.at(i); delete withdrawals[account]; } + emit AllUnstakingReset(era); + } + + /// @custom:governance + /// Reset all stakes and advance era + function resetStakes() external governance { + uint192 stakeRate = divuu(totalStaked, rsrBacking); + require( + stakeRate <= MIN_SAFE_STAKE_RATE || stakeRate >= MAX_SAFE_STAKE_RATE, + "rate still safe" + ); + + bankruptStakers(); + bankruptWithdrawals(); } /// Refresh if too much RSR has exited since the last refresh occurred diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index f6babc8eb6..ffe8d0669f 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -163,6 +163,10 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab uint48 private lastWithdrawRefresh; // {s} timestamp of last refresh() during withdraw() uint192 public withdrawalLeak; // {1} gov param -- % RSR that can be withdrawn without refresh + // stake rate under/over which governance can reset all stakes + uint192 private constant MAX_SAFE_STAKE_RATE = 1e6 * FIX_ONE; // 1e6 D18{qStRSR/qRSR} + uint192 private constant MIN_SAFE_STAKE_RATE = uint192(1e12); // 1e-6 D18{qStRSR/qRSR} + // ====================== /// @custom:oz-upgrades-unsafe-allow constructor @@ -482,6 +486,19 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab IERC20Upgradeable(address(rsr)).safeTransfer(_msgSender(), seizedRSR); } + /// @custom:governance + /// Reset all stakes and advance era + function resetStakes() external { + requireGovernanceOnly(); + require( + stakeRate <= MIN_SAFE_STAKE_RATE || stakeRate >= MAX_SAFE_STAKE_RATE, + "rate still safe" + ); + + beginEra(); + beginDraftEra(); + } + /// @return D18{qRSR/qStRSR} The exchange rate between RSR and StRSR function exchangeRate() public view returns (uint192) { // D18{qRSR/qStRSR} = D18 * D18 / D18{qStRSR/qRSR} diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 30444cd379..927858718f 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -2145,6 +2145,126 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { }) }) + describe('Reset Stakes - Governance', () => { + it('Should not allow to reset stakes if not governance', async () => { + await expect(stRSR.connect(other).resetStakes()).to.be.revertedWith('governance only') + }) + + it('Should reset stakes and perform validations on rate - MAX', async () => { + const stakeAmt: BigNumber = bn('100e18') + const seizeAmt: BigNumber = bn('1e18') + + // Stake + await rsr.connect(addr1).approve(stRSR.address, stakeAmt) + await stRSR.connect(addr1).stake(stakeAmt) + + expect(await stRSR.exchangeRate()).to.equal(fp('1')) + expect(await stRSR.totalSupply()).to.equal(stakeAmt) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmt) + + // Cannot reset stakes with this rate + await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rate still safe') + + // Seize small portion of RSR to increase stake rate - still safe + await whileImpersonating(backingManager.address, async (signer) => { + await expect(stRSR.connect(signer).seizeRSR(seizeAmt)).to.emit(stRSR, 'ExchangeRateSet') + }) + + // new rate: new strsr supply / RSR backing that strsr supply + let expectedRate = fp(stakeAmt.sub(seizeAmt)).div(stakeAmt) + expect(await stRSR.exchangeRate()).to.be.closeTo(expectedRate, 1) + expect(await stRSR.totalSupply()).to.equal(stakeAmt) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmt) + + // Attempt to reset stakes, still not possible + await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rate still safe') + + // New Seizure - rate will be unsafe + const rsrRemaining = stakeAmt.sub(seizeAmt) + const seizeAmt2 = rsrRemaining.sub(1e13) + await whileImpersonating(backingManager.address, async (signer) => { + await expect(stRSR.connect(signer).seizeRSR(seizeAmt2)).to.emit(stRSR, 'ExchangeRateSet') + }) + + // check new rate + expectedRate = fp(stakeAmt.sub(seizeAmt).sub(seizeAmt2)).div(stakeAmt) + expect(await stRSR.exchangeRate()).to.be.closeTo(expectedRate, 1) + expect(await stRSR.exchangeRate()).to.be.lte(fp('1e-6')) + expect(await stRSR.exchangeRate()).to.be.gte(fp('1e-9')) + + // Now governance can reset stakes + await expect(stRSR.connect(owner).resetStakes()).to.emit(stRSR, 'AllBalancesReset') + + // All stakes reset + expect(await stRSR.exchangeRate()).to.equal(fp('1')) + expect(await stRSR.totalSupply()).to.equal(bn(0)) + expect(await stRSR.balanceOf(addr1.address)).to.equal(bn(0)) + }) + + it('Should reset stakes and perform validations on rate - MIN', async () => { + const stakeAmt: BigNumber = bn('1000e18') + const addAmt1: BigNumber = bn('100e18') + const addAmt2: BigNumber = bn('10e30') + + // Stake + await rsr.connect(addr1).approve(stRSR.address, stakeAmt) + await stRSR.connect(addr1).stake(stakeAmt) + + expect(await stRSR.exchangeRate()).to.equal(fp('1')) + expect(await stRSR.totalSupply()).to.equal(stakeAmt) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmt) + + // Cannot reset stakes with this rate + await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rate still safe') + + // Add RSR to decrease stake rate - still safe + await rsr.connect(owner).transfer(stRSR.address, addAmt1) + + // Advance to the end of noop period + await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp()))) + await stRSR.payoutRewards() + + // Calculate payout amount + const decayFn = makeDecayFn(await stRSR.rewardRatio()) + const addedRSRStake = addAmt1.sub(decayFn(addAmt1, 1)) // 1 round + const newRate: BigNumber = fp(stakeAmt.add(addedRSRStake)).div(stakeAmt) + + // Payout rewards - Advance to get 1 round of rewards + await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp()))) + await expect(stRSR.payoutRewards()).to.emit(stRSR, 'ExchangeRateSet') + expect(await stRSR.exchangeRate()).to.be.closeTo(newRate, 1) + expect(await stRSR.totalSupply()).to.equal(stakeAmt) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmt) + + // Attempt to reset stakes, still not possible + await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rate still safe') + + // Add a large amount of funds - rate will be unsafe + await rsr.connect(owner).mint(owner.address, addAmt2) + await rsr.connect(owner).transfer(stRSR.address, addAmt2) + + // Advance to the end of noop period + await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp()))) + await stRSR.payoutRewards() + + // Payout rewards - Advance time - rate will be unsafe + await setNextBlockTimestamp(Number(ONE_PERIOD.mul(100).add(await getLatestBlockTimestamp()))) + await expect(stRSR.payoutRewards()).to.emit(stRSR, 'ExchangeRateSet') + expect(await stRSR.exchangeRate()).to.be.gte(fp('1e6')) + expect(await stRSR.exchangeRate()).to.be.lte(fp('1e9')) + expect(await stRSR.totalSupply()).to.equal(stakeAmt) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmt) + + // Now governance can reset stakes + await expect(stRSR.connect(owner).resetStakes()).to.emit(stRSR, 'AllBalancesReset') + + // All stakes reset + expect(await stRSR.exchangeRate()).to.equal(fp('1')) + expect(await stRSR.totalSupply()).to.equal(bn(0)) + expect(await stRSR.balanceOf(addr1.address)).to.equal(bn(0)) + }) + }) + describe('Transfers #fast', () => { let amount: BigNumber From 809b4a88750fdb560a7f567e5e2b1b01065c9b57 Mon Sep 17 00:00:00 2001 From: brr Date: Fri, 11 Aug 2023 16:23:07 +0100 Subject: [PATCH 368/462] C4: add test to show effect of someone frontrunning a gnosis trade (#884) --- contracts/plugins/trading/GnosisTrade.sol | 9 ++- test/Broker.test.ts | 96 ++++++++++++++++++++++- 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index d3256be6ce..e8413c5fea 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -184,11 +184,18 @@ contract GnosisTrade is ITrade { // Transfer balances to origin uint256 sellBal = sell.balanceOf(address(this)); + + // As raised in C4's review, this balance can be manupulated by a frontrunner + // It won't really affect the outcome of the trade, as protocol still gets paid + // and it just gets a better clearing price than expected. + // Fixing it would require some complex logic, as SimpleAuction does not expose + // the amount of tokens bought by the auction after the tokens are settled. + // So we will live with this for now. Worst case, there will be a mismatch between + // the trades recorded by the IDO contracts and on our side. boughtAmt = buy.balanceOf(address(this)); if (sellBal > 0) IERC20Upgradeable(address(sell)).safeTransfer(origin, sellBal); if (boughtAmt > 0) IERC20Upgradeable(address(buy)).safeTransfer(origin, boughtAmt); - // Check clearing prices if (sellBal < initBal) { soldAmt = initBal - sellBal; diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 6f86dc61f4..6d7bd621e2 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -22,6 +22,7 @@ import { GnosisMock, GnosisMockReentrant, GnosisTrade, + GnosisTrade__factory, IAssetRegistry, TestIBackingManager, TestIBasketHandler, @@ -55,6 +56,7 @@ import { } from './utils/time' import { ITradeRequest } from './utils/trades' import { useEnv } from '#/utils/env' +import { parseUnits } from 'ethers/lib/utils' const DEFAULT_THRESHOLD = fp('0.01') // 1% const DELAY_UNTIL_DEFAULT = bn('86400') // 24h @@ -590,7 +592,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { describe('Trades', () => { context('GnosisTrade', () => { - const amount = bn('100e18') + const amount = fp('100.0') let trade: GnosisTrade beforeEach(async () => { @@ -854,6 +856,98 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await trade.canSettle()).to.equal(false) }) + it('Settle frontrun regression check - should be OK', async () => { + // Initialize trade - simulate from backingManager + // token0 18 decimals + // token1 6 decimals + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: fp('100.0'), + minBuyAmount: parseUnits('95.0', 6), + } + + // Fund trade and initialize + await token0.connect(owner).mint(backingManager.address, tradeRequest.sellAmount) + + let newTradeAddress = '' + await whileImpersonating(backingManager.address, async (bmSigner) => { + await token0.connect(bmSigner).approve(broker.address, tradeRequest.sellAmount) + const brokerWithBM = broker.connect(bmSigner) + newTradeAddress = await brokerWithBM.callStatic.openTrade( + TradeKind.BATCH_AUCTION, + tradeRequest, + prices + ) + await brokerWithBM.openTrade(TradeKind.BATCH_AUCTION, tradeRequest, prices) + }) + trade = GnosisTrade__factory.connect(newTradeAddress, owner) + + await advanceTime(config.batchAuctionLength.div(10).toString()) + + // Place minimum bid + const bid = { + bidder: addr1.address, + sellAmount: tradeRequest.sellAmount, + buyAmount: tradeRequest.minBuyAmount, + } + await token1.connect(owner).mint(addr1.address, bid.buyAmount) + await token1.connect(addr1).approve(gnosis.address, bid.buyAmount) + await gnosis.placeBid(0, bid) + + // Advance time till trade can be settled + await advanceTime(config.batchAuctionLength.add(100).toString()) + + await whileImpersonating(backingManager.address, async (bmSigner) => { + const tradeWithBm = GnosisTrade__factory.connect(newTradeAddress, bmSigner) + + const normalValues = await tradeWithBm.callStatic.settle() + + expect(normalValues.boughtAmt).to.eq(tradeRequest.minBuyAmount) + expect(normalValues.soldAmt).to.eq(tradeRequest.sellAmount) + + // Simulate someone frontrunning settlement and adding more funds to the trade + await token0.connect(owner).mint(tradeWithBm.address, fp('10')) + await token1.connect(owner).mint(tradeWithBm.address, parseUnits('1', 6)) + + // Simulate settlement after manipulating the trade + let frontRunnedValues = await tradeWithBm.callStatic.settle() + expect(frontRunnedValues.boughtAmt).to.eq( + tradeRequest.minBuyAmount.add(parseUnits('1', 6)) + ) + expect(frontRunnedValues.soldAmt).to.eq(tradeRequest.sellAmount.sub(fp('10'))) + // We can manipulate boughtAmt up and soldAmt down. + // So we're unable to manipualte the clearing price down and force a violation. + + // uint192 clearingPrice = shiftl_toFix(adjustedBuyAmt, -int8(buy.decimals())).div( + // shiftl_toFix(adjustedSoldAmt, -int8(sell.decimals())) + // ); + // if (clearingPrice.lt(worstCasePrice)) { + // broker.reportViolation(); + // } + await token0.connect(owner).mint(tradeWithBm.address, fp('10')) + await token1.connect(owner).mint(tradeWithBm.address, parseUnits('1', 6)) + frontRunnedValues = await tradeWithBm.callStatic.settle() + expect(frontRunnedValues.boughtAmt).to.eq( + tradeRequest.minBuyAmount.add(parseUnits('2', 6)) + ) + expect(frontRunnedValues.soldAmt).to.eq(tradeRequest.sellAmount.sub(fp('20'))) + + expect(await broker.batchTradeDisabled()).to.be.false + await tradeWithBm.settle() + expect(await broker.batchTradeDisabled()).to.be.false + }) + + // Check status + expect(await trade.status()).to.equal(TradeStatus.CLOSED) + expect(await trade.canSettle()).to.equal(false) + + // It's potentially possible to prevent the reportViolation call to be called + // if (sellBal < initBal) { + // if sellBal get's set to initBal, then the GnosisTrade will ignore the boughtAmt + // But it's unknown if this could be exploited + }) + it('Should protect against reentrancy when settling GnosisTrade', async () => { // Create a Reetrant Gnosis const GnosisReentrantFactory: ContractFactory = await ethers.getContractFactory( From e5f0a361e119cd1b3622bd51499290d7b58e3305 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 11 Aug 2023 13:03:28 -0400 Subject: [PATCH 369/462] remove --parallel from all package.json targets (#893) --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9afb749c90..3382d563b4 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,12 @@ "test:extreme:integration": "FORK=1 EXTREME=1 PROTO_IMPL=1 npx hardhat test test/integration/**/*.test.ts", "test:unit": "yarn test:plugins && yarn test:p0 && yarn test:p1", "test:fast": "bash tools/fast-test.sh", - "test:p0": "PROTO_IMPL=0 hardhat test test/*.test.ts --parallel", - "test:p1": "PROTO_IMPL=1 hardhat test test/*.test.ts --parallel", - "test:plugins": "hardhat test test/{libraries,plugins}/*.test.ts --parallel", + "test:p0": "PROTO_IMPL=0 hardhat test test/*.test.ts", + "test:p1": "PROTO_IMPL=1 hardhat test test/*.test.ts ", + "test:plugins": "hardhat test test/{libraries,plugins}/*.test.ts", "test:plugins:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/plugins/individual-collateral/**/*.test.ts", - "test:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/integration/**/*.test.ts --parallel", - "test:scenario": "PROTO_IMPL=1 hardhat test test/scenario/*.test.ts --parallel", + "test:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/integration/**/*.test.ts", + "test:scenario": "PROTO_IMPL=1 hardhat test test/scenario/*.test.ts", "test:gas": "yarn test:gas:protocol && yarn test:gas:integration", "test:gas:protocol": "REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/{libraries,plugins,scenario}/*.test.ts test/*.test.ts", "test:gas:integration": "FORK=1 REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/integration/**/*.test.ts", From 08212ffbd227368af2adabbbdc2e5407541d7160 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 11 Aug 2023 15:57:35 -0400 Subject: [PATCH 370/462] RecollateralizationLib gas optimization (#890) --- .../p1/mixins/RecollateralizationLib.sol | 28 ++++++++++--------- test/Recollateralization.test.ts | 4 +-- .../Recollateralization.test.ts.snap | 18 ++++++------ 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index 8a9e0990f3..dd86e45ca1 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -181,6 +181,9 @@ library RecollateralizationLibP1 { int256 deltaTop; // D18{BU} even though this is int256, it is D18 // not required for range.bottom + // to minimize total operations, range.bottom is calculated from a summed UoA + uint192 uoaBottom; // {UoA} pessimistic UoA estimate of balances above basketsHeld.bottom + // (no space on the stack to cache erc20s.length) for (uint256 i = 0; i < reg.erc20s.length; ++i) { // Exclude RToken balances to avoid double counting value @@ -193,15 +196,14 @@ library RecollateralizationLibP1 { bal = bal.plus(reg.assets[i].bal(address(ctx.stRSR))); } - { + if (ctx.quantities[i] == 0) { // Skip over dust-balance assets not in the basket (uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/tok} // Intentionally include value of IFFY/DISABLED collateral - if ( - ctx.quantities[i] == 0 && - !TradeLib.isEnoughToSell(reg.assets[i], bal, lotLow, ctx.minTradeVolume) - ) continue; + if (!TradeLib.isEnoughToSell(reg.assets[i], bal, lotLow, ctx.minTradeVolume)) { + continue; + } } (uint192 low, uint192 high) = reg.assets[i].price(); // {UoA/tok} @@ -238,7 +240,7 @@ library RecollateralizationLibP1 { // {tok} = {tok/BU} * {BU} uint192 anchor = ctx.quantities[i].mul(ctx.basketsHeld.bottom, FLOOR); - // (1) Sell tokens at low price + // (1) Sum token value at low price // {UoA} = {UoA/tok} * {tok} uint192 val = low.mul(bal - anchor, FLOOR); @@ -249,12 +251,7 @@ library RecollateralizationLibP1 { // in the calculation we have already calculated the UoA amount corresponding to // the excess token balance based on its low price, so we are already set up // to straightforwardly deduct the minTradeVolume before trying to buy BUs. - val = (val < ctx.minTradeVolume) ? 0 : val - ctx.minTradeVolume; - - // (3) Buy BUs at their high price with the remaining value - // (4) Assume maximum slippage in trade - // {BU} = {UoA} * {1} / {UoA/BU} - range.bottom += val.mulDiv(FIX_ONE.minus(ctx.maxTradeSlippage), buPriceHigh, FLOOR); + uoaBottom += (val < ctx.minTradeVolume) ? 0 : val - ctx.minTradeVolume; } } @@ -271,7 +268,12 @@ library RecollateralizationLibP1 { } // range.bottom - range.bottom += ctx.basketsHeld.bottom; + // (3) Buy BUs at their high price with the remaining value + // (4) Assume maximum slippage in trade + // {BU} = {UoA} * {1} / {UoA/BU} + range.bottom = + ctx.basketsHeld.bottom + + uoaBottom.mulDiv(FIX_ONE.minus(ctx.maxTradeSlippage), buPriceHigh, FLOOR); // reverting on overflow is appropriate here // ==== (3/3) Enforce (range.bottom <= range.top <= basketsNeeded) ==== diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index c6f62c1cc9..0d2208c113 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -5191,10 +5191,10 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { let tradeAddr = await backingManager.trades(token2.address) let trade = await ethers.getContractAt('DutchTrade', tradeAddr) await backupToken1.connect(addr1).approve(trade.address, initialBal) - await advanceToTimestamp((await trade.endTime()) - 1) + await advanceBlocks((await trade.endBlock()).sub(await getLatestBlockNumber()).sub(1)) await snapshotGasCost(trade.connect(addr1).bid()) - // Expect new trade started -- bid in first block at ~1000x price + // Expect new trade started -- bid in last block expect(await backingManager.tradesOpen()).to.equal(1) expect(await backingManager.trades(token2.address)).to.equal(ZERO_ADDRESS) expect(await backingManager.trades(rsr.address)).to.not.equal(ZERO_ADDRESS) diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index b48be609f9..ee29ea21d7 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1305108`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1390986`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1445319`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1517200`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `649717`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `745259`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1675046`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1685173`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `184816`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1624723`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1616603`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184816`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1714111`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1704814`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `212916`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `202908`; From 6d4cf57d3c4eb74109e9a0260ca2d6ddba0ea131 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 14 Aug 2023 17:03:08 -0400 Subject: [PATCH 371/462] Gas tests (#895) --- package.json | 3 +- test/Broker.test.ts | 8 +- test/Recollateralization.test.ts | 1 - test/__snapshots__/Broker.test.ts.snap | 16 +- test/__snapshots__/FacadeWrite.test.ts.snap | 4 +- test/__snapshots__/Furnace.test.ts.snap | 34 ++--- test/__snapshots__/Main.test.ts.snap | 12 +- test/__snapshots__/RToken.test.ts.snap | 6 +- .../Recollateralization.test.ts.snap | 2 +- test/__snapshots__/Revenues.test.ts.snap | 26 ++-- test/__snapshots__/ZZStRSR.test.ts.snap | 14 +- test/plugins/Collateral.test.ts | 2 +- test/plugins/__snapshots__/Asset.test.ts.snap | 13 ++ .../__snapshots__/Collateral.test.ts.snap | 18 ++- .../RewardableERC20.test.ts.snap | 25 ++++ .../RewardableERC20Vault.test.ts.snap | 13 -- .../ATokenFiatCollateral.test.ts.snap | 18 +-- .../AnkrEthCollateralTestSuite.test.ts.snap | 14 +- .../CBETHCollateral.test.ts.snap | 14 +- .../CTokenFiatCollateral.test.ts.snap | 18 +-- .../__snapshots__/CometTestSuite.test.ts.snap | 18 +-- .../CrvStableMetapoolSuite.test.ts.snap | 18 +-- ...StableRTokenMetapoolTestSuite.test.ts.snap | 18 +-- .../CrvStableTestSuite.test.ts.snap | 18 +-- .../CrvVolatileTestSuite.test.ts.snap | 18 +-- .../CvxStableMetapoolSuite.test.ts.snap | 18 +-- ...StableRTokenMetapoolTestSuite.test.ts.snap | 18 +-- .../CvxStableTestSuite.test.ts.snap | 18 +-- .../CvxVolatileTestSuite.test.ts.snap | 18 +-- .../dsr/SDaiCollateralTestSuite.test.ts | 16 +- .../SDaiCollateralTestSuite.test.ts.snap | 22 ++- .../flux-finance/FTokenFiatCollateral.test.ts | 141 ++++++------------ .../FTokenFiatCollateral.test.ts.snap | 96 +++++++++--- .../SFrxEthTestSuite.test.ts.snap | 12 +- .../LidoStakedEthTestSuite.test.ts.snap | 18 +-- .../MorphoAAVEFiatCollateral.test.ts | 23 ++- .../MorphoAAVENonFiatCollateral.test.ts | 22 ++- .../MorphoAAVEFiatCollateral.test.ts.snap | 66 +++++--- .../MorphoAAVENonFiatCollateral.test.ts.snap | 44 ++++-- ...AAVESelfReferentialCollateral.test.ts.snap | 14 +- .../RethCollateralTestSuite.test.ts.snap | 14 +- .../StargateETHTestSuite.test.ts.snap | 36 ++--- .../__snapshots__/MaxBasketSize.test.ts.snap | 18 +-- 43 files changed, 538 insertions(+), 427 deletions(-) create mode 100644 test/plugins/__snapshots__/Asset.test.ts.snap create mode 100644 test/plugins/__snapshots__/RewardableERC20.test.ts.snap delete mode 100644 test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap diff --git a/package.json b/package.json index 3382d563b4..79032da344 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,9 @@ "test:plugins:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/plugins/individual-collateral/**/*.test.ts", "test:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/integration/**/*.test.ts", "test:scenario": "PROTO_IMPL=1 hardhat test test/scenario/*.test.ts", - "test:gas": "yarn test:gas:protocol && yarn test:gas:integration", + "test:gas": "yarn test:gas:protocol && yarn test:gas:collateral && yarn test:gas:integration", "test:gas:protocol": "REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/{libraries,plugins,scenario}/*.test.ts test/*.test.ts", + "test:gas:collateral": "FORK=1 REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/plugins/individual-collateral/**/*.test.ts", "test:gas:integration": "FORK=1 REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/integration/**/*.test.ts", "test:coverage": "PROTO_IMPL=1 hardhat coverage --testfiles 'test/{libraries,plugins,scenario}/*.test.ts test/*.test.ts'", "test:unit:coverage": "PROTO_IMPL=1 SLOW= hardhat coverage --testfiles 'test/*.test.ts test/libraries/*.test.ts test/plugins/*.test.ts'", diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 6d7bd621e2..61398dba6f 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -1571,6 +1571,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // Create a new trade TradeFactory = await ethers.getContractFactory('GnosisTrade') newTrade = await TradeFactory.deploy() + await setStorageAt(newTrade.address, 0, 0) }) it('Open Trade ', async () => { @@ -1614,6 +1615,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ) }) + // Bidding tested in Revenues.test.ts + it('Settle Trade ', async () => { // Fund trade and initialize await token0.connect(owner).mint(newTrade.address, amount) @@ -1664,6 +1667,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // Create a new trade TradeFactory = await ethers.getContractFactory('DutchTrade') newTrade = await TradeFactory.deploy() + await setStorageAt(newTrade.address, 0, 0) }) it('Open Trade ', async () => { @@ -1708,6 +1712,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ) }) + // Bidding tested in Revenues.test.ts + it('Settle Trade ', async () => { // Fund trade and initialize await token0.connect(owner).mint(newTrade.address, amount) @@ -1721,7 +1727,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ) // Advance time till trade can be settled - await advanceTime(config.dutchAuctionLength.add(100).toString()) + await advanceBlocks((await newTrade.endBlock()).sub(await getLatestBlockNumber())) // Settle trade await whileImpersonating(backingManager.address, async (bmSigner) => { diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 0d2208c113..5b7718a225 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -40,7 +40,6 @@ import { import { advanceTime, advanceBlocks, - advanceToTimestamp, getLatestBlockTimestamp, getLatestBlockNumber, } from './utils/time' diff --git a/test/__snapshots__/Broker.test.ts.snap b/test/__snapshots__/Broker.test.ts.snap index 8189fb7987..9c02711719 100644 --- a/test/__snapshots__/Broker.test.ts.snap +++ b/test/__snapshots__/Broker.test.ts.snap @@ -1,21 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `233492`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `259737`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `338119`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `368979`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `340233`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `371094`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `342371`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `373232`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63421`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63333`; exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Initialize Trade 1`] = `451427`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 1`] = `539971`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 1`] = `541418`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 2`] = `527809`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 2`] = `529256`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 3`] = `529947`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 3`] = `531394`; exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Settle Trade 1`] = `113056`; diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index 63df8acefd..cfc4b24738 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `9070064`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8367047`; -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464714`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464633`; exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Without governance 1`] = `114895`; diff --git a/test/__snapshots__/Furnace.test.ts.snap b/test/__snapshots__/Furnace.test.ts.snap index fbfe5b16dd..af06969a2f 100644 --- a/test/__snapshots__/Furnace.test.ts.snap +++ b/test/__snapshots__/Furnace.test.ts.snap @@ -1,35 +1,35 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 1`] = `83925`; +exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 1`] = `83931`; -exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 2`] = `89814`; +exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 2`] = `89820`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 1`] = `83925`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 1`] = `83931`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 2`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 2`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 3`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 3`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 4`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 4`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 5`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 5`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 6`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 6`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 7`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 7`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 8`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 8`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 9`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 9`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 10`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 10`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 11`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 11`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 1`] = `64025`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 1`] = `64031`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 2`] = `80657`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 2`] = `80663`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 3`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 3`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 4`] = `40755`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 4`] = `40761`; diff --git a/test/__snapshots__/Main.test.ts.snap b/test/__snapshots__/Main.test.ts.snap index 1861862025..40f226bf7b 100644 --- a/test/__snapshots__/Main.test.ts.snap +++ b/test/__snapshots__/Main.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `341625`; +exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `361898`; -exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `192993`; +exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `196758`; -exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `192993`; +exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `196758`; -exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `164144`; +exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `167914`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80416`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80532`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `69928`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `70044`; diff --git a/test/__snapshots__/RToken.test.ts.snap b/test/__snapshots__/RToken.test.ts.snap index 45f7c82958..0ffc6842b6 100644 --- a/test/__snapshots__/RToken.test.ts.snap +++ b/test/__snapshots__/RToken.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `770967`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `791756`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `597971`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `618760`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `573851`; +exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `594756`; exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56658`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index ee29ea21d7..585e4bef18 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -4,7 +4,7 @@ exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1517200`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `745259`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `732257`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1685173`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index 3408dca024..c5ac463af2 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -1,27 +1,27 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 1`] = `165168`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 1`] = `165190`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `165110`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `165243`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `165110`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `165243`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `208818`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `208840`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229460`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229593`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212360`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212493`; -exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `916913`; +exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1034629`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `748182`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `777373`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1141155`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1188577`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `319722`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `309815`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `274788`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `264881`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `731082`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `743173`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `250582`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `240675`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index d3c38d5241..629cb7687c 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`StRSRP1 contract Gas Reporting Stake 1`] = `152134`; +exports[`StRSRP1 contract Gas Reporting Stake 1`] = `139830`; -exports[`StRSRP1 contract Gas Reporting Stake 2`] = `147334`; +exports[`StRSRP1 contract Gas Reporting Stake 2`] = `135030`; -exports[`StRSRP1 contract Gas Reporting Transfer 1`] = `63389`; +exports[`StRSRP1 contract Gas Reporting Transfer 1`] = `63409`; -exports[`StRSRP1 contract Gas Reporting Transfer 2`] = `41489`; +exports[`StRSRP1 contract Gas Reporting Transfer 2`] = `41509`; -exports[`StRSRP1 contract Gas Reporting Transfer 3`] = `58601`; +exports[`StRSRP1 contract Gas Reporting Transfer 3`] = `58621`; exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `222609`; exports[`StRSRP1 contract Gas Reporting Unstake 2`] = `139758`; -exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `555617`; +exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `576204`; -exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `509621`; +exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `530208`; diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index 69225d900b..ea5db3b17e 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -2220,7 +2220,7 @@ describe('Collateral contracts', () => { it('after oracle timeout', async () => { const oracleTimeout = await tokenCollateral.oracleTimeout() await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) - await advanceBlocks(oracleTimeout / 12) + await advanceBlocks(bn(oracleTimeout).div(12)) }) it('after full price timeout', async () => { diff --git a/test/plugins/__snapshots__/Asset.test.ts.snap b/test/plugins/__snapshots__/Asset.test.ts.snap new file mode 100644 index 0000000000..95905a05a0 --- /dev/null +++ b/test/plugins/__snapshots__/Asset.test.ts.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Assets contracts #fast Gas Reporting refresh() refresh() after full price timeout 1`] = `39474`; + +exports[`Assets contracts #fast Gas Reporting refresh() refresh() after full price timeout 2`] = `39474`; + +exports[`Assets contracts #fast Gas Reporting refresh() refresh() after oracle timeout 1`] = `39474`; + +exports[`Assets contracts #fast Gas Reporting refresh() refresh() after oracle timeout 2`] = `39474`; + +exports[`Assets contracts #fast Gas Reporting refresh() refresh() during SOUND 1`] = `51879`; + +exports[`Assets contracts #fast Gas Reporting refresh() refresh() during SOUND 2`] = `51879`; diff --git a/test/plugins/__snapshots__/Collateral.test.ts.snap b/test/plugins/__snapshots__/Collateral.test.ts.snap index cc9cc3ac12..9c98a9ffd2 100644 --- a/test/plugins/__snapshots__/Collateral.test.ts.snap +++ b/test/plugins/__snapshots__/Collateral.test.ts.snap @@ -1,11 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral contracts Gas Reporting Force Updates - Hard Default - ATokens/CTokens 1`] = `69709`; +exports[`Collateral contracts Gas Reporting refresh() after hard default 1`] = `72844`; -exports[`Collateral contracts Gas Reporting Force Updates - Hard Default - ATokens/CTokens 2`] = `70845`; +exports[`Collateral contracts Gas Reporting refresh() after hard default 2`] = `73980`; -exports[`Collateral contracts Gas Reporting Force Updates - Soft Default 1`] = `59399`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 1`] = `62534`; -exports[`Collateral contracts Gas Reporting Force Updates - Soft Default 2`] = `52131`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 2`] = `55266`; -exports[`Collateral contracts Gas Reporting Force Updates - Soft Default 3`] = `51849`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 3`] = `54984`; + +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 4`] = `23429`; + +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 5`] = `54984`; + +exports[`Collateral contracts Gas Reporting refresh() during SOUND 1`] = `54984`; + +exports[`Collateral contracts Gas Reporting refresh() during SOUND 2`] = `54984`; diff --git a/test/plugins/__snapshots__/RewardableERC20.test.ts.snap b/test/plugins/__snapshots__/RewardableERC20.test.ts.snap new file mode 100644 index 0000000000..3f3231f06b --- /dev/null +++ b/test/plugins/__snapshots__/RewardableERC20.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Gas Reporting RewardableERC20WrapperTest claimRewards 1`] = `174149`; + +exports[`Gas Reporting RewardableERC20WrapperTest claimRewards 2`] = `105749`; + +exports[`Gas Reporting RewardableERC20WrapperTest deposit 1`] = `119303`; + +exports[`Gas Reporting RewardableERC20WrapperTest deposit 2`] = `86643`; + +exports[`Gas Reporting RewardableERC20WrapperTest withdraw 1`] = `96090`; + +exports[`Gas Reporting RewardableERC20WrapperTest withdraw 2`] = `64590`; + +exports[`Gas Reporting RewardableERC4626VaultTest claimRewards 1`] = `174118`; + +exports[`Gas Reporting RewardableERC4626VaultTest claimRewards 2`] = `105718`; + +exports[`Gas Reporting RewardableERC4626VaultTest deposit 1`] = `121596`; + +exports[`Gas Reporting RewardableERC4626VaultTest deposit 2`] = `88936`; + +exports[`Gas Reporting RewardableERC4626VaultTest withdraw 1`] = `101629`; + +exports[`Gas Reporting RewardableERC4626VaultTest withdraw 2`] = `70129`; diff --git a/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap b/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap deleted file mode 100644 index 5d104c5e65..0000000000 --- a/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Gas Reporting RewardableERC4626Vault claimRewards 1`] = `159832`; - -exports[`Gas Reporting RewardableERC4626Vault claimRewards 2`] = `83066`; - -exports[`Gas Reporting RewardableERC4626Vault deposit 1`] = `119033`; - -exports[`Gas Reporting RewardableERC4626Vault deposit 2`] = `85387`; - -exports[`Gas Reporting RewardableERC4626Vault withdraw 1`] = `98068`; - -exports[`Gas Reporting RewardableERC4626Vault withdraw 2`] = `66568`; diff --git a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap index e505e04797..1976652900 100644 --- a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `74518`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `75367`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `72850`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `73699`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `73073`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `73922`; exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `30918`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `74518`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `75367`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `72850`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `73699`; exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `51728`; exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `51728`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `92420`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `93195`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92346`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `93269`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `125132`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `128267`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `89264`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `92399`; diff --git a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap index b2a094843f..efb00072cb 100644 --- a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `58262`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61342`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `53793`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `56873`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `76400`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `79535`; exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `37508`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `58262`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61342`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `53793`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `56873`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `68714`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `71849`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `68714`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `71849`; diff --git a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap index 3551302c16..67c84c6cc5 100644 --- a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `57750`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `60830`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `53281`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `56361`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `91861`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `98131`; exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `36971`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `75731`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `81946`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `71262`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77477`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `84175`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `90445`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `84175`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `90445`; diff --git a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap index b1bf29c3a9..00de61ac71 100644 --- a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `119481`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `120330`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `117813`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `118662`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `74209`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `75058`; exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `31842`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `119481`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `120330`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `117813`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `118662`; exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `96669`; exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `96669`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `139939`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `140788`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `139939`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `140788`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `172725`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `175860`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `136857`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `139992`; diff --git a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap index 57d6205291..c760e4bbb0 100644 --- a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `107007`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `110087`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `102270`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `105350`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `132320`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `135455`; exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `70661`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `107007`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `110087`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `102270`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `105350`; exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `73461`; exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `70661`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `124634`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `127769`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `124634`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `127769`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `132185`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `135320`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `124916`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `128051`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap index a4b3141f3a..6aaba60bdb 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `75961`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `82121`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `71493`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `77653`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `242158`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `259238`; exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `75961`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `82121`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `71493`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77653`; exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `237273`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `254353`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `237273`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `254353`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `244851`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `261931`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `237583`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `254663`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap index 82de3742f2..2b5d33eea0 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `92858`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `99018`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `88390`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `94550`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `220114`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `232924`; exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `97863`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `104078`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `93395`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `99610`; exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `215229`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `228039`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `215229`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `228039`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `203246`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `213786`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `195978`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `206518`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap index 28185554e3..ffe45ebe78 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59818`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62898`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55350`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `58430`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `192331`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `205141`; exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59818`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62898`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55350`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58430`; exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `187446`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `200256`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `187446`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `200256`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `175071`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `185611`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `167803`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `178343`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap index de32617273..cf655d93a7 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62266`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `65346`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57798`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `60878`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `224290`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `241371`; exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62266`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `65346`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57798`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `60878`; exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `219405`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `236486`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `219405`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `236486`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `225659`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `242740`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `218391`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `235472`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap index 8e747784bc..6a295e365d 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `75961`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `82121`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `71493`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `77653`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `242158`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `259238`; exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `75961`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `82121`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `71493`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77653`; exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `237273`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `254353`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `237273`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `254353`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `244851`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `261931`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `237583`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `254663`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap index c5192687d0..49b74a184d 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `92858`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `99018`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `88390`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `94550`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `220114`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `232924`; exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `97863`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `104078`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `93395`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `99610`; exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `215229`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `228039`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `215229`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `228039`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `203246`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `213786`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `195978`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `206518`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap index 18f069f0e4..53d7c649fe 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59818`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62898`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55350`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `58430`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `192331`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `205141`; exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59818`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62898`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55350`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58430`; exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `187446`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `200256`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `187446`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `200256`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `175071`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `185611`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `167803`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `178343`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap index 71f287f75d..562706cdba 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62266`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `65346`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57798`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `60878`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `224290`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `241371`; exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62266`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `65346`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57798`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `60878`; exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `219405`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `236486`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `219405`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `236486`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `225659`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `242740`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `218391`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `235472`; diff --git a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts index 4b50a53796..a6070353d4 100644 --- a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts @@ -123,11 +123,17 @@ const mintCollateralTo: MintCollateralFunc = async ( await mintSDAI(ctx.tok, user, amount, recipient) } -// eslint-disable-next-line @typescript-eslint/no-empty-function -const reduceTargetPerRef = async () => {} +const reduceTargetPerRef = async (ctx: CollateralFixtureContext, pctDecrease: BigNumberish) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) +} -// eslint-disable-next-line @typescript-eslint/no-empty-function -const increaseTargetPerRef = async () => {} +const increaseTargetPerRef = async (ctx: CollateralFixtureContext, pctIncrease: BigNumberish) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) +} // prettier-ignore const reduceRefPerTok = async ( @@ -199,7 +205,7 @@ const opts = { increaseRefPerTok, getExpectedPrice, itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, + itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, diff --git a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap index 4fb7c27a86..0d82e1ea60 100644 --- a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap @@ -1,17 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `114677`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117757`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `106365`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `109445`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `129111`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `132246`; exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `90061`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `114496`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117576`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `106365`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `109445`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `121148`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `93388`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `121148`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `90061`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `124283`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `124283`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `132111`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `124565`; diff --git a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts index 9367ee577c..8b51724dcc 100644 --- a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts @@ -1,16 +1,10 @@ +import { setStorageAt } from '@nomicfoundation/hardhat-network-helpers' import collateralTests from '../collateralTests' -import { - CollateralFixtureContext, - CollateralStatus, - CollateralOpts, - MintCollateralFunc, -} from '../pluginTestTypes' +import { CollateralFixtureContext, CollateralOpts, MintCollateralFunc } from '../pluginTestTypes' import { ethers } from 'hardhat' import { ContractFactory, BigNumberish } from 'ethers' import { CTokenWrapper, - CTokenWrapperMock, - ICToken, MockV3Aggregator, MockV3Aggregator__factory, TestICollateral, @@ -20,7 +14,6 @@ import { bn, fp } from '../../../../common/numbers' import { ZERO_ADDRESS } from '../../../../common/constants' import { expect } from 'chai' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { advanceBlocks } from '../../../utils/time' import { USDC_HOLDER, USDT_HOLDER, @@ -192,59 +185,6 @@ all.forEach((curr: FTokenEnumeration) => { return makeCollateralFixtureContext } - const deployCollateralMockContext = async ( - opts: FTokenCollateralOpts = {} - ): Promise => { - const collateralOpts = { ...defaultCollateralOpts, ...opts } - - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) - - const comptroller = await ethers.getContractAt('ComptrollerMock', collateralOpts.comptroller!) - - const chainlinkFeed = await MockV3AggregatorFactory.deploy(6, bn('1e6')) - collateralOpts.chainlinkFeed = chainlinkFeed.address - - const FTokenMockFactory = await ethers.getContractFactory('CTokenMock') - - const underlyingFToken = await FTokenMockFactory.deploy( - 'Mock FToken', - 'Mock Ftk', - curr.underlying - ) - - const CTokenWrapperMockFactory: ContractFactory = await ethers.getContractFactory( - 'CTokenWrapperMock' - ) - - let compAddress = ZERO_ADDRESS - try { - compAddress = await comptroller.getCompAddress() - // eslint-disable-next-line no-empty - } catch {} - - const fTokenVault = ( - await CTokenWrapperMockFactory.deploy( - await underlyingFToken.name(), - await underlyingFToken.symbol(), - underlyingFToken.address, - compAddress, - collateralOpts.comptroller! - ) - ) - - collateralOpts.erc20 = fTokenVault.address - - const collateral = await deployCollateral(collateralOpts) - - return { - collateral, - chainlinkFeed, - tok: fTokenVault, - } - } - /* Define helper functions */ @@ -261,41 +201,45 @@ all.forEach((curr: FTokenEnumeration) => { await mintFToken(underlying, curr.holderUnderlying, fToken, tok, amount, recipient) } - const increaseRefPerTok = async (ctx: CollateralFixtureContext) => { - await advanceBlocks(1) - await (ctx.tok as ICToken).exchangeRateCurrent() + const reduceTargetPerRef = async (ctx: CollateralFixtureContext, pctDecrease: BigNumberish) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } + + const increaseTargetPerRef = async (ctx: CollateralFixtureContext, pctIncrease: BigNumberish) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } + + const increaseRefPerTok = async (ctx: CollateralFixtureContext, pctIncrease: BigNumberish) => { + const tok = ctx.tok as CTokenWrapper + const fToken = await ethers.getContractAt('ICToken', await tok.underlying()) + const totalSupply = await fToken.totalSupply() + await setStorageAt( + fToken.address, + 13, // interesting, the storage slot is 13 for fTokens and 14 for cTokens + totalSupply.sub(totalSupply.mul(pctIncrease).div(100)) + ) // expand supply by pctDecrease, since it's denominator of exchange rate calculation + } + + const reduceRefPerTok = async (ctx: CollateralFixtureContext, pctDecrease: BigNumberish) => { + const tok = ctx.tok as CTokenWrapper + const fToken = await ethers.getContractAt('ICToken', await tok.underlying()) + const totalSupply = await fToken.totalSupply() + await setStorageAt( + fToken.address, + 13, // interesting, the storage slot is 13 for fTokens and 14 for cTokens + totalSupply.add(totalSupply.mul(pctDecrease).div(100)) + ) // expand supply by pctDecrease, since it's denominator of exchange rate calculation } // eslint-disable-next-line @typescript-eslint/no-empty-function const collateralSpecificConstructorTests = () => {} - const collateralSpecificStatusTests = () => { - it('does revenue hiding correctly', async () => { - const { collateral, tok } = await deployCollateralMockContext({ revenueHiding: fp('0.01') }) - - const rate = fp('2') - const rateAsRefPerTok = rate.div(50) - await (tok as CTokenWrapperMock).setExchangeRate(rate) // above current - await collateral.refresh() - const before = await collateral.refPerTok() - expect(before).to.equal(rateAsRefPerTok.mul(fp('0.99')).div(fp('1'))) - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - // Should be SOUND if drops just under 1% - await (tok as CTokenWrapperMock).setExchangeRate(rate.mul(fp('0.99001')).div(fp('1'))) - await collateral.refresh() - let after = await collateral.refPerTok() - expect(before).to.eq(after) - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - // Should be DISABLED if drops just over 1% - await (tok as CTokenWrapperMock).setExchangeRate(before.mul(fp('0.98999')).div(fp('1'))) - await collateral.refresh() - after = await collateral.refPerTok() - expect(before).to.be.gt(after) - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - }) - } + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificStatusTests = () => {} const getExpectedPrice = async (ctx: CollateralFixtureContext) => { const initRefPerTok = await ctx.collateral.refPerTok() @@ -324,19 +268,20 @@ all.forEach((curr: FTokenEnumeration) => { beforeEachRewardsTest: emptyFn, makeCollateralFixtureContext, mintCollateralTo, - reduceTargetPerRef: emptyFn, - increaseTargetPerRef: emptyFn, - reduceRefPerTok: emptyFn, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, increaseRefPerTok, getExpectedPrice, itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, - itChecksRefPerTokDefault: it.skip, + itChecksTargetPerRefDefault: it, + itChecksRefPerTokDefault: it, itChecksPriceChanges: it, - itHasRevenueHiding: it.skip, // in this file + itHasRevenueHiding: it, resetFork, collateralName: curr.testName, chainlinkDefaultAnswer: bn('1e8'), + itIsPricedByPeg: true, } collateralTests(opts) diff --git a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap index 74ec91a258..ed9963c4ca 100644 --- a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap @@ -1,49 +1,97 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `115251`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118331`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `113583`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116663`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `115251`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `140108`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `113583`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `140108`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `136973`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118331`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `136973`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `116663`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `115443`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `97011`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `113775`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `97011`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `115443`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `140108`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `113775`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `140108`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `137229`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `142058`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `137229`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140390`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `123733`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118523`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `122065`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116855`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `123733`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `140364`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `122065`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `140294`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `146011`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118523`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `146011`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `116855`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118381`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `97203`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116713`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `97203`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118381`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `140364`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `116713`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `140364`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `140375`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `142314`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `140305`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140720`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `126813`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `125145`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `149076`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `149146`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `126813`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `125145`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `105493`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `105493`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `149146`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `149146`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `151096`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `149428`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `121461`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `119793`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `143510`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `143440`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `121461`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `119793`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `100141`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `100141`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `143510`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `143440`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `145460`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `143792`; diff --git a/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap index e0a213d4e2..40c07ca09a 100644 --- a/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `56918`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59998`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `52181`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55261`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `57618`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `60698`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55949`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `59029`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `71659`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `74794`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `71659`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `74794`; diff --git a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap index 759ebc8430..9764e5e036 100644 --- a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `85967`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `89047`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `81498`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `84578`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `128420`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `134690`; exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `65149`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `85967`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `89047`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `81498`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `84578`; exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `65149`; exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `65149`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `120734`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `127004`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `120734`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `127004`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `125485`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `131755`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `121016`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `127286`; diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts index 39e467e2d0..6ecee49770 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts @@ -131,11 +131,23 @@ const makeAaveFiatCollateralTestSuite = ( return makeCollateralFixtureContext } - // eslint-disable-next-line @typescript-eslint/no-empty-function - const reduceTargetPerRef = async () => {} + const reduceTargetPerRef = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctDecrease: BigNumberish + ) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } - // eslint-disable-next-line @typescript-eslint/no-empty-function - const increaseTargetPerRef = async () => {} + const increaseTargetPerRef = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctIncrease: BigNumberish + ) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } const changeRefPerTok = async ( ctx: MorphoAaveCollateralFixtureContext, @@ -311,13 +323,14 @@ const makeAaveFiatCollateralTestSuite = ( increaseRefPerTok, getExpectedPrice, itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, + itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), collateralName, chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, + itIsPricedByPeg: true, } collateralTests(opts) diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts index 0e902705fd..d6302abfb0 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts @@ -142,11 +142,23 @@ const makeAaveNonFiatCollateralTestSuite = ( Define helper functions */ - // eslint-disable-next-line @typescript-eslint/no-empty-function - const reduceTargetPerRef = async () => {} + const reduceTargetPerRef = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctDecrease: BigNumberish + ) => { + const lastRound = await ctx.targetPrRefFeed!.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + await ctx.targetPrRefFeed!.updateAnswer(nextAnswer) + } - // eslint-disable-next-line @typescript-eslint/no-empty-function - const increaseTargetPerRef = async () => {} + const increaseTargetPerRef = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctIncrease: BigNumberish + ) => { + const lastRound = await ctx.targetPrRefFeed!.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + await ctx.targetPrRefFeed!.updateAnswer(nextAnswer) + } const changeRefPerTok = async ( ctx: MorphoAaveCollateralFixtureContext, @@ -215,7 +227,7 @@ const makeAaveNonFiatCollateralTestSuite = ( increaseRefPerTok, getExpectedPrice, itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, + itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap index 7bb8adbe2a..637d4b1476 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap @@ -1,49 +1,73 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `131942`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `135022`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 2`] = `127473`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 2`] = `130553`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 1`] = `177256`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 1`] = `180391`; exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 2`] = `111194`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `131942`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `135022`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `127473`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `130553`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `169570`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 1`] = `111194`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 2`] = `169570`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 2`] = `111194`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `132145`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `172705`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `127676`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 2`] = `172705`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `177662`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 1`] = `180256`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 2`] = `172987`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `135225`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `130756`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `180797`; exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `111397`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `132145`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `135225`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `130756`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `111397`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `127676`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `111397`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `169976`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `173111`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `169976`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `173111`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `131298`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `180662`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `126829`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `173393`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 1`] = `175968`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134378`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129909`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 1`] = `179103`; exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 2`] = `110550`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `131298`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134378`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129909`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 1`] = `110550`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 2`] = `110550`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `171417`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `126829`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 2`] = `171417`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `168282`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 1`] = `178968`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 2`] = `168282`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 2`] = `171699`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap index 7d3645000a..fd7e828651 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap @@ -1,33 +1,49 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `131365`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134445`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `126896`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129976`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 1`] = `194216`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 1`] = `200486`; exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 2`] = `110550`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `178094`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `184309`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `173625`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `179840`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `186530`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 1`] = `110550`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 2`] = `186530`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 2`] = `110550`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `164997`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `192800`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `160528`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 2`] = `192800`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 1`] = `233480`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 1`] = `197551`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 2`] = `193082`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `168077`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `163608`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 1`] = `239750`; exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 2`] = `144182`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `217358`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `223573`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `219104`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 1`] = `144182`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 2`] = `144182`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `232064`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `212889`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `232064`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `225794`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 1`] = `236815`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `225794`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 2`] = `232346`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap index 0bb7fec1de..a8faf3cbe4 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `199080`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `202160`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `194611`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `197691`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 1`] = `215207`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 1`] = `218342`; exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 2`] = `144160`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `199080`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `202160`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `194611`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `197691`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `207521`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `210656`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `207521`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `210656`; diff --git a/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap index 54fc867521..35bb6dd12a 100644 --- a/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `68835`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `71915`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `64366`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `67446`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 1`] = `102946`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 1`] = `109216`; exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 2`] = `48056`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `68835`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `71915`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `64366`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `67446`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `95260`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `101530`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `95260`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `101530`; diff --git a/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap b/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap index 5757607e08..a11f838460 100644 --- a/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap @@ -1,49 +1,49 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `53187`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `56267`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48719`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `51799`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `66829`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `69964`; exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `23429`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `53187`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `56267`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48719`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `51799`; exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `23429`; exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `23429`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `64090`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `67225`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `64090`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `67225`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `71640`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `74775`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `64372`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `67507`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `53187`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `56267`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48719`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `51799`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `66829`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `69964`; exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `23429`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `53187`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `56267`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48719`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `51799`; exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `23429`; exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `23429`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `64090`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `67225`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `64090`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `67225`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `71640`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `74775`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `64372`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `67507`; diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index 0e0cede769..d0562e5cc1 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `11745060`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12085685`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9481763`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9838290`; -exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2292984`; +exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2293006`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `12996739`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13276964`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `25285711`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20658478`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10751848`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `11092473`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8471463`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8827990`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4535170`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4557434`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `17723377`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `13292502`; From 99d9db72e04db29f8e80e50a78b16a0b475d79f3 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Tue, 15 Aug 2023 05:13:53 +0530 Subject: [PATCH 372/462] Bunch of gas optimizations (#894) Co-authored-by: Taylor Brent --- .openzeppelin/ropsten.json | 11 +- contracts/interfaces/IBackingManager.sol | 4 +- contracts/interfaces/IBasketHandler.sol | 9 +- contracts/interfaces/IBroker.sol | 18 +-- contracts/interfaces/IDistributor.sol | 4 +- contracts/interfaces/IFurnace.sol | 2 +- contracts/interfaces/IMain.sol | 28 ++-- contracts/interfaces/IRToken.sol | 4 +- contracts/interfaces/IRewardable.sol | 2 +- contracts/interfaces/IStRSR.sol | 12 +- contracts/interfaces/ITrading.sol | 4 +- contracts/p1/BasketHandler.sol | 5 +- contracts/p1/Distributor.sol | 7 +- contracts/p1/RToken.sol | 13 +- contracts/p1/mixins/BasketLib.sol | 47 +++--- .../plugins/assets/aave/StaticATokenLM.sol | 2 +- .../curve/cvx/vendor/ConvexStakingWrapper.sol | 16 +- contracts/plugins/mocks/EasyAuction.sol | 20 +-- .../mocks/InvalidRefPerTokCollateral.sol | 7 +- contracts/plugins/trading/DutchTrade.sol | 39 +++-- package.json | 1 - .../addresses/3-tmp-assets-collateral.json | 2 +- scripts/addresses/3-tmp-deployments.json | 2 +- .../deploy_morpho_aavev2_plugin.ts | 138 +++++++++--------- tasks/testing/upgrade-checker-utils/trades.ts | 2 +- .../upgrade-checker-utils/upgrades/2_1_0.ts | 6 +- test/__snapshots__/Broker.test.ts.snap | 8 +- test/__snapshots__/FacadeWrite.test.ts.snap | 4 +- test/__snapshots__/RToken.test.ts.snap | 6 +- .../Recollateralization.test.ts.snap | 8 +- test/__snapshots__/Revenues.test.ts.snap | 20 +-- test/__snapshots__/ZZStRSR.test.ts.snap | 4 +- .../RewardableERC20.test.ts.snap | 8 +- .../ATokenFiatCollateral.test.ts.snap | 2 +- .../individual-collateral/collateralTests.ts | 2 + .../curve/collateralTests.ts | 2 + .../dsr/SDaiCollateralTestSuite.test.ts | 1 + .../FTokenFiatCollateral.test.ts.snap | 26 ++-- .../__snapshots__/MaxBasketSize.test.ts.snap | 16 +- 39 files changed, 261 insertions(+), 251 deletions(-) diff --git a/.openzeppelin/ropsten.json b/.openzeppelin/ropsten.json index 8370c99c6f..ba9ed634a2 100644 --- a/.openzeppelin/ropsten.json +++ b/.openzeppelin/ropsten.json @@ -866,10 +866,7 @@ }, "t_enum(TradeKind)10909": { "label": "enum TradeKind", - "members": [ - "DUTCH_AUCTION", - "BATCH_AUCTION" - ], + "members": ["DUTCH_AUCTION", "BATCH_AUCTION"], "numberOfBytes": "1" }, "t_mapping(t_contract(IERC20)6264,t_contract(ITrade)12850)": { @@ -1162,11 +1159,7 @@ }, "t_enum(CollateralStatus)20723": { "label": "enum CollateralStatus", - "members": [ - "SOUND", - "IFFY", - "DISABLED" - ], + "members": ["SOUND", "IFFY", "DISABLED"], "numberOfBytes": "1" }, "t_mapping(t_bytes32,t_bytes32)": { diff --git a/contracts/interfaces/IBackingManager.sol b/contracts/interfaces/IBackingManager.sol index 1aa4b9fb39..0699da6d6c 100644 --- a/contracts/interfaces/IBackingManager.sol +++ b/contracts/interfaces/IBackingManager.sol @@ -18,12 +18,12 @@ interface IBackingManager is IComponent, ITrading { /// Emitted when the trading delay is changed /// @param oldVal The old trading delay /// @param newVal The new trading delay - event TradingDelaySet(uint48 indexed oldVal, uint48 indexed newVal); + event TradingDelaySet(uint48 oldVal, uint48 newVal); /// Emitted when the backing buffer is changed /// @param oldVal The old backing buffer /// @param newVal The new backing buffer - event BackingBufferSet(uint192 indexed oldVal, uint192 indexed newVal); + event BackingBufferSet(uint192 oldVal, uint192 newVal); // Initialization function init( diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index f944550840..42bb8bf092 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -36,20 +36,17 @@ interface IBasketHandler is IComponent { /// @param targetName The name of the target unit as a bytes32 /// @param max The max number to use from `erc20s` /// @param erc20s The set of backup collateral tokens - event BackupConfigSet(bytes32 indexed targetName, uint256 indexed max, IERC20[] erc20s); + event BackupConfigSet(bytes32 indexed targetName, uint256 max, IERC20[] erc20s); /// Emitted when the warmup period is changed /// @param oldVal The old warmup period /// @param newVal The new warmup period - event WarmupPeriodSet(uint48 indexed oldVal, uint48 indexed newVal); + event WarmupPeriodSet(uint48 oldVal, uint48 newVal); /// Emitted when the status of a basket has changed /// @param oldStatus The previous basket status /// @param newStatus The new basket status - event BasketStatusChanged( - CollateralStatus indexed oldStatus, - CollateralStatus indexed newStatus - ); + event BasketStatusChanged(CollateralStatus oldStatus, CollateralStatus newStatus); // Initialization function init(IMain main_, uint48 warmupPeriod_) external; diff --git a/contracts/interfaces/IBroker.sol b/contracts/interfaces/IBroker.sol index c0496801a0..0c83eb9216 100644 --- a/contracts/interfaces/IBroker.sol +++ b/contracts/interfaces/IBroker.sol @@ -33,17 +33,13 @@ struct TradeRequest { * the continued proper functioning of trading platforms. */ interface IBroker is IComponent { - event GnosisSet(IGnosis indexed oldVal, IGnosis indexed newVal); - event BatchTradeImplementationSet(ITrade indexed oldVal, ITrade indexed newVal); - event DutchTradeImplementationSet(ITrade indexed oldVal, ITrade indexed newVal); - event BatchAuctionLengthSet(uint48 indexed oldVal, uint48 indexed newVal); - event DutchAuctionLengthSet(uint48 indexed oldVal, uint48 indexed newVal); - event BatchTradeDisabledSet(bool indexed prevVal, bool indexed newVal); - event DutchTradeDisabledSet( - IERC20Metadata indexed erc20, - bool indexed prevVal, - bool indexed newVal - ); + event GnosisSet(IGnosis oldVal, IGnosis newVal); + event BatchTradeImplementationSet(ITrade oldVal, ITrade newVal); + event DutchTradeImplementationSet(ITrade oldVal, ITrade newVal); + event BatchAuctionLengthSet(uint48 oldVal, uint48 newVal); + event DutchAuctionLengthSet(uint48 oldVal, uint48 newVal); + event BatchTradeDisabledSet(bool prevVal, bool newVal); + event DutchTradeDisabledSet(IERC20Metadata indexed erc20, bool prevVal, bool newVal); // Initialization function init( diff --git a/contracts/interfaces/IDistributor.sol b/contracts/interfaces/IDistributor.sol index e3c7de6ace..5f5c76c5a2 100644 --- a/contracts/interfaces/IDistributor.sol +++ b/contracts/interfaces/IDistributor.sol @@ -28,13 +28,13 @@ interface IDistributor is IComponent { /// @param dest The address set to receive the distribution /// @param rTokenDist The distribution of RToken that should go to `dest` /// @param rsrDist The distribution of RSR that should go to `dest` - event DistributionSet(address dest, uint16 rTokenDist, uint16 rsrDist); + event DistributionSet(address indexed dest, uint16 rTokenDist, uint16 rsrDist); /// Emitted when revenue is distributed /// @param erc20 The token being distributed, either RSR or the RToken itself /// @param source The address providing the revenue /// @param amount The amount of the revenue - event RevenueDistributed(IERC20 indexed erc20, address indexed source, uint256 indexed amount); + event RevenueDistributed(IERC20 indexed erc20, address indexed source, uint256 amount); // Initialization function init(IMain main_, RevenueShare memory dist) external; diff --git a/contracts/interfaces/IFurnace.sol b/contracts/interfaces/IFurnace.sol index 25c754aacf..170cb4c554 100644 --- a/contracts/interfaces/IFurnace.sol +++ b/contracts/interfaces/IFurnace.sol @@ -15,7 +15,7 @@ interface IFurnace is IComponent { /// Emitted when the melting ratio is changed /// @param oldRatio The old ratio /// @param newRatio The new ratio - event RatioSet(uint192 indexed oldRatio, uint192 indexed newRatio); + event RatioSet(uint192 oldRatio, uint192 newRatio); function ratio() external view returns (uint192); diff --git a/contracts/interfaces/IMain.sol b/contracts/interfaces/IMain.sol index 00bb261de9..f282be1479 100644 --- a/contracts/interfaces/IMain.sol +++ b/contracts/interfaces/IMain.sol @@ -49,27 +49,27 @@ interface IAuth is IAccessControlUpgradeable { /// Emitted when `unfreezeAt` is changed /// @param oldVal The old value of `unfreezeAt` /// @param newVal The new value of `unfreezeAt` - event UnfreezeAtSet(uint48 indexed oldVal, uint48 indexed newVal); + event UnfreezeAtSet(uint48 oldVal, uint48 newVal); /// Emitted when the short freeze duration governance param is changed /// @param oldDuration The old short freeze duration /// @param newDuration The new short freeze duration - event ShortFreezeDurationSet(uint48 indexed oldDuration, uint48 indexed newDuration); + event ShortFreezeDurationSet(uint48 oldDuration, uint48 newDuration); /// Emitted when the long freeze duration governance param is changed /// @param oldDuration The old long freeze duration /// @param newDuration The new long freeze duration - event LongFreezeDurationSet(uint48 indexed oldDuration, uint48 indexed newDuration); + event LongFreezeDurationSet(uint48 oldDuration, uint48 newDuration); /// Emitted when the system is paused or unpaused for trading /// @param oldVal The old value of `tradingPaused` /// @param newVal The new value of `tradingPaused` - event TradingPausedSet(bool indexed oldVal, bool indexed newVal); + event TradingPausedSet(bool oldVal, bool newVal); /// Emitted when the system is paused or unpaused for issuance /// @param oldVal The old value of `issuancePaused` /// @param newVal The new value of `issuancePaused` - event IssuancePausedSet(bool indexed oldVal, bool indexed newVal); + event IssuancePausedSet(bool oldVal, bool newVal); /** * Trading Paused: Disable everything except for OWNER actions, RToken.issue, RToken.redeem, @@ -118,39 +118,39 @@ interface IComponentRegistry { function rToken() external view returns (IRToken); - event StRSRSet(IStRSR indexed oldVal, IStRSR indexed newVal); + event StRSRSet(IStRSR oldVal, IStRSR newVal); function stRSR() external view returns (IStRSR); - event AssetRegistrySet(IAssetRegistry indexed oldVal, IAssetRegistry indexed newVal); + event AssetRegistrySet(IAssetRegistry oldVal, IAssetRegistry newVal); function assetRegistry() external view returns (IAssetRegistry); - event BasketHandlerSet(IBasketHandler indexed oldVal, IBasketHandler indexed newVal); + event BasketHandlerSet(IBasketHandler oldVal, IBasketHandler newVal); function basketHandler() external view returns (IBasketHandler); - event BackingManagerSet(IBackingManager indexed oldVal, IBackingManager indexed newVal); + event BackingManagerSet(IBackingManager oldVal, IBackingManager newVal); function backingManager() external view returns (IBackingManager); - event DistributorSet(IDistributor indexed oldVal, IDistributor indexed newVal); + event DistributorSet(IDistributor oldVal, IDistributor newVal); function distributor() external view returns (IDistributor); - event RSRTraderSet(IRevenueTrader indexed oldVal, IRevenueTrader indexed newVal); + event RSRTraderSet(IRevenueTrader oldVal, IRevenueTrader newVal); function rsrTrader() external view returns (IRevenueTrader); - event RTokenTraderSet(IRevenueTrader indexed oldVal, IRevenueTrader indexed newVal); + event RTokenTraderSet(IRevenueTrader oldVal, IRevenueTrader newVal); function rTokenTrader() external view returns (IRevenueTrader); - event FurnaceSet(IFurnace indexed oldVal, IFurnace indexed newVal); + event FurnaceSet(IFurnace oldVal, IFurnace newVal); function furnace() external view returns (IFurnace); - event BrokerSet(IBroker indexed oldVal, IBroker indexed newVal); + event BrokerSet(IBroker oldVal, IBroker newVal); function broker() external view returns (IBroker); } diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index 3111563053..9528ab2efd 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -25,7 +25,7 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea event Issuance( address indexed issuer, address indexed recipient, - uint256 indexed amount, + uint256 amount, uint192 baskets ); @@ -38,7 +38,7 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea event Redemption( address indexed redeemer, address indexed recipient, - uint256 indexed amount, + uint256 amount, uint192 baskets ); diff --git a/contracts/interfaces/IRewardable.sol b/contracts/interfaces/IRewardable.sol index 90563bad51..48da999850 100644 --- a/contracts/interfaces/IRewardable.sol +++ b/contracts/interfaces/IRewardable.sol @@ -11,7 +11,7 @@ import "./IMain.sol"; */ interface IRewardable { /// Emitted whenever a reward token balance is claimed - event RewardsClaimed(IERC20 indexed erc20, uint256 indexed amount); + event RewardsClaimed(IERC20 indexed erc20, uint256 amount); /// Claim rewards earned by holding a balance of the ERC20 token /// Must emit `RewardsClaimed` for each token rewards are claimed for diff --git a/contracts/interfaces/IStRSR.sol b/contracts/interfaces/IStRSR.sol index efdb937f32..b0279ef220 100644 --- a/contracts/interfaces/IStRSR.sol +++ b/contracts/interfaces/IStRSR.sol @@ -29,7 +29,7 @@ interface IStRSR is IERC20MetadataUpgradeable, IERC20PermitUpgradeable, ICompone uint256 indexed era, address indexed staker, uint256 rsrAmount, - uint256 indexed stRSRAmount + uint256 stRSRAmount ); /// Emitted when an unstaking is started @@ -83,19 +83,19 @@ interface IStRSR is IERC20MetadataUpgradeable, IERC20PermitUpgradeable, ICompone ); /// Emitted whenever the exchange rate changes - event ExchangeRateSet(uint192 indexed oldVal, uint192 indexed newVal); + event ExchangeRateSet(uint192 oldVal, uint192 newVal); /// Emitted whenever RSR are paids out - event RewardsPaid(uint256 indexed rsrAmt); + event RewardsPaid(uint256 rsrAmt); /// Emitted if all the RSR in the staking pool is seized and all balances are reset to zero. event AllBalancesReset(uint256 indexed newEra); /// Emitted if all the RSR in the unstakin pool is seized, and all ongoing unstaking is voided. event AllUnstakingReset(uint256 indexed newEra); - event UnstakingDelaySet(uint48 indexed oldVal, uint48 indexed newVal); - event RewardRatioSet(uint192 indexed oldVal, uint192 indexed newVal); - event WithdrawalLeakSet(uint192 indexed oldVal, uint192 indexed newVal); + event UnstakingDelaySet(uint48 oldVal, uint48 newVal); + event RewardRatioSet(uint192 oldVal, uint192 newVal); + event WithdrawalLeakSet(uint192 oldVal, uint192 newVal); // Initialization function init( diff --git a/contracts/interfaces/ITrading.sol b/contracts/interfaces/ITrading.sol index 4c40bce0e0..b0bed9bad3 100644 --- a/contracts/interfaces/ITrading.sol +++ b/contracts/interfaces/ITrading.sol @@ -13,8 +13,8 @@ import "./IRewardable.sol"; * @notice Common events and refresher function for all Trading contracts */ interface ITrading is IComponent, IRewardableComponent { - event MaxTradeSlippageSet(uint192 indexed oldVal, uint192 indexed newVal); - event MinTradeVolumeSet(uint192 indexed oldVal, uint192 indexed newVal); + event MaxTradeSlippageSet(uint192 oldVal, uint192 newVal); + event MinTradeVolumeSet(uint192 oldVal, uint192 newVal); /// Emitted when a trade is started /// @param trade The one-time-use trade contract that was just deployed diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 441007d763..387055fc63 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -255,7 +255,10 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { for (uint256 i = 0; i < size; ++i) { CollateralStatus s = assetRegistry.toColl(basket.erc20s[i]).status(); - if (s.worseThan(status_)) status_ = s; + if (s.worseThan(status_)) { + if (s == CollateralStatus.DISABLED) return CollateralStatus.DISABLED; + status_ = s; + } } } diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index 4373a9a9bc..ca818f5a14 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -104,6 +104,9 @@ contract DistributorP1 is ComponentP1, IDistributor { Transfer[] memory transfers = new Transfer[](destinations.length()); uint256 numTransfers; + address furnaceAddr = furnace; // gas-saver + address stRSRAddr = stRSR; // gas-saver + for (uint256 i = 0; i < destinations.length(); ++i) { address addrTo = destinations.at(i); @@ -114,9 +117,9 @@ contract DistributorP1 is ComponentP1, IDistributor { uint256 transferAmt = tokensPerShare * numberOfShares; if (addrTo == FURNACE) { - addrTo = furnace; + addrTo = furnaceAddr; } else if (addrTo == ST_RSR) { - addrTo = stRSR; + addrTo = stRSRAddr; } transfers[numTransfers] = Transfer({ diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index dd5b1790a9..77f15bef54 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -183,7 +183,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // == Refresh == assetRegistry.refresh(); // solhint-disable-next-line no-empty-blocks - try main.furnace().melt() {} catch {} // nice for the redeemer, but not necessary + try furnace.melt() {} catch {} // nice for the redeemer, but not necessary // == Checks and Effects == @@ -255,7 +255,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // == Refresh == assetRegistry.refresh(); // solhint-disable-next-line no-empty-blocks - try main.furnace().melt() {} catch {} // nice for the redeemer, but not necessary + try furnace.melt() {} catch {} // nice for the redeemer, but not necessary // == Checks and Effects == @@ -399,8 +399,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // Note: These are D18s, even though they are uint256s. This is because // we cannot assume we stay inside our valid range here, as that is what // we are checking in the first place - uint256 low = (FIX_ONE_256 * basketsNeeded) / supply; // D18{BU/rTok} - uint256 high = (FIX_ONE_256 * basketsNeeded + (supply - 1)) / supply; // D18{BU/rTok} + uint256 low = (FIX_ONE_256 * basketsNeeded_) / supply; // D18{BU/rTok} + uint256 high = (FIX_ONE_256 * basketsNeeded_ + (supply - 1)) / supply; // D18{BU/rTok} // here we take advantage of an implicit upcast from uint192 exchange rates require(low >= MIN_EXCHANGE_RATE && high <= MAX_EXCHANGE_RATE, "BU rate out of range"); @@ -426,8 +426,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// @return available {qRTok} The maximum redemption that can be performed in the current block function redemptionAvailable() external view returns (uint256 available) { uint256 supply = totalSupply(); - uint256 hourlyLimit = redemptionThrottle.hourlyLimit(supply); - available = redemptionThrottle.currentlyAvailable(hourlyLimit); + available = redemptionThrottle.currentlyAvailable(redemptionThrottle.hourlyLimit(supply)); if (supply < available) available = supply; } @@ -447,6 +446,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "issuance amtRate too big"); require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "issuance pctRate too big"); issuanceThrottle.useAvailable(totalSupply(), 0); + emit IssuanceThrottleSet(issuanceThrottle.params, params); issuanceThrottle.params = params; } @@ -457,6 +457,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "redemption amtRate too big"); require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "redemption pctRate too big"); redemptionThrottle.useAvailable(totalSupply(), 0); + emit RedemptionThrottleSet(redemptionThrottle.params, params); redemptionThrottle.params = params; } diff --git a/contracts/p1/mixins/BasketLib.sol b/contracts/p1/mixins/BasketLib.sol index 0ade4b65e8..6480d77055 100644 --- a/contracts/p1/mixins/BasketLib.sol +++ b/contracts/p1/mixins/BasketLib.sol @@ -76,8 +76,9 @@ library BasketLibP1 { empty(self); uint256 length = other.erc20s.length; for (uint256 i = 0; i < length; ++i) { - self.erc20s.push(other.erc20s[i]); - self.refAmts[other.erc20s[i]] = other.refAmts[other.erc20s[i]]; + IERC20 _erc20 = other.erc20s[i]; // gas-saver + self.erc20s.push(_erc20); + self.refAmts[_erc20] = other.refAmts[_erc20]; } } @@ -165,7 +166,9 @@ library BasketLibP1 { IAssetRegistry assetRegistry ) external returns (bool) { // targetNames := {} - while (targetNames.length() > 0) targetNames.remove(targetNames.at(0)); + while (targetNames.length() > 0) { + targetNames.remove(targetNames.at(targetNames.length() - 1)); + } // newBasket := {} newBasket.empty(); @@ -193,30 +196,28 @@ library BasketLibP1 { for (uint256 i = 0; i < config.erc20s.length; ++i) { // Find collateral's targetName index uint256 targetIndex; + IERC20 _erc20 = config.erc20s[i]; // gas-saver for (targetIndex = 0; targetIndex < targetsLength; ++targetIndex) { - if (targetNames.at(targetIndex) == config.targetNames[config.erc20s[i]]) break; + if (targetNames.at(targetIndex) == config.targetNames[_erc20]) break; } assert(targetIndex < targetsLength); // now, targetNames[targetIndex] == config.targetNames[erc20] // Set basket weights for good, prime collateral, // and accumulate the values of goodWeights and targetWeights - uint192 targetWeight = config.targetAmts[config.erc20s[i]]; + uint192 targetWeight = config.targetAmts[_erc20]; totalWeights[targetIndex] = totalWeights[targetIndex].plus(targetWeight); if ( - goodCollateral( - config.targetNames[config.erc20s[i]], - config.erc20s[i], - assetRegistry - ) && targetWeight.gt(FIX_ZERO) + goodCollateral(config.targetNames[_erc20], _erc20, assetRegistry) && + targetWeight.gt(FIX_ZERO) ) { goodWeights[targetIndex] = goodWeights[targetIndex].plus(targetWeight); newBasket.add( - config.erc20s[i], + _erc20, targetWeight.div( // this div is safe: targetPerRef() > 0: goodCollateral check - assetRegistry.toColl(config.erc20s[i]).targetPerRef(), + assetRegistry.toColl(_erc20).targetPerRef(), CEIL ) ); @@ -237,16 +238,17 @@ library BasketLibP1 { // backup basket for tgt to make up that weight: for (uint256 i = 0; i < targetsLength; ++i) { if (totalWeights[i].lte(goodWeights[i])) continue; // Don't need any backup weight + bytes32 _targetName = targetNames.at(i); // "tgt" = targetNames[i] // Now, unsoundPrimeWt(tgt) > 0 uint256 size = 0; // backup basket size - BackupConfig storage backup = config.backups[targetNames.at(i)]; + BackupConfig storage backup = config.backups[_targetName]; // Find the backup basket size: min(backup.max, # of good backup collateral) for (uint256 j = 0; j < backup.erc20s.length && size < backup.max; ++j) { - if (goodCollateral(targetNames.at(i), backup.erc20s[j], assetRegistry)) size++; + if (goodCollateral(_targetName, backup.erc20s[j], assetRegistry)) size++; } // Now, size = len(backups(tgt)). If empty, fail. @@ -257,17 +259,16 @@ library BasketLibP1 { // Loop: for erc20 in backups(tgt)... for (uint256 j = 0; j < backup.erc20s.length && assigned < size; ++j) { - if (goodCollateral(targetNames.at(i), backup.erc20s[j], assetRegistry)) { + if (goodCollateral(_targetName, backup.erc20s[j], assetRegistry)) { + uint192 backupWeight = totalWeights[i].minus(goodWeights[i]).div( + // this div is safe: targetPerRef > 0: goodCollateral check + assetRegistry.toColl(backup.erc20s[j]).targetPerRef().mulu(size), + CEIL + ); + // Across this .add(), targetWeight(newBasket',erc20) // = targetWeight(newBasket,erc20) + unsoundPrimeWt(tgt) / len(backups(tgt)) - newBasket.add( - backup.erc20s[j], - totalWeights[i].minus(goodWeights[i]).div( - // this div is safe: targetPerRef > 0: goodCollateral check - assetRegistry.toColl(backup.erc20s[j]).targetPerRef().mulu(size), - CEIL - ) - ); + BasketLibP1.add(newBasket, backup.erc20s[j], backupWeight); assigned++; } } diff --git a/contracts/plugins/assets/aave/StaticATokenLM.sol b/contracts/plugins/assets/aave/StaticATokenLM.sol index b24a3c14b5..f6637e1369 100644 --- a/contracts/plugins/assets/aave/StaticATokenLM.sol +++ b/contracts/plugins/assets/aave/StaticATokenLM.sol @@ -39,7 +39,7 @@ contract StaticATokenLM is using RayMathNoRounding for uint256; /// Emitted whenever a reward token balance is claimed - event RewardsClaimed(IERC20 indexed erc20, uint256 indexed amount); + event RewardsClaimed(IERC20 indexed erc20, uint256 amount); bytes public constant EIP712_REVISION = bytes("1"); bytes32 internal constant EIP712_DOMAIN = diff --git a/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol b/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol index aa83bacdf8..fb6b12065a 100644 --- a/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol +++ b/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol @@ -101,7 +101,7 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { emit OwnershipTransferred(address(0), owner); (address _lptoken, address _token, , address _rewards, , ) = IBooster(convexBooster) - .poolInfo(_poolId); + .poolInfo(_poolId); curveToken = _lptoken; convexToken = _token; convexPool = _rewards; @@ -275,10 +275,8 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { } } else { reward.claimable_reward[_accounts[u]] = reward - .claimable_reward[_accounts[u]] - .add( - _balances[u].mul(uint256(reward.reward_integral).sub(userI)).div(1e20) - ); + .claimable_reward[_accounts[u]] + .add(_balances[u].mul(uint256(reward.reward_integral).sub(userI)).div(1e20)); } reward.reward_integral_for[_accounts[u]] = reward.reward_integral; } @@ -375,8 +373,8 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { } uint256 newlyClaimable = _getDepositedBalance(_account) - .mul(I.sub(reward.reward_integral_for[_account])) - .div(1e20); + .mul(I.sub(reward.reward_integral_for[_account])) + .div(1e20); claimable[i].amount = claimable[i].amount.add( reward.claimable_reward[_account].add(newlyClaimable) ); @@ -395,8 +393,8 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { ); } newlyClaimable = _getDepositedBalance(_account) - .mul(I.sub(reward.reward_integral_for[_account])) - .div(1e20); + .mul(I.sub(reward.reward_integral_for[_account])) + .div(1e20); claimable[CVX_INDEX].amount = CvxMining.ConvertCrvToCvx(newlyClaimable); claimable[CVX_INDEX].token = cvx; } diff --git a/contracts/plugins/mocks/EasyAuction.sol b/contracts/plugins/mocks/EasyAuction.sol index ef52004075..8acf1a9ec9 100644 --- a/contracts/plugins/mocks/EasyAuction.sol +++ b/contracts/plugins/mocks/EasyAuction.sol @@ -336,8 +336,8 @@ contract EasyAuction is Ownable { atStageSolutionSubmission(auctionId) { (, , uint96 auctioneerSellAmount) = auctionData[auctionId] - .initialAuctionOrder - .decodeOrder(); + .initialAuctionOrder + .decodeOrder(); uint256 sumBidAmount = auctionData[auctionId].interimSumBidAmount; bytes32 iterOrder = auctionData[auctionId].interimOrder; @@ -438,7 +438,7 @@ contract EasyAuction is Ownable { // Auction fully filled via partial match of currentOrder uint256 sellAmountClearingOrder = sellAmountOfIter.sub(uncoveredBids); auctionData[auctionId].volumeClearingPriceOrder = sellAmountClearingOrder - .toUint96(); + .toUint96(); currentBidSum = currentBidSum.sub(uncoveredBids); clearingOrder = currentOrder; } else { @@ -474,9 +474,9 @@ contract EasyAuction is Ownable { ); fillVolumeOfAuctioneerOrder = currentBidSum - .mul(fullAuctionedAmount) - .div(minAuctionedBuyAmount) - .toUint96(); + .mul(fullAuctionedAmount) + .div(minAuctionedBuyAmount) + .toUint96(); } } auctionData[auctionId].clearingPriceOrder = clearingOrder; @@ -517,8 +517,8 @@ contract EasyAuction is Ownable { } AuctionData memory auction = auctionData[auctionId]; (, uint96 priceNumerator, uint96 priceDenominator) = auction - .clearingPriceOrder - .decodeOrder(); + .clearingPriceOrder + .decodeOrder(); (uint64 userId, , ) = orders[0].decodeOrder(); bool minFundingThresholdNotReached = auctionData[auctionId].minFundingThresholdNotReached; for (uint256 i = 0; i < orders.length; i++) { @@ -568,8 +568,8 @@ contract EasyAuction is Ownable { } else { //[11] (, uint96 priceNumerator, uint96 priceDenominator) = auctionData[auctionId] - .clearingPriceOrder - .decodeOrder(); + .clearingPriceOrder + .decodeOrder(); uint256 unsettledAuctionTokens = fullAuctionedAmount.sub(fillVolumeOfAuctioneerOrder); uint256 auctioningTokenAmount = unsettledAuctionTokens.add( feeAmount.mul(unsettledAuctionTokens).div(fullAuctionedAmount) diff --git a/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol b/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol index 3ce9386c9e..aca54d543c 100644 --- a/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol +++ b/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol @@ -18,10 +18,9 @@ contract InvalidRefPerTokCollateralMock is AppreciatingFiatCollateral { // solhint-disable no-empty-blocks - constructor( - CollateralConfig memory config, - uint192 revenueHiding - ) AppreciatingFiatCollateral(config, revenueHiding) {} + constructor(CollateralConfig memory config, uint192 revenueHiding) + AppreciatingFiatCollateral(config, revenueHiding) + {} // solhint-enable no-empty-blocks diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index b6373b32a6..002af6b057 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -165,9 +165,14 @@ contract DutchTrade is ITrade { require(sellAmount_ <= sell.balanceOf(address(this)), "unfunded trade"); sellAmount = shiftl_toFix(sellAmount_, -int8(sell.decimals())); // {sellTok} - startBlock = block.number + 1; // start in the next block - endBlock = startBlock + auctionLength / ONE_BLOCK; // FLOOR, since endBlock is inclusive - endTime = uint48(block.timestamp + ONE_BLOCK * (endBlock - startBlock)); + + uint256 _startBlock = block.number + 1; // start in the next block + startBlock = _startBlock; // gas-saver + + uint256 _endBlock = _startBlock + auctionLength / ONE_BLOCK; // FLOOR; endBlock is inclusive + endBlock = _endBlock; // gas-saver + + endTime = uint48(block.timestamp + ONE_BLOCK * (_endBlock - _startBlock)); // {1} uint192 slippage = _slippage( @@ -177,9 +182,11 @@ contract DutchTrade is ITrade { ); // {buyTok/sellTok} = {UoA/sellTok} * {1} / {UoA/buyTok} - worstPrice = prices.sellLow.mulDiv(FIX_ONE - slippage, prices.buyHigh, FLOOR); - bestPrice = prices.sellHigh.div(prices.buyLow, CEIL); // no additional slippage - assert(worstPrice <= bestPrice); + uint192 _worstPrice = prices.sellLow.mulDiv(FIX_ONE - slippage, prices.buyHigh, FLOOR); + uint192 _bestPrice = prices.sellHigh.div(prices.buyLow, CEIL); // no additional slippage + assert(_worstPrice <= _bestPrice); + worstPrice = _worstPrice; // gas-saver + bestPrice = _bestPrice; // gas-saver } /// Bid for the auction lot at the current price; settling atomically via a callback @@ -196,7 +203,7 @@ contract DutchTrade is ITrade { // Transfer in buy tokens bidder = msg.sender; - buy.safeTransferFrom(bidder, address(this), amountIn); + buy.safeTransferFrom(msg.sender, address(this), amountIn); // status must begin OPEN assert(status == TradeStatus.OPEN); @@ -281,8 +288,10 @@ contract DutchTrade is ITrade { /// @param blockNumber {block} The block number to get price for /// @return {buyTok/sellTok} function _price(uint256 blockNumber) private view returns (uint192) { - require(blockNumber >= startBlock, "auction not started"); - require(blockNumber <= endBlock, "auction over"); + uint256 _startBlock = startBlock; // gas savings + uint256 _endBlock = endBlock; // gas savings + require(blockNumber >= _startBlock, "auction not started"); + require(blockNumber <= _endBlock, "auction over"); /// Price Curve: /// - first 20%: geometrically decrease the price from 1000x the bestPrice to 1.5x it @@ -290,7 +299,7 @@ contract DutchTrade is ITrade { /// - next 50%: linearly decrease the price from bestPrice to worstPrice /// - last 5%: constant at worstPrice - uint192 progression = divuu(blockNumber - startBlock, endBlock - startBlock); // {1} + uint192 progression = divuu(blockNumber - _startBlock, _endBlock - _startBlock); // {1} // Fast geometric decay -- 0%-20% of auction if (progression < TWENTY_PERCENT) { @@ -305,19 +314,21 @@ contract DutchTrade is ITrade { // First linear decay -- 20%-45% of auction // 1.5x -> 1x the bestPrice + uint192 _bestPrice = bestPrice; // gas savings // {buyTok/sellTok} = {buyTok/sellTok} * {1} - uint192 highPrice = bestPrice.mul(ONE_POINT_FIVE, CEIL); + uint192 highPrice = _bestPrice.mul(ONE_POINT_FIVE, CEIL); return highPrice - - (highPrice - bestPrice).mulDiv(progression - TWENTY_PERCENT, TWENTY_FIVE_PERCENT); + (highPrice - _bestPrice).mulDiv(progression - TWENTY_PERCENT, TWENTY_FIVE_PERCENT); } else if (progression < NINETY_FIVE_PERCENT) { // Second linear decay -- 45%-95% of auction // bestPrice -> worstPrice + uint192 _bestPrice = bestPrice; // gas savings // {buyTok/sellTok} = {buyTok/sellTok} * {1} return - bestPrice - - (bestPrice - worstPrice).mulDiv(progression - FORTY_FIVE_PERCENT, FIFTY_PERCENT); + _bestPrice - + (_bestPrice - worstPrice).mulDiv(progression - FORTY_FIVE_PERCENT, FIFTY_PERCENT); } // Constant price -- 95%-100% of auction diff --git a/package.json b/package.json index 79032da344..976df23cc0 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "test:gas": "yarn test:gas:protocol && yarn test:gas:collateral && yarn test:gas:integration", "test:gas:protocol": "REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/{libraries,plugins,scenario}/*.test.ts test/*.test.ts", "test:gas:collateral": "FORK=1 REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/plugins/individual-collateral/**/*.test.ts", - "test:gas:integration": "FORK=1 REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/integration/**/*.test.ts", "test:coverage": "PROTO_IMPL=1 hardhat coverage --testfiles 'test/{libraries,plugins,scenario}/*.test.ts test/*.test.ts'", "test:unit:coverage": "PROTO_IMPL=1 SLOW= hardhat coverage --testfiles 'test/*.test.ts test/libraries/*.test.ts test/plugins/*.test.ts'", "eslint": "eslint test/", diff --git a/scripts/addresses/3-tmp-assets-collateral.json b/scripts/addresses/3-tmp-assets-collateral.json index ff5d1dac64..a462576f94 100644 --- a/scripts/addresses/3-tmp-assets-collateral.json +++ b/scripts/addresses/3-tmp-assets-collateral.json @@ -82,4 +82,4 @@ "sDAI": "0x83f20f44975d03b1b09e64809b757c47f942beea", "cbETH": "0xBe9895146f7AF43049ca1c1AE358B0541Ea49704" } -} \ No newline at end of file +} diff --git a/scripts/addresses/3-tmp-deployments.json b/scripts/addresses/3-tmp-deployments.json index 92393dde70..e53597cd9f 100644 --- a/scripts/addresses/3-tmp-deployments.json +++ b/scripts/addresses/3-tmp-deployments.json @@ -32,4 +32,4 @@ "stRSR": "0x5a5eb5d26871e26645bD6d006671ec0887aeca69" } } -} \ No newline at end of file +} diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts index 6bc493a48c..429eac9944 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts @@ -43,7 +43,9 @@ async function main() { /******** Morpho token vaults **************************/ console.log(`Deploying morpho token vaults to network ${hre.network.name} (${chainId}) with burner account: ${deployer.address}`) - const MorphoTokenisedDepositFactory = await ethers.getContractFactory("MorphoAaveV2TokenisedDeposit") + const MorphoTokenisedDepositFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDeposit' + ) const maUSDT = await MorphoTokenisedDepositFactory.deploy({ morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, @@ -113,14 +115,10 @@ async function main() { assetCollDeployments.erc20s.maStETH = maStETH.address /******** Morpho collateral **************************/ - const FiatCollateralFactory = await hre.ethers.getContractFactory( - "MorphoFiatCollateral" - ) - const NonFiatCollateralFactory = await hre.ethers.getContractFactory( - "MorphoNonFiatCollateral" - ) + const FiatCollateralFactory = await hre.ethers.getContractFactory('MorphoFiatCollateral') + const NonFiatCollateralFactory = await hre.ethers.getContractFactory('MorphoNonFiatCollateral') const SelfReferentialFactory = await hre.ethers.getContractFactory( - "MorphoSelfReferentialCollateral" + 'MorphoSelfReferentialCollateral' ) const stablesOracleError = fp('0.0025') // 0.25% @@ -129,41 +127,44 @@ async function main() { oracleError: stablesOracleError.toString(), maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 1 hr - targetName: ethers.utils.formatBytes32String("USD"), - defaultThreshold: stablesOracleError.add(fp("0.01")), // 1.25% + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: stablesOracleError.add(fp('0.01')), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h } { - const collateral = await FiatCollateralFactory.connect(deployer).deploy({ - ...baseStableConfig, - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDT!, - erc20: maUSDT.address, - }, + const collateral = await FiatCollateralFactory.connect(deployer).deploy( + { + ...baseStableConfig, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDT!, + erc20: maUSDT.address, + }, revenueHiding - ); + ) assetCollDeployments.collateral.maUSDT = collateral.address deployedCollateral.push(collateral.address.toString()) } { - const collateral = await FiatCollateralFactory.connect(deployer).deploy({ - ...baseStableConfig, - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, - erc20: maUSDC.address, - }, + const collateral = await FiatCollateralFactory.connect(deployer).deploy( + { + ...baseStableConfig, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, + erc20: maUSDC.address, + }, revenueHiding - ); + ) assetCollDeployments.collateral.maUSDC = collateral.address deployedCollateral.push(collateral.address.toString()) } { - const collateral = await FiatCollateralFactory.connect(deployer).deploy({ - ...baseStableConfig, - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI!, - erc20: maDAI.address, - }, + const collateral = await FiatCollateralFactory.connect(deployer).deploy( + { + ...baseStableConfig, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI!, + erc20: maDAI.address, + }, revenueHiding - ); + ) assetCollDeployments.collateral.maDAI = collateral.address deployedCollateral.push(collateral.address.toString()) } @@ -172,39 +173,41 @@ async function main() { const wbtcOracleError = fp('0.02') // 2% const btcOracleError = fp('0.005') // 0.5% const combinedBTCWBTCError = combinedError(wbtcOracleError, btcOracleError) - const collateral = await NonFiatCollateralFactory.connect(deployer).deploy({ - priceTimeout: priceTimeout, - oracleError: combinedBTCWBTCError, - maxTradeVolume: fp('1e6'), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr - targetName: ethers.utils.formatBytes32String("BTC"), - defaultThreshold: fp('0.01').add(combinedBTCWBTCError), // ~3.5% - delayUntilDefault: bn('86400'), // 24h - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.WBTC!, - erc20: maWBTC.address, - }, + const collateral = await NonFiatCollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout, + oracleError: combinedBTCWBTCError, + maxTradeVolume: fp('1e6'), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + targetName: ethers.utils.formatBytes32String('BTC'), + defaultThreshold: fp('0.01').add(combinedBTCWBTCError), // ~3.5% + delayUntilDefault: bn('86400'), // 24h + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.WBTC!, + erc20: maWBTC.address, + }, revenueHiding, networkConfig[chainId].chainlinkFeeds.wBTCBTC!, - oracleTimeout(chainId, '86400').toString(), // 1 hr - ); + oracleTimeout(chainId, '86400').toString() // 1 hr + ) assetCollDeployments.collateral.maWBTC = collateral.address deployedCollateral.push(collateral.address.toString()) } { - const collateral = await SelfReferentialFactory.connect(deployer).deploy({ - priceTimeout: priceTimeout, - oracleError: fp('0.005'), - maxTradeVolume: fp('1e6'), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr - targetName: ethers.utils.formatBytes32String("ETH"), - defaultThreshold: fp('0.05'), // 5% - delayUntilDefault: bn('86400'), // 24h - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, - erc20: maWBTC.address, - }, - revenueHiding, - ); + const collateral = await SelfReferentialFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout, + oracleError: fp('0.005'), + maxTradeVolume: fp('1e6'), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + targetName: ethers.utils.formatBytes32String('ETH'), + defaultThreshold: fp('0.05'), // 5% + delayUntilDefault: bn('86400'), // 24h + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, + erc20: maWBTC.address, + }, + revenueHiding + ) assetCollDeployments.collateral.maWETH = collateral.address deployedCollateral.push(collateral.address.toString()) } @@ -218,21 +221,22 @@ async function main() { // TAR: ETH // REF: stETH // TOK: maETH - const collateral = await NonFiatCollateralFactory.connect(deployer).deploy({ - priceTimeout: priceTimeout, - oracleError: combinedOracleErrors, - maxTradeVolume: fp('1e6'), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr - targetName: ethers.utils.formatBytes32String("ETH"), - defaultThreshold: fp('0.01').add(combinedOracleErrors), // ~1.5% - delayUntilDefault: bn('86400'), // 24h - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, - erc20: maStETH.address, - }, + const collateral = await NonFiatCollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout, + oracleError: combinedOracleErrors, + maxTradeVolume: fp('1e6'), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + targetName: ethers.utils.formatBytes32String('ETH'), + defaultThreshold: fp('0.01').add(combinedOracleErrors), // ~1.5% + delayUntilDefault: bn('86400'), // 24h + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, + erc20: maStETH.address, + }, revenueHiding, networkConfig[chainId].chainlinkFeeds.stETHETH!, // {target/ref} - oracleTimeout(chainId, '86400').toString(), // 1 hr - ); + oracleTimeout(chainId, '86400').toString() // 1 hr + ) assetCollDeployments.collateral.maWBTC = collateral.address deployedCollateral.push(collateral.address.toString()) } diff --git a/tasks/testing/upgrade-checker-utils/trades.ts b/tasks/testing/upgrade-checker-utils/trades.ts index 4db79d0220..17082bcabc 100644 --- a/tasks/testing/upgrade-checker-utils/trades.ts +++ b/tasks/testing/upgrade-checker-utils/trades.ts @@ -117,4 +117,4 @@ const mintAToken = async ( await underlying.connect(usdtSigner).approve(collateral.address, amount) await collateral.connect(usdtSigner).deposit(recipient, amount, 0, true) }) -} \ No newline at end of file +} diff --git a/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts b/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts index 6b36c96ec1..c5398e5744 100644 --- a/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts +++ b/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts @@ -127,12 +127,13 @@ export default async ( let parsedLog: LogDescription | undefined try { parsedLog = iface.parseLog(event) - } catch { } + } catch {} if (parsedLog && parsedLog.name == 'TradeStarted') { console.log( `\n====== Trade Started: sell ${logToken(parsedLog.args.sell)} / buy ${logToken( parsedLog.args.buy - )} ======\n\tmbuyAmount: ${parsedLog.args.minBuyAmount}\n\tsellAmount: ${parsedLog.args.sellAmount + )} ======\n\tmbuyAmount: ${parsedLog.args.minBuyAmount}\n\tsellAmount: ${ + parsedLog.args.sellAmount }` ) // @@ -187,7 +188,6 @@ export default async ( console.log('Trying again...') } } - }) const lastTimestamp = await getLatestBlockTimestamp(hre) diff --git a/test/__snapshots__/Broker.test.ts.snap b/test/__snapshots__/Broker.test.ts.snap index 9c02711719..889471b056 100644 --- a/test/__snapshots__/Broker.test.ts.snap +++ b/test/__snapshots__/Broker.test.ts.snap @@ -1,12 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `259737`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `259526`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `368979`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `368768`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `371094`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `370883`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `373232`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `373021`; exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63333`; diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index cfc4b24738..857bff5b7e 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8367047`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8355533`; -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464633`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464235`; exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Without governance 1`] = `114895`; diff --git a/test/__snapshots__/RToken.test.ts.snap b/test/__snapshots__/RToken.test.ts.snap index 0ffc6842b6..eb047d1e78 100644 --- a/test/__snapshots__/RToken.test.ts.snap +++ b/test/__snapshots__/RToken.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `791756`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `791646`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `618760`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `618650`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `594756`; +exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `593423`; exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56658`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 585e4bef18..f7e606d149 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1390986`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1390775`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1517200`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1516167`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `732257`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `744609`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1685173`; @@ -14,6 +14,6 @@ exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1704814`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1704288`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `202908`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index c5ac463af2..3b77878520 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -1,27 +1,27 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 1`] = `165190`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 1`] = `164974`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `165243`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `165027`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `165243`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `165027`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `208840`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `208624`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229593`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229377`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212493`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212277`; -exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1034629`; +exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1034418`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `777373`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1188577`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `309815`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `311446`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `264881`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `266512`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `743173`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `240675`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `242306`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index 629cb7687c..263e512679 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -1,8 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`StRSRP1 contract Gas Reporting Stake 1`] = `139830`; +exports[`StRSRP1 contract Gas Reporting Stake 1`] = `139717`; -exports[`StRSRP1 contract Gas Reporting Stake 2`] = `135030`; +exports[`StRSRP1 contract Gas Reporting Stake 2`] = `134917`; exports[`StRSRP1 contract Gas Reporting Transfer 1`] = `63409`; diff --git a/test/plugins/__snapshots__/RewardableERC20.test.ts.snap b/test/plugins/__snapshots__/RewardableERC20.test.ts.snap index 3f3231f06b..195b28a702 100644 --- a/test/plugins/__snapshots__/RewardableERC20.test.ts.snap +++ b/test/plugins/__snapshots__/RewardableERC20.test.ts.snap @@ -1,8 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Gas Reporting RewardableERC20WrapperTest claimRewards 1`] = `174149`; +exports[`Gas Reporting RewardableERC20WrapperTest claimRewards 1`] = `174078`; -exports[`Gas Reporting RewardableERC20WrapperTest claimRewards 2`] = `105749`; +exports[`Gas Reporting RewardableERC20WrapperTest claimRewards 2`] = `105678`; exports[`Gas Reporting RewardableERC20WrapperTest deposit 1`] = `119303`; @@ -12,9 +12,9 @@ exports[`Gas Reporting RewardableERC20WrapperTest withdraw 1`] = `96090`; exports[`Gas Reporting RewardableERC20WrapperTest withdraw 2`] = `64590`; -exports[`Gas Reporting RewardableERC4626VaultTest claimRewards 1`] = `174118`; +exports[`Gas Reporting RewardableERC4626VaultTest claimRewards 1`] = `174047`; -exports[`Gas Reporting RewardableERC4626VaultTest claimRewards 2`] = `105718`; +exports[`Gas Reporting RewardableERC4626VaultTest claimRewards 2`] = `105647`; exports[`Gas Reporting RewardableERC4626VaultTest deposit 1`] = `121596`; diff --git a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap index 1976652900..62146c7325 100644 --- a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap @@ -20,6 +20,6 @@ exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() durin exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `93269`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `128267`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `128341`; exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `92399`; diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 653af6dc07..00891b0076 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -497,6 +497,8 @@ export default function fn( describe('collateral-specific tests', collateralSpecificStatusTests) describeGas('Gas Reporting', () => { + if (IMPLEMENTATION != Implementation.P1 || !useEnv('REPORT_GAS')) return // hide pending + context('refresh()', () => { afterEach(async () => { await snapshotGasCost(collateral.refresh()) diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index 55af313bd8..14aff54151 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -643,6 +643,8 @@ export default function fn( }) describeGas('Gas Reporting', () => { + if (IMPLEMENTATION != Implementation.P1 || !useEnv('REPORT_GAS')) return // hide pending + context('refresh()', () => { afterEach(async () => { await snapshotGasCost(ctx.collateral.refresh()) diff --git a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts index a6070353d4..7ee5c7dc01 100644 --- a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts @@ -212,6 +212,7 @@ const opts = { resetFork, collateralName: 'SDaiCollateral', chainlinkDefaultAnswer, + itIsPricedByPeg: true, } collateralTests(opts) diff --git a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap index ed9963c4ca..f2f98569c7 100644 --- a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap @@ -4,9 +4,9 @@ exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refr exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116663`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `140108`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `141925`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `140108`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `97011`; exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118331`; @@ -20,17 +20,17 @@ exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refr exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `140108`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `142058`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `142132`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140390`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140464`; exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118523`; exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116855`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `140364`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `142181`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `140294`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `97203`; exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118523`; @@ -44,17 +44,17 @@ exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting ref exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `140364`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `142314`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `142388`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140720`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140646`; exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `126813`; exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `125145`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `149076`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `150963`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `149146`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `105493`; exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `126813`; @@ -68,7 +68,7 @@ exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting ref exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `149146`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `151096`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `151026`; exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `149428`; @@ -76,9 +76,9 @@ exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting ref exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `119793`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `143510`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `145257`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `143440`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `100141`; exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `121461`; diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index d0562e5cc1..3da2c08a10 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12085685`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12085575`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9838290`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9836953`; -exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2293006`; +exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2281990`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13276964`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13002663`; exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20658478`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `11092473`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `11092363`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8827990`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8826653`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4557434`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4481356`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `13292502`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `13292858`; From 11aa80b8235689484c84b452c7e1145fcef988d7 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 18 Aug 2023 14:12:11 -0400 Subject: [PATCH 373/462] deployment: overwrite addresses when working with tenderly --- scripts/deployment/phase1-common/0_setup_deployments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/deployment/phase1-common/0_setup_deployments.ts b/scripts/deployment/phase1-common/0_setup_deployments.ts index 2cd8266bbe..d9c71ff769 100644 --- a/scripts/deployment/phase1-common/0_setup_deployments.ts +++ b/scripts/deployment/phase1-common/0_setup_deployments.ts @@ -17,7 +17,7 @@ async function main() { // Check if deployment file already exists for this chainId const deploymentFilename = getDeploymentFilename(chainId) - if (chainId != '31337' && fileExists(deploymentFilename)) { + if (chainId != '31337' && chainId != '3' && fileExists(deploymentFilename)) { throw new Error(`${deploymentFilename} exists; I won't overwrite it.`) } From bc8a1772a08c3a96ad1ea8b1581d074a47327e5f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 21 Aug 2023 09:02:38 -0400 Subject: [PATCH 374/462] do not stop refreshing prices for DISABLED collateral (#902) --- contracts/plugins/assets/AppreciatingFiatCollateral.sol | 6 ------ contracts/plugins/assets/FiatCollateral.sol | 5 ----- contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol | 6 ------ contracts/plugins/assets/curve/CurveStableCollateral.sol | 6 ------ .../plugins/assets/stargate/StargatePoolFiatCollateral.sol | 2 -- contracts/plugins/mocks/BadCollateralPlugin.sol | 6 ------ 6 files changed, 31 deletions(-) diff --git a/contracts/plugins/assets/AppreciatingFiatCollateral.sol b/contracts/plugins/assets/AppreciatingFiatCollateral.sol index 3722b5644d..bf7cef6022 100644 --- a/contracts/plugins/assets/AppreciatingFiatCollateral.sol +++ b/contracts/plugins/assets/AppreciatingFiatCollateral.sol @@ -75,12 +75,6 @@ abstract contract AppreciatingFiatCollateral is FiatCollateral { /// Refresh exchange rates and update default status. /// @dev Should not need to override: can handle collateral with variable refPerTok() function refresh() public virtual override { - if (alreadyDefaulted()) { - // continue to update rates - exposedReferencePrice = _underlyingRefPerTok().mul(revenueShowing); - return; - } - CollateralStatus oldStatus = status(); // Check for hard default diff --git a/contracts/plugins/assets/FiatCollateral.sol b/contracts/plugins/assets/FiatCollateral.sol index d4d1412818..9110117bc5 100644 --- a/contracts/plugins/assets/FiatCollateral.sol +++ b/contracts/plugins/assets/FiatCollateral.sol @@ -120,7 +120,6 @@ contract FiatCollateral is ICollateral, Asset { /// Refresh exchange rates and update default status. /// @dev May need to override: limited to handling collateral with refPerTok() = 1 function refresh() public virtual override(Asset, IAsset) { - if (alreadyDefaulted()) return; CollateralStatus oldStatus = status(); // Check for soft default + save lotPrice @@ -191,10 +190,6 @@ contract FiatCollateral is ICollateral, Asset { } } - function alreadyDefaulted() internal view returns (bool) { - return _whenDefault <= block.timestamp; - } - function whenDefault() external view returns (uint256) { return _whenDefault; } diff --git a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol index a4d9e8f625..b9b1e8b233 100644 --- a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol +++ b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol @@ -61,12 +61,6 @@ contract CTokenV3Collateral is AppreciatingFiatCollateral { function refresh() public virtual override { ICusdcV3Wrapper(address(erc20)).accrue(); - if (alreadyDefaulted()) { - // continue to update rates - exposedReferencePrice = _underlyingRefPerTok().mul(revenueShowing); - return; - } - CollateralStatus oldStatus = status(); // Check for hard default diff --git a/contracts/plugins/assets/curve/CurveStableCollateral.sol b/contracts/plugins/assets/curve/CurveStableCollateral.sol index 4a2b0d35ac..22c336cee0 100644 --- a/contracts/plugins/assets/curve/CurveStableCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableCollateral.sol @@ -72,12 +72,6 @@ contract CurveStableCollateral is AppreciatingFiatCollateral, PoolTokens { /// Refresh exchange rates and update default status. /// Have to override to add custom default checks function refresh() public virtual override { - if (alreadyDefaulted()) { - // continue to update rates - exposedReferencePrice = _underlyingRefPerTok().mul(revenueShowing); - return; - } - CollateralStatus oldStatus = status(); // Check for hard default diff --git a/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol b/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol index 54a7a203fa..8ac8c13e66 100644 --- a/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol +++ b/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol @@ -58,8 +58,6 @@ contract StargatePoolFiatCollateral is FiatCollateral { /// Refresh exchange rates and update default status. /// @dev Should not need to override: can handle collateral with variable refPerTok() function refresh() public virtual override { - if (alreadyDefaulted()) return; - CollateralStatus oldStatus = status(); // Check for hard default diff --git a/contracts/plugins/mocks/BadCollateralPlugin.sol b/contracts/plugins/mocks/BadCollateralPlugin.sol index 2af22a1291..5001542aaf 100644 --- a/contracts/plugins/mocks/BadCollateralPlugin.sol +++ b/contracts/plugins/mocks/BadCollateralPlugin.sol @@ -26,12 +26,6 @@ contract BadCollateralPlugin is ATokenFiatCollateral { /// Refresh exchange rates and update default status. /// @dev Should be general enough to not need to be overridden function refresh() public virtual override { - if (alreadyDefaulted()) { - // continue to update rates - exposedReferencePrice = _underlyingRefPerTok().mul(revenueShowing); - return; - } - CollateralStatus oldStatus = status(); // Check for hard default From 54dde6dc2955e6c6dade1aef7137121474044a78 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 21 Aug 2023 10:11:41 -0400 Subject: [PATCH 375/462] update gas snapshots (#904) --- package.json | 2 +- test/__snapshots__/FacadeWrite.test.ts.snap | 2 +- test/__snapshots__/Main.test.ts.snap | 2 +- test/__snapshots__/RToken.test.ts.snap | 6 +- .../Recollateralization.test.ts.snap | 12 +-- test/__snapshots__/Revenues.test.ts.snap | 4 +- test/__snapshots__/ZZStRSR.test.ts.snap | 4 +- .../__snapshots__/Collateral.test.ts.snap | 18 ++-- .../ATokenFiatCollateral.test.ts.snap | 24 ++--- .../AnkrEthCollateralTestSuite.test.ts.snap | 16 ++-- .../CBETHCollateral.test.ts.snap | 16 ++-- .../CTokenFiatCollateral.test.ts.snap | 24 ++--- .../__snapshots__/CometTestSuite.test.ts.snap | 24 ++--- .../CrvStableMetapoolSuite.test.ts.snap | 24 ++--- ...StableRTokenMetapoolTestSuite.test.ts.snap | 24 ++--- .../CrvStableTestSuite.test.ts.snap | 24 ++--- .../CrvVolatileTestSuite.test.ts.snap | 24 ++--- .../CvxStableMetapoolSuite.test.ts.snap | 24 ++--- ...StableRTokenMetapoolTestSuite.test.ts.snap | 24 ++--- .../CvxStableTestSuite.test.ts.snap | 24 ++--- .../CvxVolatileTestSuite.test.ts.snap | 24 ++--- .../SDaiCollateralTestSuite.test.ts.snap | 24 ++--- .../FTokenFiatCollateral.test.ts.snap | 96 +++++++++---------- .../SFrxEthTestSuite.test.ts.snap | 12 +-- .../LidoStakedEthTestSuite.test.ts.snap | 24 ++--- .../MorphoAAVEFiatCollateral.test.ts.snap | 72 +++++++------- .../MorphoAAVENonFiatCollateral.test.ts.snap | 48 +++++----- ...AAVESelfReferentialCollateral.test.ts.snap | 16 ++-- .../RethCollateralTestSuite.test.ts.snap | 16 ++-- .../StargateETHTestSuite.test.ts.snap | 48 +++++----- .../__snapshots__/MaxBasketSize.test.ts.snap | 16 ++-- 31 files changed, 359 insertions(+), 359 deletions(-) diff --git a/package.json b/package.json index 976df23cc0..1d6413e41f 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "test:plugins:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/plugins/individual-collateral/**/*.test.ts", "test:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/integration/**/*.test.ts", "test:scenario": "PROTO_IMPL=1 hardhat test test/scenario/*.test.ts", - "test:gas": "yarn test:gas:protocol && yarn test:gas:collateral && yarn test:gas:integration", + "test:gas": "yarn test:gas:protocol && yarn test:gas:collateral", "test:gas:protocol": "REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/{libraries,plugins,scenario}/*.test.ts test/*.test.ts", "test:gas:collateral": "FORK=1 REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/plugins/individual-collateral/**/*.test.ts", "test:coverage": "PROTO_IMPL=1 hardhat coverage --testfiles 'test/{libraries,plugins,scenario}/*.test.ts test/*.test.ts'", diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index 857bff5b7e..1b0751af8d 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8355533`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8354959`; exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464235`; diff --git a/test/__snapshots__/Main.test.ts.snap b/test/__snapshots__/Main.test.ts.snap index 40f226bf7b..94907cfd67 100644 --- a/test/__snapshots__/Main.test.ts.snap +++ b/test/__snapshots__/Main.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `361898`; +exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `361362`; exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `196758`; diff --git a/test/__snapshots__/RToken.test.ts.snap b/test/__snapshots__/RToken.test.ts.snap index eb047d1e78..e8e5328dd7 100644 --- a/test/__snapshots__/RToken.test.ts.snap +++ b/test/__snapshots__/RToken.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `791646`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `791110`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `618650`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `618114`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `593423`; +exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `592887`; exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56658`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index f7e606d149..d47fb3da07 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1390775`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1407849`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1516167`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1533241`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `744609`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `748681`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1685173`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1702113`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1616603`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1633187`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1704288`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1721584`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `202908`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index 3b77878520..3f423f56a7 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -12,11 +12,11 @@ exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229377`; exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212277`; -exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1034418`; +exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1033882`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `777373`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1188577`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1188041`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `311446`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index 263e512679..8604200d7a 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -14,6 +14,6 @@ exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `222609`; exports[`StRSRP1 contract Gas Reporting Unstake 2`] = `139758`; -exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `576204`; +exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `575668`; -exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `530208`; +exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `529672`; diff --git a/test/plugins/__snapshots__/Collateral.test.ts.snap b/test/plugins/__snapshots__/Collateral.test.ts.snap index 9c98a9ffd2..cedffac69d 100644 --- a/test/plugins/__snapshots__/Collateral.test.ts.snap +++ b/test/plugins/__snapshots__/Collateral.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral contracts Gas Reporting refresh() after hard default 1`] = `72844`; +exports[`Collateral contracts Gas Reporting refresh() after hard default 1`] = `72710`; -exports[`Collateral contracts Gas Reporting refresh() after hard default 2`] = `73980`; +exports[`Collateral contracts Gas Reporting refresh() after hard default 2`] = `73846`; -exports[`Collateral contracts Gas Reporting refresh() during + after soft default 1`] = `62534`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 1`] = `62400`; -exports[`Collateral contracts Gas Reporting refresh() during + after soft default 2`] = `55266`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 2`] = `55132`; -exports[`Collateral contracts Gas Reporting refresh() during + after soft default 3`] = `54984`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 3`] = `54850`; -exports[`Collateral contracts Gas Reporting refresh() during + after soft default 4`] = `23429`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 4`] = `54791`; -exports[`Collateral contracts Gas Reporting refresh() during + after soft default 5`] = `54984`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 5`] = `54850`; -exports[`Collateral contracts Gas Reporting refresh() during SOUND 1`] = `54984`; +exports[`Collateral contracts Gas Reporting refresh() during SOUND 1`] = `54850`; -exports[`Collateral contracts Gas Reporting refresh() during SOUND 2`] = `54984`; +exports[`Collateral contracts Gas Reporting refresh() during SOUND 2`] = `54850`; diff --git a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap index 62146c7325..f02da1ba1a 100644 --- a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `75367`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `75233`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `73699`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `73565`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `73922`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `73788`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `30918`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `66106`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `75367`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `75233`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `73699`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `73565`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `51728`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `91924`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `51728`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `91998`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `93195`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `93061`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `93269`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `93135`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `128341`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `128207`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `92399`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `92265`; diff --git a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap index efb00072cb..6faac2e04c 100644 --- a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61342`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61208`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `56873`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `56739`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `79535`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `79401`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `37508`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `71718`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61342`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61208`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `56873`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `56739`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `71849`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `71715`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `71849`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `71715`; diff --git a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap index 67c84c6cc5..f086c155bb 100644 --- a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `60830`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `60696`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `56361`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `56227`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `98131`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `97997`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `36971`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `90314`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `81946`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `81812`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77477`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77343`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `90445`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `90311`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `90445`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `90311`; diff --git a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap index 00de61ac71..f872809ebb 100644 --- a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `120330`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `120196`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `118662`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `118528`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `75058`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `74924`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `31842`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `67242`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `120330`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `120196`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `118662`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `118528`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `96669`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `139517`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `96669`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `139517`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `140788`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `140654`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `140788`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `140654`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `175860`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `175726`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `139992`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `139858`; diff --git a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap index c760e4bbb0..4f0b595a2d 100644 --- a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `110087`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `109932`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `105350`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `105195`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `135455`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `135300`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `70661`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `127617`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `110087`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `109932`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `105350`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `105195`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `73461`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `107922`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `70661`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `104854`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `127769`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `127614`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `127769`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `127614`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `135320`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `135165`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `128051`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `127896`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap index 6aaba60bdb..9593a4f3bf 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `82121`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `81987`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `77653`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `77519`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `259238`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `259104`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `254222`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `82121`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `81987`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77653`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77519`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `77178`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `77178`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `254353`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `254219`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `254353`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `254219`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `261931`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `261797`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `254663`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `254529`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap index 2b5d33eea0..7d386b05ce 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `99018`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `98884`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `94550`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `94416`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `232924`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `232790`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `227908`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `104078`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `103944`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `99610`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `99476`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `99135`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `99135`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `228039`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `227905`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `228039`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `227905`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `213786`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `213652`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `206518`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `206384`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap index ffe45ebe78..8381cbd1f2 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62898`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62764`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `58430`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `58296`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `205141`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `205007`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `200125`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62898`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62764`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58430`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58296`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57955`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57955`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `200256`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `200122`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `200256`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `200122`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `185611`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `185477`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `178343`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `178209`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap index cf655d93a7..99c585e1e6 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `65346`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `65212`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `60878`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `60744`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `241371`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `241237`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `236355`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `65346`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `65212`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `60878`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `60744`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `60403`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `60403`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `236486`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `236352`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `236486`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `236352`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `242740`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `242606`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `235472`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `235338`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap index 6a295e365d..0959931c42 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `82121`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `81987`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `77653`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `77519`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `259238`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `259104`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `254222`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `82121`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `81987`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77653`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77519`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `77178`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `77178`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `254353`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `254219`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `254353`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `254219`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `261931`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `261797`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `254663`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `254529`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap index 49b74a184d..1225a8c886 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `99018`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `98884`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `94550`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `94416`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `232924`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `232790`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `227908`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `104078`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `103944`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `99610`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `99476`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `99135`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `99135`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `228039`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `227905`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `228039`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `227905`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `213786`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `213652`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `206518`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `206384`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap index 53d7c649fe..130ef52bef 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62898`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62764`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `58430`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `58296`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `205141`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `205007`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `200125`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62898`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62764`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58430`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58296`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57955`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57955`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `200256`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `200122`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `200256`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `200122`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `185611`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `185477`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `178343`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `178209`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap index 562706cdba..3c1e73b07a 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `65346`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `65212`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `60878`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `60744`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `241371`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `241237`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `236355`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `65346`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `65212`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `60878`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `60744`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `60403`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `60403`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `236486`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `236352`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `236486`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `236352`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `242740`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `242606`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `235472`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `235338`; diff --git a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap index 0d82e1ea60..9202afd768 100644 --- a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117757`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117728`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `109445`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `109311`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `132246`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `132112`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `90061`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `124152`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117576`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117442`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `109445`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `109311`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `93388`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `112297`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `90061`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `108970`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `124283`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `124149`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `124283`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `124149`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `132111`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `131977`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `124565`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `124431`; diff --git a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap index f2f98569c7..d91ce93686 100644 --- a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap @@ -1,97 +1,97 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118331`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118197`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116663`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116529`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `141925`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `141791`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `97011`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139977`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118331`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118197`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `116663`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `116529`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `97011`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `116188`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `97011`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `116188`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `140108`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139974`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `140108`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139974`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `142132`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141998`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140464`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140330`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118523`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118389`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116855`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116721`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `142181`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `142047`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `97203`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `140233`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118523`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118389`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `116855`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `116721`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `97203`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `116380`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `97203`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `116380`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `140364`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `140230`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `140364`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `140230`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `142388`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `142254`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140646`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140512`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `126813`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `126679`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `125145`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `125011`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `150963`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `150829`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `105493`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `149015`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `126813`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `126679`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `125145`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `125011`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `105493`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `124670`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `105493`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `124670`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `149146`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `149012`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `149146`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `149012`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `151026`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `150892`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `149428`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `149294`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `121461`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `121327`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `119793`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `119659`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `145257`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `145123`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `100141`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `143379`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `121461`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `121327`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `119793`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `119659`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `100141`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `119318`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `100141`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `119318`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `143510`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `143376`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `143440`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `143306`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `145460`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `145326`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `143792`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `143658`; diff --git a/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap index 40c07ca09a..61665d768b 100644 --- a/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59998`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59864`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55261`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55127`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `60698`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `60564`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `59029`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58895`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `74794`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `74660`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `74794`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `74660`; diff --git a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap index 9764e5e036..9514e3f06b 100644 --- a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `89047`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `88913`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `84578`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `84444`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `134690`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `134556`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `65149`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `126873`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `89047`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `88913`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `84578`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `84444`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `65149`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `84103`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `65149`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `84103`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `127004`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `126870`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `127004`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `126870`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `131755`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `131621`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `127286`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `127152`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap index 637d4b1476..f3460e5383 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap @@ -1,73 +1,73 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `135022`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134888`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 2`] = `130553`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 2`] = `130419`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 1`] = `180391`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 1`] = `180257`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 2`] = `111194`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 2`] = `172574`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `135022`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134888`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `130553`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `130419`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 1`] = `111194`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 1`] = `130078`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 2`] = `111194`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 2`] = `130078`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `172705`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `172571`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 2`] = `172705`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 2`] = `172571`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 1`] = `180256`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 1`] = `180122`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 2`] = `172987`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 2`] = `172853`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `135225`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `135091`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `130756`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `130622`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `180797`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `180663`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `111397`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `172980`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `135225`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `135091`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `130756`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `130622`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `111397`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `130281`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `111397`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `130281`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `173111`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `172977`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `173111`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `172977`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `180662`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `180528`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `173393`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `173259`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134378`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134244`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129909`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129775`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 1`] = `179103`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 1`] = `178969`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 2`] = `110550`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 2`] = `171286`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134378`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134244`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129909`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129775`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 1`] = `110550`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 1`] = `129434`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 2`] = `110550`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 2`] = `129434`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `171417`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `171283`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 2`] = `171417`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 2`] = `171283`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 1`] = `178968`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 1`] = `178834`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 2`] = `171699`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 2`] = `171565`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap index fd7e828651..75cc7c2e6a 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap @@ -1,49 +1,49 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134445`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134311`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129976`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129842`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 1`] = `200486`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 1`] = `200352`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 2`] = `110550`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 2`] = `192669`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `184309`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `184175`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `179840`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `179706`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 1`] = `110550`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 1`] = `179365`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 2`] = `110550`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 2`] = `179365`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `192800`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `192666`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 2`] = `192800`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 2`] = `192666`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 1`] = `197551`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 1`] = `197417`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 2`] = `193082`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 2`] = `192948`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `168077`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `167943`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `163608`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `163474`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 1`] = `239750`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 1`] = `239616`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 2`] = `144182`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 2`] = `231933`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `223573`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `223439`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `219104`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `218970`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 1`] = `144182`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 1`] = `218629`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 2`] = `144182`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 2`] = `218629`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `232064`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `231930`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `232064`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `231930`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 1`] = `236815`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 1`] = `236681`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 2`] = `232346`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 2`] = `232212`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap index a8faf3cbe4..30864ba004 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `202160`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `202026`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `197691`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `197557`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 1`] = `218342`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 1`] = `218208`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 2`] = `144160`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 2`] = `210525`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `202160`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `202026`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `197691`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `197557`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `210656`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `210522`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `210656`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `210522`; diff --git a/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap index 35bb6dd12a..66168f0739 100644 --- a/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `71915`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `71781`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `67446`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `67312`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 1`] = `109216`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 1`] = `109082`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 2`] = `48056`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 2`] = `101399`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `71915`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `71781`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `67446`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `67312`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `101530`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `101396`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `101530`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `101396`; diff --git a/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap b/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap index a11f838460..eba13d6f0e 100644 --- a/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap @@ -1,49 +1,49 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `56267`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `56133`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `51799`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `51665`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `69964`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `69830`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `23429`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `67216`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `56267`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `56133`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `51799`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `51665`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `23429`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `51324`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `23429`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `51324`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `67225`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `67091`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `67225`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `67091`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `74775`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `74641`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `67507`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `67373`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `56267`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `56133`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `51799`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `51665`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `69964`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `69830`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `23429`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `67216`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `56267`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `56133`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `51799`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `51665`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `23429`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `51324`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `23429`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `51324`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `67225`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `67091`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `67225`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `67091`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `74775`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `74641`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `67507`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `67373`; diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index 3da2c08a10..f7ca06c407 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12085575`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12071639`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9836953`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9810015`; exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2281990`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13002663`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13596421`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20658478`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `21212846`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `11092363`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `11078427`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8826653`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8799715`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4481356`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `6630749`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `13292858`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `15560638`; From 5d551f4baee4fe01fa5c1d646590ef698e67f5b9 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 21 Aug 2023 11:55:54 -0400 Subject: [PATCH 376/462] Single price (#900) --- CHANGELOG.md | 22 ++ contracts/facade/FacadeAct.sol | 6 +- contracts/interfaces/IAsset.sol | 15 +- contracts/interfaces/IBasketHandler.sol | 7 +- contracts/interfaces/IBroker.sol | 2 +- contracts/p0/BasketHandler.sol | 21 +- contracts/p0/Broker.sol | 11 + contracts/p0/RevenueTrader.sol | 4 +- contracts/p0/mixins/TradingLib.sol | 53 +++-- contracts/p1/BasketHandler.sol | 22 +- contracts/p1/Broker.sol | 27 ++- contracts/p1/RevenueTrader.sol | 4 +- .../p1/mixins/RecollateralizationLib.sol | 79 ++++---- contracts/p1/mixins/Trading.sol | 2 +- contracts/plugins/assets/Asset.sol | 55 +++-- contracts/plugins/assets/FiatCollateral.sol | 2 +- contracts/plugins/assets/RTokenAsset.sol | 25 +-- docs/collateral.md | 24 +-- docs/deployment.md | 2 +- docs/recollateralization.md | 16 +- scripts/confirmation/1_confirm_assets.ts | 39 ++-- test/Broker.test.ts | 21 +- test/Facade.test.ts | 19 +- test/Main.test.ts | 48 +---- test/Recollateralization.test.ts | 11 +- test/Revenues.test.ts | 6 +- test/__snapshots__/Broker.test.ts.snap | 6 +- test/__snapshots__/FacadeWrite.test.ts.snap | 2 +- test/__snapshots__/Main.test.ts.snap | 8 +- test/__snapshots__/RToken.test.ts.snap | 6 +- .../Recollateralization.test.ts.snap | 12 +- test/__snapshots__/Revenues.test.ts.snap | 8 +- test/__snapshots__/ZZStRSR.test.ts.snap | 8 +- test/integration/AssetPlugins.test.ts | 18 +- test/plugins/Asset.test.ts | 188 +++++++++--------- test/plugins/Collateral.test.ts | 88 +++++--- .../__snapshots__/Collateral.test.ts.snap | 8 +- .../aave/ATokenFiatCollateral.test.ts | 23 ++- .../ATokenFiatCollateral.test.ts.snap | 24 +-- .../AnkrEthCollateralTestSuite.test.ts.snap | 16 +- .../CBETHCollateral.test.ts.snap | 16 +- .../individual-collateral/collateralTests.ts | 61 +++--- .../compoundv2/CTokenFiatCollateral.test.ts | 23 ++- .../CTokenFiatCollateral.test.ts.snap | 24 +-- .../__snapshots__/CometTestSuite.test.ts.snap | 24 +-- .../curve/collateralTests.ts | 43 ++-- .../CrvStableMetapoolSuite.test.ts.snap | 24 +-- ...StableRTokenMetapoolTestSuite.test.ts.snap | 24 +-- .../CrvStableTestSuite.test.ts.snap | 24 +-- .../CrvVolatileTestSuite.test.ts.snap | 24 +-- .../CvxStableMetapoolSuite.test.ts.snap | 24 +-- ...StableRTokenMetapoolTestSuite.test.ts.snap | 24 +-- .../CvxStableTestSuite.test.ts.snap | 24 +-- .../CvxVolatileTestSuite.test.ts.snap | 24 +-- .../SDaiCollateralTestSuite.test.ts.snap | 24 +-- .../FTokenFiatCollateral.test.ts.snap | 96 ++++----- .../SFrxEthTestSuite.test.ts.snap | 12 +- .../LidoStakedEthTestSuite.test.ts.snap | 24 +-- .../MorphoAAVEFiatCollateral.test.ts.snap | 72 +++---- .../MorphoAAVENonFiatCollateral.test.ts.snap | 48 ++--- ...AAVESelfReferentialCollateral.test.ts.snap | 16 +- .../RethCollateralTestSuite.test.ts.snap | 16 +- test/scenario/ComplexBasket.test.ts | 8 +- .../__snapshots__/MaxBasketSize.test.ts.snap | 14 +- 64 files changed, 834 insertions(+), 837 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 163413e8af..2c799ae6f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +# 3.1.0 - Unreleased + +### Upgrade Steps -- Required + +Upgrade `BackingManager`, `Broker`, and _all_ assets + +Then call `Broker.cacheComponents()`. + +### Core Protocol Contracts + +- `BackingManager` + - Replace use of `lotPrice()` with `price()` +- `Broker` [+1 slot] + - Disallow starting dutch trades with non-RTokenAsset assets when `lastSave() != block.timestamp` + +## Plugins + +### Assets + +- Remove `lotPrice()` +- Alter `price().high` to decay upwards to 3x over the price timeout + # 3.0.0 - Unreleased ### Upgrade Steps diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 7c6d952a8c..823edd2546 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -113,11 +113,11 @@ contract FacadeAct is IFacadeAct, Multicall { } surpluses[i] = erc20s[i].balanceOf(address(revenueTrader)); - (uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/tok} - if (lotLow == 0) continue; + (uint192 low, ) = reg.assets[i].price(); // {UoA/tok} + if (low == 0) continue; // {qTok} = {UoA} / {UoA/tok} - minTradeAmounts[i] = minTradeVolume.safeDiv(lotLow, FLOOR).shiftl_toUint( + minTradeAmounts[i] = minTradeVolume.safeDiv(low, FLOOR).shiftl_toUint( int8(reg.assets[i].erc20Decimals()) ); diff --git a/contracts/interfaces/IAsset.sol b/contracts/interfaces/IAsset.sol index bd796190a7..b5423ab2b5 100644 --- a/contracts/interfaces/IAsset.sol +++ b/contracts/interfaces/IAsset.sol @@ -27,16 +27,11 @@ interface IAsset is IRewardable { function refresh() external; /// Should not revert + /// low should be nonzero if the asset could be worth selling /// @return low {UoA/tok} The lower end of the price estimate /// @return high {UoA/tok} The upper end of the price estimate function price() external view returns (uint192 low, uint192 high); - /// Should not revert - /// lotLow should be nonzero when the asset might be worth selling - /// @return lotLow {UoA/tok} The lower end of the lot price estimate - /// @return lotHigh {UoA/tok} The upper end of the lot price estimate - function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh); - /// @return {tok} The balance of the ERC20 in whole tokens function bal(address account) external view returns (uint192); @@ -67,8 +62,14 @@ interface TestIAsset is IAsset { /// @return {s} Seconds that an oracle value is considered valid function oracleTimeout() external view returns (uint48); - /// @return {s} Seconds that the lotPrice should decay over, after stale price + /// @return {s} Seconds that the price().low should decay over, after stale price function priceTimeout() external view returns (uint48); + + /// @return {UoA/tok} The last saved low price + function savedLowPrice() external view returns (uint192); + + /// @return {UoA/tok} The last saved high price + function savedHighPrice() external view returns (uint192); } /// CollateralStatus must obey a linear ordering. That is: diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index 42bb8bf092..e43cf6735f 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -133,16 +133,11 @@ interface IBasketHandler is IComponent { function basketsHeldBy(address account) external view returns (BasketRange memory); /// Should not revert + /// low should be nonzero when BUs are worth selling /// @return low {UoA/BU} The lower end of the price estimate /// @return high {UoA/BU} The upper end of the price estimate function price() external view returns (uint192 low, uint192 high); - /// Should not revert - /// lotLow should be nonzero if a BU could be worth selling - /// @return lotLow {UoA/tok} The lower end of the lot price estimate - /// @return lotHigh {UoA/tok} The upper end of the lot price estimate - function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh); - /// @return timestamp The timestamp at which the basket was last set function timestamp() external view returns (uint48); diff --git a/contracts/interfaces/IBroker.sol b/contracts/interfaces/IBroker.sol index 0c83eb9216..cecaf8d7b0 100644 --- a/contracts/interfaces/IBroker.sol +++ b/contracts/interfaces/IBroker.sol @@ -11,7 +11,7 @@ enum TradeKind { BATCH_AUCTION } -/// Cache of all (lot) prices for a pair to prevent re-lookup +/// Cache of all prices for a pair to prevent re-lookup struct TradePrices { uint192 sellLow; // {UoA/sellTok} can be 0 uint192 sellHigh; // {UoA/sellTok} should not be 0 diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 357b0a7251..7cec1d292d 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -368,26 +368,11 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } /// Should not revert + /// low should be nonzero when the asset might be worth selling /// @return low {UoA/BU} The lower end of the price estimate /// @return high {UoA/BU} The upper end of the price estimate // returns sum(quantity(erc20) * price(erc20) for erc20 in basket.erc20s) function price() external view returns (uint192 low, uint192 high) { - return _price(false); - } - - /// Should not revert - /// lowLow should be nonzero when the asset might be worth selling - /// @return lotLow {UoA/BU} The lower end of the lot price estimate - /// @return lotHigh {UoA/BU} The upper end of the lot price estimate - // returns sum(quantity(erc20) * lotPrice(erc20) for erc20 in basket.erc20s) - function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh) { - return _price(true); - } - - /// Returns the price of a BU, using the lot prices if `useLotPrice` is true - /// @return low {UoA/BU} The lower end of the lot price estimate - /// @return high {UoA/BU} The upper end of the lot price estimate - function _price(bool useLotPrice) internal view returns (uint192 low, uint192 high) { IAssetRegistry reg = main.assetRegistry(); uint256 low256; @@ -397,9 +382,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { uint192 qty = quantity(basket.erc20s[i]); if (qty == 0) continue; - (uint192 lowP, uint192 highP) = useLotPrice - ? reg.toAsset(basket.erc20s[i]).lotPrice() - : reg.toAsset(basket.erc20s[i]).price(); + (uint192 lowP, uint192 highP) = reg.toAsset(basket.erc20s[i]).price(); low256 += qty.safeMul(lowP, RoundingMode.FLOOR); diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 41584907d2..51619620ed 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -224,6 +224,11 @@ contract BrokerP0 is ComponentP0, IBroker { "dutch auctions disabled for token pair" ); require(dutchAuctionLength > 0, "dutch auctions not enabled"); + require( + priceIsCurrent(req.sell) && priceIsCurrent(req.buy), + "dutch auctions require live prices" + ); + DutchTrade trade = DutchTrade(Clones.clone(address(dutchTradeImplementation))); trades[address(trade)] = true; @@ -248,4 +253,10 @@ contract BrokerP0 is ComponentP0, IBroker { emit DutchTradeDisabledSet(erc20, dutchTradeDisabled[erc20], disabled); dutchTradeDisabled[erc20] = disabled; } + + /// @return true if the price is current, or it's the RTokenAsset + function priceIsCurrent(IAsset asset) private view returns (bool) { + return + asset.lastSave() == block.timestamp || address(asset.erc20()) == address(main.rToken()); + } } diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index 2f380927fe..1d47a5e494 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -83,7 +83,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { main.assetRegistry().refresh(); IAsset assetToBuy = main.assetRegistry().toAsset(tokenToBuy); - (uint192 buyLow, uint192 buyHigh) = assetToBuy.lotPrice(); // {UoA/tok} + (uint192 buyLow, uint192 buyHigh) = assetToBuy.price(); // {UoA/tok} require(buyHigh > 0 && buyHigh < FIX_MAX, "buy asset price unknown"); // For each ERC20: start auction of given kind @@ -99,7 +99,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { require(address(trades[erc20]) == address(0), "trade open"); require(erc20.balanceOf(address(this)) > 0, "0 balance"); - (uint192 sellLow, uint192 sellHigh) = assetToSell.lotPrice(); // {UoA/tok} + (uint192 sellLow, uint192 sellHigh) = assetToSell.price(); // {UoA/tok} TradingLibP0.TradeInfo memory trade = TradingLibP0.TradeInfo({ sell: assetToSell, diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index a71df6c027..d8638e915f 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -274,7 +274,7 @@ library TradingLibP0 { view returns (BasketRange memory range) { - (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.lotPrice(); // {UoA/BU} + (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.price(); // {UoA/BU} // Cap ctx.basketsHeld.top if (ctx.basketsHeld.top > ctx.rToken.basketsNeeded()) { @@ -303,21 +303,14 @@ library TradingLibP0 { bal = bal.plus(asset.bal(address(ctx.stRSR))); } - { - // Skip over dust-balance assets not in the basket - (uint192 lotLow, ) = asset.lotPrice(); // {UoA/tok} - - // Intentionally include value of IFFY/DISABLED collateral - if ( - ctx.bh.quantity(erc20s[i]) == 0 && - !isEnoughToSell(asset, bal, lotLow, ctx.minTradeVolume) - ) continue; - } - (uint192 low, uint192 high) = asset.price(); // {UoA/tok} - // price() is better than lotPrice() here: it's important to not underestimate how - // much value could be in a token that is unpriced by using a decaying high lotPrice. - // price() will return [0, FIX_MAX] in this case, which is preferable. + + // Skip over dust-balance assets not in the basket + // Intentionally include value of IFFY/DISABLED collateral + if ( + ctx.bh.quantity(erc20s[i]) == 0 && + !isEnoughToSell(asset, bal, low, ctx.minTradeVolume) + ) continue; // throughout these sections +/- is same as Fix.plus/Fix.minus and is Fix.gt/.lt @@ -354,7 +347,7 @@ library TradingLibP0 { // (2) Lose minTradeVolume to dust (why: auctions can return tokens) // Q: Why is this precisely where we should take out minTradeVolume? - // A: Our use of isEnoughToSell always uses the low price (lotLow, technically), + // A: Our use of isEnoughToSell always uses the low price (low, technically), // so min trade volumes are always assesed based on low prices. At this point // in the calculation we have already calculated the UoA amount corresponding to // the excess token balance based on its low price, so we are already set up @@ -453,19 +446,19 @@ library TradingLibP0 { // {tok} = {BU} * {tok/BU} uint192 needed = range.top.mul(ctx.bh.quantity(erc20s[i]), CEIL); // {tok} if (bal.gt(needed)) { - (uint192 lotLow, uint192 lotHigh) = asset.lotPrice(); // {UoA/sellTok} - if (lotHigh == 0) continue; // Skip worthless assets + (uint192 low, uint192 high) = asset.price(); // {UoA/sellTok} + if (high == 0) continue; // Skip worthless assets // by calculating this early we can duck the stack limit but be less gas-efficient bool enoughToSell = isEnoughToSell( asset, bal.minus(needed), - lotLow, + low, ctx.minTradeVolume ); // {UoA} = {sellTok} * {UoA/sellTok} - uint192 delta = bal.minus(needed).mul(lotLow, FLOOR); + uint192 delta = bal.minus(needed).mul(low, FLOOR); // status = asset.status() if asset.isCollateral() else SOUND CollateralStatus status; // starts SOUND @@ -476,8 +469,8 @@ library TradingLibP0 { if (isBetterSurplus(maxes, status, delta) && enoughToSell) { trade.sell = asset; trade.sellAmount = bal.minus(needed); - trade.prices.sellLow = lotLow; - trade.prices.sellHigh = lotHigh; + trade.prices.sellLow = low; + trade.prices.sellHigh = high; maxes.surplusStatus = status; maxes.surplus = delta; @@ -487,17 +480,17 @@ library TradingLibP0 { needed = range.bottom.mul(ctx.bh.quantity(erc20s[i]), CEIL); // {buyTok}; if (bal.lt(needed)) { uint192 amtShort = needed.minus(bal); // {buyTok} - (uint192 lotLow, uint192 lotHigh) = asset.lotPrice(); // {UoA/buyTok} + (uint192 low, uint192 high) = asset.price(); // {UoA/buyTok} // {UoA} = {buyTok} * {UoA/buyTok} - uint192 delta = amtShort.mul(lotHigh, CEIL); + uint192 delta = amtShort.mul(high, CEIL); // The best asset to buy is whichever asset has the largest deficit if (delta.gt(maxes.deficit)) { trade.buy = ICollateral(address(asset)); trade.buyAmount = amtShort; - trade.prices.buyLow = lotLow; - trade.prices.buyHigh = lotHigh; + trade.prices.buyLow = low; + trade.prices.buyHigh = high; maxes.deficit = delta; } @@ -512,13 +505,13 @@ library TradingLibP0 { uint192 rsrAvailable = rsrAsset.bal(address(ctx.bm)).plus( rsrAsset.bal(address(ctx.stRSR)) ); - (uint192 lotLow, uint192 lotHigh) = rsrAsset.lotPrice(); // {UoA/RSR} + (uint192 low, uint192 high) = rsrAsset.price(); // {UoA/RSR} - if (lotHigh > 0 && isEnoughToSell(rsrAsset, rsrAvailable, lotLow, ctx.minTradeVolume)) { + if (high > 0 && isEnoughToSell(rsrAsset, rsrAvailable, low, ctx.minTradeVolume)) { trade.sell = rsrAsset; trade.sellAmount = rsrAvailable; - trade.prices.sellLow = lotLow; - trade.prices.sellHigh = lotHigh; + trade.prices.sellLow = low; + trade.prices.sellHigh = high; } } } diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 387055fc63..03bce5b6c1 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -309,27 +309,11 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } /// Should not revert + /// low should be nonzero when BUs are worth selling /// @return low {UoA/BU} The lower end of the price estimate /// @return high {UoA/BU} The upper end of the price estimate // returns sum(quantity(erc20) * price(erc20) for erc20 in basket.erc20s) function price() external view returns (uint192 low, uint192 high) { - return _price(false); - } - - /// Should not revert - /// lowLow should be nonzero when the asset might be worth selling - /// @return lotLow {UoA/BU} The lower end of the lot price estimate - /// @return lotHigh {UoA/BU} The upper end of the lot price estimate - // returns sum(quantity(erc20) * lotPrice(erc20) for erc20 in basket.erc20s) - function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh) { - return _price(true); - } - - /// Returns the price of a BU, using the lot prices if `useLotPrice` is true - /// @param useLotPrice Whether to use lotPrice() or price() - /// @return low {UoA/BU} The lower end of the price estimate - /// @return high {UoA/BU} The upper end of the price estimate - function _price(bool useLotPrice) internal view returns (uint192 low, uint192 high) { uint256 low256; uint256 high256; @@ -338,9 +322,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { uint192 qty = quantity(basket.erc20s[i]); if (qty == 0) continue; - (uint192 lowP, uint192 highP) = useLotPrice - ? assetRegistry.toAsset(basket.erc20s[i]).lotPrice() - : assetRegistry.toAsset(basket.erc20s[i]).price(); + (uint192 lowP, uint192 highP) = assetRegistry.toAsset(basket.erc20s[i]).price(); low256 += qty.safeMul(lowP, RoundingMode.FLOOR); diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 98e52120e4..2cf4600878 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -62,6 +62,10 @@ contract BrokerP1 is ComponentP1, IBroker { // Whether Dutch Auctions are currently disabled, per ERC20 mapping(IERC20Metadata => bool) public dutchTradeDisabled; + // === 3.1.0 === + + IRToken private rToken; + // ==== Invariant ==== // (trades[addr] == true) iff this contract has created an ITrade clone at addr @@ -80,10 +84,7 @@ contract BrokerP1 is ComponentP1, IBroker { uint48 dutchAuctionLength_ ) external initializer { __Component_init(main_); - - backingManager = main_.backingManager(); - rsrTrader = main_.rsrTrader(); - rTokenTrader = main_.rTokenTrader(); + cacheComponents(); setGnosis(gnosis_); setBatchTradeImplementation(batchTradeImplementation_); @@ -92,6 +93,14 @@ contract BrokerP1 is ComponentP1, IBroker { setDutchAuctionLength(dutchAuctionLength_); } + /// Call after upgrade to >= 3.1.0 + function cacheComponents() public { + backingManager = main.backingManager(); + rsrTrader = main.rsrTrader(); + rTokenTrader = main.rTokenTrader(); + rToken = main.rToken(); + } + /// Handle a trade request by deploying a customized disposable trading contract /// @param kind TradeKind.DUTCH_AUCTION or TradeKind.BATCH_AUCTION /// @dev Requires setting an allowance in advance @@ -252,6 +261,11 @@ contract BrokerP1 is ComponentP1, IBroker { "dutch auctions disabled for token pair" ); require(dutchAuctionLength > 0, "dutch auctions not enabled"); + require( + priceIsCurrent(req.sell) && priceIsCurrent(req.buy), + "dutch auctions require live prices" + ); + DutchTrade trade = DutchTrade(address(dutchTradeImplementation).clone()); trades[address(trade)] = true; @@ -266,6 +280,11 @@ contract BrokerP1 is ComponentP1, IBroker { return trade; } + /// @return true if the price is current, or it's the RTokenAsset + function priceIsCurrent(IAsset asset) private view returns (bool) { + return asset.lastSave() == block.timestamp || address(asset.erc20()) == address(rToken); + } + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index fe7b409b50..1065fb96e7 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -135,7 +135,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { } // Cache and validate buyHigh - (uint192 buyLow, uint192 buyHigh) = assetToBuy.lotPrice(); // {UoA/tok} + (uint192 buyLow, uint192 buyHigh) = assetToBuy.price(); // {UoA/tok} require(buyHigh > 0 && buyHigh < FIX_MAX, "buy asset price unknown"); // For each ERC20 that isn't the tokenToBuy, start an auction of the given kind @@ -147,7 +147,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { require(erc20.balanceOf(address(this)) > 0, "0 balance"); IAsset assetToSell = assetRegistry.toAsset(erc20); - (uint192 sellLow, uint192 sellHigh) = assetToSell.lotPrice(); // {UoA/tok} + (uint192 sellLow, uint192 sellHigh) = assetToSell.price(); // {UoA/tok} TradeInfo memory trade = TradeInfo({ sell: assetToSell, diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index dd86e45ca1..c6eff7ff4f 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -11,7 +11,7 @@ import "./TradeLib.sol"; /// Struct purposes: /// 1. Configure trading /// 2. Stay under stack limit with fewer vars -/// 3. Cache information such as component addresses to save on gas +/// 3. Cache information such as component addresses and basket quantities, to save on gas struct TradingContext { BasketRange basketsHeld; // {BU} // basketsHeld.top is the number of partial baskets units held @@ -80,7 +80,7 @@ library RecollateralizationLibP1 { ctx.minTradeVolume = bm.minTradeVolume(); ctx.maxTradeSlippage = bm.maxTradeSlippage(); - // Calculate quantities + // Cache quantities Registry memory reg = ctx.ar.getRegistry(); ctx.quantities = new uint192[](reg.erc20s.length); for (uint256 i = 0; i < reg.erc20s.length; ++i) { @@ -90,6 +90,7 @@ library RecollateralizationLibP1 { // ============================ // Compute a target basket range for trading - {BU} + // The basket range is the full range of projected outcomes for the rebalancing process BasketRange memory range = basketRange(ctx, reg); // Select a pair to trade next, if one exists @@ -129,7 +130,8 @@ library RecollateralizationLibP1 { // Compute the target basket range // Algorithm intuition: Trade conservatively. Quantify uncertainty based on the proportion of // token balances requiring trading vs not requiring trading. Seek to decrease uncertainty - // the largest amount possible with each trade. + // the largest amount possible with each trade. As long as trades clear within the expected + // range of prices, the basket range should narrow with each iteration (under constant prices) // // How do we know this algorithm converges? // Assumption: constant oracle prices; monotonically increasing refPerTok() @@ -141,12 +143,12 @@ library RecollateralizationLibP1 { // run-to-run, but will never increase it // // Preconditions: - // - ctx is correctly populated, with current basketsHeld.bottom + basketsHeld.top - // - reg contains erc20 + asset + quantities arrays in same order and without duplicates + // - ctx is correctly populated, with current basketsHeld + quantities + // - reg contains erc20 + asset arrays in same order and without duplicates // Trading Strategy: // - We will not aim to hold more than rToken.basketsNeeded() BUs - // - No double trades: if we buy B in one trade, we won't sell B in another trade - // Caveat: Unless the asset we're selling is IFFY/DISABLED + // - No double trades: capital converted from token A to token B should not go to token C + // unless the clearing price was outside the expected price range // - The best price we might get for a trade is at the high sell price and low buy price // - The worst price we might get for a trade is at the low sell price and // the high buy price, multiplied by ( 1 - maxTradeSlippage ) @@ -164,7 +166,9 @@ library RecollateralizationLibP1 { view returns (BasketRange memory range) { - (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.lotPrice(); // {UoA/BU} + (uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.price(); // {UoA/BU} + require(buPriceLow > 0 && buPriceHigh < FIX_MAX, "BUs unpriced"); + uint192 basketsNeeded = ctx.rToken.basketsNeeded(); // {BU} // Cap ctx.basketsHeld.top @@ -196,21 +200,17 @@ library RecollateralizationLibP1 { bal = bal.plus(reg.assets[i].bal(address(ctx.stRSR))); } - if (ctx.quantities[i] == 0) { - // Skip over dust-balance assets not in the basket - (uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/tok} + (uint192 low, uint192 high) = reg.assets[i].price(); // {UoA/tok} - // Intentionally include value of IFFY/DISABLED collateral - if (!TradeLib.isEnoughToSell(reg.assets[i], bal, lotLow, ctx.minTradeVolume)) { - continue; - } + // Skip over dust-balance assets not in the basket + // Intentionally include value of IFFY/DISABLED collateral + if ( + ctx.quantities[i] == 0 && + !TradeLib.isEnoughToSell(reg.assets[i], bal, low, ctx.minTradeVolume) + ) { + continue; } - (uint192 low, uint192 high) = reg.assets[i].price(); // {UoA/tok} - // price() is better than lotPrice() here: it's important to not underestimate how - // much value could be in a token that is unpriced by using a decaying high lotPrice. - // price() will return [0, FIX_MAX] in this case, which is preferable. - // throughout these sections +/- is same as Fix.plus/Fix.minus and is Fix.gt/.lt // deltaTop: optimistic case @@ -246,8 +246,8 @@ library RecollateralizationLibP1 { // (2) Lose minTradeVolume to dust (why: auctions can return tokens) // Q: Why is this precisely where we should take out minTradeVolume? - // A: Our use of isEnoughToSell always uses the low price (lotLow, technically), - // so min trade volumes are always assesed based on low prices. At this point + // A: Our use of isEnoughToSell always uses the low price, + // so min trade volumes are always assessed based on low prices. At this point // in the calculation we have already calculated the UoA amount corresponding to // the excess token balance based on its low price, so we are already set up // to straightforwardly deduct the minTradeVolume before trying to buy BUs. @@ -305,9 +305,9 @@ library RecollateralizationLibP1 { /// prices.buyLow {UoA/buyTok} The best-case price of the buy token on secondary markets /// prices.buyHigh {UoA/buyTok} The worst-case price of the buy token on secondary markets /// - // Defining "sell" and "buy": - // If bal(e) > (quantity(e) * range.top), then e is in surplus by the difference - // If bal(e) < (quantity(e) * range.bottom), then e is in deficit by the difference + // For each asset e: + // If bal(e) > (quantity(e) * range.top), then e is in surplus by the difference + // If bal(e) < (quantity(e) * range.bottom), then e is in deficit by the difference // // First, ignoring RSR: // `trade.sell` is the token from erc20s with the greatest surplus value (in UoA), @@ -345,11 +345,11 @@ library RecollateralizationLibP1 { uint192 needed = range.top.mul(ctx.quantities[i], CEIL); // {tok} if (bal.gt(needed)) { - (uint192 lotLow, uint192 lotHigh) = reg.assets[i].lotPrice(); // {UoA/sellTok} - if (lotHigh == 0) continue; // skip over worthless assets + (uint192 low, uint192 high) = reg.assets[i].price(); // {UoA/sellTok} + if (high == 0) continue; // skip over worthless assets // {UoA} = {sellTok} * {UoA/sellTok} - uint192 delta = bal.minus(needed).mul(lotLow, FLOOR); + uint192 delta = bal.minus(needed).mul(low, FLOOR); // status = asset.status() if asset.isCollateral() else SOUND CollateralStatus status; // starts SOUND @@ -364,14 +364,14 @@ library RecollateralizationLibP1 { TradeLib.isEnoughToSell( reg.assets[i], bal.minus(needed), - lotLow, + low, ctx.minTradeVolume ) ) { trade.sell = reg.assets[i]; trade.sellAmount = bal.minus(needed); - trade.prices.sellLow = lotLow; - trade.prices.sellHigh = lotHigh; + trade.prices.sellLow = low; + trade.prices.sellHigh = high; maxes.surplusStatus = status; maxes.surplus = delta; @@ -382,17 +382,17 @@ library RecollateralizationLibP1 { if (bal.lt(needed)) { uint192 amtShort = needed.minus(bal); // {buyTok} - (uint192 lotLow, uint192 lotHigh) = reg.assets[i].lotPrice(); // {UoA/buyTok} + (uint192 low, uint192 high) = reg.assets[i].price(); // {UoA/buyTok} // {UoA} = {buyTok} * {UoA/buyTok} - uint192 delta = amtShort.mul(lotHigh, CEIL); + uint192 delta = amtShort.mul(high, CEIL); // The best asset to buy is whichever asset has the largest deficit if (delta.gt(maxes.deficit)) { trade.buy = reg.assets[i]; trade.buyAmount = amtShort; - trade.prices.buyLow = lotLow; - trade.prices.buyHigh = lotHigh; + trade.prices.buyLow = low; + trade.prices.buyHigh = high; maxes.deficit = delta; } @@ -407,16 +407,15 @@ library RecollateralizationLibP1 { uint192 rsrAvailable = rsrAsset.bal(address(ctx.bm)).plus( rsrAsset.bal(address(ctx.stRSR)) ); - (uint192 lotLow, uint192 lotHigh) = rsrAsset.lotPrice(); // {UoA/RSR} + (uint192 low, uint192 high) = rsrAsset.price(); // {UoA/RSR} if ( - lotHigh > 0 && - TradeLib.isEnoughToSell(rsrAsset, rsrAvailable, lotLow, ctx.minTradeVolume) + high > 0 && TradeLib.isEnoughToSell(rsrAsset, rsrAvailable, low, ctx.minTradeVolume) ) { trade.sell = rsrAsset; trade.sellAmount = rsrAvailable; - trade.prices.sellLow = lotLow; - trade.prices.sellHigh = lotHigh; + trade.prices.sellLow = low; + trade.prices.sellHigh = high; } } } diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 863f7ab113..387bf2d7f2 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -118,7 +118,7 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl TradePrices memory prices ) internal returns (ITrade trade) { IERC20 sell = req.sell.erc20(); - assert(address(trades[sell]) == address(0)); + assert(address(trades[sell]) == address(0)); // ensure calling class has checked this IERC20Upgradeable(address(sell)).safeApprove(address(broker), 0); IERC20Upgradeable(address(sell)).safeApprove(address(broker), req.sellAmount); diff --git a/contracts/plugins/assets/Asset.sol b/contracts/plugins/assets/Asset.sol index ba6908c8a8..a254f390ce 100644 --- a/contracts/plugins/assets/Asset.sol +++ b/contracts/plugins/assets/Asset.sol @@ -11,6 +11,8 @@ contract Asset is IAsset, VersionedAsset { using FixLib for uint192; using OracleLib for AggregatorV3Interface; + uint192 public constant MAX_HIGH_PRICE_BUFFER = 2 * FIX_ONE; // {UoA/tok} 200% + AggregatorV3Interface public immutable chainlinkFeed; // {UoA/tok} IERC20Metadata public immutable erc20; @@ -106,54 +108,45 @@ contract Asset is IAsset, VersionedAsset { } /// Should not revert + /// low should be nonzero if the asset could be worth selling /// @dev Should be general enough to not need to be overridden - /// @return {UoA/tok} The lower end of the price estimate - /// @return {UoA/tok} The upper end of the price estimate - function price() public view virtual returns (uint192, uint192) { - try this.tryPrice() returns (uint192 low, uint192 high, uint192) { - assert(low <= high); - return (low, high); - } catch (bytes memory errData) { - // see: docs/solidity-style.md#Catching-Empty-Data - if (errData.length == 0) revert(); // solhint-disable-line reason-string - return (0, FIX_MAX); - } - } - - /// Should not revert - /// lotLow should be nonzero when the asset might be worth selling - /// @dev Should be general enough to not need to be overridden - /// @return lotLow {UoA/tok} The lower end of the lot price estimate - /// @return lotHigh {UoA/tok} The upper end of the lot price estimate - function lotPrice() external view virtual returns (uint192 lotLow, uint192 lotHigh) { + /// @return _low {UoA/tok} The lower end of the price estimate + /// @return _high {UoA/tok} The upper end of the price estimate + function price() public view virtual returns (uint192 _low, uint192 _high) { try this.tryPrice() returns (uint192 low, uint192 high, uint192) { // if the price feed is still functioning, use that - lotLow = low; - lotHigh = high; + _low = low; + _high = high; } catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string - // if the price feed is broken, use a decayed historical value + // if the price feed is broken, decay _low downwards and _high upwards uint48 delta = uint48(block.timestamp) - lastSave; // {s} if (delta <= oracleTimeout) { - lotLow = savedLowPrice; - lotHigh = savedHighPrice; + // use saved prices for at least the oracleTimeout + _low = savedLowPrice; + _high = savedHighPrice; } else if (delta >= oracleTimeout + priceTimeout) { - return (0, 0); // no price after full timeout + // use unpriced after a full timeout, incase 3x was not enough + return (0, FIX_MAX); } else { // oracleTimeout <= delta <= oracleTimeout + priceTimeout - // {1} = {s} / {s} - uint192 lotMultiplier = divuu(oracleTimeout + priceTimeout - delta, priceTimeout); - + // Decay _low downwards from savedLowPrice to 0 // {UoA/tok} = {UoA/tok} * {1} - lotLow = savedLowPrice.mul(lotMultiplier); - lotHigh = savedHighPrice.mul(lotMultiplier); + _low = savedLowPrice.muluDivu(oracleTimeout + priceTimeout - delta, priceTimeout); + + // Decay _high upwards to 3x savedHighPrice + _high = savedHighPrice.plus( + savedHighPrice.mul( + MAX_HIGH_PRICE_BUFFER.muluDivu(delta - oracleTimeout, priceTimeout) + ) + ); } } - assert(lotLow <= lotHigh); + assert(_low <= _high); } /// @return {tok} The balance of the ERC20 in whole tokens diff --git a/contracts/plugins/assets/FiatCollateral.sol b/contracts/plugins/assets/FiatCollateral.sol index 9110117bc5..ac6e4576bd 100644 --- a/contracts/plugins/assets/FiatCollateral.sol +++ b/contracts/plugins/assets/FiatCollateral.sol @@ -122,7 +122,7 @@ contract FiatCollateral is ICollateral, Asset { function refresh() public virtual override(Asset, IAsset) { CollateralStatus oldStatus = status(); - // Check for soft default + save lotPrice + // Check for soft default + save price try this.tryPrice() returns (uint192 low, uint192 high, uint192 pegPrice) { // {UoA/tok}, {UoA/tok}, {target/ref} // (0, 0) is a valid price; (0, FIX_MAX) is unpriced diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index fd8c78fa24..0f30067a3f 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -73,7 +73,7 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { // solhint-disable no-empty-blocks function refresh() public virtual override { - // No need to save lastPrice; can piggyback off the backing collateral's lotPrice() + // No need to save lastPrice; can piggyback off the backing collateral's saved prices cachedOracleData.cachedAtTime = 0; // force oracle refresh } @@ -93,27 +93,6 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { } } - /// Should not revert - /// lotLow should be nonzero when the asset might be worth selling - /// @return lotLow {UoA/tok} The lower end of the lot price estimate - /// @return lotHigh {UoA/tok} The upper end of the lot price estimate - function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh) { - (uint192 buLow, uint192 buHigh) = basketHandler.lotPrice(); // {UoA/BU} - - // Here we take advantage of the fact that we know RToken has 18 decimals - // to convert between uint256 an uint192. Fits due to assumed max totalSupply. - uint192 supply = _safeWrap(IRToken(address(erc20)).totalSupply()); - - if (supply == 0) return (buLow, buHigh); - - BasketRange memory range = basketRange(); // {BU} - - // {UoA/tok} = {BU} * {UoA/BU} / {tok} - lotLow = range.bottom.mulDiv(buLow, supply, FLOOR); - lotHigh = range.top.mulDiv(buHigh, supply, CEIL); - assert(lotLow <= lotHigh); // not obviously true - } - /// @return {tok} The balance of the ERC20 in whole tokens function bal(address account) external view returns (uint192) { // The RToken has 18 decimals, so there's no reason to waste gas here doing a shiftl_toFix @@ -174,7 +153,7 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { ); } - /// Computationally expensive basketRange calculation; used in price() & lotPrice() + /// Computationally expensive basketRange calculation; used in price() function basketRange() private view returns (BasketRange memory range) { BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(backingManager)); uint192 basketsNeeded = IRToken(address(erc20)).basketsNeeded(); // {BU} diff --git a/docs/collateral.md b/docs/collateral.md index aa4fe491cd..ea48f8f023 100644 --- a/docs/collateral.md +++ b/docs/collateral.md @@ -55,16 +55,11 @@ interface IAsset is IRewardable { function refresh() external; /// Should not revert + /// low should be nonzero when the asset might be worth selling /// @return low {UoA/tok} The lower end of the price estimate /// @return high {UoA/tok} The upper end of the price estimate function price() external view returns (uint192 low, uint192 high); - /// Should not revert - /// lotLow should be nonzero when the asset might be worth selling - /// @return lotLow {UoA/tok} The lower end of the lot price estimate - /// @return lotHigh {UoA/tok} The upper end of the lot price estimate - function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh); - /// @return {tok} The balance of the ERC20 in whole tokens function bal(address account) external view returns (uint192); @@ -79,6 +74,9 @@ interface IAsset is IRewardable { /// @param {UoA} The max trade volume, in UoA function maxTradeVolume() external view returns (uint192); + + /// @return {s} The timestamp of the last refresh() that saved prices + function lastSave() external view returns (uint48); } /// CollateralStatus must obey a linear ordering. That is: @@ -317,7 +315,7 @@ The same wrapper approach is easily used to tokenize positions in protocols that Because it’s called at the beginning of many transactions, `refresh()` should never revert. If `refresh()` encounters a critical error, it should change the Collateral contract’s state so that `status()` becomes `DISABLED`. -To prevent `refresh()` from reverting due to overflow or other numeric errors, the base collateral plugin [Fiat Collateral](../contracts/plugins/asset/FiatCollateral.sol) has a `tryPrice()` function that encapsulates both the oracle lookup as well as any subsequent math required. This function is always executed via a try-catch in `price()`/`lotPrice()`/`refresh()`. Extenders of this contract should not have to override any of these three functions, just `tryPrice()`. +To prevent `refresh()` from reverting due to overflow or other numeric errors, the base collateral plugin [Fiat Collateral](../contracts/plugins/asset/FiatCollateral.sol) has a `tryPrice()` function that encapsulates both the oracle lookup as well as any subsequent math required. This function is always executed via a try-catch in `price()`/`refresh()`. Extenders of this contract should not have to override any of these three functions, just `tryPrice()`. ### The `IFFY` status should be temporary. @@ -364,7 +362,7 @@ The values returned by the following view methods should never change: Collateral implementors who extend from [Fiat Collateral](../contracts/plugins/asset/FiatCollateral.sol) can restrict their attention to overriding the following four functions: -- `tryPrice()` (not on the ICollateral interface; used by `price()`/`lotPrice()`/`refresh()`) +- `tryPrice()` (not on the ICollateral interface; used by `price()`/`refresh()`) - `refPerTok()` - `targetPerRef()` - `claimRewards()` @@ -441,15 +439,7 @@ Lower estimate must be <= upper estimate. Should return `(0, FIX_MAX)` if pricing data is unavailable or stale. -Should be gas-efficient. - -### lotPrice() `{UoA/tok}` - -Should never revert. - -Lower estimate must be <= upper estimate. - -The low estimate should be nonzero while the asset is worth selling. +Recommend decaying low estimate downwards and high estimate upwards over time. Should be gas-efficient. diff --git a/docs/deployment.md b/docs/deployment.md index 6fbf565027..98c96678ef 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -220,7 +220,7 @@ yarn deploy:run:confirm --network mainnet This checks that: -- For each asset, confirm `lotPrice()` and `price()` are close. +- For each asset, confirm: - `main.tradingPaused()` and `main.issuancePaused()` are true - `timelockController.minDelay()` is > 1e12 diff --git a/docs/recollateralization.md b/docs/recollateralization.md index aecb345c94..06cf836594 100644 --- a/docs/recollateralization.md +++ b/docs/recollateralization.md @@ -64,21 +64,7 @@ If there does not exist a trade that meets these constraints, then the protocol #### Trade Sizing -The `IAsset` interface defines two types of prices: - -```solidity -/// @return low {UoA/tok} The lower end of the price estimate -/// @return high {UoA/tok} The upper end of the price estimate -function price() external view returns (uint192 low, uint192 high); - -/// lotLow should be nonzero when the asset might be worth selling -/// @return lotLow {UoA/tok} The lower end of the lot price estimate -/// @return lotHigh {UoA/tok} The upper end of the lot price estimate -function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh); - -``` - -All trades have a worst-case exchange rate that is a function of (among other things) the selling asset's `lotPrice().low` and the buying asset's `lotPrice().high`. +All trades have a worst-case exchange rate that is a function of (among other things) the selling asset's `price().low` and the buying asset's `price().high`. #### Trade Examples diff --git a/scripts/confirmation/1_confirm_assets.ts b/scripts/confirmation/1_confirm_assets.ts index 0c62bfbac1..b5ea27b8ec 100644 --- a/scripts/confirmation/1_confirm_assets.ts +++ b/scripts/confirmation/1_confirm_assets.ts @@ -2,7 +2,8 @@ import hre from 'hardhat' import { getChainId } from '../../common/blockchain-utils' import { developmentChains, networkConfig } from '../../common/configuration' -import { CollateralStatus } from '../../common/constants' +import { CollateralStatus, MAX_UINT192 } from '../../common/constants' +import { getLatestBlockTimestamp } from '#/utils/time' import { getDeploymentFile, IAssetCollDeployments, @@ -27,18 +28,20 @@ async function main() { const assets = Object.values(assetsColls.assets) const collateral = Object.values(assetsColls.collateral) - // Confirm lotPrice() == price() for (const a of assets) { console.log(`confirming asset ${a}`) const asset = await hre.ethers.getContractAt('Asset', a) - const [lotLow, lotHigh] = await asset.lotPrice() const [low, high] = await asset.price() // {UoA/tok} - if (low.eq(0) || high.eq(0)) throw new Error('misconfigured oracle') - - if (!lotLow.eq(low) || !lotHigh.eq(high)) { - console.log('lotLow, low, lotHigh, high', lotLow, low, lotHigh, high) - throw new Error('lot price off') - } + const timestamp = await getLatestBlockTimestamp(hre) + if ( + low.eq(0) || + low.eq(MAX_UINT192) || + high.eq(0) || + high.eq(MAX_UINT192) || + await asset.lastSave() !== timestamp || + await asset.lastSave() !== timestamp + ) + throw new Error('misconfigured oracle') } // Collateral @@ -49,14 +52,18 @@ async function main() { if ((await coll.status()) != CollateralStatus.SOUND) throw new Error('collateral unsound') - const [lotLow, lotHigh] = await coll.lotPrice() const [low, high] = await coll.price() // {UoA/tok} - if (low.eq(0) || high.eq(0)) throw new Error('misconfigured oracle') - - if (!lotLow.eq(low) || !lotHigh.eq(high)) { - console.log('lotLow, low, lotHigh, high', lotLow, low, lotHigh, high) - throw new Error('lot price off') - } + const timestamp = await getLatestBlockTimestamp(hre) + if ( + low.eq(0) || + low.eq(MAX_UINT192) || + high.eq(0) || + high.eq(MAX_UINT192) || + await coll.lastSave() !== timestamp || + await coll.lastSave() !== timestamp + ) + throw new Error('misconfigured oracle') + } } } diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 61398dba6f..5db42e6e2b 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -1,4 +1,4 @@ -import { loadFixture, setStorageAt } from '@nomicfoundation/hardhat-network-helpers' +import { loadFixture, getStorageAt, setStorageAt } from '@nomicfoundation/hardhat-network-helpers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' import { expect } from 'chai' @@ -463,6 +463,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await token0.connect(bmSigner).approve(broker.address, tradeRequest.sellAmount) // Should succeed in callStatic + await assetRegistry.refresh() await broker .connect(bmSigner) .callStatic.openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) @@ -483,6 +484,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { .withArgs(token0.address, true, false) // Should succeed in callStatic + await assetRegistry.refresh() await broker .connect(bmSigner) .callStatic.openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) @@ -1648,6 +1650,14 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { let TradeFactory: ContractFactory let newTrade: DutchTrade + // Increment `lastSave` in storage slot 1 + const incrementLastSave = async (addr: string) => { + const asArray = ethers.utils.arrayify(await getStorageAt(addr, 1)) + asArray[7] = asArray[7] + 1 // increment least significant byte of lastSave + const asHex = ethers.utils.hexlify(asArray) + await setStorageAt(addr, 1, asHex) + } + beforeEach(async () => { amount = bn('100e18') @@ -1675,6 +1685,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // Backing Manager await whileImpersonating(backingManager.address, async (bmSigner) => { await token0.connect(bmSigner).approve(broker.address, amount) + await assetRegistry.refresh() + await incrementLastSave(tradeRequest.sell) + await incrementLastSave(tradeRequest.buy) await snapshotGasCost( broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) ) @@ -1683,6 +1696,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // RSR Trader await whileImpersonating(rsrTrader.address, async (rsrSigner) => { await token0.connect(rsrSigner).approve(broker.address, amount) + await assetRegistry.refresh() + await incrementLastSave(tradeRequest.sell) + await incrementLastSave(tradeRequest.buy) await snapshotGasCost( broker.connect(rsrSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) ) @@ -1691,6 +1707,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // RToken Trader await whileImpersonating(rTokenTrader.address, async (rtokSigner) => { await token0.connect(rtokSigner).approve(broker.address, amount) + await assetRegistry.refresh() + await incrementLastSave(tradeRequest.sell) + await incrementLastSave(tradeRequest.buy) await snapshotGasCost( broker.connect(rtokSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) ) diff --git a/test/Facade.test.ts b/test/Facade.test.ts index 5415dd39d6..956b154b01 100644 --- a/test/Facade.test.ts +++ b/test/Facade.test.ts @@ -45,6 +45,8 @@ import { IMPLEMENTATION, defaultFixture, ORACLE_ERROR, + ORACLE_TIMEOUT, + PRICE_TIMEOUT, } from './fixtures' import { advanceBlocks, getLatestBlockTimestamp, setNextBlockTimestamp } from './utils/time' import { CollateralStatus, TradeKind, MAX_UINT256, ZERO_ADDRESS } from '#/common/constants' @@ -270,7 +272,7 @@ describe('FacadeRead + FacadeAct contracts', () => { it('Should handle UNPRICED when returning issuable quantities', async () => { // Set unpriced assets, should return UoA = 0 - await setOraclePrice(tokenAsset.address, MAX_UINT256.div(2).sub(1)) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) const [toks, quantities, uoas] = await facade.callStatic.issue(rToken.address, issueAmount) expect(toks.length).to.equal(4) expect(toks[0]).to.equal(token.address) @@ -283,9 +285,9 @@ describe('FacadeRead + FacadeAct contracts', () => { expect(quantities[2]).to.equal(issueAmount.div(4)) expect(quantities[3]).to.equal(issueAmount.div(4).mul(50).div(bn('1e10'))) expect(uoas.length).to.equal(4) - // Three assets are unpriced + // Assets are unpriced expect(uoas[0]).to.equal(0) - expect(uoas[1]).to.equal(issueAmount.div(4)) + expect(uoas[1]).to.equal(0) expect(uoas[2]).to.equal(0) expect(uoas[3]).to.equal(0) }) @@ -505,7 +507,10 @@ describe('FacadeRead + FacadeAct contracts', () => { expect(backing).to.equal(fp('1')) expect(overCollateralization).to.equal(fp('0.5')) - await setOraclePrice(rsrAsset.address, MAX_UINT256.div(2).sub(1)) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await setOraclePrice(tokenAsset.address, bn('1e8')) + await setOraclePrice(usdcAsset.address, bn('1e8')) + await assetRegistry.refresh() ;[backing, overCollateralization] = await facade.callStatic.backingOverview(rToken.address) // Check values - Fully collateralized and no over-collateralization @@ -560,11 +565,11 @@ describe('FacadeRead + FacadeAct contracts', () => { const tokenSurplus = bn('0.5e18') await token.connect(addr1).transfer(trader.address, tokenSurplus) - // Set lotLow to 0 == revenueOverview() should not revert + // Set low to 0 == revenueOverview() should not revert await setOraclePrice(usdcAsset.address, bn('0')) await usdcAsset.refresh() - const [lotLow] = await usdcAsset.lotPrice() - expect(lotLow).to.equal(0) + const [low] = await usdcAsset.price() + expect(low).to.equal(0) // revenue let [erc20s, canStart, surpluses, minTradeAmounts] = diff --git a/test/Main.test.ts b/test/Main.test.ts index b8613d40a0..ac0ec60a94 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -70,6 +70,7 @@ import { Implementation, IMPLEMENTATION, ORACLE_ERROR, + ORACLE_TIMEOUT, PRICE_TIMEOUT, REVENUE_HIDING, } from './fixtures' @@ -2756,8 +2757,10 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { // Check BU price -- 1/4 of the basket has lost half its value await expectPrice(basketHandler.address, fp('0.875'), ORACLE_ERROR, true) - // Set collateral1 price to invalid value that should produce [0, FIX_MAX] - await setOraclePrice(collateral1.address, MAX_UINT192) + // Set collateral1 price to [0, FIX_MAX] + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await setOraclePrice(collateral0.address, bn('1e8')) + await assetRegistry.refresh() // Check BU price -- 1/4 of the basket has lost all its value const asset = await ethers.getContractAt('Asset', basketHandler.address) @@ -2838,17 +2841,9 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { REVENUE_HIDING ) await assetRegistry.connect(owner).swapRegistered(newColl.address) - await setOraclePrice(newColl.address, MAX_UINT192) // overflow - await expectUnpriced(newColl.address) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) await newColl.setTargetPerRef(1) - await freshBasketHandler.setPrimeBasket([await newColl.erc20()], [fp('1000')]) - await freshBasketHandler.refreshBasket() - - // Expect [something > 0, FIX_MAX] - const bh = await ethers.getContractAt('Asset', basketHandler.address) - const [lowPrice, highPrice] = await bh.price() - expect(lowPrice).to.be.gt(0) - expect(highPrice).to.equal(MAX_UINT192) + await expectUnpriced(basketHandler.address) }) it('Should handle overflow in price calculation and return [FIX_MAX, FIX_MAX] - case 1', async () => { @@ -2905,35 +2900,6 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(highPrice).to.equal(MAX_UINT192) }) - it('Should distinguish between price/lotPrice', async () => { - // Set basket with single collateral - await basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1')]) - await basketHandler.refreshBasket() - - await collateral0.refresh() - const [low, high] = await collateral0.price() - await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) // oracle error - - // lotPrice() should begin at 100% - let [lowPrice, highPrice] = await basketHandler.price() - let [lotLowPrice, lotHighPrice] = await basketHandler.lotPrice() - expect(lowPrice).to.equal(0) - expect(highPrice).to.equal(MAX_UINT192) - expect(lotLowPrice).to.be.eq(low) - expect(lotHighPrice).to.be.eq(high) - - // Advance time past 100% period -- lotPrice() should begin to fall - await advanceTime(await collateral0.oracleTimeout()) - ;[lowPrice, highPrice] = await basketHandler.price() - ;[lotLowPrice, lotHighPrice] = await basketHandler.lotPrice() - expect(lowPrice).to.equal(0) - expect(highPrice).to.equal(MAX_UINT192) - expect(lotLowPrice).to.be.closeTo(low, low.div(bn('1e5'))) // small decay expected - expect(lotLowPrice).to.be.lt(low) - expect(lotHighPrice).to.be.closeTo(high, high.div(bn('1e5'))) // small decay expected - expect(lotHighPrice).to.be.lt(high) - }) - it('Should disable basket on asset deregistration + return quantities correctly', async () => { // Check values expect(await facadeTest.wholeBasketsHeldBy(rToken.address, addr1.address)).to.equal( diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 5b7718a225..014dca14ab 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -1015,9 +1015,6 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) it('Should not recollateralize when switching basket if all assets are UNPRICED', async () => { - // Set price to use lot price - await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) - // Setup prime basket await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) @@ -1029,7 +1026,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Advance time post warmup period - temporary IFFY->SOUND await advanceTime(Number(config.warmupPeriod) + 1) - // Set to sell price = 0 + // Set all assets to UNPRICED await advanceTime(Number(ORACLE_TIMEOUT.add(PRICE_TIMEOUT))) // Check state remains SOUND @@ -1188,8 +1185,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) }) - it('Should recollateralize correctly when switching basket - Using lot price', async () => { - // Set price to unpriced (will use lotPrice to size trade) + it('Should recollateralize correctly when switching basket', async () => { + // Set oracle value out-of-range await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) // Setup prime basket @@ -1218,7 +1215,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await toMinBuyAmt(sellAmt, fp('1'), fp('1')), 6 ).add(1) - // since within oracleTimeout lotPrice() should still be at 100% of original price + // since within oracleTimeout, price() should still be at 100% of original price await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) .to.emit(backingManager, 'TradeStarted') diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 6b4dad02c8..191c5864e0 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -554,13 +554,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rsr.balanceOf(rTokenTrader.address)).to.equal(0) }) - it('Should launch revenue auction at lotPrice if UNPRICED', async () => { - // After oracleTimeout the lotPrice should be the original price still + it('Should launch revenue auction if UNPRICED', async () => { + // After oracleTimeout it should still launch auction for RToken await advanceTime(ORACLE_TIMEOUT.toString()) await rsr.connect(addr1).transfer(rTokenTrader.address, issueAmount) await rTokenTrader.callStatic.manageTokens([rsr.address], [TradeKind.BATCH_AUCTION]) - // After oracleTimeout the lotPrice should be the original price still + // After priceTimeout it should not buy RToken await advanceTime(PRICE_TIMEOUT.toString()) await rsr.connect(addr1).transfer(rTokenTrader.address, issueAmount) await expect( diff --git a/test/__snapshots__/Broker.test.ts.snap b/test/__snapshots__/Broker.test.ts.snap index 889471b056..897ad0109f 100644 --- a/test/__snapshots__/Broker.test.ts.snap +++ b/test/__snapshots__/Broker.test.ts.snap @@ -2,11 +2,11 @@ exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `259526`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `368768`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `374656`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `370883`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `376771`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `373021`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `378909`; exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63333`; diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index 1b0751af8d..92ea4d72d1 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8354959`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8320542`; exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464235`; diff --git a/test/__snapshots__/Main.test.ts.snap b/test/__snapshots__/Main.test.ts.snap index 94907cfd67..4728df82a4 100644 --- a/test/__snapshots__/Main.test.ts.snap +++ b/test/__snapshots__/Main.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `361362`; +exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `361384`; exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `196758`; exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `196758`; -exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `167914`; +exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `167892`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80532`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80510`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `70044`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `70022`; diff --git a/test/__snapshots__/RToken.test.ts.snap b/test/__snapshots__/RToken.test.ts.snap index e8e5328dd7..4a613f6302 100644 --- a/test/__snapshots__/RToken.test.ts.snap +++ b/test/__snapshots__/RToken.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `791110`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `791197`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `618114`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `618201`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `592887`; +exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `592909`; exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56658`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index d47fb3da07..26e5a305aa 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1407849`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1392247`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1533241`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1527925`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `748681`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `748791`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1702113`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1684579`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1633187`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1625939`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1721584`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1714413`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `202908`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index 3f423f56a7..4a58b2ec34 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -12,16 +12,16 @@ exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229377`; exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212277`; -exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1033882`; +exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1039136`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `777373`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `777525`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1188041`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1188936`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `311446`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `266512`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `743173`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `743325`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `242306`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index 8604200d7a..529ecbcfcf 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`StRSRP1 contract Gas Reporting Stake 1`] = `139717`; +exports[`StRSRP1 contract Gas Reporting Stake 1`] = `156559`; exports[`StRSRP1 contract Gas Reporting Stake 2`] = `134917`; @@ -10,10 +10,10 @@ exports[`StRSRP1 contract Gas Reporting Transfer 2`] = `41509`; exports[`StRSRP1 contract Gas Reporting Transfer 3`] = `58621`; -exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `222609`; +exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `241951`; exports[`StRSRP1 contract Gas Reporting Unstake 2`] = `139758`; -exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `575668`; +exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `575755`; -exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `529672`; +exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `529759`; diff --git a/test/integration/AssetPlugins.test.ts b/test/integration/AssetPlugins.test.ts index 672f5566de..c9bd6d3ddd 100644 --- a/test/integration/AssetPlugins.test.ts +++ b/test/integration/AssetPlugins.test.ts @@ -1082,7 +1082,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, }) it('Should handle invalid/stale Price - Assets', async () => { - await advanceTime(ORACLE_TIMEOUT.toString()) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) // Stale Oracle await expectUnpriced(compAsset.address) @@ -1126,7 +1126,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, it('Should handle invalid/stale Price - Collateral - Fiat', async () => { // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) await expectUnpriced(daiCollateral.address) await expectUnpriced(usdcCollateral.address) @@ -1206,7 +1206,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await cUsdtCollateral.status()).to.equal(CollateralStatus.SOUND) // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) // Compound await expectUnpriced(cDaiCollateral.address) @@ -1279,7 +1279,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, it('Should handle invalid/stale Price - Collateral - ATokens Fiat', async () => { // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) // Aave await expectUnpriced(aDaiCollateral.address) @@ -1356,7 +1356,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, it('Should handle invalid/stale Price - Collateral - Non-Fiatcoins', async () => { // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) // Aave await expectUnpriced(wbtcCollateral.address) @@ -1428,7 +1428,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, it('Should handle invalid/stale Price - Collateral - CTokens Non-Fiat', async () => { // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) // Compound await expectUnpriced(cWBTCCollateral.address) @@ -1509,7 +1509,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, const delayUntilDefault = bn('86400') // 24h // Dows not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) // Aave await expectUnpriced(wethCollateral.address) @@ -1570,7 +1570,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, const delayUntilDefault = bn('86400') // 24h // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) // Compound await expectUnpriced(cETHCollateral.address) @@ -1646,7 +1646,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, it('Should handle invalid/stale Price - Collateral - EUR Fiat', async () => { // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) await expectUnpriced(eurtCollateral.address) diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index 3d9661263f..e783be4022 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -261,22 +261,22 @@ describe('Assets contracts #fast', () => { await expectPrice(compAsset.address, bn('0'), bn('0'), false) await expectPrice(aaveAsset.address, bn('0'), bn('0'), false) - // Fallback prices should be zero - let [lotLow, lotHigh] = await rsrAsset.lotPrice() - expect(lotLow).to.eq(0) - expect(lotHigh).to.eq(0) - ;[lotLow, lotHigh] = await rsrAsset.lotPrice() - expect(lotLow).to.eq(0) - expect(lotHigh).to.eq(0) - ;[lotLow, lotHigh] = await aaveAsset.lotPrice() - expect(lotLow).to.eq(0) - expect(lotHigh).to.eq(0) + // prices should be zero + let [low, high] = await rsrAsset.price() + expect(low).to.eq(0) + expect(high).to.eq(0) + ;[low, high] = await rsrAsset.price() + expect(low).to.eq(0) + expect(high).to.eq(0) + ;[low, high] = await aaveAsset.price() + expect(low).to.eq(0) + expect(high).to.eq(0) // Update values of underlying tokens of RToken to 0 await setOraclePrice(collateral0.address, bn(0)) await setOraclePrice(collateral1.address, bn(0)) - // RTokenAsset should be unpriced now + // RTokenAsset should be 0 await expectRTokenPrice( rTokenAsset.address, bn(0), @@ -284,11 +284,9 @@ describe('Assets contracts #fast', () => { await backingManager.maxTradeSlippage(), config.minTradeVolume.mul((await assetRegistry.erc20s()).length) ) - - // Should have lot price - ;[lotLow, lotHigh] = await rTokenAsset.lotPrice() - expect(lotLow).to.eq(0) - expect(lotHigh).to.eq(0) + ;[low, high] = await rTokenAsset.price() + expect(low).to.eq(0) + expect(high).to.eq(0) }) it('Should return 0 price for RTokenAsset in full haircut scenario', async () => { @@ -306,7 +304,7 @@ describe('Assets contracts #fast', () => { ) }) - it('Should not revert RToken price if supply is zero', async () => { + it('Should not revert during RToken price() if supply is zero', async () => { // Redeem RToken to make price function revert // Note: To get RToken price to 0, a full basket refresh needs to occur (covered in RToken tests) await rToken.connect(wallet).redeem(amt) @@ -318,12 +316,6 @@ describe('Assets contracts #fast', () => { config.minTradeVolume.mul((await assetRegistry.erc20s()).length) ) expect(await rTokenAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) - - // Should have lot price, equal to price when feed works OK - const [lowPrice, highPrice] = await rTokenAsset.price() - const [lotLow, lotHigh] = await rTokenAsset.lotPrice() - expect(lotLow).to.equal(lowPrice) - expect(lotHigh).to.equal(highPrice) }) it('Should calculate trade min correctly', async () => { @@ -350,35 +342,59 @@ describe('Assets contracts #fast', () => { expect(await rTokenAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) }) - it('Should be unpriced if price is stale', async () => { - await advanceTime(ORACLE_TIMEOUT.toString()) + it('Should remain at saved price if oracle is stale', async () => { + await advanceTime(ORACLE_TIMEOUT.sub(12).toString()) - // Check unpriced - await expectUnpriced(rsrAsset.address) - await expectUnpriced(compAsset.address) - await expectUnpriced(aaveAsset.address) + // lastSave should not be block timestamp after refresh + await rsrAsset.refresh() + await compAsset.refresh() + await aaveAsset.refresh() + expect(await rsrAsset.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await compAsset.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await aaveAsset.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + + // Check price + await expectPrice(rsrAsset.address, fp('1'), ORACLE_ERROR, false) + await expectPrice(compAsset.address, fp('1'), ORACLE_ERROR, false) + await expectPrice(aaveAsset.address, fp('1'), ORACLE_ERROR, false) }) - it('Should be unpriced in case of invalid timestamp', async () => { + it('Should remain at saved price in case of invalid timestamp', async () => { await setInvalidOracleTimestamp(rsrAsset.address) await setInvalidOracleTimestamp(compAsset.address) await setInvalidOracleTimestamp(aaveAsset.address) - // Check unpriced - await expectUnpriced(rsrAsset.address) - await expectUnpriced(compAsset.address) - await expectUnpriced(aaveAsset.address) + // lastSave should not be block timestamp after refresh + await rsrAsset.refresh() + await compAsset.refresh() + await aaveAsset.refresh() + expect(await rsrAsset.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await compAsset.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await aaveAsset.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + + // Check price is still at saved price + await expectPrice(rsrAsset.address, fp('1'), ORACLE_ERROR, false) + await expectPrice(compAsset.address, fp('1'), ORACLE_ERROR, false) + await expectPrice(aaveAsset.address, fp('1'), ORACLE_ERROR, false) }) - it('Should be unpriced in case of invalid answered round', async () => { + it('Should remain at saved price in case of invalid answered round', async () => { await setInvalidOracleAnsweredRound(rsrAsset.address) await setInvalidOracleAnsweredRound(compAsset.address) await setInvalidOracleAnsweredRound(aaveAsset.address) - // Check unpriced - await expectUnpriced(rsrAsset.address) - await expectUnpriced(compAsset.address) - await expectUnpriced(aaveAsset.address) + // lastSave should not be block timestamp after refresh + await rsrAsset.refresh() + await compAsset.refresh() + await aaveAsset.refresh() + expect(await rsrAsset.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await compAsset.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await aaveAsset.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + + // Check price is still at saved price + await expectPrice(rsrAsset.address, fp('1'), ORACLE_ERROR, false) + await expectPrice(compAsset.address, fp('1'), ORACLE_ERROR, false) + await expectPrice(aaveAsset.address, fp('1'), ORACLE_ERROR, false) }) it('Should handle unpriced edge cases for RToken', async () => { @@ -505,37 +521,35 @@ describe('Assets contracts #fast', () => { expect(await unpricedRSRAsset.lastSave()).to.equal(currBlockTimestamp) }) - it('Should not revert on refresh if unpriced', async () => { + it('Should not revert on refresh if stale', async () => { // Check initial prices - use RSR as example - const currBlockTimestamp: number = await getLatestBlockTimestamp() - await expectPrice(rsrAsset.address, fp('1'), ORACLE_ERROR, true) + const startBlockTimestamp: number = await getLatestBlockTimestamp() + await expectPrice(rsrAsset.address, fp('1'), ORACLE_ERROR, false) const [prevLowPrice, prevHighPrice] = await rsrAsset.price() expect(await rsrAsset.savedLowPrice()).to.equal(prevLowPrice) expect(await rsrAsset.savedHighPrice()).to.equal(prevHighPrice) - expect(await rsrAsset.lastSave()).to.equal(currBlockTimestamp) + expect(await rsrAsset.lastSave()).to.equal(startBlockTimestamp) // Set invalid oracle await setInvalidOracleTimestamp(rsrAsset.address) - // Check unpriced - uses still previous prices - await expectUnpriced(rsrAsset.address) + // Check price - uses still previous prices + await rsrAsset.refresh() let [lowPrice, highPrice] = await rsrAsset.price() - expect(lowPrice).to.equal(bn(0)) - expect(highPrice).to.equal(MAX_UINT192) + expect(lowPrice).to.equal(prevLowPrice) + expect(highPrice).to.equal(prevHighPrice) expect(await rsrAsset.savedLowPrice()).to.equal(prevLowPrice) expect(await rsrAsset.savedHighPrice()).to.equal(prevHighPrice) - expect(await rsrAsset.lastSave()).to.equal(currBlockTimestamp) + expect(await rsrAsset.lastSave()).to.equal(startBlockTimestamp) - // Perform refresh + // Check price - no update on prices/timestamp await rsrAsset.refresh() - - // Check still unpriced - no update on prices/timestamp - await expectUnpriced(rsrAsset.address) ;[lowPrice, highPrice] = await rsrAsset.price() - expect(lowPrice).to.equal(bn(0)) - expect(highPrice).to.equal(MAX_UINT192) + expect(lowPrice).to.equal(prevLowPrice) + expect(highPrice).to.equal(prevHighPrice) expect(await rsrAsset.savedLowPrice()).to.equal(prevLowPrice) expect(await rsrAsset.savedHighPrice()).to.equal(prevHighPrice) + expect(await rsrAsset.lastSave()).to.equal(startBlockTimestamp) }) it('Reverts if Chainlink feed reverts or runs out of gas', async () => { @@ -560,72 +574,64 @@ describe('Assets contracts #fast', () => { // Reverting with no reason await invalidChainlinkFeed.setSimplyRevert(true) await expect(invalidRSRAsset.price()).to.be.reverted - await expect(invalidRSRAsset.lotPrice()).to.be.reverted await expect(invalidRSRAsset.refresh()).to.be.reverted // Runnning out of gas (same error) await invalidChainlinkFeed.setSimplyRevert(false) await expect(invalidRSRAsset.price()).to.be.reverted - await expect(invalidRSRAsset.lotPrice()).to.be.reverted await expect(invalidRSRAsset.refresh()).to.be.reverted }) - it('Should handle lot price correctly', async () => { + it('Should handle price decay correctly', async () => { await rsrAsset.refresh() - // Check lot prices - use RSR as example - const currBlockTimestamp: number = await getLatestBlockTimestamp() - await expectPrice(rsrAsset.address, fp('1'), ORACLE_ERROR, true) + // Check prices - use RSR as example + const startBlockTimestamp: number = await getLatestBlockTimestamp() + await expectPrice(rsrAsset.address, fp('1'), ORACLE_ERROR, false) const [prevLowPrice, prevHighPrice] = await rsrAsset.price() expect(await rsrAsset.savedLowPrice()).to.equal(prevLowPrice) expect(await rsrAsset.savedHighPrice()).to.equal(prevHighPrice) - expect(await rsrAsset.lastSave()).to.equal(currBlockTimestamp) - - // Lot price equals price when feed works OK - const [lotLowPrice1, lotHighPrice1] = await rsrAsset.lotPrice() - expect(lotLowPrice1).to.equal(prevLowPrice) - expect(lotHighPrice1).to.equal(prevHighPrice) + expect(await rsrAsset.lastSave()).to.equal(startBlockTimestamp) // Set invalid oracle await setInvalidOracleTimestamp(rsrAsset.address) // Check unpriced - uses still previous prices - await expectUnpriced(rsrAsset.address) const [lowPrice, highPrice] = await rsrAsset.price() - expect(lowPrice).to.equal(bn(0)) - expect(highPrice).to.equal(MAX_UINT192) + expect(lowPrice).to.equal(prevLowPrice) + expect(highPrice).to.equal(prevHighPrice) expect(await rsrAsset.savedLowPrice()).to.equal(prevLowPrice) expect(await rsrAsset.savedHighPrice()).to.equal(prevHighPrice) - expect(await rsrAsset.lastSave()).to.equal(currBlockTimestamp) + expect(await rsrAsset.lastSave()).to.equal(startBlockTimestamp) - // At first lot price doesn't decrease - const [lotLowPrice2, lotHighPrice2] = await rsrAsset.lotPrice() - expect(lotLowPrice2).to.eq(lotLowPrice1) - expect(lotHighPrice2).to.eq(lotHighPrice1) + // At first price doesn't decrease + const [lowPrice2, highPrice2] = await rsrAsset.price() + expect(lowPrice2).to.eq(lowPrice) + expect(highPrice2).to.eq(highPrice) // Advance past oracleTimeout await advanceTime(await rsrAsset.oracleTimeout()) - // Now lot price decreases - const [lotLowPrice3, lotHighPrice3] = await rsrAsset.lotPrice() - expect(lotLowPrice3).to.be.lt(lotLowPrice2) - expect(lotHighPrice3).to.be.lt(lotHighPrice2) + // Now price widens + const [lowPrice3, highPrice3] = await rsrAsset.price() + expect(lowPrice3).to.be.lt(lowPrice2) + expect(highPrice3).to.be.gt(highPrice2) - // Advance block, lot price keeps decreasing + // Advance block, price keeps widening await advanceBlocks(1) - const [lotLowPrice4, lotHighPrice4] = await rsrAsset.lotPrice() - expect(lotLowPrice4).to.be.lt(lotLowPrice3) - expect(lotHighPrice4).to.be.lt(lotHighPrice3) + const [lowPrice4, highPrice4] = await rsrAsset.price() + expect(lowPrice4).to.be.lt(lowPrice3) + expect(highPrice4).to.be.gt(highPrice3) - // Advance blocks beyond PRICE_TIMEOUT + // Advance blocks beyond PRICE_TIMEOUT; price should be [O, FIX_MAX] await advanceTime(PRICE_TIMEOUT.toNumber()) // Lot price returns 0 once time elapses - const [lotLowPrice5, lotHighPrice5] = await rsrAsset.lotPrice() - expect(lotLowPrice5).to.be.lt(lotLowPrice4) - expect(lotHighPrice5).to.be.lt(lotHighPrice4) - expect(lotLowPrice5).to.be.equal(bn(0)) - expect(lotHighPrice5).to.be.equal(bn(0)) + const [lowPrice5, highPrice5] = await rsrAsset.price() + expect(lowPrice5).to.be.lt(lowPrice4) + expect(highPrice5).to.be.gt(highPrice4) + expect(lowPrice5).to.be.equal(bn(0)) + expect(highPrice5).to.be.equal(MAX_UINT192) }) }) @@ -696,9 +702,9 @@ describe('Assets contracts #fast', () => { it('refresh() after full price timeout', async () => { await advanceTime((await rsrAsset.priceTimeout()) + (await rsrAsset.oracleTimeout())) - const lotP = await rsrAsset.lotPrice() - expect(lotP[0]).to.equal(0) - expect(lotP[1]).to.equal(0) + const p = await rsrAsset.price() + expect(p[0]).to.equal(0) + expect(p[1]).to.equal(MAX_UINT192) }) }) }) diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index ea5db3b17e..02484890e1 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -590,10 +590,10 @@ describe('Collateral contracts', () => { await expectPrice(aTokenCollateral.address, bn('0'), bn('0'), false) await expectPrice(cTokenCollateral.address, bn('0'), bn('0'), false) - // Lot prices should be zero - const [lotLow, lotHigh] = await tokenCollateral.lotPrice() - expect(lotLow).to.eq(0) - expect(lotHigh).to.eq(0) + // price should be zero + const [low, high] = await tokenCollateral.price() + expect(low).to.eq(0) + expect(high).to.eq(0) // When refreshed, sets status to Unpriced await tokenCollateral.refresh() @@ -605,38 +605,56 @@ describe('Collateral contracts', () => { expect(await cTokenCollateral.status()).to.equal(CollateralStatus.IFFY) }) - it('Should be unpriced in case of invalid timestamp', async () => { + it('Should remain at saved price in case of invalid timestamp', async () => { await setInvalidOracleTimestamp(tokenCollateral.address) + await setInvalidOracleTimestamp(usdcCollateral.address) - // Check price of token - await expectUnpriced(tokenCollateral.address) - await expectUnpriced(aTokenCollateral.address) - await expectUnpriced(cTokenCollateral.address) - - // When refreshed, sets status to Unpriced + // lastSave should not be block timestamp after refresh await tokenCollateral.refresh() + await usdcCollateral.refresh() await aTokenCollateral.refresh() await cTokenCollateral.refresh() - + expect(await tokenCollateral.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await usdcCollateral.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await aTokenCollateral.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await cTokenCollateral.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + + // Check price is still at saved price + await expectPrice(tokenCollateral.address, fp('1'), ORACLE_ERROR, false) + await expectPrice(usdcCollateral.address, fp('1'), ORACLE_ERROR, false) + await expectPrice(aTokenCollateral.address, fp('1'), ORACLE_ERROR, false) + await expectPrice(cTokenCollateral.address, fp('1').div(50), ORACLE_ERROR, false) + + // Sets status to IFFY expect(await tokenCollateral.status()).to.equal(CollateralStatus.IFFY) + expect(await usdcCollateral.status()).to.equal(CollateralStatus.IFFY) expect(await aTokenCollateral.status()).to.equal(CollateralStatus.IFFY) expect(await cTokenCollateral.status()).to.equal(CollateralStatus.IFFY) }) - it('Should be unpriced in case of invalid answered round', async () => { + it('Should remain at saved price in case of invalid answered round', async () => { await setInvalidOracleAnsweredRound(tokenCollateral.address) + await setInvalidOracleAnsweredRound(usdcCollateral.address) - // Check price of token - await expectUnpriced(tokenCollateral.address) - await expectUnpriced(aTokenCollateral.address) - await expectUnpriced(cTokenCollateral.address) - - // When refreshed, sets status to Unpriced + // lastSave should not be block timestamp after refresh await tokenCollateral.refresh() + await usdcCollateral.refresh() await aTokenCollateral.refresh() await cTokenCollateral.refresh() - + expect(await tokenCollateral.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await usdcCollateral.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await aTokenCollateral.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await cTokenCollateral.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + + // Check price is still at saved price + await expectPrice(tokenCollateral.address, fp('1'), ORACLE_ERROR, false) + await expectPrice(usdcCollateral.address, fp('1'), ORACLE_ERROR, false) + await expectPrice(aTokenCollateral.address, fp('1'), ORACLE_ERROR, false) + await expectPrice(cTokenCollateral.address, fp('1').div(50), ORACLE_ERROR, false) + + // Sets status to IFFY expect(await tokenCollateral.status()).to.equal(CollateralStatus.IFFY) + expect(await usdcCollateral.status()).to.equal(CollateralStatus.IFFY) expect(await aTokenCollateral.status()).to.equal(CollateralStatus.IFFY) expect(await cTokenCollateral.status()).to.equal(CollateralStatus.IFFY) }) @@ -897,14 +915,24 @@ describe('Collateral contracts', () => { } }) - it('Unpriced if price is stale', async () => { - await advanceTime(ORACLE_TIMEOUT.toString()) + it('Should remain at saved price if oracle is stale', async () => { + await advanceTime(ORACLE_TIMEOUT.sub(12).toString()) - // Check unpriced - await expectUnpriced(tokenCollateral.address) - await expectUnpriced(usdcCollateral.address) - await expectUnpriced(cTokenCollateral.address) - await expectUnpriced(aTokenCollateral.address) + // lastSave should not be block timestamp after refresh + await tokenCollateral.refresh() + await usdcCollateral.refresh() + await cTokenCollateral.refresh() + await aTokenCollateral.refresh() + expect(await tokenCollateral.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await usdcCollateral.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await cTokenCollateral.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + expect(await aTokenCollateral.lastSave()).to.not.equal(await getLatestBlockTimestamp()) + + // Check price + await expectPrice(tokenCollateral.address, fp('1'), ORACLE_ERROR, false) + await expectPrice(usdcCollateral.address, fp('1'), ORACLE_ERROR, false) + await expectPrice(cTokenCollateral.address, fp('1').div(50), ORACLE_ERROR, false) + await expectPrice(aTokenCollateral.address, fp('1'), ORACLE_ERROR, false) }) it('Enters IFFY state when price becomes stale', async () => { @@ -2221,15 +2249,15 @@ describe('Collateral contracts', () => { const oracleTimeout = await tokenCollateral.oracleTimeout() await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) await advanceBlocks(bn(oracleTimeout).div(12)) + await snapshotGasCost(tokenCollateral.refresh()) }) it('after full price timeout', async () => { await advanceTime( (await tokenCollateral.priceTimeout()) + (await tokenCollateral.oracleTimeout()) ) - const lotP = await tokenCollateral.lotPrice() - expect(lotP[0]).to.equal(0) - expect(lotP[1]).to.equal(0) + await expectUnpriced(tokenCollateral.address) + await snapshotGasCost(tokenCollateral.refresh()) }) }) }) diff --git a/test/plugins/__snapshots__/Collateral.test.ts.snap b/test/plugins/__snapshots__/Collateral.test.ts.snap index cedffac69d..9b37ebf315 100644 --- a/test/plugins/__snapshots__/Collateral.test.ts.snap +++ b/test/plugins/__snapshots__/Collateral.test.ts.snap @@ -1,8 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral contracts Gas Reporting refresh() after hard default 1`] = `72710`; +exports[`Collateral contracts Gas Reporting refresh() after full price timeout 1`] = `47097`; -exports[`Collateral contracts Gas Reporting refresh() after hard default 2`] = `73846`; +exports[`Collateral contracts Gas Reporting refresh() after hard default 1`] = `72688`; + +exports[`Collateral contracts Gas Reporting refresh() after hard default 2`] = `73824`; + +exports[`Collateral contracts Gas Reporting refresh() after oracle timeout 1`] = `47097`; exports[`Collateral contracts Gas Reporting refresh() during + after soft default 1`] = `62400`; diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index b74fda69de..11497aa3fc 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -22,7 +22,12 @@ import { IRTokenSetup, networkConfig, } from '../../../../common/configuration' -import { CollateralStatus, MAX_UINT48, ZERO_ADDRESS } from '../../../../common/constants' +import { + CollateralStatus, + MAX_UINT48, + MAX_UINT192, + ZERO_ADDRESS, +} from '../../../../common/constants' import { expectEvents, expectInIndirectReceipt } from '../../../../common/events' import { bn, fp } from '../../../../common/numbers' import { whileImpersonating } from '../../../utils/impersonation' @@ -653,10 +658,14 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi describe('Price Handling', () => { it('Should handle invalid/stale Price', async () => { // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + await advanceTime(ORACLE_TIMEOUT.sub(12).toString()) - // stkAAVEound - await expectUnpriced(aDaiCollateral.address) + // Price is at saved prices + const savedLowPrice = await aDaiCollateral.savedLowPrice() + const savedHighPrice = await aDaiCollateral.savedHighPrice() + const p = await aDaiCollateral.price() + expect(p[0]).to.equal(savedLowPrice) + expect(p[1]).to.equal(savedHighPrice) // Refresh should mark status IFFY await aDaiCollateral.refresh() @@ -1000,9 +1009,9 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await advanceTime( (await aDaiCollateral.priceTimeout()) + (await aDaiCollateral.oracleTimeout()) ) - const lotP = await aDaiCollateral.lotPrice() - expect(lotP[0]).to.equal(0) - expect(lotP[1]).to.equal(0) + const p = await aDaiCollateral.price() + expect(p[0]).to.equal(0) + expect(p[1]).to.equal(MAX_UINT192) await snapshotGasCost(aDaiCollateral.refresh()) await snapshotGasCost(aDaiCollateral.refresh()) // 2nd refresh can be different than 1st }) diff --git a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap index f02da1ba1a..5f8655593d 100644 --- a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `75233`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `75222`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `73565`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `73554`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `73788`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `73766`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `66106`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `66084`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `75233`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `75222`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `73565`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `73554`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `91924`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `91976`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `91998`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `91902`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `93061`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `93039`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `93135`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `93113`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `128207`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `128111`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `92265`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `92243`; diff --git a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap index 6faac2e04c..1865ad22d4 100644 --- a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61208`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61197`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `56739`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `56728`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `79401`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `79379`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `71718`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `71696`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61208`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61197`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `56739`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `56728`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `71715`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `71693`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `71715`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `71693`; diff --git a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap index f086c155bb..b2f52853e9 100644 --- a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `60696`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `60685`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `56227`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `56216`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `97997`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `97975`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `90314`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `90292`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `81812`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `81801`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77343`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77332`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `90311`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `90289`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `90311`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `90289`; diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 00891b0076..2fddefca79 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -25,7 +25,7 @@ import { CollateralTestSuiteFixtures, CollateralStatus, } from './pluginTestTypes' -import { expectPrice } from '../../utils/oracles' +import { expectPrice, expectUnpriced } from '../../utils/oracles' import snapshotGasCost from '../../utils/snapshotGasCost' import { IMPLEMENTATION, Implementation } from '../../fixtures' @@ -261,7 +261,7 @@ export default function fn( const updateAnswerTx = await chainlinkFeed.updateAnswer(0) await updateAnswerTx.wait() - // (0, FIX_MAX) is returned + // (0, 0) is returned const [low, high] = await collateral.price() expect(low).to.equal(0) expect(high).to.equal(0) @@ -271,33 +271,28 @@ export default function fn( expect(await collateral.status()).to.equal(CollateralStatus.IFFY) }) - it('reverts in case of invalid timestamp', async () => { + it('does not revert in case of invalid timestamp', async () => { await chainlinkFeed.setInvalidTimestamp() - // Check price of token - const [low, high] = await collateral.price() - expect(low).to.equal(0) - expect(high).to.equal(MAX_UINT192) - - // When refreshed, sets status to Unpriced + // When refreshed, sets status to IFFY await collateral.refresh() expect(await collateral.status()).to.equal(CollateralStatus.IFFY) }) it('does not update the saved prices if collateral is unpriced', async () => { /* - want to cover this block from the refresh function + want to cover this block from the refresh function is it even possible to cover this w/ the tryPrice from AppreciatingFiatCollateral? - + if (high < FIX_MAX) { - savedLowPrice = low; - savedHighPrice = high; - lastSave = uint48(block.timestamp); + savedLowPrice = low; + savedHighPrice = high; + lastSave = uint48(block.timestamp); } else { - // must be unpriced - assert(low == 0); + // must be unpriced + assert(low == 0); } - */ + */ expect(true) }) @@ -350,29 +345,27 @@ export default function fn( expect(await collateral.status()).to.equal(CollateralStatus.IFFY) }) - it('decays lotPrice over priceTimeout period', async () => { - // Prices should start out equal + it('decays price over priceTimeout period', async () => { await collateral.refresh() - const p = await collateral.price() - let lotP = await collateral.lotPrice() - expect(p.length).to.equal(lotP.length) - expect(p[0]).to.equal(lotP[0]) - expect(p[1]).to.equal(lotP[1]) + const savedLow = await collateral.savedLowPrice() + const savedHigh = await collateral.savedHighPrice() + // Price should start out at saved prices + let p = await collateral.price() + expect(p[0]).to.equal(savedLow) + expect(p[1]).to.equal(savedHigh) await advanceTime(await collateral.oracleTimeout()) // Should be roughly half, after half of priceTimeout const priceTimeout = await collateral.priceTimeout() await advanceTime(priceTimeout / 2) - lotP = await collateral.lotPrice() - expect(lotP[0]).to.be.closeTo(p[0].div(2), p[0].div(2).div(10000)) // 1 part in 10 thousand - expect(lotP[1]).to.be.closeTo(p[1].div(2), p[1].div(2).div(10000)) // 1 part in 10 thousand + p = await collateral.price() + expect(p[0]).to.be.closeTo(savedLow.div(2), p[0].div(2).div(10000)) // 1 part in 10 thousand + expect(p[1]).to.be.closeTo(savedHigh.mul(2), p[1].mul(2).div(10000)) // 1 part in 10 thousand - // Should be 0 after full priceTimeout + // Should be unpriced after full priceTimeout await advanceTime(priceTimeout / 2) - lotP = await collateral.lotPrice() - expect(lotP[0]).to.equal(0) - expect(lotP[1]).to.equal(0) + await expectUnpriced(collateral.address) }) }) @@ -535,9 +528,9 @@ export default function fn( await advanceTime( (await collateral.priceTimeout()) + (await collateral.oracleTimeout()) ) - const lotP = await collateral.lotPrice() - expect(lotP[0]).to.equal(0) - expect(lotP[1]).to.equal(0) + const p = await collateral.price() + expect(p[0]).to.equal(0) + expect(p[1]).to.equal(MAX_UINT192) }) }) }) diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 2a16daceb9..53025c4ef8 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -22,7 +22,12 @@ import { IRTokenSetup, networkConfig, } from '../../../../common/configuration' -import { CollateralStatus, MAX_UINT48, ZERO_ADDRESS } from '../../../../common/constants' +import { + CollateralStatus, + MAX_UINT48, + MAX_UINT192, + ZERO_ADDRESS, +} from '../../../../common/constants' import { expectEvents, expectInIndirectReceipt } from '../../../../common/events' import { bn, fp, toBNDecimals } from '../../../../common/numbers' import { whileImpersonating } from '../../../utils/impersonation' @@ -666,10 +671,14 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi describe('Price Handling', () => { it('Should handle invalid/stale Price', async () => { // Does not revert with stale price - await advanceTime(ORACLE_TIMEOUT.toString()) + await advanceTime(ORACLE_TIMEOUT.sub(12).toString()) - // Compound - await expectUnpriced(cDaiCollateral.address) + // Price is at saved prices + const savedLowPrice = await cDaiCollateral.savedLowPrice() + const savedHighPrice = await cDaiCollateral.savedHighPrice() + const p = await cDaiCollateral.price() + expect(p[0]).to.equal(savedLowPrice) + expect(p[1]).to.equal(savedHighPrice) // Refresh should mark status IFFY await cDaiCollateral.refresh() @@ -1028,9 +1037,9 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await advanceTime( (await cDaiCollateral.priceTimeout()) + (await cDaiCollateral.oracleTimeout()) ) - const lotP = await cDaiCollateral.lotPrice() - expect(lotP[0]).to.equal(0) - expect(lotP[1]).to.equal(0) + const p = await cDaiCollateral.price() + expect(p[0]).to.equal(0) + expect(p[1]).to.equal(MAX_UINT192) await snapshotGasCost(cDaiCollateral.refresh()) await snapshotGasCost(cDaiCollateral.refresh()) // 2nd refresh can be different than 1st }) diff --git a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap index f872809ebb..e390fbcda3 100644 --- a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `120196`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `120185`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `118528`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `118517`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `74924`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `74902`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `67242`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `67220`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `120196`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `120185`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `118528`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `118517`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `139517`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `139495`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `139517`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `139495`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `140654`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `140632`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `140654`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `140632`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `175726`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `175704`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `139858`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `139836`; diff --git a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap index 4f0b595a2d..2d49b4c967 100644 --- a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `109932`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `109921`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `105195`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `105184`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `135300`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `135278`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `127617`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `127595`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `109932`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `109921`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `105195`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `105184`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `107922`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `107911`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `104854`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `104843`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `127614`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `127592`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `127614`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `127592`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `135165`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `135143`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `127896`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `127874`; diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index 14aff54151..5ee8b6984c 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -8,7 +8,7 @@ import { ethers } from 'hardhat' import { ERC20Mock, InvalidMockV3Aggregator } from '../../../../typechain' import { BigNumber } from 'ethers' import { bn, fp } from '../../../../common/numbers' -import { MAX_UINT48, ZERO_ADDRESS, ONE_ADDRESS } from '../../../../common/constants' +import { MAX_UINT48, MAX_UINT192, ZERO_ADDRESS, ONE_ADDRESS } from '../../../../common/constants' import { expect } from 'chai' import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' import { useEnv } from '#/utils/env' @@ -403,7 +403,7 @@ export default function fn( expect(low).to.equal(0) expect(high).to.equal(0) - // When refreshed, sets status to Unpriced + // When refreshed, sets status to IFFY await ctx.collateral.refresh() expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) }) @@ -411,13 +411,15 @@ export default function fn( it('does not revert in case of invalid timestamp', async () => { await ctx.feeds[0].setInvalidTimestamp() - // When refreshed, sets status to Unpriced + // When refreshed, sets status to IFFY await ctx.collateral.refresh() expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) }) - it('Handles stale price', async () => { - await advanceTime(await ctx.collateral.priceTimeout()) + it('handles stale price', async () => { + await advanceTime( + (await ctx.collateral.oracleTimeout()) + (await ctx.collateral.priceTimeout()) + ) // (0, FIX_MAX) is returned await expectUnpriced(ctx.collateral.address) @@ -427,28 +429,27 @@ export default function fn( expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) }) - it('decays lotPrice over priceTimeout period', async () => { - // Prices should start out equal - const p = await ctx.collateral.price() - let lotP = await ctx.collateral.lotPrice() - expect(p.length).to.equal(lotP.length) - expect(p[0]).to.equal(lotP[0]) - expect(p[1]).to.equal(lotP[1]) + it('decays price over priceTimeout period', async () => { + const savedLow = await ctx.collateral.savedLowPrice() + const savedHigh = await ctx.collateral.savedHighPrice() + // Price should start out at saved prices + await ctx.collateral.refresh() + let p = await ctx.collateral.price() + expect(p[0]).to.equal(savedLow) + expect(p[1]).to.equal(savedHigh) await advanceTime(await ctx.collateral.oracleTimeout()) // Should be roughly half, after half of priceTimeout const priceTimeout = await ctx.collateral.priceTimeout() await advanceTime(priceTimeout / 2) - lotP = await ctx.collateral.lotPrice() - expect(lotP[0]).to.be.closeTo(p[0].div(2), p[0].div(2).div(10000)) // 1 part in 10 thousand - expect(lotP[1]).to.be.closeTo(p[1].div(2), p[1].div(2).div(10000)) // 1 part in 10 thousand + p = await ctx.collateral.price() + expect(p[0]).to.be.closeTo(savedLow.div(2), p[0].div(2).div(10000)) // 1 part in 10 thousand + expect(p[1]).to.be.closeTo(savedHigh.mul(2), p[1].mul(2).div(10000)) // 1 part in 10 thousand // Should be 0 after full priceTimeout await advanceTime(priceTimeout / 2) - lotP = await ctx.collateral.lotPrice() - expect(lotP[0]).to.equal(0) - expect(lotP[1]).to.equal(0) + await expectUnpriced(ctx.collateral.address) }) }) @@ -686,9 +687,9 @@ export default function fn( await advanceTime( (await ctx.collateral.priceTimeout()) + (await ctx.collateral.oracleTimeout()) ) - const lotP = await ctx.collateral.lotPrice() - expect(lotP[0]).to.equal(0) - expect(lotP[1]).to.equal(0) + const p = await ctx.collateral.price() + expect(p[0]).to.equal(0) + expect(p[1]).to.equal(MAX_UINT192) }) }) }) diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap index 9593a4f3bf..c397dd75b2 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `81987`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `81995`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `77519`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `77527`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `259104`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `259452`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `254222`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `254570`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `81987`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `81995`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77519`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77527`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `77178`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `77186`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `77178`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `77186`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `254219`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `254567`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `254219`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `254567`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `261797`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `262145`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `254529`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `254877`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap index 7d386b05ce..b9d8d3a9c7 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `98884`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `107067`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `94416`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `102525`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `232790`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `232814`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `227908`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `227932`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `103944`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `103956`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `99476`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `99488`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `99135`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `99147`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `99135`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `99147`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `227905`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `227929`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `227905`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `227929`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `213652`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `213664`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `206384`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `206396`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap index 8381cbd1f2..e0d9367b5d 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62764`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62753`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `58296`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `58285`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `205007`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `204534`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `200125`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `199652`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62764`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62753`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58296`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58285`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57955`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57944`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57955`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57944`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `200122`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `199649`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `200122`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `199649`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `185477`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `185136`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `178209`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `177868`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap index 99c585e1e6..11f409aefa 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `65212`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `65201`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `60744`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `60733`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `241237`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `240731`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `236355`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `235849`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `65212`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `65201`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `60744`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `60733`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `60403`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `60392`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `60403`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `60392`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `236352`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `235846`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `236352`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `235846`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `242606`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `242100`; -exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `235338`; +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `234832`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap index 0959931c42..ba002c3bba 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `81987`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `81995`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `77519`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `77527`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `259104`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `259452`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `254222`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `254570`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `81987`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `81995`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77519`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77527`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `77178`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `77186`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `77178`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `77186`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `254219`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `254567`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `254219`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `254567`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `261797`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `262145`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `254529`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `254877`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap index 1225a8c886..f60389a6fa 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `98884`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `106993`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `94416`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `102599`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `232790`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `232814`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `227908`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `227932`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `103944`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `103956`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `99476`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `99488`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `99135`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `99147`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `99135`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `99147`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `227905`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `227929`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `227905`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `227929`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `213652`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `213664`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `206384`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `206396`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap index 130ef52bef..a82a997fc8 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62764`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62753`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `58296`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `58285`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `205007`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `204534`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `200125`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `199652`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62764`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62753`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58296`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58285`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57955`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57944`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57955`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57944`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `200122`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `199649`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `200122`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `199649`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `185477`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `185136`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `178209`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `177868`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap index 3c1e73b07a..9da426d197 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `65212`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `65201`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `60744`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `60733`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `241237`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `240731`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `236355`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `235849`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `65212`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `65201`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `60744`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `60733`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `60403`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `60392`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `60403`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `60392`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `236352`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `235846`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `236352`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `235846`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `242606`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `242100`; -exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `235338`; +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `234832`; diff --git a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap index 9202afd768..3638eadd37 100644 --- a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117728`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117612`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `109311`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `109300`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `132112`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `132090`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `124152`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `124130`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117442`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117431`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `109311`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `109300`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `112297`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `112286`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `108970`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `108959`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `124149`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `124127`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `124149`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `124127`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `131977`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `132029`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `124431`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `124483`; diff --git a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap index d91ce93686..24698bfd3e 100644 --- a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap @@ -1,97 +1,97 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118197`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118186`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116529`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116518`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `141791`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `141769`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139977`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139955`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118197`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118186`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `116529`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `116518`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `116188`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `116177`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `116188`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `116177`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139974`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139952`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139974`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139952`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141998`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141976`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140330`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140308`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118389`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118378`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116721`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116710`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `142047`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `142025`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `140233`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `140211`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118389`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118378`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `116721`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `116710`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `116380`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `116369`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `116380`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `116369`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `140230`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `140208`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `140230`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `140208`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `142254`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `142232`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140512`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140490`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `126679`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `126668`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `125011`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `125000`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `150829`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `150807`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `149015`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `148993`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `126679`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `126668`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `125011`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `125000`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `124670`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `124659`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `124670`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `124659`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `149012`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `148990`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `149012`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `148990`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `150892`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `150870`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `149294`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `149272`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `121327`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `121316`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `119659`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `119648`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `145123`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `145101`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `143379`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `143357`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `121327`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `121316`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `119659`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `119648`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `119318`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `119307`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `119318`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `119307`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `143376`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `143354`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `143306`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `143284`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `145326`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `145304`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `143658`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `143636`; diff --git a/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap index 61665d768b..13a93c34d8 100644 --- a/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59864`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59853`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55127`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55116`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `60564`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `60553`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58895`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58884`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `74660`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `74638`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `74660`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `74638`; diff --git a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap index 9514e3f06b..0d86536827 100644 --- a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `88913`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `88902`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `84444`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `84433`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `134556`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `134534`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `126873`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `126851`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `88913`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `88902`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `84444`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `84433`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `84103`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `84092`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `84103`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `84092`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `126870`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `126848`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `126870`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `126848`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `131621`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `131599`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `127152`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `127130`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap index f3460e5383..0f5db3ba5a 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap @@ -1,73 +1,73 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134888`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134877`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 2`] = `130419`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 2`] = `130408`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 1`] = `180257`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 1`] = `180235`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 2`] = `172574`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 2`] = `172552`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134888`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134877`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `130419`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `130408`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 1`] = `130078`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 1`] = `130067`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 2`] = `130078`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 2`] = `130067`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `172571`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `172549`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 2`] = `172571`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 2`] = `172549`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 1`] = `180122`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 1`] = `180100`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 2`] = `172853`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 2`] = `172831`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `135091`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `135080`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `130622`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `130611`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `180663`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `180641`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `172980`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `172958`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `135091`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `135080`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `130622`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `130611`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `130281`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `130270`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `130281`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `130270`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `172977`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `172955`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `172977`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `172955`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `180528`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `180506`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `173259`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `173237`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134244`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134233`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129775`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129764`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 1`] = `178969`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 1`] = `178947`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 2`] = `171286`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 2`] = `171264`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134244`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134233`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129775`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129764`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 1`] = `129434`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 1`] = `129423`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 2`] = `129434`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 2`] = `129423`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `171283`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `171261`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 2`] = `171283`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 2`] = `171261`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 1`] = `178834`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 1`] = `178812`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 2`] = `171565`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 2`] = `171543`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap index 75cc7c2e6a..b69f4cba3f 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap @@ -1,49 +1,49 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134311`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134300`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129842`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129831`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 1`] = `200352`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 1`] = `200330`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 2`] = `192669`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 2`] = `192647`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `184175`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `184164`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `179706`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `179695`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 1`] = `179365`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 1`] = `179354`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 2`] = `179365`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 2`] = `179354`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `192666`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `192644`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 2`] = `192666`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 2`] = `192644`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 1`] = `197417`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 1`] = `197395`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 2`] = `192948`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 2`] = `192926`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `167943`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `167932`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `163474`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `163463`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 1`] = `239616`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 1`] = `239594`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 2`] = `231933`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 2`] = `231911`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `223439`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `223428`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `218970`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `218959`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 1`] = `218629`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 1`] = `218618`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 2`] = `218629`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 2`] = `218618`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `231930`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `231908`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `231930`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `231908`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 1`] = `236681`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 1`] = `236659`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 2`] = `232212`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 2`] = `232190`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap index 30864ba004..6727634337 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `202026`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `202015`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `197557`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `197546`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 1`] = `218208`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 1`] = `218186`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 2`] = `210525`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 2`] = `210503`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `202026`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `202015`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `197557`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `197546`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `210522`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `210500`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `210522`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `210500`; diff --git a/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap index 66168f0739..06ad7d31b9 100644 --- a/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `71781`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `71770`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `67312`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `67301`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 1`] = `109082`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 1`] = `109060`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 2`] = `101399`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 2`] = `101377`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `71781`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `71770`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `67312`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `67301`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `101396`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `101374`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `101396`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `101374`; diff --git a/test/scenario/ComplexBasket.test.ts b/test/scenario/ComplexBasket.test.ts index f55d5a3652..e97f5bea25 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -1598,8 +1598,8 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // Running auctions will trigger recollateralization - cETHVault partial sale for weth // Will sell about 841K of cETHVault, expect to receive 8167 wETH (minimum) // We would still have about 438K to sell of cETHVault - let [, lotHigh] = await cETHVaultCollateral.lotPrice() - const sellAmtUnscaled = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(lotHigh) + let [, high] = await cETHVaultCollateral.price() + const sellAmtUnscaled = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(high) const sellAmt = toBNDecimals(sellAmtUnscaled, 8) const sellAmtRemainder = (await cETHVault.balanceOf(backingManager.address)).sub(sellAmt) // Price for cETHVault = 1200 / 50 = $24 at rate 50% = $12 @@ -1744,8 +1744,8 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // 13K wETH @ 1200 = 15,600,000 USD of value, in RSR ~= 156,000 RSR (@100 usd) // We exceed maxTradeVolume so we need two auctions - Will first sell 10M in value // Sells about 101K RSR, for 8167 WETH minimum - ;[, lotHigh] = await rsrAsset.lotPrice() - const sellAmtRSR1 = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(lotHigh) + ;[, high] = await rsrAsset.price() + const sellAmtRSR1 = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(high) const buyAmtBidRSR1 = toMinBuyAmt( sellAmtRSR1, rsrPrice, diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index f7ca06c407..629bb089a0 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12071639`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12069526`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9810015`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9807837`; exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2281990`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13596421`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13594221`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `21212846`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20999017`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `11078427`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `11078514`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8799715`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8799737`; exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `6630749`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `15560638`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `14802600`; From 45d8c362026b6b76e70a245d1fd0800e91743658 Mon Sep 17 00:00:00 2001 From: brr Date: Mon, 21 Aug 2023 17:03:19 +0100 Subject: [PATCH 377/462] c4: #5 Potential Early Exploit in Morho-Aave ERC4626 Implementation (#897) Co-authored-by: Patrick McKelvy --- .../plugins/assets/morpho-aave/IMorpho.sol | 6 + .../morpho-aave/MorphoTokenisedDeposit.sol | 2 +- .../individual-collateral/collateralTests.ts | 13 +- .../MorphoAaveV2TokenisedDeposit.test.ts | 138 ++++++++++++++---- 4 files changed, 127 insertions(+), 32 deletions(-) diff --git a/contracts/plugins/assets/morpho-aave/IMorpho.sol b/contracts/plugins/assets/morpho-aave/IMorpho.sol index 9883baa8ca..bc9ec4bce7 100644 --- a/contracts/plugins/assets/morpho-aave/IMorpho.sol +++ b/contracts/plugins/assets/morpho-aave/IMorpho.sol @@ -7,6 +7,12 @@ import { IERC4626 } from "../../../vendor/oz/IERC4626.sol"; interface IMorpho { function supply(address _poolToken, uint256 _amount) external; + function supply( + address _poolToken, + address _onBehalf, + uint256 _amount + ) external; + function withdraw(address _poolToken, uint256 _amount) external; } diff --git a/contracts/plugins/assets/morpho-aave/MorphoTokenisedDeposit.sol b/contracts/plugins/assets/morpho-aave/MorphoTokenisedDeposit.sol index 6e33542725..d2664e782c 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoTokenisedDeposit.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoTokenisedDeposit.sol @@ -64,7 +64,7 @@ abstract contract MorphoTokenisedDeposit is RewardableERC4626Vault { } function _decimalsOffset() internal view virtual override returns (uint8) { - return 0; + return 9; } function _withdraw( diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 00891b0076..36ff13e747 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -127,14 +127,17 @@ export default function fn( describe('functions', () => { it('returns the correct bal (18 decimals)', async () => { - const amount = bn('20').mul(bn(10).pow(await ctx.tok.decimals())) + const decimals = await ctx.tok.decimals() + const amount = bn('20').mul(bn(10).pow(decimals)) await mintCollateralTo(ctx, amount, alice, alice.address) const aliceBal = await collateral.bal(alice.address) - expect(aliceBal).to.closeTo( - amount.mul(bn(10).pow(18 - (await ctx.tok.decimals()))), - bn('100').mul(bn(10).pow(18 - (await ctx.tok.decimals()))) - ) + const amount18d = + decimals <= 18 + ? amount.mul(bn(10).pow(18 - decimals)) + : amount.div(bn(10).pow(decimals - 18)) + const dist18d = decimals <= 18 ? bn('100').mul(bn(10).pow(18 - decimals)) : bn('10') + expect(aliceBal).to.closeTo(amount18d, dist18d) }) }) diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts index c529655694..20d9a1406a 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts @@ -2,10 +2,11 @@ import { ITokens, networkConfig } from '#/common/configuration' import { ethers } from 'hardhat' import { whileImpersonating } from '../../../utils/impersonation' import { whales } from '#/tasks/testing/upgrade-checker-utils/constants' -import { Signer } from 'ethers' +import { BigNumber, Signer } from 'ethers' import { formatUnits, parseUnits } from 'ethers/lib/utils' import { expect } from 'chai' import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { bn } from '#/common/numbers' type ITokenSymbol = keyof ITokens const networkConfigToUse = networkConfig[31337] @@ -14,32 +15,47 @@ const mkToken = (symbol: ITokenSymbol) => ({ address: networkConfigToUse.tokens[symbol]! as string, symbol: symbol, }) -const mkTestCase = (symbol: T, amount: string) => ({ +const mkTestCase = ( + symbol: T, + amount: string, + inflationStartAmount: string +) => ({ token: mkToken(symbol), poolToken: mkToken(`a${symbol}` as ITokenSymbol), amount, + inflationStartAmount, }) const TOKENS_TO_TEST = [ - mkTestCase('USDC', '1000.0'), - mkTestCase('USDT', '1000.0'), - mkTestCase('DAI', '1000.0'), - mkTestCase('WETH', '1.0'), - mkTestCase('stETH', '1.0'), - mkTestCase('WBTC', '1.0'), + mkTestCase('USDC', '1000.0', '1'), + mkTestCase('USDT', '1000.0', '1'), + mkTestCase('DAI', '1000.0', '1'), + mkTestCase('WETH', '200.0', '1'), + mkTestCase('stETH', '1.0', '2'), + mkTestCase('WBTC', '1.0', '1'), ] type ITestSuiteVariant = typeof TOKENS_TO_TEST[number] -const execTestForToken = ({ token, poolToken, amount }: ITestSuiteVariant) => { +const execTestForToken = ({ + token, + poolToken, + amount, + inflationStartAmount, +}: ITestSuiteVariant) => { describe('Tokenised Morpho Position - ' + token.symbol, () => { const beforeEachFn = async () => { const factories = { ERC20Mock: await ethers.getContractFactory('ERC20Mock'), MorphoTokenisedDeposit: await ethers.getContractFactory('MorphoAaveV2TokenisedDeposit'), } + const instances = { underlying: factories.ERC20Mock.attach(token.address), morpho: factories.ERC20Mock.attach(networkConfigToUse.tokens.MORPHO!), + morphoAaveV2Controller: await ethers.getContractAt( + 'IMorpho', + networkConfigToUse.MORPHO_AAVE_CONTROLLER! + ), tokenVault: await factories.MorphoTokenisedDeposit.deploy({ underlyingERC20: token.address, poolToken: poolToken.address, @@ -51,6 +67,8 @@ const execTestForToken = ({ token, poolToken, amount }: ITestSuiteVariant) => { } const underlyingDecimals = await instances.underlying.decimals() const shareDecimals = await instances.tokenVault.decimals() + const amountBN = parseUnits(amount, underlyingDecimals) + const signers = await ethers.getSigners() const users = { alice: signers[0], @@ -59,32 +77,46 @@ const execTestForToken = ({ token, poolToken, amount }: ITestSuiteVariant) => { } await whileImpersonating(whales[token.address.toLowerCase()], async (whaleSigner) => { - await instances.underlying - .connect(whaleSigner) - .transfer(users.alice.address, parseUnits(amount, underlyingDecimals)) - await instances.underlying - .connect(whaleSigner) - .transfer(users.bob.address, parseUnits(amount, underlyingDecimals)) - await instances.underlying - .connect(whaleSigner) - .transfer(users.charlie.address, parseUnits(amount, underlyingDecimals)) + await instances.underlying.connect(whaleSigner).transfer(users.alice.address, amountBN) + await instances.underlying.connect(whaleSigner).transfer(users.bob.address, amountBN) + await instances.underlying.connect(whaleSigner).transfer(users.charlie.address, amountBN) }) return { factories, instances, + amountBN, users, methods: { + async mint(user: Signer, amount: BigNumber) { + await whileImpersonating(whales[token.address.toLowerCase()], async (whaleSigner) => { + await instances.underlying + .connect(whaleSigner) + .transfer(await user.getAddress(), amount) + }) + }, deposit: async (user: Signer, amount: string, dest?: string) => { + await instances.underlying.connect(user).approve(instances.tokenVault.address, 0) await instances.underlying .connect(user) - .approve(instances.tokenVault.address, parseUnits(amount, underlyingDecimals)) + .approve(instances.tokenVault.address, ethers.constants.MaxUint256) await instances.tokenVault .connect(user) .deposit(parseUnits(amount, underlyingDecimals), dest ?? (await user.getAddress())) }, + + depositBN: async (user: Signer, amount: BigNumber, dest?: string) => { + await instances.underlying.connect(user).approve(instances.tokenVault.address, 0) + await instances.underlying + .connect(user) + .approve(instances.tokenVault.address, ethers.constants.MaxUint256) + + await instances.tokenVault + .connect(user) + .deposit(amount, dest ?? (await user.getAddress())) + }, shares: async (user: Signer) => { return formatUnits( - await instances.tokenVault.connect(user).maxRedeem(await user.getAddress()), + await instances.tokenVault.connect(user).balanceOf(await user.getAddress()), shareDecimals ) }, @@ -103,17 +135,26 @@ const execTestForToken = ({ token, poolToken, amount }: ITestSuiteVariant) => { await user.getAddress() ) }, + redeem: async (user: Signer, shares: string, dest?: string) => { + await instances.tokenVault + .connect(user) + .redeem( + parseUnits(shares, await instances.tokenVault.decimals()), + dest ?? (await user.getAddress()), + await user.getAddress() + ) + }, balanceUnderlying: async (user: Signer) => { return formatUnits( - await instances.underlying.connect(user).balanceOf(await user.getAddress()), + await instances.underlying.balanceOf(await user.getAddress()), underlyingDecimals ) }, + balanceUnderlyingBn: async (user: Signer) => { + return await instances.underlying.balanceOf(await user.getAddress()) + }, balanceMorpho: async (user: Signer) => { - return formatUnits( - await instances.morpho.connect(user).balanceOf(await user.getAddress()), - 18 - ) + return formatUnits(await instances.morpho.balanceOf(await user.getAddress()), 18) }, transferShares: async (from: Signer, to: Signer, amount: string) => { await instances.tokenVault @@ -146,7 +187,6 @@ const execTestForToken = ({ token, poolToken, amount }: ITestSuiteVariant) => { const fraction = (percent: number) => ((amountAsNumber * percent) / 100).toFixed(1) const closeTo = async (actual: Promise, expected: string) => { - await new Promise((r) => setTimeout(r, 200)) expect(parseFloat(await actual)).to.closeTo(parseFloat(expected), 0.5) } @@ -212,11 +252,57 @@ const execTestForToken = ({ token, poolToken, amount }: ITestSuiteVariant) => { await closeTo(methods.assets(bob), fraction(50)) }) + it('Regression Test - C4 July 2023 Issue #5', async () => { + const { + users: { alice, bob }, + methods, + instances, + amountBN, + } = context + const orignalBalance = await methods.balanceUnderlying(bob) + await instances.underlying + .connect(bob) + .approve(instances.morphoAaveV2Controller.address, ethers.constants.MaxUint256) + + await instances.underlying + .connect(bob) + .approve(instances.tokenVault.address, ethers.constants.MaxUint256) + + // Mint a few more tokens so we have enough for the initial 1 wei of a share + await methods.mint(bob, bn(inflationStartAmount).mul(10)) + await methods.depositBN(bob, bn(inflationStartAmount)) + + await instances.morphoAaveV2Controller + .connect(bob) + ['supply(address,address,uint256)']( + await instances.tokenVault.poolToken(), + instances.tokenVault.address, + amountBN + ) + + await closeTo(methods.balanceUnderlying(bob), '0.0') + expect(await methods.shares(alice)).to.equal('0.0') + await methods.depositBN(alice, amountBN.div(2)) + + expect(await methods.shares(alice)).to.not.equal('0.0') + // expect(await methods.shares(alice)).to.equal('0.0') // <- inflation attack check + // Bob inflated his 1 wei of a share share to be worth all of Alices deposit + // ^ The attack above ultimately does not seem to be worth it for the attacker + // half 25% of the attackers funds end up locked in the zero'th share of the vault + + await methods.withdraw(bob, await methods.assets(bob)) + const postWithdrawalBalance = parseFloat(await methods.balanceUnderlying(bob)) + + // Bob should loose funds from the attack + expect(postWithdrawalBalance).lt(parseFloat(orignalBalance)) + }) + /** * There is a test for claiming rewards in the MorphoAAVEFiatCollateral.test.ts */ }) } + describe('MorphoAaveV2TokenisedDeposit', () => { TOKENS_TO_TEST.forEach(execTestForToken) }) From 2cb41d451ab542c2e3fa40ba48623b8856b1fcef Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Tue, 22 Aug 2023 11:11:55 -0300 Subject: [PATCH 378/462] avoid revert when checking recoll auctions --- contracts/facade/FacadeAct.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 7c6d952a8c..a071b74c2d 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -253,7 +253,8 @@ contract FacadeAct is IFacadeAct, Multicall { bytes1 majorVersion = bytes(bm.version())[0]; if (majorVersion == MAJOR_VERSION_3) { - bm.rebalance(TradeKind.DUTCH_AUCTION); + // solhint-disable-next-line no-empty-blocks + try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch {} } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { IERC20[] memory emptyERC20s = new IERC20[](0); address(bm).functionCall( From 91cc33ef69a1f4591586c1f9bc1d95c6e707989b Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Tue, 22 Aug 2023 11:14:35 -0300 Subject: [PATCH 379/462] rollback change in facade --- contracts/facade/FacadeAct.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index a071b74c2d..7c6d952a8c 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -253,8 +253,7 @@ contract FacadeAct is IFacadeAct, Multicall { bytes1 majorVersion = bytes(bm.version())[0]; if (majorVersion == MAJOR_VERSION_3) { - // solhint-disable-next-line no-empty-blocks - try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch {} + bm.rebalance(TradeKind.DUTCH_AUCTION); } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { IERC20[] memory emptyERC20s = new IERC20[](0); address(bm).functionCall( From 19d84ef987c6eda99a46e0a83123cfcde650d116 Mon Sep 17 00:00:00 2001 From: brr Date: Tue, 22 Aug 2023 18:07:08 +0100 Subject: [PATCH 380/462] C4: #35 #32, fixes rETH / cbETH / ankrETH ref unit and adds soft default checks (#899) Co-authored-by: Taylor Brent --- .../assets/ankr/AnkrStakedEthCollateral.sol | 41 ++++-- contracts/plugins/assets/ankr/README.md | 12 +- .../plugins/assets/cbeth/CBETHCollateral.sol | 45 +++--- contracts/plugins/assets/cbeth/README.md | 6 +- contracts/plugins/assets/rocket-eth/README.md | 12 +- .../assets/rocket-eth/RethCollateral.sol | 44 +++--- .../ankr/AnkrEthCollateralTestSuite.test.ts | 128 ++++++++++++++---- .../cbeth/CBETHCollateral.test.ts | 97 +++++++------ .../individual-collateral/collateralTests.ts | 4 +- .../RethCollateralTestSuite.test.ts | 84 ++++++------ 10 files changed, 295 insertions(+), 178 deletions(-) diff --git a/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol b/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol index 251686c301..594db5465e 100644 --- a/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol +++ b/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol @@ -9,23 +9,34 @@ import "./vendor/IAnkrETH.sol"; /** * @title Ankr Staked Eth Collateral - * @notice Collateral plugin for Ankr ankrETH, + * @notice Collateral plugin for Ankr's ankrETH * tok = ankrETH - * ref = ETH + * ref = ETH2 * tar = ETH * UoA = USD + * @dev Not ready to deploy yet. Missing a {target/tok} feed from Chainlink. */ contract AnkrStakedEthCollateral is AppreciatingFiatCollateral { using OracleLib for AggregatorV3Interface; using FixLib for uint192; - // solhint-disable no-empty-blocks - /// @param config.chainlinkFeed Feed units: {UoA/ref} - constructor(CollateralConfig memory config, uint192 revenueHiding) - AppreciatingFiatCollateral(config, revenueHiding) - {} + AggregatorV3Interface public immutable targetPerTokChainlinkFeed; // {target/tok} + uint48 public immutable targetPerTokChainlinkTimeout; - // solhint-enable no-empty-blocks + /// @param config.chainlinkFeed {UoA/target} price of ETH in USD terms + /// @param _targetPerTokChainlinkFeed {target/tok} price of cbETH in ETH terms + constructor( + CollateralConfig memory config, + uint192 revenueHiding, + AggregatorV3Interface _targetPerTokChainlinkFeed, + uint48 _targetPerTokChainlinkTimeout + ) AppreciatingFiatCollateral(config, revenueHiding) { + require(address(_targetPerTokChainlinkFeed) != address(0), "missing targetPerTok feed"); + require(_targetPerTokChainlinkTimeout != 0, "targetPerTokChainlinkTimeout zero"); + + targetPerTokChainlinkFeed = _targetPerTokChainlinkFeed; + targetPerTokChainlinkTimeout = _targetPerTokChainlinkTimeout; + } /// Can revert, used by other contract functions in order to catch errors /// @return low {UoA/tok} The low price estimate @@ -41,22 +52,22 @@ contract AnkrStakedEthCollateral is AppreciatingFiatCollateral { uint192 pegPrice ) { - uint192 pricePerRef = chainlinkFeed.price(oracleTimeout); // {UoA/ref} + uint192 targetPerTok = targetPerTokChainlinkFeed.price(targetPerTokChainlinkTimeout); - // {UoA/tok} = {UoA/ref} * {ref/tok} - uint192 p = pricePerRef.mul(_underlyingRefPerTok()); + // {UoA/tok} = {UoA/target} * {target/tok} + uint192 p = chainlinkFeed.price(oracleTimeout).mul(targetPerTok); uint192 err = p.mul(oracleError, CEIL); - low = p - err; high = p + err; + low = p - err; // assert(low <= high); obviously true just by inspection - pegPrice = targetPerRef(); // ETH/ETH + // {target/ref} = {target/tok} / {ref/tok} + pegPrice = targetPerTok.div(_underlyingRefPerTok()); } /// @return {ref/tok} Quantity of whole reference units per whole collateral tokens function _underlyingRefPerTok() internal view override returns (uint192) { - uint256 rate = IAnkrETH(address(erc20)).ratio(); - return FIX_ONE.div(_safeWrap(rate), FLOOR); + return FIX_ONE.div(_safeWrap(IAnkrETH(address(erc20)).ratio()), FLOOR); } } diff --git a/contracts/plugins/assets/ankr/README.md b/contracts/plugins/assets/ankr/README.md index 37652a5e8f..667f144c85 100644 --- a/contracts/plugins/assets/ankr/README.md +++ b/contracts/plugins/assets/ankr/README.md @@ -8,22 +8,22 @@ This plugin allows the usage of [ankrETH](https://www.ankr.com/about-staking/) a The `ankrETH` token represents the users staked ETH plus accumulated staking rewards. It is immediately liquid, which enables users to trade them instantly, or unstake them to redeem the original underlying asset. -User's balances in `ankrETH` remain constant, but the value of each ankrETH token grows over time. It is a reward-bearing token, meaning that the fair value of 1 ankrETH token vs. ETH increases over time as staking rewards accumulate. When possible, users will have the option to redeem ankrETH and unstake ETH with accumulated [staking rewards](https://www.ankr.com/docs/staking/liquid-staking/eth/overview/). +User's balances in `ankrETH` remain constant, but the value of each ankrETH token grows over time. It is a reward-bearing token, meaning that the fair value of 1 ankrETH token vs. ETH2 increases over time as staking rewards accumulate. When possible, users will have the option to redeem ankrETH and unstake ETH2 for ETH with accumulated [staking rewards](https://www.ankr.com/docs/staking/liquid-staking/eth/overview/). ## Implementation ### Units -| tok | ref | target | UoA | -| ------- | --- | ------ | --- | -| ankrETH | ETH | ETH | USD | +| tok | ref | target | UoA | +| ------- | ---- | ------ | --- | +| ankrETH | ETH2 | ETH | USD | ### Functions #### refPerTok {ref/tok} -The exchange rate between ETH and ankrETH can be fetched using the ankrETH contract function `ratio()`. From this, we can obtain the inverse rate from ankrETH to ETH, and use that as `refPerTok`. +The exchange rate between ETH2 and ankrETH can be fetched using the ankrETH contract function `ratio()`. From this, we can obtain the inverse rate from ankrETH to ETH2, and use that as `refPerTok`. -This new ratio, increases over time, which means that the amount of ETH redeemable for each ankrETH token always increases. +This new ratio, increases over time, which means that the amount of ETH redeemable for each ankrETH token always increases, though redemptions sit behind a withdrawal queue. `ratio()` returns the exchange rate in 18 decimals. diff --git a/contracts/plugins/assets/cbeth/CBETHCollateral.sol b/contracts/plugins/assets/cbeth/CBETHCollateral.sol index 40ee822e38..f9aec23f54 100644 --- a/contracts/plugins/assets/cbeth/CBETHCollateral.sol +++ b/contracts/plugins/assets/cbeth/CBETHCollateral.sol @@ -2,8 +2,9 @@ pragma solidity 0.8.19; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { _safeWrap } from "../../../libraries/Fixed.sol"; -import "../AppreciatingFiatCollateral.sol"; +import { CEIL, FixLib, _safeWrap } from "../../../libraries/Fixed.sol"; +import { AggregatorV3Interface, OracleLib } from "../OracleLib.sol"; +import { CollateralConfig, AppreciatingFiatCollateral } from "../AppreciatingFiatCollateral.sol"; interface CBEth is IERC20Metadata { function mint(address account, uint256 amount) external returns (bool); @@ -15,25 +16,34 @@ interface CBEth is IERC20Metadata { function exchangeRate() external view returns (uint256 _exchangeRate); } +/** + * @title CBEthCollateral + * @notice Collateral plugin for Coinbase's staked ETH + * tok = cbETH + * ref = ETH2 + * tar = ETH + * UoA = USD + */ contract CBEthCollateral is AppreciatingFiatCollateral { using OracleLib for AggregatorV3Interface; using FixLib for uint192; - CBEth public immutable token; - AggregatorV3Interface public immutable refPerTokChainlinkFeed; - uint48 public immutable refPerTokChainlinkTimeout; + AggregatorV3Interface public immutable targetPerTokChainlinkFeed; // {target/tok} + uint48 public immutable targetPerTokChainlinkTimeout; - /// @param config.chainlinkFeed {UoA/ref} price of DAI in USD terms + /// @param config.chainlinkFeed {UoA/target} price of ETH in USD terms + /// @param _targetPerTokChainlinkFeed {target/tok} price of cbETH in ETH terms constructor( CollateralConfig memory config, uint192 revenueHiding, - AggregatorV3Interface _refPerTokChainlinkFeed, - uint48 _refPerTokChainlinkTimeout + AggregatorV3Interface _targetPerTokChainlinkFeed, + uint48 _targetPerTokChainlinkTimeout ) AppreciatingFiatCollateral(config, revenueHiding) { - token = CBEth(address(config.erc20)); + require(address(_targetPerTokChainlinkFeed) != address(0), "missing targetPerTok feed"); + require(_targetPerTokChainlinkTimeout != 0, "targetPerTokChainlinkTimeout zero"); - refPerTokChainlinkFeed = _refPerTokChainlinkFeed; - refPerTokChainlinkTimeout = _refPerTokChainlinkTimeout; + targetPerTokChainlinkFeed = _targetPerTokChainlinkFeed; + targetPerTokChainlinkTimeout = _targetPerTokChainlinkTimeout; } /// Can revert, used by other contract functions in order to catch errors @@ -50,21 +60,22 @@ contract CBEthCollateral is AppreciatingFiatCollateral { uint192 pegPrice ) { - // {UoA/tok} = {UoA/ref} * {ref/tok} - uint192 p = chainlinkFeed.price(oracleTimeout).mul( - refPerTokChainlinkFeed.price(refPerTokChainlinkTimeout) - ); + uint192 targetPerTok = targetPerTokChainlinkFeed.price(targetPerTokChainlinkTimeout); + + // {UoA/tok} = {UoA/target} * {target/tok} + uint192 p = chainlinkFeed.price(oracleTimeout).mul(targetPerTok); uint192 err = p.mul(oracleError, CEIL); high = p + err; low = p - err; // assert(low <= high); obviously true just by inspection - pegPrice = targetPerRef(); // {target/ref} ETH/ETH is always 1 + // {target/ref} = {target/tok} / {ref/tok} + pegPrice = targetPerTok.div(_underlyingRefPerTok()); } /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens function _underlyingRefPerTok() internal view override returns (uint192) { - return _safeWrap(token.exchangeRate()); + return _safeWrap(CBEth(address(erc20)).exchangeRate()); } } diff --git a/contracts/plugins/assets/cbeth/README.md b/contracts/plugins/assets/cbeth/README.md index fe74735ca5..aa654f92eb 100644 --- a/contracts/plugins/assets/cbeth/README.md +++ b/contracts/plugins/assets/cbeth/README.md @@ -8,9 +8,9 @@ This plugin allows `CBETH` holders to use their tokens as collateral in the Rese ### Units -| tok | ref | target | UoA | -| ----- | --- | ------ | --- | -| cbeth | ETH | ETH | ETH | +| tok | ref | target | UoA | +| ----- | ---- | ------ | --- | +| cbeth | ETH2 | ETH | USD | ### Functions diff --git a/contracts/plugins/assets/rocket-eth/README.md b/contracts/plugins/assets/rocket-eth/README.md index c896231af0..78b2ab3441 100644 --- a/contracts/plugins/assets/rocket-eth/README.md +++ b/contracts/plugins/assets/rocket-eth/README.md @@ -15,15 +15,15 @@ stake in the POS ETH2.0 consenus layer. ### Units -| tok | ref | target | UoA | -| ---- | --- | ------ | --- | -| rETH | ETH | ETH | USD | +| tok | ref | target | UoA | +| ---- | ---- | ------ | --- | +| rETH | ETH2 | ETH | USD | ### refPerTok() -Gets the exchange rate for `rETH` to `ETH` from the rETH token contract using the [getExchangeRate()](https://github.com/rocket-pool/rocketpool/blob/master/contracts/contract/token/RocketTokenRETH.sol#L66) -function. This is the rate used by rocket pool when converting between reth and eth and is closely followed by secondary markets. -While the value of ETH/rETH **should** be only-increasing, it is possible that slashing or inactivity events could occur for the rETH +Gets the exchange rate for `rETH` to `ETH2` from the rETH token contract using the [getExchangeRate()](https://github.com/rocket-pool/rocketpool/blob/master/contracts/contract/token/RocketTokenRETH.sol#L66) +function. This is the rate used by rocket pool when converting between reth and eth2 and is closely followed by secondary markets. +While the value of ETH2/rETH **should** be only-increasing, it is possible that slashing or inactivity events could occur for the rETH validators. As such, `rETH` inherits `AppreciatingFiatCollateral` to allow for some amount of revenue-hiding. The amount of revenue-hiding should be determined by the deployer, but can likely be quite high, as it is more likely that any dips, however large, would be temporary, and, in particularly bad instances, be covered by the Rocket Pool protocol. diff --git a/contracts/plugins/assets/rocket-eth/RethCollateral.sol b/contracts/plugins/assets/rocket-eth/RethCollateral.sol index 42888b93d7..f7f4386650 100644 --- a/contracts/plugins/assets/rocket-eth/RethCollateral.sol +++ b/contracts/plugins/assets/rocket-eth/RethCollateral.sol @@ -1,17 +1,16 @@ // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.19; -import "@openzeppelin/contracts/utils/math/Math.sol"; -import "../../../libraries/Fixed.sol"; -import "../AppreciatingFiatCollateral.sol"; -import "../OracleLib.sol"; -import "./vendor/IReth.sol"; +import { CEIL, FixLib, _safeWrap } from "../../../libraries/Fixed.sol"; +import { AggregatorV3Interface, OracleLib } from "../OracleLib.sol"; +import { CollateralConfig, AppreciatingFiatCollateral } from "../AppreciatingFiatCollateral.sol"; +import { IReth } from "./vendor/IReth.sol"; /** * @title RethCollateral - * @notice Collateral plugin for Rocket-Pool ETH, + * @notice Collateral plugin for Rocket-Pool ETH * tok = rETH - * ref = ETH + * ref = ETH2 * tar = ETH * UoA = USD */ @@ -19,20 +18,22 @@ contract RethCollateral is AppreciatingFiatCollateral { using OracleLib for AggregatorV3Interface; using FixLib for uint192; - AggregatorV3Interface public immutable refPerTokChainlinkFeed; - uint48 public immutable refPerTokChainlinkTimeout; + AggregatorV3Interface public immutable targetPerTokChainlinkFeed; + uint48 public immutable targetPerTokChainlinkTimeout; - /// @param config.chainlinkFeed Feed units: {UoA/ref} + /// @param config.chainlinkFeed {UoA/target} price of ETH in USD terms + /// @param _targetPerTokChainlinkFeed {target/tok} price of rETH in ETH terms constructor( CollateralConfig memory config, uint192 revenueHiding, - AggregatorV3Interface _refPerTokChainlinkFeed, - uint48 _refPerTokChainlinkTimeout + AggregatorV3Interface _targetPerTokChainlinkFeed, + uint48 _targetPerTokChainlinkTimeout ) AppreciatingFiatCollateral(config, revenueHiding) { - require(address(_refPerTokChainlinkFeed) != address(0), "missing refPerTok feed"); - require(_refPerTokChainlinkTimeout != 0, "refPerTokChainlinkTimeout zero"); - refPerTokChainlinkFeed = _refPerTokChainlinkFeed; - refPerTokChainlinkTimeout = _refPerTokChainlinkTimeout; + require(address(_targetPerTokChainlinkFeed) != address(0), "missing targetPerTok feed"); + require(_targetPerTokChainlinkTimeout != 0, "targetPerTokChainlinkTimeout zero"); + + targetPerTokChainlinkFeed = _targetPerTokChainlinkFeed; + targetPerTokChainlinkTimeout = _targetPerTokChainlinkTimeout; } /// Can revert, used by other contract functions in order to catch errors @@ -49,17 +50,18 @@ contract RethCollateral is AppreciatingFiatCollateral { uint192 pegPrice ) { - // {UoA/tok} = {UoA/ref} * {ref/tok} - uint192 p = chainlinkFeed.price(oracleTimeout).mul( - refPerTokChainlinkFeed.price(refPerTokChainlinkTimeout) - ); + uint192 targetPerTok = targetPerTokChainlinkFeed.price(targetPerTokChainlinkTimeout); + + // {UoA/tok} = {UoA/target} * {target/tok} + uint192 p = chainlinkFeed.price(oracleTimeout).mul(targetPerTok); uint192 err = p.mul(oracleError, CEIL); high = p + err; low = p - err; // assert(low <= high); obviously true just by inspection - pegPrice = targetPerRef(); // {target/ref} ETH/ETH is always 1 + // {target/ref} = {target/tok} / {ref/tok} + pegPrice = targetPerTok.div(_underlyingRefPerTok()); } /// @return {ref/tok} Quantity of whole reference units per whole collateral tokens diff --git a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts index eed4bb3c43..79f063c586 100644 --- a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts @@ -5,7 +5,6 @@ import { ethers } from 'hardhat' import { expect } from 'chai' import { ContractFactory, BigNumberish, BigNumber } from 'ethers' import { - ERC20Mock, MockV3Aggregator, MockV3Aggregator__factory, TestICollateral, @@ -33,13 +32,19 @@ import { whileImpersonating } from '../../../utils/impersonation' interface AnkrETHCollateralFixtureContext extends CollateralFixtureContext { ankreth: IAnkrETH + targetPerTokChainlinkFeed: MockV3Aggregator +} + +interface AnkrETHCollateralOpts extends CollateralOpts { + targetPerTokChainlinkFeed?: string + targetPerTokChainlinkTimeout?: BigNumberish } /* Define deployment functions */ -export const defaultAnkrEthCollateralOpts: CollateralOpts = { +export const defaultAnkrETHCollateralOpts: AnkrETHCollateralOpts = { erc20: ANKRETH, targetName: ethers.utils.formatBytes32String('ETH'), rewardERC20: ZERO_ADDRESS, @@ -53,8 +58,24 @@ export const defaultAnkrEthCollateralOpts: CollateralOpts = { revenueHiding: fp('0'), } -export const deployCollateral = async (opts: CollateralOpts = {}): Promise => { - opts = { ...defaultAnkrEthCollateralOpts, ...opts } +export const deployCollateral = async ( + opts: AnkrETHCollateralOpts = {} +): Promise => { + opts = { ...defaultAnkrETHCollateralOpts, ...opts } + + if (opts.targetPerTokChainlinkFeed === undefined) { + // Use mock targetPerTok feed until Chainlink deploys a real one + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + const targetPerTokChainlinkFeed = ( + await MockV3AggregatorFactory.deploy(18, targetPerTokChainlinkDefaultAnswer) + ) + opts.targetPerTokChainlinkFeed = targetPerTokChainlinkFeed.address + } + if (opts.targetPerTokChainlinkTimeout === undefined) { + opts.targetPerTokChainlinkTimeout = ORACLE_TIMEOUT + } const AnkrETHCollateralFactory: ContractFactory = await ethers.getContractFactory( 'AnkrStakedEthCollateral' @@ -73,6 +94,8 @@ export const deployCollateral = async (opts: CollateralOpts = {}): Promise = () => Promise const makeCollateralFixtureContext = ( alice: SignerWithAddress, - opts: CollateralOpts = {} + opts: AnkrETHCollateralOpts = {} ): Fixture => { - const collateralOpts = { ...defaultAnkrEthCollateralOpts, ...opts } + const collateralOpts = { ...defaultAnkrETHCollateralOpts, ...opts } const makeCollateralFixtureContext = async () => { const MockV3AggregatorFactory = ( @@ -105,17 +129,22 @@ const makeCollateralFixtureContext = ( collateralOpts.chainlinkFeed = chainlinkFeed.address + const targetPerTokChainlinkFeed = ( + await MockV3AggregatorFactory.deploy(18, targetPerTokChainlinkDefaultAnswer) + ) + collateralOpts.targetPerTokChainlinkFeed = targetPerTokChainlinkFeed.address + collateralOpts.targetPerTokChainlinkTimeout = ORACLE_TIMEOUT + const ankreth = (await ethers.getContractAt('IAnkrETH', ANKRETH)) as IAnkrETH - const rewardToken = (await ethers.getContractAt('ERC20Mock', ZERO_ADDRESS)) as ERC20Mock const collateral = await deployCollateral(collateralOpts) return { alice, collateral, chainlinkFeed, + targetPerTokChainlinkFeed, ankreth, tok: ankreth, - rewardToken, } } @@ -135,50 +164,78 @@ const mintCollateralTo: MintCollateralFunc = as await mintAnkrETH(ctx.ankreth, user, amount, recipient) } -// eslint-disable-next-line @typescript-eslint/no-empty-function -const reduceTargetPerRef = async () => {} +const changeTargetPerRef = async ( + ctx: AnkrETHCollateralFixtureContext, + percentChange: BigNumber +) => { + // We leave the actual refPerTok exchange where it is and just change {target/tok} + { + const lastRound = await ctx.targetPerTokChainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) + await ctx.targetPerTokChainlinkFeed.updateAnswer(nextAnswer) + } +} -// eslint-disable-next-line @typescript-eslint/no-empty-function -const increaseTargetPerRef = async () => {} +const reduceTargetPerRef = async ( + ctx: AnkrETHCollateralFixtureContext, + pctDecrease: BigNumberish +) => { + await changeTargetPerRef(ctx, bn(pctDecrease).mul(-1)) +} -const reduceRefPerTok = async (ctx: AnkrETHCollateralFixtureContext, pctDecrease: BigNumberish) => { +const increaseTargetPerRef = async ( + ctx: AnkrETHCollateralFixtureContext, + pctIncrease: BigNumberish +) => { + await changeTargetPerRef(ctx, bn(pctIncrease)) +} + +const changeRefPerTok = async (ctx: AnkrETHCollateralFixtureContext, percentChange: BigNumber) => { const ankrETH = (await ethers.getContractAt('IAnkrETH', ANKRETH)) as IAnkrETH - // Increase ratio so refPerTok decreases + // Move ratio in opposite direction as percentChange const currentRatio = await ankrETH.ratio() - const newRatio: BigNumberish = currentRatio.add(currentRatio.mul(pctDecrease).div(100)) + const newRatio: BigNumberish = currentRatio.add(currentRatio.mul(percentChange.mul(-1)).div(100)) // Impersonate AnkrETH Owner await whileImpersonating(ANKRETH_OWNER, async (ankrEthOwnerSigner) => { await ankrETH.connect(ankrEthOwnerSigner).updateRatio(newRatio) }) + + { + const lastRound = await ctx.targetPerTokChainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) + await ctx.targetPerTokChainlinkFeed.updateAnswer(nextAnswer) + } + + { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } +} + +const reduceRefPerTok = async (ctx: AnkrETHCollateralFixtureContext, pctDecrease: BigNumberish) => { + await changeRefPerTok(ctx, bn(pctDecrease).mul(-1)) } const increaseRefPerTok = async ( ctx: AnkrETHCollateralFixtureContext, pctIncrease: BigNumberish ) => { - const ankrETH = (await ethers.getContractAt('IAnkrETH', ANKRETH)) as IAnkrETH - - // Decrease ratio so refPerTok increases - const currentRatio = await ankrETH.ratio() - const newRatio: BigNumberish = currentRatio.sub(currentRatio.mul(pctIncrease).div(100)) - - // Impersonate AnkrETH Owner - await whileImpersonating(ANKRETH_OWNER, async (ankrEthOwnerSigner) => { - await ankrETH.connect(ankrEthOwnerSigner).updateRatio(newRatio) - }) + await changeRefPerTok(ctx, bn(pctIncrease)) } const getExpectedPrice = async (ctx: AnkrETHCollateralFixtureContext): Promise => { const clData = await ctx.chainlinkFeed.latestRoundData() const clDecimals = await ctx.chainlinkFeed.decimals() - const refPerTok = await ctx.collateral.refPerTok() + const clRptData = await ctx.targetPerTokChainlinkFeed.latestRoundData() + const clRptDecimals = await ctx.targetPerTokChainlinkFeed.decimals() return clData.answer .mul(bn(10).pow(18 - clDecimals)) - .mul(refPerTok) + .mul(clRptData.answer.mul(bn(10).pow(18 - clRptDecimals))) .div(fp('1')) } @@ -187,7 +244,19 @@ const getExpectedPrice = async (ctx: AnkrETHCollateralFixtureContext): Promise {} +const collateralSpecificConstructorTests = () => { + it('does not allow missing targetPerTok chainlink feed', async () => { + await expect( + deployCollateral({ targetPerTokChainlinkFeed: ethers.constants.AddressZero }) + ).to.be.revertedWith('missing targetPerTok feed') + }) + + it('does not allow targetPerTok oracle timeout at 0', async () => { + await expect(deployCollateral({ targetPerTokChainlinkTimeout: 0 })).to.be.revertedWith( + 'targetPerTokChainlinkTimeout zero' + ) + }) +} // eslint-disable-next-line @typescript-eslint/no-empty-function const collateralSpecificStatusTests = () => {} @@ -212,13 +281,14 @@ const opts = { increaseRefPerTok, getExpectedPrice, itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, + itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, resetFork, collateralName: 'AnkrStakedETH', chainlinkDefaultAnswer, + itIsPricedByPeg: true, } collateralTests(opts) diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts index 60473286fc..fa6340bfb4 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts @@ -26,12 +26,12 @@ import hre from 'hardhat' interface CbEthCollateralFixtureContext extends CollateralFixtureContext { cbETH: CBEth - refPerTokChainlinkFeed: MockV3Aggregator + targetPerTokChainlinkFeed: MockV3Aggregator } interface CbEthCollateralOpts extends CollateralOpts { - refPerTokChainlinkFeed?: string - refPerTokChainlinkTimeout?: BigNumberish + targetPerTokChainlinkFeed?: string + targetPerTokChainlinkTimeout?: BigNumberish } export const deployCollateral = async ( @@ -54,8 +54,8 @@ export const deployCollateral = async ( delayUntilDefault: opts.delayUntilDefault, }, opts.revenueHiding, - opts.refPerTokChainlinkFeed ?? CBETH_ETH_PRICE_FEED, - opts.refPerTokChainlinkTimeout ?? ORACLE_TIMEOUT, + opts.targetPerTokChainlinkFeed ?? CBETH_ETH_PRICE_FEED, + opts.targetPerTokChainlinkTimeout ?? ORACLE_TIMEOUT, { gasLimit: 2000000000 } ) await collateral.deployed() @@ -66,7 +66,7 @@ export const deployCollateral = async ( } const chainlinkDefaultAnswer = bn('1600e8') -const refPerTokChainlinkDefaultAnswer = fp('1') +const targetPerTokChainlinkDefaultAnswer = fp('1.04027709') type Fixture = () => Promise @@ -85,12 +85,12 @@ const makeCollateralFixtureContext = ( await MockV3AggregatorFactory.deploy(8, chainlinkDefaultAnswer) ) collateralOpts.chainlinkFeed = chainlinkFeed.address - const refPerTokChainlinkFeed = ( - await MockV3AggregatorFactory.deploy(18, refPerTokChainlinkDefaultAnswer) + const targetPerTokChainlinkFeed = ( + await MockV3AggregatorFactory.deploy(18, targetPerTokChainlinkDefaultAnswer) ) - collateralOpts.refPerTokChainlinkFeed = refPerTokChainlinkFeed.address - collateralOpts.refPerTokChainlinkTimeout = PRICE_TIMEOUT + collateralOpts.targetPerTokChainlinkFeed = targetPerTokChainlinkFeed.address + collateralOpts.targetPerTokChainlinkTimeout = ORACLE_TIMEOUT const cbETH = (await ethers.getContractAt('CBEth', CB_ETH)) as unknown as CBEth const collateral = await deployCollateral(collateralOpts) @@ -99,7 +99,7 @@ const makeCollateralFixtureContext = ( alice, collateral, chainlinkFeed, - refPerTokChainlinkFeed, + targetPerTokChainlinkFeed, cbETH, tok: cbETH as unknown as ERC20Mock, } @@ -120,11 +120,28 @@ const mintCollateralTo: MintCollateralFunc = asyn await mintCBETH(amount, recipient) } -// eslint-disable-next-line @typescript-eslint/no-empty-function -const reduceTargetPerRef = async () => {} +const changeTargetPerRef = async (ctx: CbEthCollateralFixtureContext, percentChange: BigNumber) => { + // We leave the actual refPerTok exchange where it is and just change {target/tok} + { + const lastRound = await ctx.targetPerTokChainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) + await ctx.targetPerTokChainlinkFeed.updateAnswer(nextAnswer) + } +} -// eslint-disable-next-line @typescript-eslint/no-empty-function -const increaseTargetPerRef = async () => {} +const reduceTargetPerRef = async ( + ctx: CbEthCollateralFixtureContext, + pctDecrease: BigNumberish +) => { + await changeTargetPerRef(ctx, bn(pctDecrease).mul(-1)) +} + +const increaseTargetPerRef = async ( + ctx: CbEthCollateralFixtureContext, + pctDecrease: BigNumberish +) => { + await changeTargetPerRef(ctx, bn(pctDecrease)) +} const changeRefPerTok = async (ctx: CbEthCollateralFixtureContext, percentChange: BigNumber) => { await whileImpersonating(hre, CB_ETH_ORACLE, async (oracleSigner) => { @@ -133,9 +150,9 @@ const changeRefPerTok = async (ctx: CbEthCollateralFixtureContext, percentChange .connect(oracleSigner) .updateExchangeRate(rate.add(rate.mul(percentChange).div(bn('100')))) { - const lastRound = await ctx.refPerTokChainlinkFeed.latestRoundData() + const lastRound = await ctx.targetPerTokChainlinkFeed.latestRoundData() const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) - await ctx.refPerTokChainlinkFeed.updateAnswer(nextAnswer) + await ctx.targetPerTokChainlinkFeed.updateAnswer(nextAnswer) } { @@ -146,32 +163,19 @@ const changeRefPerTok = async (ctx: CbEthCollateralFixtureContext, percentChange }) } -// prettier-ignore -const reduceRefPerTok = async ( - ctx: CbEthCollateralFixtureContext, - pctDecrease: BigNumberish -) => { - await changeRefPerTok( - ctx, - bn(pctDecrease).mul(-1) - ) +const reduceRefPerTok = async (ctx: CbEthCollateralFixtureContext, pctDecrease: BigNumberish) => { + await changeRefPerTok(ctx, bn(pctDecrease).mul(-1)) } -// prettier-ignore -const increaseRefPerTok = async ( - ctx: CbEthCollateralFixtureContext, - pctIncrease: BigNumberish -) => { - await changeRefPerTok( - ctx, - bn(pctIncrease) - ) + +const increaseRefPerTok = async (ctx: CbEthCollateralFixtureContext, pctIncrease: BigNumberish) => { + await changeRefPerTok(ctx, bn(pctIncrease)) } const getExpectedPrice = async (ctx: CbEthCollateralFixtureContext): Promise => { const clData = await ctx.chainlinkFeed.latestRoundData() const clDecimals = await ctx.chainlinkFeed.decimals() - const clRptData = await ctx.refPerTokChainlinkFeed.latestRoundData() - const clRptDecimals = await ctx.refPerTokChainlinkFeed.decimals() + const clRptData = await ctx.targetPerTokChainlinkFeed.latestRoundData() + const clRptDecimals = await ctx.targetPerTokChainlinkFeed.decimals() return clData.answer .mul(bn(10).pow(18 - clDecimals)) @@ -184,7 +188,19 @@ const getExpectedPrice = async (ctx: CbEthCollateralFixtureContext): Promise {} +const collateralSpecificConstructorTests = () => { + it('does not allow missing targetPerTok chainlink feed', async () => { + await expect( + deployCollateral({ targetPerTokChainlinkFeed: ethers.constants.AddressZero }) + ).to.be.revertedWith('missing targetPerTok feed') + }) + + it('does not allow targetPerTok oracle timeout at 0', async () => { + await expect(deployCollateral({ targetPerTokChainlinkTimeout: 0 })).to.be.revertedWith( + 'targetPerTokChainlinkTimeout zero' + ) + }) +} // eslint-disable-next-line @typescript-eslint/no-empty-function const collateralSpecificStatusTests = () => {} @@ -221,13 +237,14 @@ const opts = { increaseRefPerTok, getExpectedPrice, itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, + itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, - resetFork: resetFork, + resetFork, collateralName: 'CBEthCollateral', chainlinkDefaultAnswer, + itIsPricedByPeg: true, } collateralTests(opts) diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 36ff13e747..ebda92aa3d 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -220,8 +220,8 @@ export default function fn( const newPrice = await getExpectedPrice(ctx) await expectPrice(collateral.address, newPrice, oracleError, true) const [newLow, newHigh] = await collateral.price() - expect(oldLow).to.not.equal(newLow) - expect(oldHigh).to.not.equal(newHigh) + expect(oldLow).to.be.lt(newLow) + expect(oldHigh).to.be.lt(newHigh) } else { // Check new prices -- no increase expected await expectPrice(collateral.address, expectedPrice, oracleError, true) diff --git a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts index 21b019a34c..506ed02fd2 100644 --- a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts @@ -38,12 +38,12 @@ import { whileImpersonating } from '#/test/utils/impersonation' interface RethCollateralFixtureContext extends CollateralFixtureContext { weth: WETH9 reth: IReth - refPerTokChainlinkFeed: MockV3Aggregator + targetPerTokChainlinkFeed: MockV3Aggregator } interface RethCollateralOpts extends CollateralOpts { - refPerTokChainlinkFeed?: string - refPerTokChainlinkTimeout?: BigNumberish + targetPerTokChainlinkFeed?: string + targetPerTokChainlinkTimeout?: BigNumberish } /* @@ -61,8 +61,8 @@ export const defaultRethCollateralOpts: RethCollateralOpts = { maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, - refPerTokChainlinkFeed: RETH_ETH_PRICE_FEED, - refPerTokChainlinkTimeout: ORACLE_TIMEOUT, + targetPerTokChainlinkFeed: RETH_ETH_PRICE_FEED, + targetPerTokChainlinkTimeout: ORACLE_TIMEOUT, revenueHiding: fp('0'), } @@ -84,8 +84,8 @@ export const deployCollateral = async (opts: RethCollateralOpts = {}): Promise = () => Promise @@ -116,11 +116,11 @@ const makeCollateralFixtureContext = ( await MockV3AggregatorFactory.deploy(8, chainlinkDefaultAnswer) ) - const refPerTokChainlinkFeed = ( + const targetPerTokChainlinkFeed = ( await MockV3AggregatorFactory.deploy(18, refPerTokChainlinkDefaultAnswer) ) collateralOpts.chainlinkFeed = chainlinkFeed.address - collateralOpts.refPerTokChainlinkFeed = refPerTokChainlinkFeed.address + collateralOpts.targetPerTokChainlinkFeed = targetPerTokChainlinkFeed.address const weth = (await ethers.getContractAt('WETH9', WETH)) as WETH9 const reth = (await ethers.getContractAt('IReth', RETH)) as IReth @@ -133,7 +133,7 @@ const makeCollateralFixtureContext = ( chainlinkFeed, weth, reth, - refPerTokChainlinkFeed, + targetPerTokChainlinkFeed, tok: reth, rewardToken, } @@ -205,19 +205,29 @@ const mintCollateralTo: MintCollateralFunc = async await mintRETH(ctx.reth, user, amount, recipient) } -// eslint-disable-next-line @typescript-eslint/no-empty-function -const reduceTargetPerRef = async () => {} - -// eslint-disable-next-line @typescript-eslint/no-empty-function -const increaseTargetPerRef = async () => {} +const changeTargetPerRef = async (ctx: RethCollateralFixtureContext, percentChange: BigNumber) => { + // We leave the actual refPerTok exchange where it is and just change {target/tok} + { + const lastRound = await ctx.targetPerTokChainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) + await ctx.targetPerTokChainlinkFeed.updateAnswer(nextAnswer) + } +} -const rocketBalanceKey = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('network.balance.total')) +const reduceTargetPerRef = async (ctx: RethCollateralFixtureContext, pctDecrease: BigNumberish) => { + await changeTargetPerRef(ctx, bn(pctDecrease).mul(-1)) +} -// prettier-ignore -const reduceRefPerTok = async ( +const increaseTargetPerRef = async ( ctx: RethCollateralFixtureContext, - pctDecrease: BigNumberish + pctDecrease: BigNumberish ) => { + await changeTargetPerRef(ctx, bn(pctDecrease)) +} + +const rocketBalanceKey = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('network.balance.total')) + +const reduceRefPerTok = async (ctx: RethCollateralFixtureContext, pctDecrease: BigNumberish) => { const rethNetworkBalances = await ethers.getContractAt( 'IRocketNetworkBalances', RETH_NETWORK_BALANCES @@ -229,16 +239,12 @@ const reduceRefPerTok = async ( await rocketStorage.connect(rethSigner).setUint(rocketBalanceKey, lowerBal) }) - const lastRound = await ctx.refPerTokChainlinkFeed.latestRoundData() + const lastRound = await ctx.targetPerTokChainlinkFeed.latestRoundData() const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) - await ctx.refPerTokChainlinkFeed.updateAnswer(nextAnswer) + await ctx.targetPerTokChainlinkFeed.updateAnswer(nextAnswer) } -// prettier-ignore -const increaseRefPerTok = async ( - ctx: RethCollateralFixtureContext, - pctIncrease: BigNumberish -) => { +const increaseRefPerTok = async (ctx: RethCollateralFixtureContext, pctIncrease: BigNumberish) => { const rethNetworkBalances = await ethers.getContractAt( 'IRocketNetworkBalances', RETH_NETWORK_BALANCES @@ -250,17 +256,17 @@ const increaseRefPerTok = async ( await rocketStorage.connect(rethSigner).setUint(rocketBalanceKey, lowerBal) }) - const lastRound = await ctx.refPerTokChainlinkFeed.latestRoundData() + const lastRound = await ctx.targetPerTokChainlinkFeed.latestRoundData() const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) - await ctx.refPerTokChainlinkFeed.updateAnswer(nextAnswer) + await ctx.targetPerTokChainlinkFeed.updateAnswer(nextAnswer) } const getExpectedPrice = async (ctx: RethCollateralFixtureContext): Promise => { const clData = await ctx.chainlinkFeed.latestRoundData() const clDecimals = await ctx.chainlinkFeed.decimals() - const clRptData = await ctx.refPerTokChainlinkFeed.latestRoundData() - const clRptDecimals = await ctx.refPerTokChainlinkFeed.decimals() + const clRptData = await ctx.targetPerTokChainlinkFeed.latestRoundData() + const clRptDecimals = await ctx.targetPerTokChainlinkFeed.decimals() return clData.answer .mul(bn(10).pow(18 - clDecimals)) @@ -272,17 +278,16 @@ const getExpectedPrice = async (ctx: RethCollateralFixtureContext): Promise { - it('does not allow missing refPerTok chainlink feed', async () => { + it('does not allow missing targetPerTok chainlink feed', async () => { await expect( - deployCollateral({ refPerTokChainlinkFeed: ethers.constants.AddressZero }) - ).to.be.revertedWith('missing refPerTok feed') + deployCollateral({ targetPerTokChainlinkFeed: ethers.constants.AddressZero }) + ).to.be.revertedWith('missing targetPerTok feed') }) - it('does not allow refPerTok oracle timeout at 0', async () => { - await expect(deployCollateral({ refPerTokChainlinkTimeout: 0 })).to.be.revertedWith( - 'refPerTokChainlinkTimeout zero' + it('does not allow targetPerTok oracle timeout at 0', async () => { + await expect(deployCollateral({ targetPerTokChainlinkTimeout: 0 })).to.be.revertedWith( + 'targetPerTokChainlinkTimeout zero' ) }) } @@ -310,13 +315,14 @@ const opts = { increaseRefPerTok, getExpectedPrice, itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, + itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, resetFork, collateralName: 'RocketPoolETH', chainlinkDefaultAnswer, + itIsPricedByPeg: true, } collateralTests(opts) From f8a7519f18d15dc68bc9ab2b3d9e39e40baedf86 Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Tue, 22 Aug 2023 13:09:06 -0400 Subject: [PATCH 381/462] document recommended throttle limits. (#905) --- docs/deployment-variables.md | 217 +++++++++++++++++++++++++++++++++++ docs/system-design.md | 208 --------------------------------- 2 files changed, 217 insertions(+), 208 deletions(-) create mode 100644 docs/deployment-variables.md diff --git a/docs/deployment-variables.md b/docs/deployment-variables.md new file mode 100644 index 0000000000..30a80c383b --- /dev/null +++ b/docs/deployment-variables.md @@ -0,0 +1,217 @@ +# Deployment Parameters + +### `dist` (revenue split) + +The fraction of revenues that should go towards RToken holders vs stakers, as given by the relative values of `dist.rTokenDist` and `dist.rsrDist`. This can be thought of as a single variable between 0 and 100% (during deployment). + +Default value: 60% to stakers and 40% to RToken holders. +Mainnet reasonable range: 0% to 100% + +### `minTradeVolume` + +Dimension: `{UoA}` + +The minimum sized trade that can be performed, in terms of the unit of account. + +Setting this too high will result in auctions happening infrequently or the RToken taking a haircut when it cannot be sure it has enough staked RSR to succeed in rebalancing at par. + +Setting this too low may allow griefers to delay important auctions. The variable should be set such that donations of size `minTradeVolume` would be worth delaying trading `batchAuctionLength` seconds. + +This variable should NOT be interpreted to mean that auction sizes above this value will necessarily clear. It could be the case that gas frictions are so high that auctions launched at this size are not worthy of bids. + +This parameter can be set to zero. + +Default value: `1e21` = $1k +Mainnet reasonable range: 1e19 to 1e23 + +#### `rTokenMaxTradeVolume` + +Dimension: `{UoA}` + +The maximum sized trade for any trade involving RToken, in terms of the unit of account. The high end of the price is applied to this variable to convert it to a token quantity. + +This parameter can be set to zero. + +Default value: `1e24` = $1M +Mainnet reasonable range: 1e22 to 1e27. + +### `rewardRatio` + +Dimension: `{1}` + +The `rewardRatio` is the fraction of the current reward amount that should be handed out per block. + +Default value: `68764601000000` = a half life of 14 days. + +Mainnet reasonable range: 1e11 to 1e13 + +To calculate: `ln(2) / (60*60*24*desired_days_in_half_life/12)`, and then multiply by 1e18. + +### `unstakingDelay` + +Dimension: `{seconds}` + +The unstaking delay is the number of seconds that all RSR unstakings must be delayed in order to account for stakers trying to frontrun defaults. It must be longer than governance cycle, and must be long enough that RSR stakers do not unstake in advance of foreseeable basket change in order to avoid being expensed for slippage. + +Default value: `1209600` = 2 weeks +Mainnet reasonable range: 1 to 31536000 + +### `tradingDelay` + +Dimension: `{seconds}` + +The trading delay is how many seconds should pass after the basket has been changed before a trade can be opened. In the long term this can be set to 0 after MEV searchers are firmly integrated, but at the start it may be useful to have a delay before trading in order to avoid worst-case prices. + +Default value: `7200` = 2 hours +Mainnet reasonable range: 0 to 604800 + +### `warmupPeriod` + +Dimension: `{seconds}` + +The warmup period is how many seconds should pass after the basket regained the SOUND status before an RToken can be issued and/or a trade can be opened. + +Default value: `900` = 15 minutes +Mainnet reasonable range: 0 to 604800 + +### `batchAuctionLength` + +Dimension: `{seconds}` + +The auction length is how many seconds long Gnosis EasyAuctions should be. + +Default value: `900` = 15 minutes +Mainnet reasonable range: 60 to 3600 + +### `dutchAuctionLength` + +Dimension: `{seconds}` + +The dutch auction length is how many seconds long falling-price dutch auctions should be. A longer period will result in less slippage due to better price granularity, and a shorter period will result in more slippage. + +In general, the dutchAuctionLength should be a multiple of the blocktime. This is not enforced at a smart-contract level. + +Default value: `1800` = 30 minutes +Mainnet reasonable range: 300 to 3600 + +At 30 minutes, a 12-second blocktime chain would have 10.87% price drops during the first 40% of the auction, and 0.055% price drops during the second 60%. + +### `backingBuffer` + +Dimension: `{1}` + +The backing buffer is a percentage value that describes how much overcollateralization to hold in the form of RToken. This buffer allows collateral tokens to be converted into RToken, which is a more efficient form of revenue production than trading each individual collateral for the desired RToken, and also adds a small buffer that can prevent RSR from being seized when there are small losses due to slippage during rebalancing. + +Default value: `1e15` = 0.1% +Mainnet reasonable range: 1e12 to 1e18 + +### `maxTradeSlippage` + +Dimension: `{1}` + +The max trade slippage is a percentage value that describes the maximum deviation from oracle prices that any trade can clear at. Oracle prices have ranges of their own; the maximum trade slippage permits additional price movement beyond the worst-case oracle price. + +Default value: `0.01e18` = 1% +Mainnet reasonable range: 1e12 to 1e18 + +### `shortFreeze` + +Dimension: `{s}` + +The number of seconds a short freeze lasts. Governance can freeze forever. + +Default value: `259200` = 3 days +Mainnet reasonable range: 3600 to 2592000 (1 hour to 1 month) + +### `longFreeze` + +Dimension: `{s}` + +The number of seconds a long freeze lasts. Long freezes can be disabled by removing all addresses from the `LONG_FREEZER` role. A long freezer has 6 charges that can be used. + +Default value: `604800` = 7 days +Mainnet reasonable range: 86400 to 31536000 (1 day to 1 year) + +### `withdrawalLeak` + +Dimension: `{1}` + +The fraction of RSR stake that should be permitted to withdraw without a refresh. When cumulative withdrawals (or a single withdrawal) exceed this fraction, gas must be paid to refresh all assets. + +Setting this number larger allows unstakers to save more on gas at the cost of allowing more RSR to exit improperly prior to a default. + +Default value: `5e16` = 5% +Mainnet reasonable range: 0 to 25e16 (0 to 25%) + +### `RToken Supply Throttles` + +In order to restrict the system to organic patterns of behavior, we maintain two supply throttles, one for net issuance and one for net redemption. When a supply change occurs, a check is performed to ensure this does not move the supply more than an acceptable range over a period; a period is fixed to be an hour. The acceptable range (per throttle) is a function of the `amtRate` and `pctRate` variables. **It is the maximum of whichever variable provides the larger rate.** + +The recommended starting values (amt-rate normalized to $USD) for these parameters are as follows: +|**Parameter**|**Value**| +|-------------|---------| +|issuanceThrottle.amtRate|$250k| +|issuanceThrottle.pctRate|5%| +|redemptionThrottle.amtRate|$500k| +|redemptionThrottle.pctRate|7.5%| + +Be sure to convert a $ amtRate (units of `{qUSD}`) back into RTokens (units of `{qTok}`). + +Note the differing units: the `amtRate` variable is in terms of `{qRTok/hour}` while the `pctRate` variable is in terms of `{1/hour}`, i.e a fraction. + +#### `issuanceThrottle.amtRate` + +Dimension: `{qRTok/hour}` + +A quantity of RToken that serves as a lower-bound for how much net issuance to allow per hour. + +Must be at least 1 whole RToken, or 1e18. Can be as large as 1e48. Set it to 1e48 if you want to effectively disable the issuance throttle altogether. + +Default value: `2.5e23` = 250,000 RToken +Mainnet reasonable range: 1e23 to 1e27 + +#### `issuanceThrottle.pctRate` + +Dimension: `{1/hour}` + +A fraction of the RToken supply that indicates how much net issuance to allow per hour. + +Can be 0 to solely rely on `amtRate`; cannot be above 1e18. + +Default value: `5e16` = 5% per hour +Mainnet reasonable range: 1e15 to 1e18 (0.1% per hour to 100% per hour) + +#### `redemptionThrottle.amtRate` + +Dimension: `{qRTok/hour}` + +A quantity of RToken that serves as a lower-bound for how much net redemption to allow per hour. + +Must be at least 1 whole RToken, or 1e18. Can be as large as 1e48. Set it to 1e48 if you want to effectively disable the redemption throttle altogether. + +Default value: `5e23` = 500,000 RToken +Mainnet reasonable range: 1e23 to 1e27 + +#### `redemptionThrottle.pctRate` + +Dimension: `{1/hour}` + +A fraction of the RToken supply that indicates how much net redemption to allow per hour. + +Can be 0 to solely rely on `amtRate`; cannot be above 1e18. + +Default value: `7.5e16` = 7.5% per hour +Mainnet reasonable range: 1e15 to 1e18 (0.1% per hour to 100% per hour) + +### Governance Parameters + +Governance is 8 days end-to-end. + +**Default values** + +- Voting delay: 2 day +- Voting period: 3 days +- Execution delay: 3 days + +Proposal Threshold: 0.01% +Quorum: 10% of the StRSR supply (not RSR) diff --git a/docs/system-design.md b/docs/system-design.md index 8e8ba41292..76c746a2db 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -169,211 +169,3 @@ Linear Phase (last 60% of auction): During this phase, the price decreases linea The `dutchAuctionLength` can be configured to be any value. The suggested default is 30 minutes for a blockchain with a 12-second blocktime. At this ratio of blocktime to auction length, there is a 10.87% price drop per block during the geometric/exponential period and a 0.05% drop during the linear period. The duration of the auction can be adjusted, which will impact the size of the price decreases per block. The "best plausible price" is equal to the exchange rate at the high price of the sell token and the low price of the buy token. The "worst-case price" is equal to the exchange rate at the low price of the sell token and the high price of the sell token, plus an additional discount ranging from 0 to `maxTradeSlippage()`. At minimum auction size the full `maxTradeSlippage()` is applied, while at max auction size no further discount is applied. - -## Deployment Parameters - -### `dist` (revenue split) - -The fraction of revenues that should go towards RToken holders vs stakers, as given by the relative values of `dist.rTokenDist` and `dist.rsrDist`. This can be thought of as a single variable between 0 and 100% (during deployment). - -Default value: 60% to stakers and 40% to RToken holders. -Mainnet reasonable range: 0% to 100% - -### `minTradeVolume` - -Dimension: `{UoA}` - -The minimum sized trade that can be performed, in terms of the unit of account. - -Setting this too high will result in auctions happening infrequently or the RToken taking a haircut when it cannot be sure it has enough staked RSR to succeed in rebalancing at par. - -Setting this too low may allow griefers to delay important auctions. The variable should be set such that donations of size `minTradeVolume` would be worth delaying trading `batchAuctionLength` seconds. - -This variable should NOT be interpreted to mean that auction sizes above this value will necessarily clear. It could be the case that gas frictions are so high that auctions launched at this size are not worthy of bids. - -This parameter can be set to zero. - -Default value: `1e21` = $1k -Mainnet reasonable range: 1e19 to 1e23 - -#### `rTokenMaxTradeVolume` - -Dimension: `{UoA}` - -The maximum sized trade for any trade involving RToken, in terms of the unit of account. The high end of the price is applied to this variable to convert it to a token quantity. - -This parameter can be set to zero. - -Default value: `1e24` = $1M -Mainnet reasonable range: 1e22 to 1e27. - -### `rewardRatio` - -Dimension: `{1}` - -The `rewardRatio` is the fraction of the current reward amount that should be handed out per block. - -Default value: `3209014700000` = a half life of 30 days. - -Mainnet reasonable range: 1e11 to 1e13 - -To calculate: `ln(2) / (60*60*24*desired_days_in_half_life/12)`, and then multiply by 1e18. - -### `unstakingDelay` - -Dimension: `{seconds}` - -The unstaking delay is the number of seconds that all RSR unstakings must be delayed in order to account for stakers trying to frontrun defaults. It must be longer than governance cycle, and must be long enough that RSR stakers do not unstake in advance of foreseeable basket change in order to avoid being expensed for slippage. - -Default value: `1209600` = 2 weeks -Mainnet reasonable range: 1 to 31536000 - -### `tradingDelay` - -Dimension: `{seconds}` - -The trading delay is how many seconds should pass after the basket has been changed before a trade can be opened. In the long term this can be set to 0 after MEV searchers are firmly integrated, but at the start it may be useful to have a delay before trading in order to avoid worst-case prices. - -Default value: `7200` = 2 hours -Mainnet reasonable range: 0 to 604800 - -### `warmupPeriod` - -Dimension: `{seconds}` - -The warmup period is how many seconds should pass after the basket regained the SOUND status before an RToken can be issued and/or a trade can be opened. - -Default value: `900` = 15 minutes -Mainnet reasonable range: 0 to 604800 - -### `batchAuctionLength` - -Dimension: `{seconds}` - -The auction length is how many seconds long Gnosis EasyAuctions should be. - -Default value: `900` = 15 minutes -Mainnet reasonable range: 60 to 3600 - -### `dutchAuctionLength` - -Dimension: `{seconds}` - -The dutch auction length is how many seconds long falling-price dutch auctions should be. A longer period will result in less slippage due to better price granularity, and a shorter period will result in more slippage. - -In general, the dutchAuctionLength should be a multiple of the blocktime. This is not enforced at a smart-contract level. - -Default value: `1800` = 30 minutes -Mainnet reasonable range: 300 to 3600 - -At 30 minutes, a 12-second blocktime chain would have 10.87% price drops during the first 40% of the auction, and 0.055% price drops during the second 60%. - -### `backingBuffer` - -Dimension: `{1}` - -The backing buffer is a percentage value that describes how much overcollateralization to hold in the form of RToken. This buffer allows collateral tokens to be converted into RToken, which is a more efficient form of revenue production than trading each individual collateral for the desired RToken, and also adds a small buffer that can prevent RSR from being seized when there are small losses due to slippage during rebalancing. - -Default value: `1e15` = 0.1% -Mainnet reasonable range: 1e12 to 1e18 - -### `maxTradeSlippage` - -Dimension: `{1}` - -The max trade slippage is a percentage value that describes the maximum deviation from oracle prices that any trade can clear at. Oracle prices have ranges of their own; the maximum trade slippage permits additional price movement beyond the worst-case oracle price. - -Default value: `0.01e18` = 1% -Mainnet reasonable range: 1e12 to 1e18 - -### `shortFreeze` - -Dimension: `{s}` - -The number of seconds a short freeze lasts. Governance can freeze forever. - -Default value: `259200` = 3 days -Mainnet reasonable range: 3600 to 2592000 (1 hour to 1 month) - -### `longFreeze` - -Dimension: `{s}` - -The number of seconds a long freeze lasts. Long freezes can be disabled by removing all addresses from the `LONG_FREEZER` role. A long freezer has 6 charges that can be used. - -Default value: `604800` = 7 days -Mainnet reasonable range: 86400 to 31536000 (1 day to 1 year) - -### `withdrawalLeak` - -Dimension: `{1}` - -The fraction of RSR stake that should be permitted to withdraw without a refresh. When cumulative withdrawals (or a single withdrawal) exceed this fraction, gas must be paid to refresh all assets. - -Setting this number larger allows unstakers to save more on gas at the cost of allowing more RSR to exit improperly prior to a default. - -Default value: `5e16` = 5% -Mainnet reasonable range: 0 to 25e16 (0 to 25%) - -### `RToken Supply Throttles` - -In order to restrict the system to organic patterns of behavior, we maintain two supply throttles, one for net issuance and one for net redemption. When a supply change occurs, a check is performed to ensure this does not move the supply more than an acceptable range over a period; a period is fixed to be an hour. The acceptable range (per throttle) is a function of the `amtRate` and `pctRate` variables. **It is the maximum of whichever variable provides the larger rate.** - -Note the differing units: the `amtRate` variable is in terms of `{qRTok/hour}` while the `pctRate` variable is in terms of `{1/hour}`, i.e a fraction. - -#### `issuanceThrottle.amtRate` - -Dimension: `{qRTok/hour}` - -A quantity of RToken that serves as a lower-bound for how much net issuance to allow per hour. - -Must be at least 1 whole RToken, or 1e18. Can be as large as 1e48. Set it to 1e48 if you want to effectively disable the issuance throttle altogether. - -Default value: `1e24` = 1,000,000 RToken -Mainnet reasonable range: 1e23 to 1e27 - -#### `issuanceThrottle.pctRate` - -Dimension: `{1/hour}` - -A fraction of the RToken supply that indicates how much net issuance to allow per hour. - -Can be 0 to solely rely on `amtRate`; cannot be above 1e18. - -Default value: `2.5e16` = 2.5% per hour -Mainnet reasonable range: 1e15 to 1e18 (0.1% per hour to 100% per hour) - -#### `redemptionThrottle.amtRate` - -Dimension: `{qRTok/hour}` - -A quantity of RToken that serves as a lower-bound for how much net redemption to allow per hour. - -Must be at least 1 whole RToken, or 1e18. Can be as large as 1e48. Set it to 1e48 if you want to effectively disable the redemption throttle altogether. - -Default value: `2e24` = 2,000,000 RToken -Mainnet reasonable range: 1e23 to 1e27 - -#### `redemptionThrottle.pctRate` - -Dimension: `{1/hour}` - -A fraction of the RToken supply that indicates how much net redemption to allow per hour. - -Can be 0 to solely rely on `amtRate`; cannot be above 1e18. - -Default value: `5e16` = 5% per hour -Mainnet reasonable range: 1e15 to 1e18 (0.1% per hour to 100% per hour) - -### Governance Parameters - -Governance is 8 days end-to-end. - -**Default values** - -- Voting delay: 2 day -- Voting period: 3 days -- Execution delay: 3 days - -Proposal Threshold: 0.01% -Quorum: 10% of the StRSR supply (not RSR) From 006eb166b8ec1b4bb7f1b10ec3d793c017057194 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Tue, 22 Aug 2023 17:09:01 -0300 Subject: [PATCH 382/462] fix rebalance in facade (#908) --- contracts/facade/FacadeAct.sol | 7 +++++-- test/Facade.test.ts | 14 ++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 7c6d952a8c..549ccf16ae 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -253,12 +253,15 @@ contract FacadeAct is IFacadeAct, Multicall { bytes1 majorVersion = bytes(bm.version())[0]; if (majorVersion == MAJOR_VERSION_3) { - bm.rebalance(TradeKind.DUTCH_AUCTION); + // solhint-disable-next-line no-empty-blocks + try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch {} } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { IERC20[] memory emptyERC20s = new IERC20[](0); - address(bm).functionCall( + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = address(bm).call{ value: 0 }( abi.encodeWithSignature("manageTokens(address[])", emptyERC20s) ); + success = success; // hush warning } else { _revertUnrecognizedVersion(); } diff --git a/test/Facade.test.ts b/test/Facade.test.ts index 5415dd39d6..f500a6a977 100644 --- a/test/Facade.test.ts +++ b/test/Facade.test.ts @@ -664,6 +664,11 @@ describe('FacadeRead + FacadeAct contracts', () => { }) it('Should return nextRecollateralizationAuction', async () => { + // Confirm no auction to run yet - should not revert + let [canStart, sell, buy, sellAmount] = + await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + expect(canStart).to.equal(false) + // Setup prime basket await basketHandler.connect(owner).setPrimeBasket([usdc.address], [fp('1')]) @@ -676,7 +681,7 @@ describe('FacadeRead + FacadeAct contracts', () => { const sellAmt: BigNumber = await token.balanceOf(backingManager.address) // Confirm nextRecollateralizationAuction is true - let [canStart, sell, buy, sellAmount] = + ;[canStart, sell, buy, sellAmount] = await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) expect(canStart).to.equal(true) expect(sell).to.equal(token.address) @@ -739,6 +744,11 @@ describe('FacadeRead + FacadeAct contracts', () => { // Upgrade BackingManager to V2 await backingManager.connect(owner).upgradeTo(backingManagerV2.address) + // Confirm no auction to run yet - should not revert + let [canStart, sell, buy, sellAmount] = + await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + expect(canStart).to.equal(false) + // Setup prime basket await basketHandler.connect(owner).setPrimeBasket([usdc.address], [fp('1')]) @@ -751,7 +761,7 @@ describe('FacadeRead + FacadeAct contracts', () => { const sellAmt: BigNumber = await token.balanceOf(backingManager.address) // Confirm nextRecollateralizationAuction is true - let [canStart, sell, buy, sellAmount] = + ;[canStart, sell, buy, sellAmount] = await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) expect(canStart).to.equal(true) expect(sell).to.equal(token.address) From 1b37c77004e919aab2158fbbddee05dd63bf5449 Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Tue, 22 Aug 2023 16:44:39 -0400 Subject: [PATCH 383/462] C4 39 use comet decimals instead of wrapper decimals. (#889) --- contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol | 4 +++- contracts/plugins/assets/compoundv3/vendor/IComet.sol | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol index b9b1e8b233..5e7bd1238c 100644 --- a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol +++ b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol @@ -31,6 +31,7 @@ contract CTokenV3Collateral is AppreciatingFiatCollateral { IERC20 public immutable rewardERC20; IComet public immutable comet; uint256 public immutable reservesThresholdIffy; // {qUSDC} + uint8 public immutable cometDecimals; /// @param config.chainlinkFeed Feed units: {UoA/ref} constructor( @@ -41,6 +42,7 @@ contract CTokenV3Collateral is AppreciatingFiatCollateral { rewardERC20 = ICusdcV3Wrapper(address(config.erc20)).rewardERC20(); comet = IComet(address(ICusdcV3Wrapper(address(erc20)).underlyingComet())); reservesThresholdIffy = reservesThresholdIffy_; + cometDecimals = comet.decimals(); } function bal(address account) external view override(Asset, IAsset) returns (uint192) { @@ -53,7 +55,7 @@ contract CTokenV3Collateral is AppreciatingFiatCollateral { } function _underlyingRefPerTok() internal view virtual override returns (uint192) { - return shiftl_toFix(ICusdcV3Wrapper(address(erc20)).exchangeRate(), -int8(erc20Decimals)); + return shiftl_toFix(ICusdcV3Wrapper(address(erc20)).exchangeRate(), -int8(cometDecimals)); } /// Refresh exchange rates and update default status. diff --git a/contracts/plugins/assets/compoundv3/vendor/IComet.sol b/contracts/plugins/assets/compoundv3/vendor/IComet.sol index 44fffae2d7..e249a3663a 100644 --- a/contracts/plugins/assets/compoundv3/vendor/IComet.sol +++ b/contracts/plugins/assets/compoundv3/vendor/IComet.sol @@ -6,4 +6,6 @@ interface IComet { /// @dev uint104 function targetReserves() external view returns (uint256); + + function decimals() external view returns (uint8); } From 755cf3cdf663c3e9d23206a1b38dd8338c40bd3d Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Wed, 23 Aug 2023 13:06:11 -0400 Subject: [PATCH 384/462] update Asset price comment. (#910) Co-authored-by: Taylor Brent --- contracts/plugins/assets/Asset.sol | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contracts/plugins/assets/Asset.sol b/contracts/plugins/assets/Asset.sol index a254f390ce..14f3b03003 100644 --- a/contracts/plugins/assets/Asset.sol +++ b/contracts/plugins/assets/Asset.sol @@ -112,6 +112,14 @@ contract Asset is IAsset, VersionedAsset { /// @dev Should be general enough to not need to be overridden /// @return _low {UoA/tok} The lower end of the price estimate /// @return _high {UoA/tok} The upper end of the price estimate + /// @notice If the price feed is broken, _low will decay downwards and _high will decay upwards + /// If tryPrice() is broken for more than `oracleTimeout + priceTimeout` seconds, + /// _low will be 0 and _high will be FIX_MAX. + /// Because the price decay begins at `oracleTimeout` seconds and not `updateTime` from the + /// price feed, the price feed can be broken for up to `2 * oracleTimeout` seconds without + /// affecting the price estimate. This could happen if the Asset is refreshed just before + /// the oracleTimeout is reached, forcing a second period of oracleTimeout to pass before + /// the price begins to decay. function price() public view virtual returns (uint192 _low, uint192 _high) { try this.tryPrice() returns (uint192 low, uint192 high, uint192) { // if the price feed is still functioning, use that From 4fb96fa28022998ceb87d9a26d12feebb141d0b6 Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Thu, 24 Aug 2023 00:25:34 +0530 Subject: [PATCH 385/462] C4 Audit (Plugin) Changes (#896) Co-authored-by: Taylor Brent --- contracts/plugins/assets/Asset.sol | 2 + .../plugins/assets/EURFiatCollateral.sol | 2 +- .../plugins/assets/aave/StaticATokenLM.sol | 2 +- .../assets/curve/CurveStableCollateral.sol | 3 + .../curve/CurveStableMetapoolCollateral.sol | 3 + .../CurveStableRTokenMetapoolCollateral.sol | 3 + .../assets/curve/CurveVolatileCollateral.sol | 66 ----- .../plugins/assets/erc20/RewardableERC20.sol | 13 +- .../assets/erc20/RewardableERC20Wrapper.sol | 1 + .../MorphoAaveV2TokenisedDeposit.sol | 4 +- .../stargate/StargatePoolFiatCollateral.sol | 3 + .../stargate/StargateRewardableWrapper.sol | 34 ++- .../interfaces/IStargateLPStaking.sol | 2 + .../stargate/mocks/StargateLPStakingMock.sol | 16 +- docs/collateral.md | 4 + test/plugins/RewardableERC20.test.ts | 43 ++++ .../curve/crv/CrvVolatileTestSuite.test.ts | 225 ----------------- .../curve/cvx/CvxVolatileTestSuite.test.ts | 227 ------------------ .../StargateRewardableWrapper.test.ts | 49 +++- 19 files changed, 165 insertions(+), 537 deletions(-) delete mode 100644 contracts/plugins/assets/curve/CurveVolatileCollateral.sol delete mode 100644 test/plugins/individual-collateral/curve/crv/CrvVolatileTestSuite.test.ts delete mode 100644 test/plugins/individual-collateral/curve/cvx/CvxVolatileTestSuite.test.ts diff --git a/contracts/plugins/assets/Asset.sol b/contracts/plugins/assets/Asset.sol index ba6908c8a8..1bb044c239 100644 --- a/contracts/plugins/assets/Asset.sol +++ b/contracts/plugins/assets/Asset.sol @@ -38,6 +38,8 @@ contract Asset is IAsset, VersionedAsset { /// @param oracleError_ {1} The % the oracle feed can be off by /// @param maxTradeVolume_ {UoA} The max trade volume, in UoA /// @param oracleTimeout_ {s} The number of seconds until a oracle value becomes invalid + /// @dev oracleTimeout_ is also used as the timeout value in lotPrice(), should be highest of + /// all assets' oracleTimeout in a collateral if there are multiple oracles constructor( uint48 priceTimeout_, AggregatorV3Interface chainlinkFeed_, diff --git a/contracts/plugins/assets/EURFiatCollateral.sol b/contracts/plugins/assets/EURFiatCollateral.sol index ae6dcfc3ca..67d0c12f34 100644 --- a/contracts/plugins/assets/EURFiatCollateral.sol +++ b/contracts/plugins/assets/EURFiatCollateral.sol @@ -34,7 +34,7 @@ contract EURFiatCollateral is FiatCollateral { /// 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 - /// @return pegPrice {UoA/ref} + /// @return pegPrice {target/ref} function tryPrice() external view diff --git a/contracts/plugins/assets/aave/StaticATokenLM.sol b/contracts/plugins/assets/aave/StaticATokenLM.sol index f6637e1369..bbecab06dd 100644 --- a/contracts/plugins/assets/aave/StaticATokenLM.sol +++ b/contracts/plugins/assets/aave/StaticATokenLM.sol @@ -554,7 +554,7 @@ contract StaticATokenLM is * @param user The user to compute for * @param balance The balance of the user * @param fresh Flag to account for rewards not claimed by contract yet - * @return The amound of pending rewards in RAY + * @return The amount of pending rewards in RAY */ function _getPendingRewards( address user, diff --git a/contracts/plugins/assets/curve/CurveStableCollateral.sol b/contracts/plugins/assets/curve/CurveStableCollateral.sol index 22c336cee0..f5706f75e6 100644 --- a/contracts/plugins/assets/curve/CurveStableCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableCollateral.sol @@ -20,6 +20,9 @@ import "../curve/PoolTokens.sol"; * ref = stablePlainPool pool invariant * tar = USD * UoA = USD + * + * @notice Curve pools with native ETH or ERC777 should be avoided, + * see docs/collateral.md for information */ contract CurveStableCollateral is AppreciatingFiatCollateral, PoolTokens { using OracleLib for AggregatorV3Interface; diff --git a/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol b/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol index 450cf1f70b..874b48b94e 100644 --- a/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol @@ -17,6 +17,9 @@ interface ICurveMetaPool is ICurvePool, IERC20Metadata { * ref = PairedUSDToken/USDBasePool pool invariant * tar = USD * UoA = USD + * + * @notice Curve pools with native ETH or ERC777 should be avoided, + * see docs/collateral.md for information */ contract CurveStableMetapoolCollateral is CurveStableCollateral { using OracleLib for AggregatorV3Interface; diff --git a/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol b/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol index 005a7911b8..420e002f4a 100644 --- a/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol @@ -12,6 +12,9 @@ import "./CurveStableMetapoolCollateral.sol"; * ref = PairedUSDRToken/USDBasePool pool invariant * tar = USD * UoA = USD + * + * @notice Curve pools with native ETH or ERC777 should be avoided, + * see docs/collateral.md for information */ contract CurveStableRTokenMetapoolCollateral is CurveStableMetapoolCollateral { using FixLib for uint192; diff --git a/contracts/plugins/assets/curve/CurveVolatileCollateral.sol b/contracts/plugins/assets/curve/CurveVolatileCollateral.sol deleted file mode 100644 index 4846f4fa25..0000000000 --- a/contracts/plugins/assets/curve/CurveVolatileCollateral.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity 0.8.19; - -import "./CurveStableCollateral.sol"; - -/** - * @title CurveVolatileCollateral - * This plugin contract extends CrvCurveStableCollateral to work for - * volatile pools like TriCrypto. - * - * tok = ConvexStakingWrapper(volatilePlainPool) - * ref = volatilePlainPool pool invariant - * tar = volatilePlainPool pool invariant - * UoA = USD - */ -contract CurveVolatileCollateral is CurveStableCollateral { - using FixLib for uint192; - - // this isn't saved by our parent classes, but we'll need to track it - uint192 internal immutable _defaultThreshold; // {1} - - /// @dev config Unused members: chainlinkFeed, oracleError, oracleTimeout - constructor( - CollateralConfig memory config, - uint192 revenueHiding, - PTConfiguration memory ptConfig - ) CurveStableCollateral(config, revenueHiding, ptConfig) { - _defaultThreshold = config.defaultThreshold; - } - - // Override this later to implement non-stable pools - function _anyDepeggedInPool() internal view override returns (bool) { - uint192[] memory balances = getBalances(); // [{tok}] - uint192[] memory vals = new uint192[](balances.length); // {UoA} - uint192 valSum; // {UoA} - - // Calculate vals - for (uint8 i = 0; i < nTokens; i++) { - try this.tokenPrice(i) returns (uint192 low, uint192 high) { - // {UoA/tok} = {UoA/tok} + {UoA/tok} - uint192 mid = (low + high) / 2; - - // {UoA} = {tok} * {UoA/tok} - vals[i] = balances[i].mul(mid); - valSum += vals[i]; - } 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 distribution of capital - uint192 expected = FIX_ONE.divu(nTokens); // {1} - for (uint8 i = 0; i < nTokens; i++) { - uint192 observed = divuu(vals[i], valSum); // {1} - if (observed > expected) { - if (observed - expected > _defaultThreshold) return true; - } - } - - return false; - } -} diff --git a/contracts/plugins/assets/erc20/RewardableERC20.sol b/contracts/plugins/assets/erc20/RewardableERC20.sol index cfa132d7cf..58fd23855c 100644 --- a/contracts/plugins/assets/erc20/RewardableERC20.sol +++ b/contracts/plugins/assets/erc20/RewardableERC20.sol @@ -54,7 +54,7 @@ abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard { uint256 shares = balanceOf(account); // {qRewards} - uint256 _accumuatedRewards = accumulatedRewards[account]; + uint256 _accumulatedRewards = accumulatedRewards[account]; // {qRewards/share} uint256 _rewardsPerShare = rewardsPerShare; @@ -63,10 +63,10 @@ abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard { uint256 delta = _rewardsPerShare - accountRewardsPerShare; // {qRewards} = {qRewards/share} * {qShare} - _accumuatedRewards += (delta * shares) / one; + _accumulatedRewards += (delta * shares) / one; } lastRewardsPerShare[account] = _rewardsPerShare; - accumulatedRewards[account] = _accumuatedRewards; + accumulatedRewards[account] = _accumulatedRewards; } function _claimAndSyncRewards() internal virtual { @@ -82,9 +82,14 @@ abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard { if (balanceAfterClaimingRewards > _previousBalance) { uint256 delta = balanceAfterClaimingRewards - _previousBalance; + uint256 deltaPerShare = (delta * one) / _totalSupply; + + balanceAfterClaimingRewards = _previousBalance + (deltaPerShare * _totalSupply) / one; + // {qRewards/share} += {qRewards} * {qShare/share} / {qShare} - _rewardsPerShare += (delta * one) / _totalSupply; + _rewardsPerShare += deltaPerShare; } + lastRewardBalance = balanceAfterClaimingRewards; rewardsPerShare = _rewardsPerShare; } diff --git a/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol b/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol index 285582a02f..e2a4ec927f 100644 --- a/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol +++ b/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol @@ -45,6 +45,7 @@ abstract contract RewardableERC20Wrapper is RewardableERC20 { underlying.safeTransferFrom(msg.sender, address(this), _amount); _afterDeposit(_amount, _to); } + emit Deposited(msg.sender, _to, _amount); } diff --git a/contracts/plugins/assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol b/contracts/plugins/assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol index 3d4d0d240e..eff7ccd9b5 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol @@ -32,7 +32,7 @@ contract MorphoAaveV2TokenisedDeposit is MorphoTokenisedDeposit { morphoLens = config.morphoLens; } - function getMorphoPoolBalance(address poolToken) + function getMorphoPoolBalance(address _poolToken) internal view virtual @@ -40,7 +40,7 @@ contract MorphoAaveV2TokenisedDeposit is MorphoTokenisedDeposit { returns (uint256) { (, , uint256 supplyBalance) = morphoLens.getCurrentSupplyBalanceInOf( - poolToken, + _poolToken, address(this) ); return supplyBalance; diff --git a/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol b/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol index 8ac8c13e66..2a97424f65 100644 --- a/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol +++ b/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol @@ -112,6 +112,9 @@ contract StargatePoolFiatCollateral is FiatCollateral { if (_totalSupply != 0) { _rate = divuu(pool.totalLiquidity(), _totalSupply); + } else { + // In case the pool has no tokens at all, the rate is 1:1 + _rate = FIX_ONE; } } diff --git a/contracts/plugins/assets/stargate/StargateRewardableWrapper.sol b/contracts/plugins/assets/stargate/StargateRewardableWrapper.sol index 44621f3152..d1f6878053 100644 --- a/contracts/plugins/assets/stargate/StargateRewardableWrapper.sol +++ b/contracts/plugins/assets/stargate/StargateRewardableWrapper.sol @@ -25,6 +25,7 @@ contract StargateRewardableWrapper is RewardableERC20Wrapper { address(pool_) != address(0), "Invalid address" ); + require(address(stargate_) == address(stakingContract_.stargate()), "Wrong stargate"); uint256 poolLength = stakingContract_.poolLength(); uint256 pid = type(uint256).max; @@ -36,8 +37,6 @@ contract StargateRewardableWrapper is RewardableERC20Wrapper { } require(pid != type(uint256).max, "Invalid pool"); - pool_.approve(address(stakingContract_), type(uint256).max); // TODO: Change this! - pool = pool_; poolId = pid; stakingContract = stakingContract_; @@ -45,18 +44,35 @@ contract StargateRewardableWrapper is RewardableERC20Wrapper { } function _claimAssetRewards() internal override { - stakingContract.deposit(poolId, 0); + IStargateLPStaking.PoolInfo memory poolInfo = stakingContract.poolInfo(poolId); + + if (poolInfo.allocPoint != 0 && totalSupply() != 0) { + stakingContract.deposit(poolId, 0); + } else { + stakingContract.emergencyWithdraw(poolId); + } } - function _afterDeposit(uint256 _amount, address to) internal override { - require(to == msg.sender, "Only the sender can deposit"); + function _afterDeposit(uint256, address) internal override { + uint256 underlyingBalance = underlying.balanceOf(address(this)); + IStargateLPStaking.PoolInfo memory poolInfo = stakingContract.poolInfo(poolId); - stakingContract.deposit(poolId, _amount); + if (poolInfo.allocPoint != 0 && underlyingBalance != 0) { + pool.approve(address(stakingContract), underlyingBalance); + stakingContract.deposit(poolId, underlyingBalance); + } } - function _beforeWithdraw(uint256 _amount, address to) internal override { - require(to == msg.sender, "Only the sender can withdraw"); + function _beforeWithdraw(uint256 _amount, address) internal override { + IStargateLPStaking.PoolInfo memory poolInfo = stakingContract.poolInfo(poolId); - stakingContract.withdraw(poolId, _amount); + if (poolInfo.allocPoint != 0) { + uint256 underlyingBalance = underlying.balanceOf(address(this)); + if (underlyingBalance < _amount) { + stakingContract.withdraw(poolId, _amount - underlyingBalance); + } + } else { + stakingContract.emergencyWithdraw(poolId); + } } } diff --git a/contracts/plugins/assets/stargate/interfaces/IStargateLPStaking.sol b/contracts/plugins/assets/stargate/interfaces/IStargateLPStaking.sol index 4210c5734f..53b6684366 100644 --- a/contracts/plugins/assets/stargate/interfaces/IStargateLPStaking.sol +++ b/contracts/plugins/assets/stargate/interfaces/IStargateLPStaking.sol @@ -6,6 +6,8 @@ import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; interface IStargateLPStaking { function poolLength() external view returns (uint256); + function stargate() external view returns (IERC20); + // Info of each pool. struct PoolInfo { // Address of LP token contract. diff --git a/contracts/plugins/assets/stargate/mocks/StargateLPStakingMock.sol b/contracts/plugins/assets/stargate/mocks/StargateLPStakingMock.sol index b5c03837f1..2fa2cebe45 100644 --- a/contracts/plugins/assets/stargate/mocks/StargateLPStakingMock.sol +++ b/contracts/plugins/assets/stargate/mocks/StargateLPStakingMock.sol @@ -10,9 +10,11 @@ contract StargateLPStakingMock is IStargateLPStaking { mapping(uint256 => mapping(address => uint256)) poolToUserBalance; ERC20Mock public immutable stargateMock; + IERC20 public immutable stargate; constructor(ERC20Mock stargateMock_) { stargateMock = stargateMock_; + stargate = stargateMock_; } function poolLength() external view override returns (uint256) { @@ -46,7 +48,14 @@ contract StargateLPStakingMock is IStargateLPStaking { poolToUserBalance[pid][sender] -= amount; } - function emergencyWithdraw(uint256 pid) external override {} + function emergencyWithdraw(uint256 pid) external override { + IERC20 pool = _poolInfo[pid].lpToken; + + uint256 amount = poolToUserBalance[pid][msg.sender]; + poolToUserBalance[pid][msg.sender] = 0; + + pool.transfer(msg.sender, amount); + } function addRewardsToUser( uint256 pid, @@ -59,9 +68,14 @@ contract StargateLPStakingMock is IStargateLPStaking { function addPool(IERC20 lpToken) internal { PoolInfo memory info; info.lpToken = lpToken; + info.allocPoint = 10; _poolInfo.push(info); } + function setAllocPoint(uint256 pid, uint256 allocPoint) external { + _poolInfo[pid].allocPoint = allocPoint; + } + function _emitUserRewards(uint256 pid, address user) private { uint256 amount = poolToUserRewardsPending[pid][user]; stargateMock.mint(user, amount); diff --git a/docs/collateral.md b/docs/collateral.md index aa4fe491cd..d9589954e1 100644 --- a/docs/collateral.md +++ b/docs/collateral.md @@ -125,6 +125,10 @@ interface ICollateral is IAsset { ``` +## Some security considerations + +The protocol specifically does not allow the use of any assets that have a callback mechanism, such as ERC777 or native ETH. In order to support these assets, they must be wrapped in an ERC20 contract that does not have a callback mechanism. This is a security consideration to prevent reentrancy attacks. This recommendation extends to LP tokens that contain assets with callback mechanisms (Such as Curve raw ETH pools - CRV/ETH for example) as well as tokens/LPs that involve WETH with unwrapping built-in. + ## Accounting Units and Exchange Rates To create a Collateral plugin, you need to select its accounting units (`{tok}`, `{ref}`, `{target}`, and `{UoA}`), and implement views of the exchange rates: `refPerTok()` and `targetPerRef()`. diff --git a/test/plugins/RewardableERC20.test.ts b/test/plugins/RewardableERC20.test.ts index 53459332d9..abc94deb66 100644 --- a/test/plugins/RewardableERC20.test.ts +++ b/test/plugins/RewardableERC20.test.ts @@ -16,6 +16,7 @@ import { useEnv } from '#/utils/env' import { Implementation } from '../fixtures' import snapshotGasCost from '../utils/snapshotGasCost' import { formatUnits, parseUnits } from 'ethers/lib/utils' +import { MAX_UINT256 } from '#/common/constants' type Fixture = () => Promise @@ -630,6 +631,48 @@ for (const wrapperName of wrapperNames) { }) }) + describe(`${wrapperName.replace('Test', '')} Special Case: Fractional Rewards Tracking`, () => { + // Assets + let rewardableVault: RewardableERC20WrapperTest | RewardableERC4626VaultTest + let rewardableAsset: ERC20MockRewarding + + // Main + let alice: Wallet + let bob: Wallet + + const initBalance = parseUnits('1000000', 18) + const rewardAmount = parseUnits('1.9', 6) + + const fixture = getFixture(18, 6) + + before('load wallets', async () => { + ;[alice, bob] = (await ethers.getSigners()) as unknown as Wallet[] + }) + + beforeEach(async () => { + // Deploy fixture + ;({ rewardableVault, rewardableAsset } = await loadFixture(fixture)) + + await rewardableAsset.mint(alice.address, initBalance) + await rewardableAsset.connect(alice).approve(rewardableVault.address, MAX_UINT256) + await rewardableAsset.mint(bob.address, initBalance) + await rewardableAsset.connect(bob).approve(rewardableVault.address, MAX_UINT256) + }) + + it('Correctly handles fractional rewards', async () => { + expect(await rewardableVault.rewardsPerShare()).to.equal(0) + + await rewardableVault.connect(alice).deposit(initBalance, alice.address) + + for (let i = 0; i < 10; i++) { + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + await rewardableVault.claimRewards() + + expect(await rewardableVault.rewardsPerShare()).to.equal(Math.floor(1.9 * (i + 1))) + } + }) + }) + const IMPLEMENTATION: Implementation = useEnv('PROTO_IMPL') == Implementation.P1.toString() ? Implementation.P1 : Implementation.P0 diff --git a/test/plugins/individual-collateral/curve/crv/CrvVolatileTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvVolatileTestSuite.test.ts deleted file mode 100644 index 3230147ce7..0000000000 --- a/test/plugins/individual-collateral/curve/crv/CrvVolatileTestSuite.test.ts +++ /dev/null @@ -1,225 +0,0 @@ -import collateralTests from '../collateralTests' -import { - CurveCollateralFixtureContext, - CurveCollateralOpts, - MintCurveCollateralFunc, -} from '../pluginTestTypes' -import { mintWPool, makeWTricryptoPoolVolatile, resetFork } from './helpers' -import { ethers } from 'hardhat' -import { ContractFactory, BigNumberish } from 'ethers' -import { - ERC20Mock, - MockV3Aggregator, - MockV3Aggregator__factory, - TestICollateral, -} from '../../../../../typechain' -import { bn } from '../../../../../common/numbers' -import { ZERO_ADDRESS } from '../../../../../common/constants' -import { expect } from 'chai' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { - PRICE_TIMEOUT, - USDT_ORACLE_TIMEOUT, - USDT_ORACLE_ERROR, - MAX_TRADE_VOL, - DEFAULT_THRESHOLD, - DELAY_UNTIL_DEFAULT, - CurvePoolType, - CRV, - TRI_CRYPTO_HOLDER, - TRI_CRYPTO, - TRI_CRYPTO_TOKEN, - WBTC_BTC_FEED, - BTC_USD_FEED, - BTC_ORACLE_TIMEOUT, - WETH_USD_FEED, - WBTC_BTC_ORACLE_ERROR, - WBTC_ORACLE_TIMEOUT, - WETH_ORACLE_TIMEOUT, - USDT_USD_FEED, - BTC_USD_ORACLE_ERROR, - WETH_ORACLE_ERROR, -} from '../constants' - -type Fixture = () => Promise - -export const defaultCrvVolatileCollateralOpts: CurveCollateralOpts = { - erc20: ZERO_ADDRESS, - targetName: ethers.utils.formatBytes32String('TRICRYPTO'), - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: USDT_USD_FEED, // unused but cannot be zero - oracleTimeout: USDT_ORACLE_TIMEOUT, // max of oracleTimeouts - oracleError: bn('1'), // unused but cannot be zero - maxTradeVolume: MAX_TRADE_VOL, - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - revenueHiding: bn('0'), - nTokens: 3, - curvePool: TRI_CRYPTO, - lpToken: TRI_CRYPTO_TOKEN, - poolType: CurvePoolType.Plain, - feeds: [[USDT_USD_FEED], [WBTC_BTC_FEED, BTC_USD_FEED], [WETH_USD_FEED]], - oracleTimeouts: [ - [USDT_ORACLE_TIMEOUT], - [WBTC_ORACLE_TIMEOUT, BTC_ORACLE_TIMEOUT], - [WETH_ORACLE_TIMEOUT], - ], - oracleErrors: [ - [USDT_ORACLE_ERROR], - [WBTC_BTC_ORACLE_ERROR, BTC_USD_ORACLE_ERROR], - [WETH_ORACLE_ERROR], - ], -} - -const makeFeeds = async () => { - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) - - // Substitute all 3 feeds: DAI, USDC, USDT - const wethFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const wbtcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const btcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - - const wethFeedOrg = MockV3AggregatorFactory.attach(WETH_USD_FEED) - const wbtcFeedOrg = MockV3AggregatorFactory.attach(WBTC_BTC_FEED) - const btcFeedOrg = MockV3AggregatorFactory.attach(BTC_USD_FEED) - const usdtFeedOrg = MockV3AggregatorFactory.attach(USDT_USD_FEED) - - await wethFeed.updateAnswer(await wethFeedOrg.latestAnswer()) - await wbtcFeed.updateAnswer(await wbtcFeedOrg.latestAnswer()) - await btcFeed.updateAnswer(await btcFeedOrg.latestAnswer()) - await usdtFeed.updateAnswer(await usdtFeedOrg.latestAnswer()) - - return { wethFeed, wbtcFeed, btcFeed, usdtFeed } -} - -export const deployCollateral = async ( - opts: CurveCollateralOpts = {} -): Promise<[TestICollateral, CurveCollateralOpts]> => { - if (!opts.erc20 && !opts.feeds) { - const { wethFeed, wbtcFeed, btcFeed, usdtFeed } = await makeFeeds() - - const fix = await makeWTricryptoPoolVolatile() - - opts.feeds = [[wethFeed.address], [wbtcFeed.address, btcFeed.address], [usdtFeed.address]] - opts.erc20 = fix.wrapper.address - } - - opts = { ...defaultCrvVolatileCollateralOpts, ...opts } - - const CrvVolatileCollateralFactory: ContractFactory = await ethers.getContractFactory( - 'CurveVolatileCollateral' - ) - - const collateral = await CrvVolatileCollateralFactory.deploy( - { - erc20: opts.erc20, - targetName: opts.targetName, - priceTimeout: opts.priceTimeout, - chainlinkFeed: opts.chainlinkFeed, - oracleError: opts.oracleError, - oracleTimeout: opts.oracleTimeout, - maxTradeVolume: opts.maxTradeVolume, - defaultThreshold: opts.defaultThreshold, - delayUntilDefault: opts.delayUntilDefault, - }, - opts.revenueHiding, - { - nTokens: opts.nTokens, - curvePool: opts.curvePool, - poolType: opts.poolType, - feeds: opts.feeds, - oracleTimeouts: opts.oracleTimeouts, - oracleErrors: opts.oracleErrors, - lpToken: opts.lpToken, - } - ) - await collateral.deployed() - - // sometimes we are trying to test a negative test case and we want this to fail silently - // fortunately this syntax fails silently because our tools are terrible - await expect(collateral.refresh()) - - return [collateral, opts] -} - -const makeCollateralFixtureContext = ( - alice: SignerWithAddress, - opts: CurveCollateralOpts = {} -): Fixture => { - const collateralOpts = { ...defaultCrvVolatileCollateralOpts, ...opts } - - const makeCollateralFixtureContext = async () => { - const { wethFeed, wbtcFeed, btcFeed, usdtFeed } = await makeFeeds() - - collateralOpts.feeds = [ - [usdtFeed.address], - [wbtcFeed.address, btcFeed.address], - [wethFeed.address], - ] - - const fix = await makeWTricryptoPoolVolatile() - - collateralOpts.erc20 = fix.wrapper.address - collateralOpts.curvePool = fix.curvePool.address - const collateral = ((await deployCollateral(collateralOpts))[0] as unknown) - const crv = await ethers.getContractAt('ERC20Mock', CRV) - - return { - alice, - collateral, - curvePool: fix.curvePool, - wrapper: fix.wrapper, - rewardTokens: [crv], - poolTokens: [fix.usdt, fix.wbtc, fix.weth], - feeds: [usdtFeed, btcFeed, wethFeed], // exclude wbtcFeed - } - } - - return makeCollateralFixtureContext -} - -/* - Define helper functions -*/ - -const mintCollateralTo: MintCurveCollateralFunc = async ( - ctx: CurveCollateralFixtureContext, - amount: BigNumberish, - user: SignerWithAddress, - recipient: string -) => { - await mintWPool(ctx, amount, user, recipient, TRI_CRYPTO_HOLDER) -} - -/* - Define collateral-specific tests -*/ - -// eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificConstructorTests = () => {} - -// eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificStatusTests = () => {} - -/* - Run the test suite -*/ - -const opts = { - deployCollateral, - collateralSpecificConstructorTests, - collateralSpecificStatusTests, - makeCollateralFixtureContext, - mintCollateralTo, - itChecksTargetPerRefDefault: it, - itChecksRefPerTokDefault: it, - itHasRevenueHiding: it, - isMetapool: false, - resetFork, - collateralName: 'CurveVolatileCollateral - CurveGaugeWrapper', -} - -collateralTests(opts) diff --git a/test/plugins/individual-collateral/curve/cvx/CvxVolatileTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxVolatileTestSuite.test.ts deleted file mode 100644 index 014b053b86..0000000000 --- a/test/plugins/individual-collateral/curve/cvx/CvxVolatileTestSuite.test.ts +++ /dev/null @@ -1,227 +0,0 @@ -import collateralTests from '../collateralTests' -import { - CurveCollateralFixtureContext, - CurveCollateralOpts, - MintCurveCollateralFunc, -} from '../pluginTestTypes' -import { mintWPool, makeWTricryptoPoolVolatile, resetFork } from './helpers' -import { ethers } from 'hardhat' -import { ContractFactory, BigNumberish } from 'ethers' -import { - ERC20Mock, - MockV3Aggregator, - MockV3Aggregator__factory, - TestICollateral, -} from '../../../../../typechain' -import { bn } from '../../../../../common/numbers' -import { ZERO_ADDRESS } from '../../../../../common/constants' -import { expect } from 'chai' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { - PRICE_TIMEOUT, - CVX, - USDT_ORACLE_TIMEOUT, - USDT_ORACLE_ERROR, - MAX_TRADE_VOL, - DEFAULT_THRESHOLD, - DELAY_UNTIL_DEFAULT, - CurvePoolType, - CRV, - TRI_CRYPTO_HOLDER, - TRI_CRYPTO, - TRI_CRYPTO_TOKEN, - WBTC_BTC_FEED, - BTC_USD_FEED, - BTC_ORACLE_TIMEOUT, - WETH_USD_FEED, - WBTC_BTC_ORACLE_ERROR, - WBTC_ORACLE_TIMEOUT, - WETH_ORACLE_TIMEOUT, - USDT_USD_FEED, - BTC_USD_ORACLE_ERROR, - WETH_ORACLE_ERROR, -} from '../constants' - -type Fixture = () => Promise - -export const defaultCvxVolatileCollateralOpts: CurveCollateralOpts = { - erc20: ZERO_ADDRESS, - targetName: ethers.utils.formatBytes32String('TRICRYPTO'), - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: USDT_USD_FEED, // unused but cannot be zero - oracleTimeout: USDT_ORACLE_TIMEOUT, // max of oracleTimeouts - oracleError: bn('1'), // unused but cannot be zero - maxTradeVolume: MAX_TRADE_VOL, - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - revenueHiding: bn('0'), - nTokens: 3, - curvePool: TRI_CRYPTO, - lpToken: TRI_CRYPTO_TOKEN, - poolType: CurvePoolType.Plain, - feeds: [[USDT_USD_FEED], [WBTC_BTC_FEED, BTC_USD_FEED], [WETH_USD_FEED]], - oracleTimeouts: [ - [USDT_ORACLE_TIMEOUT], - [WBTC_ORACLE_TIMEOUT, BTC_ORACLE_TIMEOUT], - [WETH_ORACLE_TIMEOUT], - ], - oracleErrors: [ - [USDT_ORACLE_ERROR], - [WBTC_BTC_ORACLE_ERROR, BTC_USD_ORACLE_ERROR], - [WETH_ORACLE_ERROR], - ], -} - -const makeFeeds = async () => { - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) - - // Substitute all 3 feeds: DAI, USDC, USDT - const wethFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const wbtcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const btcFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - const usdtFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) - - const wethFeedOrg = MockV3AggregatorFactory.attach(WETH_USD_FEED) - const wbtcFeedOrg = MockV3AggregatorFactory.attach(WBTC_BTC_FEED) - const btcFeedOrg = MockV3AggregatorFactory.attach(BTC_USD_FEED) - const usdtFeedOrg = MockV3AggregatorFactory.attach(USDT_USD_FEED) - - await wethFeed.updateAnswer(await wethFeedOrg.latestAnswer()) - await wbtcFeed.updateAnswer(await wbtcFeedOrg.latestAnswer()) - await btcFeed.updateAnswer(await btcFeedOrg.latestAnswer()) - await usdtFeed.updateAnswer(await usdtFeedOrg.latestAnswer()) - - return { wethFeed, wbtcFeed, btcFeed, usdtFeed } -} - -export const deployCollateral = async ( - opts: CurveCollateralOpts = {} -): Promise<[TestICollateral, CurveCollateralOpts]> => { - if (!opts.erc20 && !opts.feeds) { - const { wethFeed, wbtcFeed, btcFeed, usdtFeed } = await makeFeeds() - - const fix = await makeWTricryptoPoolVolatile() - - opts.feeds = [[wethFeed.address], [wbtcFeed.address, btcFeed.address], [usdtFeed.address]] - opts.erc20 = fix.wrapper.address - } - - opts = { ...defaultCvxVolatileCollateralOpts, ...opts } - - const CvxVolatileCollateralFactory: ContractFactory = await ethers.getContractFactory( - 'CurveVolatileCollateral' - ) - - const collateral = await CvxVolatileCollateralFactory.deploy( - { - erc20: opts.erc20, - targetName: opts.targetName, - priceTimeout: opts.priceTimeout, - chainlinkFeed: opts.chainlinkFeed, - oracleError: opts.oracleError, - oracleTimeout: opts.oracleTimeout, - maxTradeVolume: opts.maxTradeVolume, - defaultThreshold: opts.defaultThreshold, - delayUntilDefault: opts.delayUntilDefault, - }, - opts.revenueHiding, - { - nTokens: opts.nTokens, - curvePool: opts.curvePool, - poolType: opts.poolType, - feeds: opts.feeds, - oracleTimeouts: opts.oracleTimeouts, - oracleErrors: opts.oracleErrors, - lpToken: opts.lpToken, - } - ) - await collateral.deployed() - - // sometimes we are trying to test a negative test case and we want this to fail silently - // fortunately this syntax fails silently because our tools are terrible - await expect(collateral.refresh()) - - return [collateral, opts] -} - -const makeCollateralFixtureContext = ( - alice: SignerWithAddress, - opts: CurveCollateralOpts = {} -): Fixture => { - const collateralOpts = { ...defaultCvxVolatileCollateralOpts, ...opts } - - const makeCollateralFixtureContext = async () => { - const { wethFeed, wbtcFeed, btcFeed, usdtFeed } = await makeFeeds() - - collateralOpts.feeds = [ - [usdtFeed.address], - [wbtcFeed.address, btcFeed.address], - [wethFeed.address], - ] - - const fix = await makeWTricryptoPoolVolatile() - - collateralOpts.erc20 = fix.wrapper.address - collateralOpts.curvePool = fix.curvePool.address - const collateral = ((await deployCollateral(collateralOpts))[0] as unknown) - const cvx = await ethers.getContractAt('ERC20Mock', CVX) - const crv = await ethers.getContractAt('ERC20Mock', CRV) - - return { - alice, - collateral, - curvePool: fix.curvePool, - wrapper: fix.wrapper, - rewardTokens: [cvx, crv], - poolTokens: [fix.usdt, fix.wbtc, fix.weth], - feeds: [usdtFeed, btcFeed, wethFeed], // exclude wbtcFeed - } - } - - return makeCollateralFixtureContext -} - -/* - Define helper functions -*/ - -const mintCollateralTo: MintCurveCollateralFunc = async ( - ctx: CurveCollateralFixtureContext, - amount: BigNumberish, - user: SignerWithAddress, - recipient: string -) => { - await mintWPool(ctx, amount, user, recipient, TRI_CRYPTO_HOLDER) -} - -/* - Define collateral-specific tests -*/ - -// eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificConstructorTests = () => {} - -// eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificStatusTests = () => {} - -/* - Run the test suite -*/ - -const opts = { - deployCollateral, - collateralSpecificConstructorTests, - collateralSpecificStatusTests, - makeCollateralFixtureContext, - mintCollateralTo, - itChecksTargetPerRefDefault: it, - itChecksRefPerTokDefault: it, - itHasRevenueHiding: it, - isMetapool: false, - resetFork, - collateralName: 'CurveVolatileCollateral - ConvexStakingWrapper', -} - -collateralTests(opts) diff --git a/test/plugins/individual-collateral/stargate/StargateRewardableWrapper.test.ts b/test/plugins/individual-collateral/stargate/StargateRewardableWrapper.test.ts index e0c4eb4e37..76bf95cb27 100644 --- a/test/plugins/individual-collateral/stargate/StargateRewardableWrapper.test.ts +++ b/test/plugins/individual-collateral/stargate/StargateRewardableWrapper.test.ts @@ -255,10 +255,10 @@ describeFork('Wrapped S*USDC', () => { }) it('claims previous rewards', async () => { + await wrapper.connect(bob).deposit(await mockPool.balanceOf(bob.address), bob.address) await stakingContract.addRewardsToUser(bn('0'), wrapper.address, bn('20000e18')) const availableReward = await stakingContract.pendingStargate('0', wrapper.address) await mockPool.mint(bob.address, initialAmount) - await wrapper.connect(bob).deposit(await mockPool.balanceOf(bob.address), bob.address) await wrapper.connect(bob).claimRewards() expect(availableReward).to.be.eq(await stargate.balanceOf(bob.address)) @@ -383,5 +383,52 @@ describeFork('Wrapped S*USDC', () => { // charles rewards - 0 }) }) + + describe('Emergency - Ignore Rewards', () => { + const amount = bn('20000e6') + + beforeEach(async () => { + const requiredAmount = await stgUSDC.amountLPtoLD(amount) + + await allocateUSDC(bob.address, requiredAmount.sub(await usdc.balanceOf(bob.address))) + + await usdc.connect(bob).approve(router.address, requiredAmount) + await router.connect(bob).addLiquidity(await stgUSDC.poolId(), requiredAmount, bob.address) + + await stgUSDC.connect(bob).approve(wstgUSDC.address, ethers.constants.MaxUint256) + }) + + it('deposits & withdraws correctly when in emergency already', async () => { + // Set staking contract in emergency mode + await stakingContract.setAllocPoint(0, 0) + + await wstgUSDC.connect(bob).deposit(await stgUSDC.balanceOf(bob.address), bob.address) + + expect(await stgUSDC.balanceOf(bob.address)).to.equal(0) + expect(await wstgUSDC.balanceOf(bob.address)).to.closeTo(amount, 10) + expect(await usdc.balanceOf(bob.address)).to.equal(0) + + await wstgUSDC.connect(bob).withdraw(await wstgUSDC.balanceOf(bob.address), bob.address) + + expect(await wstgUSDC.balanceOf(bob.address)).to.closeTo(bn('0'), 10) + expect(await stgUSDC.balanceOf(bob.address)).to.closeTo(amount, 10) + }) + + it('deposits & withdraws correctly when put in emergency while operating', async () => { + await wstgUSDC.connect(bob).deposit(await stgUSDC.balanceOf(bob.address), bob.address) + + expect(await stgUSDC.balanceOf(bob.address)).to.equal(0) + expect(await wstgUSDC.balanceOf(bob.address)).to.closeTo(amount, 10) + expect(await usdc.balanceOf(bob.address)).to.equal(0) + + // Set staking contract in emergency mode + await stakingContract.setAllocPoint(0, 0) + + await wstgUSDC.connect(bob).withdraw(await wstgUSDC.balanceOf(bob.address), bob.address) + + expect(await wstgUSDC.balanceOf(bob.address)).to.closeTo(bn('0'), 10) + expect(await stgUSDC.balanceOf(bob.address)).to.closeTo(amount, 10) + }) + }) }) }) From 8aa658e0cbd8f3fe815831c1454c3a3e4350b9bb Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 23 Aug 2023 16:12:45 -0400 Subject: [PATCH 386/462] Restrict disabling dutch auctions to those started by the BackingManager (#903) --- CHANGELOG.md | 1 + contracts/p0/Broker.sol | 17 ++++++++++------- contracts/p1/Broker.sol | 17 ++++++++++------- test/Revenues.test.ts | 18 +++++++++--------- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c799ae6f1..c4c5452c71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Then call `Broker.cacheComponents()`. - Replace use of `lotPrice()` with `price()` - `Broker` [+1 slot] - Disallow starting dutch trades with non-RTokenAsset assets when `lastSave() != block.timestamp` + - Only permit BackingManager-started dutch auctions to report violations and disable trading ## Plugins diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 51619620ed..9527a213dc 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -101,13 +101,16 @@ contract BrokerP0 is ComponentP0, IBroker { emit BatchTradeDisabledSet(batchTradeDisabled, true); batchTradeDisabled = true; } else if (kind == TradeKind.DUTCH_AUCTION) { - IERC20Metadata sell = trade.sell(); - emit DutchTradeDisabledSet(sell, dutchTradeDisabled[sell], true); - dutchTradeDisabled[sell] = true; - - IERC20Metadata buy = trade.buy(); - emit DutchTradeDisabledSet(buy, dutchTradeDisabled[buy], true); - dutchTradeDisabled[buy] = true; + // Only allow BackingManager-started trades to disable Dutch Auctions + if (DutchTrade(address(trade)).origin() == main.backingManager()) { + IERC20Metadata sell = trade.sell(); + emit DutchTradeDisabledSet(sell, dutchTradeDisabled[sell], true); + dutchTradeDisabled[sell] = true; + + IERC20Metadata buy = trade.buy(); + emit DutchTradeDisabledSet(buy, dutchTradeDisabled[buy], true); + dutchTradeDisabled[buy] = true; + } } else { revert("unrecognized trade kind"); } diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 2cf4600878..a8fd023aee 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -146,13 +146,16 @@ contract BrokerP1 is ComponentP1, IBroker { emit BatchTradeDisabledSet(batchTradeDisabled, true); batchTradeDisabled = true; } else if (kind == TradeKind.DUTCH_AUCTION) { - IERC20Metadata sell = trade.sell(); - emit DutchTradeDisabledSet(sell, dutchTradeDisabled[sell], true); - dutchTradeDisabled[sell] = true; - - IERC20Metadata buy = trade.buy(); - emit DutchTradeDisabledSet(buy, dutchTradeDisabled[buy], true); - dutchTradeDisabled[buy] = true; + // Only allow BackingManager-started trades to disable Dutch Auctions + if (DutchTrade(address(trade)).origin() == backingManager) { + IERC20Metadata sell = trade.sell(); + emit DutchTradeDisabledSet(sell, dutchTradeDisabled[sell], true); + dutchTradeDisabled[sell] = true; + + IERC20Metadata buy = trade.buy(); + emit DutchTradeDisabledSet(buy, dutchTradeDisabled[buy], true); + dutchTradeDisabled[buy] = true; + } } else { revert("unrecognized trade kind"); } diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 191c5864e0..86ba41c71c 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2228,7 +2228,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(furnace.address)).to.be.closeTo(minBuyAmtRToken.sub(10), 50) }) - it('Should report violation when Dutch Auction clears in geometric phase', async () => { + it('Should not report violation when Dutch Auction clears in geometric phase', async () => { // This test needs to be in this file and not Broker.test.ts because settleTrade() // requires the BackingManager _actually_ started the trade @@ -2322,24 +2322,24 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Advance time near end of geometric phase await advanceBlocks(config.dutchAuctionLength.div(12).div(5).sub(5)) - // Should settle RSR auction + // Should settle RSR auction without disabling dutch auctions await rsr.connect(addr1).approve(rsrTrade.address, sellAmt.mul(10)) await expect(rsrTrade.connect(addr1).bid()) .to.emit(rsrTrader, 'TradeSettled') .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, anyValue) - expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(true) - expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(true) + expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(false) - // Should still be able to settle RToken auction, even though aaveToken is now disabled + // Should still be able to settle RToken auction await rToken.connect(addr1).approve(rTokenTrade.address, sellAmtRToken.mul(10)) await expect(rTokenTrade.connect(addr1).bid()) .to.emit(rTokenTrader, 'TradeSettled') .withArgs(anyValue, aaveToken.address, rToken.address, sellAmtRToken, anyValue) - // Check all 3 tokens are disabled for dutch auctions - expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(true) - expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(true) - expect(await broker.dutchTradeDisabled(rToken.address)).to.equal(true) + // Check all no tokens are disabled for dutch auctions + expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rToken.address)).to.equal(false) }) it('Should not report violation when Dutch Auction clears in first linear phase', async () => { From 2c98933c00935ffdae6cd447acf50deaf7ecd4c2 Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Thu, 24 Aug 2023 15:58:18 -0400 Subject: [PATCH 387/462] Stargate fixes (#881) Co-authored-by: Akshat Mittal --- .../stargate/StargatePoolETHCollateral.sol | 8 ++++++++ .../stargate/StargatePoolFiatCollateral.sol | 8 ++++++++ .../stargate/StargateRewardableWrapper.sol | 2 -- .../deploy_stargate_eth_collateral.ts | 16 ++++++++-------- .../deploy_stargate_usdc_collateral.ts | 4 ++-- .../deploy_stargate_usdt_collateral.ts | 4 ++-- 6 files changed, 28 insertions(+), 14 deletions(-) diff --git a/contracts/plugins/assets/stargate/StargatePoolETHCollateral.sol b/contracts/plugins/assets/stargate/StargatePoolETHCollateral.sol index f33f9f1bb2..cf06c6df8d 100644 --- a/contracts/plugins/assets/stargate/StargatePoolETHCollateral.sol +++ b/contracts/plugins/assets/stargate/StargatePoolETHCollateral.sol @@ -6,6 +6,14 @@ import "../OracleLib.sol"; import "./interfaces/IStargatePool.sol"; import "./StargatePoolFiatCollateral.sol"; +/** + * @title StargatePoolETHCollateral + * @notice Collateral plugin for Stargate ETH, + * tok = wstgETH + * ref = ETH + * tar = ETH + * UoA = USD + */ contract StargatePoolETHCollateral is StargatePoolFiatCollateral { using FixLib for uint192; using OracleLib for AggregatorV3Interface; diff --git a/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol b/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol index 2a97424f65..2a785a97a5 100644 --- a/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol +++ b/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol @@ -8,6 +8,14 @@ import "./interfaces/IStargatePool.sol"; import "./StargateRewardableWrapper.sol"; +/** + * @title StargatePoolFiatCollateral + * @notice Collateral plugin for Stargate USD Stablecoins, + * tok = wstgUSDC / wstgUSDT + * ref = USDC / USDT + * tar = USD + * UoA = USD + */ contract StargatePoolFiatCollateral is FiatCollateral { using FixLib for uint192; using OracleLib for AggregatorV3Interface; diff --git a/contracts/plugins/assets/stargate/StargateRewardableWrapper.sol b/contracts/plugins/assets/stargate/StargateRewardableWrapper.sol index d1f6878053..277acc3a7b 100644 --- a/contracts/plugins/assets/stargate/StargateRewardableWrapper.sol +++ b/contracts/plugins/assets/stargate/StargateRewardableWrapper.sol @@ -9,7 +9,6 @@ import "../erc20/RewardableERC20Wrapper.sol"; contract StargateRewardableWrapper is RewardableERC20Wrapper { IStargateLPStaking public immutable stakingContract; IStargatePool public immutable pool; - IERC20 public immutable stargate; uint256 public immutable poolId; constructor( @@ -40,7 +39,6 @@ contract StargateRewardableWrapper is RewardableERC20Wrapper { pool = pool_; poolId = pid; stakingContract = stakingContract_; - stargate = stargate_; } function _claimAssetRewards() internal override { diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_stargate_eth_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_eth_collateral.ts index 522bc917f0..062619d2df 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_stargate_eth_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_eth_collateral.ts @@ -14,8 +14,8 @@ import { } from '../../common' import { priceTimeout, oracleTimeout } from '../../utils' import { - StargatePoolFiatCollateral, - StargatePoolFiatCollateral__factory, + StargatePoolETHCollateral, + StargatePoolETHCollateral__factory, } from '../../../../typechain' import { ContractFactory } from 'ethers' @@ -65,10 +65,10 @@ async function main() { `Deployed Wrapper for Stargate ETH on ${hre.network.name} (${chainId}): ${erc20.address} ` ) - const StargateCollateralFactory: StargatePoolFiatCollateral__factory = - await hre.ethers.getContractFactory('StargatePoolFiatCollateral') + const StargateCollateralFactory: StargatePoolETHCollateral__factory = + await hre.ethers.getContractFactory('StargatePoolETHCollateral') - const collateral = await StargateCollateralFactory.connect( + const collateral = await StargateCollateralFactory.connect( deployer ).deploy({ priceTimeout: priceTimeout.toString(), @@ -76,10 +76,10 @@ async function main() { oracleError: fp('0.005').toString(), // 0.5%, erc20: erc20.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24h hr, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.05').toString(), - delayUntilDefault: bn('86400').toString(), // 24h + defaultThreshold: 0, + delayUntilDefault: 0, }) await collateral.deployed() await (await collateral.refresh()).wait() diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts index 6b91f8d531..041c639de9 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts @@ -73,12 +73,12 @@ async function main() { ).deploy({ priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, - oracleError: fp('0.001').toString(), // 0.1%, + oracleError: fp('0.0025').toString(), // 0.25%, erc20: erc20.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24h hr, targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.05').toString(), + defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h }) await collateral.deployed() diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdt_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdt_collateral.ts index 6d0d723c7a..30278b042d 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdt_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdt_collateral.ts @@ -73,12 +73,12 @@ async function main() { ).deploy({ priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDT!, - oracleError: fp('0.001').toString(), // 0.1%, + oracleError: fp('0.0025').toString(), // 0.25%, erc20: erc20.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24h hr, targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.05').toString(), + defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h }) await collateral.deployed() From a0480a88a24cc0cca783356afb1ad43b96ae02fb Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Thu, 24 Aug 2023 17:25:32 -0400 Subject: [PATCH 388/462] 3.0.0 rc5 (#882) Co-authored-by: Akshat Mittal Co-authored-by: brr Co-authored-by: Taylor Brent Co-authored-by: Julian M. Rodriguez <56316686+julianmrodri@users.noreply.github.com> --- .github/workflows/{4bytes.yml => 4byte.yml} | 11 +- .github/workflows/tests.yml | 8 - .openzeppelin/ropsten.json | 3148 +++++++++++++++++ README.md | 5 + common/configuration.ts | 107 +- contracts/facade/FacadeRead.sol | 8 +- contracts/facade/FacadeTest.sol | 24 +- contracts/interfaces/IBackingManager.sol | 4 +- contracts/interfaces/IBasketHandler.sol | 9 +- contracts/interfaces/IBroker.sol | 24 +- contracts/interfaces/IDistributor.sol | 4 +- contracts/interfaces/IFurnace.sol | 2 +- contracts/interfaces/IMain.sol | 28 +- contracts/interfaces/IRToken.sol | 4 +- contracts/interfaces/IRevenueTrader.sol | 4 + contracts/interfaces/IRewardable.sol | 2 +- contracts/interfaces/IStRSR.sol | 16 +- contracts/interfaces/ITrading.sol | 4 +- contracts/libraries/NetworkConfigLib.sol | 2 +- contracts/p0/AssetRegistry.sol | 7 + contracts/p0/BackingManager.sol | 2 +- contracts/p0/BasketHandler.sol | 4 +- contracts/p0/Broker.sol | 50 +- contracts/p0/Deployer.sol | 6 +- contracts/p0/Furnace.sol | 10 +- contracts/p0/RToken.sol | 6 +- contracts/p0/RevenueTrader.sol | 20 + contracts/p0/StRSR.sol | 18 + contracts/p0/mixins/Trading.sol | 1 - contracts/p1/AssetRegistry.sol | 9 + contracts/p1/BackingManager.sol | 11 +- contracts/p1/BasketHandler.sol | 23 +- contracts/p1/Broker.sol | 55 +- contracts/p1/Distributor.sol | 7 +- contracts/p1/Furnace.sol | 10 +- contracts/p1/RToken.sol | 19 +- contracts/p1/RevenueTrader.sol | 32 +- contracts/p1/StRSR.sol | 17 + contracts/p1/mixins/BasketLib.sol | 47 +- .../p1/mixins/RecollateralizationLib.sol | 28 +- contracts/p1/mixins/Trading.sol | 1 - contracts/plugins/assets/OracleLib.sol | 11 +- .../plugins/assets/aave/StaticATokenLM.sol | 2 +- .../curve/cvx/vendor/ConvexStakingWrapper.sol | 16 +- .../morpho-aave/MorphoFiatCollateral.sol | 9 +- .../morpho-aave/MorphoNonFiatCollateral.sol | 2 +- contracts/plugins/mocks/ChainlinkMock.sol | 18 + contracts/plugins/mocks/EasyAuction.sol | 20 +- contracts/plugins/mocks/InvalidBrokerMock.sol | 12 +- .../mocks/InvalidRefPerTokCollateral.sol | 5 + .../plugins/mocks/InvalidRevTraderP1Mock.sol | 24 +- contracts/plugins/trading/DutchTrade.sol | 235 +- contracts/plugins/trading/GnosisTrade.sol | 13 +- docs/build-on-top.md | 17 + docs/recollateralization.md | 2 + docs/system-design.md | 5 +- hardhat.config.ts | 5 +- package.json | 36 +- scripts/4byte.ts | 51 + scripts/4bytes-syncced.json | 1126 ------ scripts/4bytes.ts | 105 - .../addresses/3-tmp-assets-collateral.json | 85 + scripts/addresses/3-tmp-deployments.json | 35 + .../deploy_morpho_aavev2_plugin.ts | 189 +- .../upgrade-checker-utils/constants.ts | 2 +- tasks/testing/upgrade-checker-utils/trades.ts | 2 +- .../upgrade-checker-utils/upgrades/2_1_0.ts | 6 +- test/Broker.test.ts | 476 ++- test/Facade.test.ts | 12 +- test/Furnace.test.ts | 39 + test/Main.test.ts | 15 + test/RToken.test.ts | 52 + test/Recollateralization.test.ts | 64 +- test/Revenues.test.ts | 452 ++- test/Upgradeability.test.ts | 8 +- test/ZTradingExtremes.test.ts | 6 + test/ZZStRSR.test.ts | 120 + test/__snapshots__/Broker.test.ts.snap | 16 +- test/__snapshots__/FacadeWrite.test.ts.snap | 4 +- test/__snapshots__/Furnace.test.ts.snap | 34 +- test/__snapshots__/Main.test.ts.snap | 12 +- test/__snapshots__/RToken.test.ts.snap | 6 +- .../Recollateralization.test.ts.snap | 18 +- test/__snapshots__/Revenues.test.ts.snap | 26 +- test/__snapshots__/ZZStRSR.test.ts.snap | 14 +- test/fixtures.ts | 32 +- test/integration/EasyAuction.test.ts | 16 +- test/plugins/Asset.test.ts | 40 +- test/plugins/Collateral.test.ts | 92 +- test/plugins/OracleDeprecation.test.ts | 64 + test/plugins/__snapshots__/Asset.test.ts.snap | 13 + .../__snapshots__/Collateral.test.ts.snap | 18 +- .../RewardableERC20.test.ts.snap | 25 + .../RewardableERC20Vault.test.ts.snap | 13 - .../aave/ATokenFiatCollateral.test.ts | 211 +- .../ATokenFiatCollateral.test.ts.snap | 25 + .../AnkrEthCollateralTestSuite.test.ts.snap | 17 + .../CBETHCollateral.test.ts.snap | 17 + .../individual-collateral/collateralTests.ts | 51 + .../compoundv2/CTokenFiatCollateral.test.ts | 175 +- .../CTokenFiatCollateral.test.ts.snap | 25 + .../__snapshots__/CometTestSuite.test.ts.snap | 25 + .../curve/collateralTests.ts | 56 + .../CrvStableMetapoolSuite.test.ts.snap | 25 + ...StableRTokenMetapoolTestSuite.test.ts.snap | 25 + .../CrvStableTestSuite.test.ts.snap | 25 + .../CrvVolatileTestSuite.test.ts.snap | 25 + .../CvxStableMetapoolSuite.test.ts.snap | 25 + ...StableRTokenMetapoolTestSuite.test.ts.snap | 25 + .../CvxStableTestSuite.test.ts.snap | 25 + .../CvxVolatileTestSuite.test.ts.snap | 25 + .../dsr/SDaiCollateralTestSuite.test.ts | 17 +- .../SDaiCollateralTestSuite.test.ts.snap | 25 + .../flux-finance/FTokenFiatCollateral.test.ts | 141 +- .../FTokenFiatCollateral.test.ts.snap | 97 + .../SFrxEthTestSuite.test.ts.snap | 13 + .../LidoStakedEthTestSuite.test.ts.snap | 25 + .../MorphoAAVEFiatCollateral.test.ts | 573 +-- .../MorphoAAVENonFiatCollateral.test.ts | 391 +- ...orphoAAVESelfReferentialCollateral.test.ts | 8 +- .../MorphoAaveV2TokenisedDeposit.test.ts | 14 +- .../MorphoAAVEFiatCollateral.test.ts.snap | 73 + .../MorphoAAVENonFiatCollateral.test.ts.snap | 49 + ...AAVESelfReferentialCollateral.test.ts.snap | 17 + .../RethCollateralTestSuite.test.ts.snap | 17 + .../StargateETHTestSuite.test.ts.snap | 49 + .../__snapshots__/MaxBasketSize.test.ts.snap | 18 +- test/utils/trades.ts | 32 +- yarn.lock | 2815 ++++++++------- 129 files changed, 8886 insertions(+), 3807 deletions(-) rename .github/workflows/{4bytes.yml => 4byte.yml} (59%) create mode 100644 .openzeppelin/ropsten.json create mode 100644 docs/build-on-top.md create mode 100644 scripts/4byte.ts delete mode 100644 scripts/4bytes-syncced.json delete mode 100644 scripts/4bytes.ts create mode 100644 scripts/addresses/3-tmp-assets-collateral.json create mode 100644 scripts/addresses/3-tmp-deployments.json create mode 100644 test/plugins/OracleDeprecation.test.ts create mode 100644 test/plugins/__snapshots__/Asset.test.ts.snap create mode 100644 test/plugins/__snapshots__/RewardableERC20.test.ts.snap delete mode 100644 test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap create mode 100644 test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap diff --git a/.github/workflows/4bytes.yml b/.github/workflows/4byte.yml similarity index 59% rename from .github/workflows/4bytes.yml rename to .github/workflows/4byte.yml index 72ce1fb622..3e456ddbae 100644 --- a/.github/workflows/4bytes.yml +++ b/.github/workflows/4byte.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - main pull_request: types: - closed @@ -13,7 +14,7 @@ jobs: name: '4byte Sync' runs-on: ubuntu-latest permissions: - contents: write + contents: read steps: - uses: actions/checkout@v3 with: @@ -23,10 +24,4 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - - run: yarn run:4bytes - - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: 4bytes-syncced.json - commit_options: '--no-verify --signoff' - file_pattern: 'scripts/4bytes-syncced.json' + - run: yarn run:4byte diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1f3a2cdc5a..a0215996f0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,6 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - run: yarn devchain & env: MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} @@ -37,7 +36,6 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - run: yarn lint plugin-tests: @@ -50,7 +48,6 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - run: yarn test:plugins - name: 'Cache hardhat network fork' uses: actions/cache@v3 @@ -76,7 +73,6 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - run: yarn test:p0 env: NODE_OPTIONS: '--max-old-space-size=8192' @@ -91,7 +87,6 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - run: yarn test:p1 env: NODE_OPTIONS: '--max-old-space-size=8192' @@ -106,7 +101,6 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - run: yarn test:scenario env: NODE_OPTIONS: '--max-old-space-size=8192' @@ -121,7 +115,6 @@ jobs: node-version: 16.x cache: 'yarn' - run: yarn install --immutable - - run: yarn compile - run: yarn test:extreme - name: 'Cache hardhat network fork' uses: actions/cache@v3 @@ -157,7 +150,6 @@ jobs: hardhat-network-fork-${{ runner.os }}- hardhat-network-fork- - run: yarn install --immutable - - run: yarn compile - run: yarn test:integration env: NODE_OPTIONS: '--max-old-space-size=8192' diff --git a/.openzeppelin/ropsten.json b/.openzeppelin/ropsten.json new file mode 100644 index 0000000000..ba9ed634a2 --- /dev/null +++ b/.openzeppelin/ropsten.json @@ -0,0 +1,3148 @@ +{ + "manifestVersion": "3.2", + "proxies": [], + "impls": { + "387f5c1d576149b40bc0e5061719ad027b6edbb10b7c3cc32ae96a49ca2ddd6d": { + "address": "0x773cf50adCF1730964D4A9b664BaEd4b9FFC2450", + "txHash": "0xfb5f15c28821dc69c191b1de79f54ac0eb95944c80d6cb601a3f944434d84218", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC165Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41" + }, + { + "label": "_roles", + "offset": 0, + "slot": "101", + "type": "t_mapping(t_bytes32,t_struct(RoleData)80_storage)", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:61" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:259" + }, + { + "label": "longFreezes", + "offset": 0, + "slot": "151", + "type": "t_mapping(t_address,t_uint256)", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:36" + }, + { + "label": "unfreezeAt", + "offset": 0, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:38" + }, + { + "label": "shortFreeze", + "offset": 6, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:39" + }, + { + "label": "longFreeze", + "offset": 12, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:40" + }, + { + "label": "tradingPaused", + "offset": 18, + "slot": "152", + "type": "t_bool", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:45", + "renamedFrom": "paused" + }, + { + "label": "issuancePaused", + "offset": 19, + "slot": "152", + "type": "t_bool", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:46" + }, + { + "label": "__gap", + "offset": 0, + "slot": "153", + "type": "t_array(t_uint256)48_storage", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:225" + }, + { + "label": "rToken", + "offset": 0, + "slot": "201", + "type": "t_contract(IRToken)9273", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:34" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "202", + "type": "t_contract(IStRSR)9610", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:42" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "203", + "type": "t_contract(IAssetRegistry)7781", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:50" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "204", + "type": "t_contract(IBasketHandler)8090", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:58" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "205", + "type": "t_contract(IBackingManager)7843", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:66" + }, + { + "label": "distributor", + "offset": 0, + "slot": "206", + "type": "t_contract(IDistributor)8548", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:74" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "207", + "type": "t_contract(IRevenueTrader)9402", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:82" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "208", + "type": "t_contract(IRevenueTrader)9402", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:90" + }, + { + "label": "furnace", + "offset": 0, + "slot": "209", + "type": "t_contract(IFurnace)8609", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:98" + }, + { + "label": "broker", + "offset": 0, + "slot": "210", + "type": "t_contract(IBroker)8239", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:106" + }, + { + "label": "__gap", + "offset": 0, + "slot": "211", + "type": "t_array(t_uint256)40_storage", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:119" + }, + { + "label": "__gap", + "offset": 0, + "slot": "251", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "301", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "rsr", + "offset": 0, + "slot": "351", + "type": "t_contract(IERC20)4390", + "contract": "MainP1", + "src": "contracts/p1/Main.sol:19" + }, + { + "label": "__gap", + "offset": 0, + "slot": "352", + "type": "t_array(t_uint256)49_storage", + "contract": "MainP1", + "src": "contracts/p1/Main.sol:71" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)40_storage": { + "label": "uint256[40]", + "numberOfBytes": "1280" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)7781": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)7843": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)8090": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IBroker)8239": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)8548": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)4390": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)8609": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IRToken)9273": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)9402": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)9610": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)80_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(RoleData)80_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "fe0f2dec194b882efa0d8220d324cba3ec32136c7c6322c221bd90103690d736": { + "address": "0x81b9Ae0740CcA7cDc5211b2737de735FBC4BeB3C", + "txHash": "0x8450c16d3aaf3b9ebcb9879d39c8bc62c0717a6ddc38530804ac13f59b95b70d", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22719", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "201", + "type": "t_contract(IBasketHandler)21243", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:19" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "202", + "type": "t_contract(IBackingManager)20996", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:20" + }, + { + "label": "_erc20s", + "offset": 0, + "slot": "203", + "type": "t_struct(AddressSet)17778_storage", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:23" + }, + { + "label": "assets", + "offset": 0, + "slot": "205", + "type": "t_mapping(t_contract(IERC20)11530,t_contract(IAsset)20691)", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:26" + }, + { + "label": "lastRefresh", + "offset": 0, + "slot": "206", + "type": "t_uint48", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:30" + }, + { + "label": "__gap", + "offset": 0, + "slot": "207", + "type": "t_array(t_uint256)46_storage", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:233" + } + ], + "types": { + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAsset)20691": { + "label": "contract IAsset", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)20996": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)21243": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IERC20)11530": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)22719": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)11530,t_contract(IAsset)20691)": { + "label": "mapping(contract IERC20 => contract IAsset)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)17778_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)17477_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Set)17477_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "75937c367e73897978ccbb283109f2c51fa3d603982f50cba839720fd5494c6f": { + "address": "0x1BD20253c49515D348dad1Af70ff2c0473FEa358", + "txHash": "0x6280f33e907da360d6ef84c181626cddcc4595ba685c3bf881ee9dc43e10bbc0", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)12158", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_status", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:74" + }, + { + "label": "broker", + "offset": 0, + "slot": "251", + "type": "t_contract(IBroker)11036", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:27" + }, + { + "label": "trades", + "offset": 0, + "slot": "252", + "type": "t_mapping(t_contract(IERC20)6264,t_contract(ITrade)12850)", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:30" + }, + { + "label": "tradesOpen", + "offset": 0, + "slot": "253", + "type": "t_uint48", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:31" + }, + { + "label": "maxTradeSlippage", + "offset": 6, + "slot": "253", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:34" + }, + { + "label": "minTradeVolume", + "offset": 0, + "slot": "254", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:35" + }, + { + "label": "tradesNonce", + "offset": 0, + "slot": "255", + "type": "t_uint256", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "256", + "type": "t_array(t_uint256)45_storage", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:155" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "301", + "type": "t_contract(IAssetRegistry)10578", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:30" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "302", + "type": "t_contract(IBasketHandler)10887", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:31" + }, + { + "label": "distributor", + "offset": 0, + "slot": "303", + "type": "t_contract(IDistributor)11345", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:32" + }, + { + "label": "rToken", + "offset": 0, + "slot": "304", + "type": "t_contract(IRToken)12378", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:33" + }, + { + "label": "rsr", + "offset": 0, + "slot": "305", + "type": "t_contract(IERC20)6264", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:34" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "306", + "type": "t_contract(IStRSR)12715", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:35" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "307", + "type": "t_contract(IRevenueTrader)12507", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:36" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "308", + "type": "t_contract(IRevenueTrader)12507", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:37" + }, + { + "label": "tradingDelay", + "offset": 20, + "slot": "308", + "type": "t_uint48", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:41" + }, + { + "label": "backingBuffer", + "offset": 0, + "slot": "309", + "type": "t_uint192", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:42" + }, + { + "label": "furnace", + "offset": 0, + "slot": "310", + "type": "t_contract(IFurnace)11714", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:45" + }, + { + "label": "tradeEnd", + "offset": 0, + "slot": "311", + "type": "t_mapping(t_enum(TradeKind)10909,t_uint48)", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:46" + }, + { + "label": "__gap", + "offset": 0, + "slot": "312", + "type": "t_array(t_uint256)39_storage", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:310" + } + ], + "types": { + "t_array(t_uint256)39_storage": { + "label": "uint256[39]", + "numberOfBytes": "1248" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IAssetRegistry)10578": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)10887": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IBroker)11036": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)11345": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)6264": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)11714": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)12158": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)12378": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)12507": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)12715": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_contract(ITrade)12850": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_enum(TradeKind)10909": { + "label": "enum TradeKind", + "members": ["DUTCH_AUCTION", "BATCH_AUCTION"], + "numberOfBytes": "1" + }, + "t_mapping(t_contract(IERC20)6264,t_contract(ITrade)12850)": { + "label": "mapping(contract IERC20 => contract ITrade)", + "numberOfBytes": "32" + }, + "t_mapping(t_enum(TradeKind)10909,t_uint48)": { + "label": "mapping(enum TradeKind => uint48)", + "numberOfBytes": "32" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "bbf9dd201f53ccf79786486dfa2cd67b1e7bd910977c30dfc96ef0ba62009025": { + "address": "0x0776Ad71Ae99D759354B3f06fe17454b94837B0D", + "txHash": "0xefc0dd100e291ebe15d63ad9d67ae1be0763329f4dcd1306074bca1a38c928fe", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22719", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "201", + "type": "t_contract(IAssetRegistry)20934", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:34" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "202", + "type": "t_contract(IBackingManager)20996", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:35" + }, + { + "label": "rsr", + "offset": 0, + "slot": "203", + "type": "t_contract(IERC20)11530", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:36" + }, + { + "label": "rToken", + "offset": 0, + "slot": "204", + "type": "t_contract(IRToken)22939", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:37" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "205", + "type": "t_contract(IStRSR)23276", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:38" + }, + { + "label": "config", + "offset": 0, + "slot": "206", + "type": "t_struct(BasketConfig)49596_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:42" + }, + { + "label": "basket", + "offset": 0, + "slot": "210", + "type": "t_struct(Basket)49606_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:46" + }, + { + "label": "nonce", + "offset": 0, + "slot": "212", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:48" + }, + { + "label": "timestamp", + "offset": 6, + "slot": "212", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:49" + }, + { + "label": "disabled", + "offset": 12, + "slot": "212", + "type": "t_bool", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:53" + }, + { + "label": "_targetNames", + "offset": 0, + "slot": "213", + "type": "t_struct(Bytes32Set)17671_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:59" + }, + { + "label": "_newBasket", + "offset": 0, + "slot": "215", + "type": "t_struct(Basket)49606_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:60" + }, + { + "label": "warmupPeriod", + "offset": 0, + "slot": "217", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:66" + }, + { + "label": "lastStatusTimestamp", + "offset": 6, + "slot": "217", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:70" + }, + { + "label": "lastStatus", + "offset": 12, + "slot": "217", + "type": "t_enum(CollateralStatus)20723", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:71" + }, + { + "label": "basketHistory", + "offset": 0, + "slot": "218", + "type": "t_mapping(t_uint48,t_struct(Basket)49606_storage)", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:77" + }, + { + "label": "_targetAmts", + "offset": 0, + "slot": "219", + "type": "t_struct(Bytes32ToUintMap)17283_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:80" + }, + { + "label": "__gap", + "offset": 0, + "slot": "222", + "type": "t_array(t_uint256)37_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:670" + } + ], + "types": { + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_contract(IERC20)11530)dyn_storage": { + "label": "contract IERC20[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)37_storage": { + "label": "uint256[37]", + "numberOfBytes": "1184" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)20934": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)20996": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IERC20)11530": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)22719": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)22939": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)23276": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_enum(CollateralStatus)20723": { + "label": "enum CollateralStatus", + "members": ["SOUND", "IFFY", "DISABLED"], + "numberOfBytes": "1" + }, + "t_mapping(t_bytes32,t_bytes32)": { + "label": "mapping(bytes32 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(BackupConfig)49576_storage)": { + "label": "mapping(bytes32 => struct BackupConfig)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)11530,t_bytes32)": { + "label": "mapping(contract IERC20 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)11530,t_uint192)": { + "label": "mapping(contract IERC20 => uint192)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint48,t_struct(Basket)49606_storage)": { + "label": "mapping(uint48 => struct Basket)", + "numberOfBytes": "32" + }, + "t_struct(BackupConfig)49576_storage": { + "label": "struct BackupConfig", + "members": [ + { + "label": "max", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)11530)dyn_storage", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Basket)49606_storage": { + "label": "struct Basket", + "members": [ + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)11530)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "refAmts", + "type": "t_mapping(t_contract(IERC20)11530,t_uint192)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(BasketConfig)49596_storage": { + "label": "struct BasketConfig", + "members": [ + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)11530)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "targetAmts", + "type": "t_mapping(t_contract(IERC20)11530,t_uint192)", + "offset": 0, + "slot": "1" + }, + { + "label": "targetNames", + "type": "t_mapping(t_contract(IERC20)11530,t_bytes32)", + "offset": 0, + "slot": "2" + }, + { + "label": "backups", + "type": "t_mapping(t_bytes32,t_struct(BackupConfig)49576_storage)", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_struct(Bytes32Set)17671_storage": { + "label": "struct EnumerableSet.Bytes32Set", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)17477_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Bytes32ToBytes32Map)16360_storage": { + "label": "struct EnumerableMap.Bytes32ToBytes32Map", + "members": [ + { + "label": "_keys", + "type": "t_struct(Bytes32Set)17671_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_values", + "type": "t_mapping(t_bytes32,t_bytes32)", + "offset": 0, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(Bytes32ToUintMap)17283_storage": { + "label": "struct EnumerableMap.Bytes32ToUintMap", + "members": [ + { + "label": "_inner", + "type": "t_struct(Bytes32ToBytes32Map)16360_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "96" + }, + "t_struct(Set)17477_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "25acc9f9e0863580ed5e42b5b697f5c8de88f433a7369fd0b823c5535d615b73": { + "address": "0xDAacEE75C863a79f07699b094DB07793D3A52D6D", + "txHash": "0x226d1a61e9836ab01be911e23b0d1e6e34d9d20ecce6a5a0a1bddea14431b84f", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)12158", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "201", + "type": "t_contract(IBackingManager)10640", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:31" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "202", + "type": "t_contract(IRevenueTrader)12507", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:32" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "203", + "type": "t_contract(IRevenueTrader)12507", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:33" + }, + { + "label": "batchTradeImplementation", + "offset": 0, + "slot": "204", + "type": "t_contract(ITrade)12850", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:37", + "renamedFrom": "tradeImplementation" + }, + { + "label": "gnosis", + "offset": 0, + "slot": "205", + "type": "t_contract(IGnosis)11814", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:40" + }, + { + "label": "batchAuctionLength", + "offset": 20, + "slot": "205", + "type": "t_uint48", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:44", + "renamedFrom": "auctionLength" + }, + { + "label": "batchTradeDisabled", + "offset": 26, + "slot": "205", + "type": "t_bool", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:49" + }, + { + "label": "trades", + "offset": 0, + "slot": "206", + "type": "t_mapping(t_address,t_bool)", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:52" + }, + { + "label": "dutchTradeImplementation", + "offset": 0, + "slot": "207", + "type": "t_contract(ITrade)12850", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:57" + }, + { + "label": "dutchAuctionLength", + "offset": 20, + "slot": "207", + "type": "t_uint48", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:60" + }, + { + "label": "dutchTradeDisabled", + "offset": 0, + "slot": "208", + "type": "t_mapping(t_contract(IERC20Metadata)6289,t_bool)", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:63" + }, + { + "label": "__gap", + "offset": 0, + "slot": "209", + "type": "t_array(t_uint256)42_storage", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:274" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)42_storage": { + "label": "uint256[42]", + "numberOfBytes": "1344" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IBackingManager)10640": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IERC20Metadata)6289": { + "label": "contract IERC20Metadata", + "numberOfBytes": "20" + }, + "t_contract(IGnosis)11814": { + "label": "contract IGnosis", + "numberOfBytes": "20" + }, + "t_contract(IMain)12158": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)12507": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(ITrade)12850": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20Metadata)6289,t_bool)": { + "label": "mapping(contract IERC20Metadata => bool)", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "8bb3ef847ff4554ef7b76887a6e564f582e88926ce12f3a30993a52dd8e5417d": { + "address": "0xc3E9E42DE399F50C5Fc2BC971f0b8D10A631688D", + "txHash": "0x869f513695166f606c3a9aa21a5629345ede8e3b2d8b8bb33f6c8ee3b8243342", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)22719", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "destinations", + "offset": 0, + "slot": "201", + "type": "t_struct(AddressSet)17778_storage", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:17" + }, + { + "label": "distribution", + "offset": 0, + "slot": "203", + "type": "t_mapping(t_address,t_struct(RevenueShare)21688_storage)", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:18" + }, + { + "label": "rsr", + "offset": 0, + "slot": "204", + "type": "t_contract(IERC20)11530", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:36" + }, + { + "label": "rToken", + "offset": 0, + "slot": "205", + "type": "t_contract(IERC20)11530", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:37" + }, + { + "label": "furnace", + "offset": 0, + "slot": "206", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:38" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "207", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:39" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "208", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:40" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "209", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:41" + }, + { + "label": "__gap", + "offset": 0, + "slot": "210", + "type": "t_array(t_uint256)44_storage", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:203" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)44_storage": { + "label": "uint256[44]", + "numberOfBytes": "1408" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IERC20)11530": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)22719": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_struct(RevenueShare)21688_storage)": { + "label": "mapping(address => struct RevenueShare)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)17778_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)17477_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(RevenueShare)21688_storage": { + "label": "struct RevenueShare", + "members": [ + { + "label": "rTokenDist", + "type": "t_uint16", + "offset": 0, + "slot": "0" + }, + { + "label": "rsrDist", + "type": "t_uint16", + "offset": 2, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Set)17477_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "4c00631ae462a60fb290aeb6dffd7b15c289fa8cd5f5821368d41a9ab59a3db1": { + "address": "0x6647c880Eb8F57948AF50aB45fca8FE86C154D24", + "txHash": "0xb0fb3a3ade3ccea4131e82db2c2af3d7f492e36d8fd96aeb51a9c7cb852694aa", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)12158", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "rToken", + "offset": 0, + "slot": "201", + "type": "t_contract(IRToken)12378", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:21" + }, + { + "label": "ratio", + "offset": 0, + "slot": "202", + "type": "t_uint192", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:24" + }, + { + "label": "lastPayout", + "offset": 24, + "slot": "202", + "type": "t_uint48", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:27" + }, + { + "label": "lastPayoutBal", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:28" + }, + { + "label": "__gap", + "offset": 0, + "slot": "204", + "type": "t_array(t_uint256)47_storage", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:106" + } + ], + "types": { + "t_array(t_uint256)47_storage": { + "label": "uint256[47]", + "numberOfBytes": "1504" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IMain)12158": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)12378": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "aeb2026b6b8a8117d4f6d9e7dd9ae207d11090a9c7cb204f34cf5d73ae6d6783": { + "address": "0x089848C5228ADe0DF18883197Cd82628b32E95BA", + "txHash": "0x9f9d9c08d2398ee71df5ee642dba3ef748159941bb3bc00b61d98b588378ff31", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)9053", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_status", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:74" + }, + { + "label": "broker", + "offset": 0, + "slot": "251", + "type": "t_contract(IBroker)8239", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:27" + }, + { + "label": "trades", + "offset": 0, + "slot": "252", + "type": "t_mapping(t_contract(IERC20)4390,t_contract(ITrade)9717)", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:30" + }, + { + "label": "tradesOpen", + "offset": 0, + "slot": "253", + "type": "t_uint48", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:31" + }, + { + "label": "maxTradeSlippage", + "offset": 6, + "slot": "253", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:34" + }, + { + "label": "minTradeVolume", + "offset": 0, + "slot": "254", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:35" + }, + { + "label": "tradesNonce", + "offset": 0, + "slot": "255", + "type": "t_uint256", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "256", + "type": "t_array(t_uint256)45_storage", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:155" + }, + { + "label": "tokenToBuy", + "offset": 0, + "slot": "301", + "type": "t_contract(IERC20)4390", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:19" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "302", + "type": "t_contract(IAssetRegistry)7781", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:20" + }, + { + "label": "distributor", + "offset": 0, + "slot": "303", + "type": "t_contract(IDistributor)8548", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:21" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "304", + "type": "t_contract(IBackingManager)7843", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:22" + }, + { + "label": "furnace", + "offset": 0, + "slot": "305", + "type": "t_contract(IFurnace)8609", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:23" + }, + { + "label": "rToken", + "offset": 0, + "slot": "306", + "type": "t_contract(IRToken)9273", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:24" + }, + { + "label": "rsr", + "offset": 0, + "slot": "307", + "type": "t_contract(IERC20)4390", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:25" + }, + { + "label": "__gap", + "offset": 0, + "slot": "308", + "type": "t_array(t_uint256)43_storage", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:190" + } + ], + "types": { + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IAssetRegistry)7781": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)7843": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBroker)8239": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)8548": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)4390": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)8609": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)9053": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)9273": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(ITrade)9717": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_mapping(t_contract(IERC20)4390,t_contract(ITrade)9717)": { + "label": "mapping(contract IERC20 => contract ITrade)", + "numberOfBytes": "32" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "38cc110882c73c48f9ef431eca3acd80c3851cbda1a1de240447152cf30f88f7": { + "address": "0xCFA67f42A0fDe4F0Fb612ea5e66170B0465B84c1", + "txHash": "0xf8fe997d4c24db6cb10f8a1931f741844fa5562ca56f97d9060642d3df81565a", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)12158", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_balances", + "offset": 0, + "slot": "201", + "type": "t_mapping(t_address,t_uint256)", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:37" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "202", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:39" + }, + { + "label": "_totalSupply", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:41" + }, + { + "label": "_name", + "offset": 0, + "slot": "204", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:43" + }, + { + "label": "_symbol", + "offset": 0, + "slot": "205", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:44" + }, + { + "label": "__gap", + "offset": 0, + "slot": "206", + "type": "t_array(t_uint256)45_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:394" + }, + { + "label": "_HASHED_NAME", + "offset": 0, + "slot": "251", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:32" + }, + { + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "252", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:33" + }, + { + "label": "__gap", + "offset": 0, + "slot": "253", + "type": "t_array(t_uint256)50_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:120" + }, + { + "label": "_nonces", + "offset": 0, + "slot": "303", + "type": "t_mapping(t_address,t_struct(Counter)2607_storage)", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:37" + }, + { + "label": "_PERMIT_TYPEHASH_DEPRECATED_SLOT", + "offset": 0, + "slot": "304", + "type": "t_bytes32", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:51", + "renamedFrom": "_PERMIT_TYPEHASH" + }, + { + "label": "__gap", + "offset": 0, + "slot": "305", + "type": "t_array(t_uint256)48_storage", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:129" + }, + { + "label": "mandate", + "offset": 0, + "slot": "353", + "type": "t_string_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:44" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "354", + "type": "t_contract(IAssetRegistry)10578", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:47" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "355", + "type": "t_contract(IBasketHandler)10887", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:48" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "356", + "type": "t_contract(IBackingManager)10640", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:49" + }, + { + "label": "furnace", + "offset": 0, + "slot": "357", + "type": "t_contract(IFurnace)11714", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:50" + }, + { + "label": "basketsNeeded", + "offset": 0, + "slot": "358", + "type": "t_uint192", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:55" + }, + { + "label": "issuanceThrottle", + "offset": 0, + "slot": "359", + "type": "t_struct(Throttle)15183_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:58" + }, + { + "label": "redemptionThrottle", + "offset": 0, + "slot": "363", + "type": "t_struct(Throttle)15183_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:59" + }, + { + "label": "__gap", + "offset": 0, + "slot": "367", + "type": "t_array(t_uint256)42_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:535" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)42_storage": { + "label": "uint256[42]", + "numberOfBytes": "1344" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)10578": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)10640": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)10887": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)11714": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)12158": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Counter)2607_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Counter)2607_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Params)15175_storage": { + "label": "struct ThrottleLib.Params", + "members": [ + { + "label": "amtRate", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "pctRate", + "type": "t_uint192", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Throttle)15183_storage": { + "label": "struct ThrottleLib.Throttle", + "members": [ + { + "label": "params", + "type": "t_struct(Params)15175_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "lastTimestamp", + "type": "t_uint48", + "offset": 0, + "slot": "2" + }, + { + "label": "lastAvailable", + "type": "t_uint256", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "397f5b1d44ab33f58c1c65d3016c7c7cfeb66dde054e9935b80d41f66f4f316a": { + "address": "0x5a5eb5d26871e26645bD6d006671ec0887aeca69", + "txHash": "0x724bba93e8c6297fd00ec195f10155802d5c06d81918cdf8b8cdf1c13c759278", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)12158", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_HASHED_NAME", + "offset": 0, + "slot": "201", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:32" + }, + { + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "202", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:33" + }, + { + "label": "__gap", + "offset": 0, + "slot": "203", + "type": "t_array(t_uint256)50_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:120" + }, + { + "label": "name", + "offset": 0, + "slot": "253", + "type": "t_string_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:48" + }, + { + "label": "symbol", + "offset": 0, + "slot": "254", + "type": "t_string_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:49" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "255", + "type": "t_contract(IAssetRegistry)10578", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:54" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "256", + "type": "t_contract(IBackingManager)10640", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:55" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "257", + "type": "t_contract(IBasketHandler)10887", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:56" + }, + { + "label": "rsr", + "offset": 0, + "slot": "258", + "type": "t_contract(IERC20)6264", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:57" + }, + { + "label": "era", + "offset": 0, + "slot": "259", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:62" + }, + { + "label": "stakes", + "offset": 0, + "slot": "260", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:66" + }, + { + "label": "totalStakes", + "offset": 0, + "slot": "261", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:67" + }, + { + "label": "stakeRSR", + "offset": 0, + "slot": "262", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:68" + }, + { + "label": "stakeRate", + "offset": 0, + "slot": "263", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:69" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "264", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_mapping(t_address,t_uint256)))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:74" + }, + { + "label": "draftEra", + "offset": 0, + "slot": "265", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:79" + }, + { + "label": "draftQueues", + "offset": 0, + "slot": "266", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(CumulativeDraft)31147_storage)dyn_storage))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:87" + }, + { + "label": "firstRemainingDraft", + "offset": 0, + "slot": "267", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:88" + }, + { + "label": "totalDrafts", + "offset": 0, + "slot": "268", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:89" + }, + { + "label": "draftRSR", + "offset": 0, + "slot": "269", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:90" + }, + { + "label": "draftRate", + "offset": 0, + "slot": "270", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:91" + }, + { + "label": "_nonces", + "offset": 0, + "slot": "271", + "type": "t_mapping(t_address,t_struct(Counter)2607_storage)", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:129" + }, + { + "label": "_delegationNonces", + "offset": 0, + "slot": "272", + "type": "t_mapping(t_address,t_struct(Counter)2607_storage)", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:131" + }, + { + "label": "unstakingDelay", + "offset": 0, + "slot": "273", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:141" + }, + { + "label": "rewardRatio", + "offset": 6, + "slot": "273", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:142" + }, + { + "label": "payoutLastPaid", + "offset": 0, + "slot": "274", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:153" + }, + { + "label": "rsrRewardsAtLastPayout", + "offset": 0, + "slot": "275", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:156" + }, + { + "label": "leaked", + "offset": 0, + "slot": "276", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:162" + }, + { + "label": "lastWithdrawRefresh", + "offset": 24, + "slot": "276", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:163" + }, + { + "label": "withdrawalLeak", + "offset": 0, + "slot": "277", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:164" + }, + { + "label": "__gap", + "offset": 0, + "slot": "278", + "type": "t_array(t_uint256)28_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:976" + }, + { + "label": "_delegates", + "offset": 0, + "slot": "306", + "type": "t_mapping(t_address,t_address)", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:31" + }, + { + "label": "_eras", + "offset": 0, + "slot": "307", + "type": "t_array(t_struct(Checkpoint)33345_storage)dyn_storage", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:34" + }, + { + "label": "_checkpoints", + "offset": 0, + "slot": "308", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(Checkpoint)33345_storage)dyn_storage))", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:38" + }, + { + "label": "_totalSupplyCheckpoints", + "offset": 0, + "slot": "309", + "type": "t_mapping(t_uint256,t_array(t_struct(Checkpoint)33345_storage)dyn_storage)", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:40" + }, + { + "label": "__gap", + "offset": 0, + "slot": "310", + "type": "t_array(t_uint256)46_storage", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:243" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_struct(Checkpoint)33345_storage)dyn_storage": { + "label": "struct StRSRP1Votes.Checkpoint[]", + "numberOfBytes": "32" + }, + "t_array(t_struct(CumulativeDraft)31147_storage)dyn_storage": { + "label": "struct StRSRP1.CumulativeDraft[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)28_storage": { + "label": "uint256[28]", + "numberOfBytes": "896" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)10578": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)10640": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)10887": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IERC20)6264": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)12158": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_address)": { + "label": "mapping(address => address)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_array(t_struct(Checkpoint)33345_storage)dyn_storage)": { + "label": "mapping(address => struct StRSRP1Votes.Checkpoint[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_array(t_struct(CumulativeDraft)31147_storage)dyn_storage)": { + "label": "mapping(address => struct StRSRP1.CumulativeDraft[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Counter)2607_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_array(t_struct(Checkpoint)33345_storage)dyn_storage)": { + "label": "mapping(uint256 => struct StRSRP1Votes.Checkpoint[])", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(Checkpoint)33345_storage)dyn_storage))": { + "label": "mapping(uint256 => mapping(address => struct StRSRP1Votes.Checkpoint[]))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(CumulativeDraft)31147_storage)dyn_storage))": { + "label": "mapping(uint256 => mapping(address => struct StRSRP1.CumulativeDraft[]))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_mapping(t_address,t_uint256)))": { + "label": "mapping(uint256 => mapping(address => mapping(address => uint256)))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_uint256))": { + "label": "mapping(uint256 => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Checkpoint)33345_storage": { + "label": "struct StRSRP1Votes.Checkpoint", + "members": [ + { + "label": "fromBlock", + "type": "t_uint48", + "offset": 0, + "slot": "0" + }, + { + "label": "val", + "type": "t_uint224", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Counter)2607_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(CumulativeDraft)31147_storage": { + "label": "struct StRSRP1.CumulativeDraft", + "members": [ + { + "label": "drafts", + "type": "t_uint176", + "offset": 0, + "slot": "0" + }, + { + "label": "availableAt", + "type": "t_uint64", + "offset": 22, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_uint176": { + "label": "uint176", + "numberOfBytes": "22" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint224": { + "label": "uint224", + "numberOfBytes": "28" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + } + } +} diff --git a/README.md b/README.md index ae48bf253d..2ff52fa10b 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ For a much more detailed explanation of the economic design, including an hour-l - [System Design](docs/system-design.md): The overall architecture of our system, and some detailed descriptions about what our protocol is _intended_ to do. - [Our Solidity Style](docs/solidity-style.md): Common practices, details, and conventions relevant to reading and writing our Solidity source code, estpecially where those go beyond standard practice. - [Writing Collateral Plugins](docs/collateral.md): An overview of how to develop collateral plugins and the concepts / questions involved. +- [Building on Top](docs/build-on-top.md): How to build on top of Reserve, including information about long-lived fork environments. - [MEV](docs/mev.md): A resource for MEV searchers and others looking to interact with the deployed protocol programatically. - [Rebalancing Algorithm](docs/recollateralization.md): Description of our trading algorithm during the recollateralization process - [Changelog](CHANGELOG.md): Release changelog @@ -156,6 +157,10 @@ Usage: `https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}` To get setup with tenderly, install the [tenderly cli](https://github.com/Tenderly/tenderly-cli). and login with `tenderly login --authentication-method access-key --access-key {your_access_key} --force`. +## Responsible Disclosure + +[Immunifi](https://immunefi.com/bounty/reserve/) + ## External Documentation [Video overview](https://youtu.be/341MhkOWsJE) diff --git a/common/configuration.ts b/common/configuration.ts index ab3fff947d..c12edfa2ce 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -65,6 +65,9 @@ export interface ITokens { maUSDC?: string maUSDT?: string maDAI?: string + maWBTC?: string + maWETH?: string + maStETH?: string } export interface IFeeds { @@ -163,7 +166,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { sUSDT: '0x38EA452219524Bb87e18dE1C24D3bB59510BD783', sETH: '0x101816545F6bd2b1076434B54383a1E633390A2E', MORPHO: '0x9994e35db50125e0df82e4c2dde62496ce330999', - astETH: "0x1982b2F5814301d4e9a8b0201555376e62F82428" + astETH: '0x1982b2F5814301d4e9a8b0201555376e62F82428', }, chainlinkFeeds: { RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', @@ -189,7 +192,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH - wBTCBTC: "0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23", // "WBTC/BTC" + wBTCBTC: '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', // "WBTC/BTC" }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5', @@ -201,7 +204,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { EASY_AUCTION_OWNER: '0x0da0c3e52c977ed3cbc641ff02dd271c3ed55afe', MORPHO_AAVE_LENS: '0x507fA343d0A90786d86C7cd885f5C49263A91FF4', MORPHO_AAVE_CONTROLLER: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', - MORPHO_REWARDS_DISTRIBUTOR: "0x3b14e5c73e0a56d607a8688098326fd4b4292135" + MORPHO_REWARDS_DISTRIBUTOR: '0x3b14e5c73e0a56d607a8688098326fd4b4292135', }, '3': { name: 'ropsten', @@ -266,7 +269,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { sUSDC: '0xdf0770dF86a8034b3EFEf0A1Bb3c889B8332FF56', sUSDT: '0x38EA452219524Bb87e18dE1C24D3bB59510BD783', sETH: '0x101816545F6bd2b1076434B54383a1E633390A2E', - astETH: "0x1982b2F5814301d4e9a8b0201555376e62F82428", + astETH: '0x1982b2F5814301d4e9a8b0201555376e62F82428', MORPHO: '0x9994e35db50125e0df82e4c2dde62496ce330999', }, chainlinkFeeds: { @@ -293,7 +296,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH - wBTCBTC: "0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23" // "WBTC/BTC" + wBTCBTC: '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', // "WBTC/BTC" }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', @@ -302,7 +305,99 @@ export const networkConfig: { [key: string]: INetworkConfig } = { GNOSIS_EASY_AUCTION: '0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101', MORPHO_AAVE_LENS: '0x507fA343d0A90786d86C7cd885f5C49263A91FF4', MORPHO_AAVE_CONTROLLER: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', - MORPHO_REWARDS_DISTRIBUTOR: "0x3b14e5c73e0a56d607a8688098326fd4b4292135" + MORPHO_REWARDS_DISTRIBUTOR: '0x3b14e5c73e0a56d607a8688098326fd4b4292135', + }, + '3': { + name: 'tenderly', + tokens: { + DAI: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + BUSD: '0x4Fabb145d64652a948d72533023f6E7A623C7C53', + USDP: '0x8E870D67F660D95d5be530380D0eC0bd388289E1', + TUSD: '0x0000000000085d4780B73119b644AE5ecd22b376', + sUSD: '0x57Ab1ec28D129707052df4dF418D58a2D46d5f51', + FRAX: '0x853d955aCEf822Db058eb8505911ED77F175b99e', + MIM: '0x99d8a9c45b2eca8864373a26d1459e3dff1e17f3', + eUSD: '0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F', + aDAI: '0x028171bCA77440897B824Ca71D1c56caC55b68A3', + aUSDC: '0xBcca60bB61934080951369a648Fb03DF4F96263C', + aUSDT: '0x3Ed3B47Dd13EC9a98b44e6204A523E766B225811', + aBUSD: '0xA361718326c15715591c299427c62086F69923D9', + aUSDP: '0x2e8F4bdbE3d47d7d7DE490437AeA9915D930F1A3', + aWETH: '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e', + cDAI: '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', + cUSDC: '0x39AA39c021dfbaE8faC545936693aC917d5E7563', + cUSDT: '0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9', + cUSDP: '0x041171993284df560249B57358F931D9eB7b925D', + cETH: '0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5', + cWBTC: '0xccF4429DB6322D5C611ee964527D42E5d685DD6a', + fUSDC: '0x465a5a630482f3abD6d3b84B39B29b07214d19e5', + fUSDT: '0x81994b9607e06ab3d5cF3AffF9a67374f05F27d7', + fFRAX: '0x1C9A2d6b33B4826757273D47ebEe0e2DddcD978B', + fDAI: '0xe2bA8693cE7474900A045757fe0efCa900F6530b', + AAVE: '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9', + stkAAVE: '0x4da27a545c0c5B758a6BA100e3a049001de870f5', + COMP: '0xc00e94Cb662C3520282E6f5717214004A7f26888', + WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + aWBTC: '0x9ff58f4ffb29fa2266ab25e75e2a8b3503311656', + aCRV: '0x8dae6cb04688c62d939ed9b68d32bc62e49970b1', + WBTC: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', + EURT: '0xC581b735A1688071A1746c968e0798D642EDE491', + RSR: '0x320623b8e4ff03373931769a31fc52a4e78b5d70', + CRV: '0xD533a949740bb3306d119CC777fa900bA034cd52', + CVX: '0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B', + ankrETH: '0xE95A203B1a91a908F9B9CE46459d101078c2c3cb', + frxETH: '0x5E8422345238F34275888049021821E8E08CAa1f', + sfrxETH: '0xac3E018457B222d93114458476f3E3416Abbe38F', + stETH: '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', + wstETH: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', + rETH: '0xae78736Cd615f374D3085123A210448E74Fc6393', + cUSDCv3: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', + ONDO: '0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3', + sDAI: '0x83f20f44975d03b1b09e64809b757c47f942beea', + cbETH: '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704', + STG: '0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6', + sUSDC: '0xdf0770dF86a8034b3EFEf0A1Bb3c889B8332FF56', + sUSDT: '0x38EA452219524Bb87e18dE1C24D3bB59510BD783', + sETH: '0x101816545F6bd2b1076434B54383a1E633390A2E', + astETH: '0x1982b2F5814301d4e9a8b0201555376e62F82428', + MORPHO: '0x9994e35db50125e0df82e4c2dde62496ce330999', + }, + chainlinkFeeds: { + RSR: '0x759bBC1be8F90eE6457C44abc7d443842a976d02', + AAVE: '0x547a514d5e3769680Ce22B2361c10Ea13619e8a9', + COMP: '0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5', + DAI: '0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9', + USDC: '0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6', + USDT: '0x3E7d1eAB13ad0104d2750B8863b489D65364e32D', + BUSD: '0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A', + USDP: '0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3', + TUSD: '0xec746eCF986E2927Abd291a2A1716c940100f8Ba', + sUSD: '0xad35Bd71b9aFE6e4bDc266B345c198eaDEf9Ad94', + FRAX: '0xB9E1E3A9feFf48998E45Fa90847ed4D467E8BcfD', + MIM: '0x7A364e8770418566e3eb2001A96116E6138Eb32F', + ETH: '0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419', + WBTC: '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', + BTC: '0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c', + EURT: '0x01D391A48f4F7339aC64CA2c83a07C22F95F587a', + EUR: '0xb49f677943BC038e9857d61E7d053CaA2C1734C1', + CVX: '0xd962fC30A72A84cE50161031391756Bf2876Af5D', + CRV: '0xCd627aA160A6fA45Eb793D19Ef54f5062F20f33f', + stETHETH: '0x86392dc19c0b719886221c78ab11eb8cf5c52812', // stETH/ETH + stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD + rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH + cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH + wBTCBTC: '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', // "WBTC/BTC" + }, + AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', + AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', + FLUX_FINANCE_COMPTROLLER: '0x95Af143a021DF745bc78e845b54591C53a8B3A51', + COMPTROLLER: '0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B', + GNOSIS_EASY_AUCTION: '0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101', + MORPHO_AAVE_LENS: '0x507fA343d0A90786d86C7cd885f5C49263A91FF4', + MORPHO_AAVE_CONTROLLER: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', + MORPHO_REWARDS_DISTRIBUTOR: '0x3b14e5c73e0a56d607a8688098326fd4b4292135', }, '5': { name: 'goerli', diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 4896b2d756..694ae839c6 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -49,6 +49,8 @@ contract FacadeRead is IFacadeRead { return basketsHeld.bottom.mulDiv(totalSupply, needed).shiftl_toUint(decimals); } + /// Do no use inifite approvals. Instead, use BasketHandler.quote() to determine the amount + /// of backing tokens to approve. /// @return tokens The erc20 needed for the issuance /// @return deposits {qTok} The deposits necessary to issue `amount` RToken /// @return depositsUoA {UoA} The UoA value of the deposits necessary to issue `amount` RToken @@ -348,9 +350,9 @@ contract FacadeRead is IFacadeRead { uint192 uoaHeldInBaskets; // {UoA} { (address[] memory basketERC20s, uint256[] memory quantities) = rToken - .main() - .basketHandler() - .quote(basketsNeeded, FLOOR); + .main() + .basketHandler() + .quote(basketsNeeded, FLOOR); IAssetRegistry reg = rToken.main().assetRegistry(); IBackingManager bm = rToken.main().backingManager(); diff --git a/contracts/facade/FacadeTest.sol b/contracts/facade/FacadeTest.sol index e0683265fe..d16c6ab6d3 100644 --- a/contracts/facade/FacadeTest.sol +++ b/contracts/facade/FacadeTest.sol @@ -24,6 +24,11 @@ contract FacadeTest is IFacadeTest { /// Prompt all traders to run auctions /// Relatively gas-inefficient, shouldn't be used in production. Use multicall instead function runAuctionsForAllTraders(IRToken rToken) external { + runAuctionsForAllTradersForKind(rToken, TradeKind.BATCH_AUCTION); + } + + // Prompt all traders to run auctions of a specific kind + function runAuctionsForAllTradersForKind(IRToken rToken, TradeKind kind) public { IMain main = rToken.main(); IBackingManager backingManager = main.backingManager(); IRevenueTrader rsrTrader = main.rsrTrader(); @@ -55,12 +60,17 @@ contract FacadeTest is IFacadeTest { try main.backingManager().forwardRevenue(erc20s) {} catch {} // Start exact RSR auctions - (IERC20[] memory rsrERC20s, TradeKind[] memory rsrKinds) = traderERC20s(rsrTrader, erc20s); + (IERC20[] memory rsrERC20s, TradeKind[] memory rsrKinds) = traderERC20s( + rsrTrader, + kind, + erc20s + ); try main.rsrTrader().manageTokens(rsrERC20s, rsrKinds) {} catch {} // Start exact RToken auctions (IERC20[] memory rTokenERC20s, TradeKind[] memory rTokenKinds) = traderERC20s( rTokenTrader, + kind, erc20s ); try main.rTokenTrader().manageTokens(rTokenERC20s, rTokenKinds) {} catch {} @@ -115,11 +125,11 @@ contract FacadeTest is IFacadeTest { // === Private === - function traderERC20s(IRevenueTrader trader, IERC20[] memory erc20sAll) - private - view - returns (IERC20[] memory erc20s, TradeKind[] memory kinds) - { + function traderERC20s( + IRevenueTrader trader, + TradeKind kind, + IERC20[] memory erc20sAll + ) private view returns (IERC20[] memory erc20s, TradeKind[] memory kinds) { uint256 len; IERC20[] memory traderERC20sAll = new IERC20[](erc20sAll.length); for (uint256 i = 0; i < erc20sAll.length; ++i) { @@ -136,7 +146,7 @@ contract FacadeTest is IFacadeTest { kinds = new TradeKind[](len); for (uint256 i = 0; i < len; ++i) { erc20s[i] = traderERC20sAll[i]; - kinds[i] = TradeKind.BATCH_AUCTION; + kinds[i] = kind; } } } diff --git a/contracts/interfaces/IBackingManager.sol b/contracts/interfaces/IBackingManager.sol index 1aa4b9fb39..0699da6d6c 100644 --- a/contracts/interfaces/IBackingManager.sol +++ b/contracts/interfaces/IBackingManager.sol @@ -18,12 +18,12 @@ interface IBackingManager is IComponent, ITrading { /// Emitted when the trading delay is changed /// @param oldVal The old trading delay /// @param newVal The new trading delay - event TradingDelaySet(uint48 indexed oldVal, uint48 indexed newVal); + event TradingDelaySet(uint48 oldVal, uint48 newVal); /// Emitted when the backing buffer is changed /// @param oldVal The old backing buffer /// @param newVal The new backing buffer - event BackingBufferSet(uint192 indexed oldVal, uint192 indexed newVal); + event BackingBufferSet(uint192 oldVal, uint192 newVal); // Initialization function init( diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index f944550840..42bb8bf092 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -36,20 +36,17 @@ interface IBasketHandler is IComponent { /// @param targetName The name of the target unit as a bytes32 /// @param max The max number to use from `erc20s` /// @param erc20s The set of backup collateral tokens - event BackupConfigSet(bytes32 indexed targetName, uint256 indexed max, IERC20[] erc20s); + event BackupConfigSet(bytes32 indexed targetName, uint256 max, IERC20[] erc20s); /// Emitted when the warmup period is changed /// @param oldVal The old warmup period /// @param newVal The new warmup period - event WarmupPeriodSet(uint48 indexed oldVal, uint48 indexed newVal); + event WarmupPeriodSet(uint48 oldVal, uint48 newVal); /// Emitted when the status of a basket has changed /// @param oldStatus The previous basket status /// @param newStatus The new basket status - event BasketStatusChanged( - CollateralStatus indexed oldStatus, - CollateralStatus indexed newStatus - ); + event BasketStatusChanged(CollateralStatus oldStatus, CollateralStatus newStatus); // Initialization function init(IMain main_, uint48 warmupPeriod_) external; diff --git a/contracts/interfaces/IBroker.sol b/contracts/interfaces/IBroker.sol index cbb2f9cbdf..0c83eb9216 100644 --- a/contracts/interfaces/IBroker.sol +++ b/contracts/interfaces/IBroker.sol @@ -33,12 +33,13 @@ struct TradeRequest { * the continued proper functioning of trading platforms. */ interface IBroker is IComponent { - event GnosisSet(IGnosis indexed oldVal, IGnosis indexed newVal); - event BatchTradeImplementationSet(ITrade indexed oldVal, ITrade indexed newVal); - event DutchTradeImplementationSet(ITrade indexed oldVal, ITrade indexed newVal); - event BatchAuctionLengthSet(uint48 indexed oldVal, uint48 indexed newVal); - event DutchAuctionLengthSet(uint48 indexed oldVal, uint48 indexed newVal); - event DisabledSet(bool indexed prevVal, bool indexed newVal); + event GnosisSet(IGnosis oldVal, IGnosis newVal); + event BatchTradeImplementationSet(ITrade oldVal, ITrade newVal); + event DutchTradeImplementationSet(ITrade oldVal, ITrade newVal); + event BatchAuctionLengthSet(uint48 oldVal, uint48 newVal); + event DutchAuctionLengthSet(uint48 oldVal, uint48 newVal); + event BatchTradeDisabledSet(bool prevVal, bool newVal); + event DutchTradeDisabledSet(IERC20Metadata indexed erc20, bool prevVal, bool newVal); // Initialization function init( @@ -62,7 +63,9 @@ interface IBroker is IComponent { /// Only callable by one of the trading contracts the broker deploys function reportViolation() external; - function disabled() external view returns (bool); + function batchTradeDisabled() external view returns (bool); + + function dutchTradeDisabled(IERC20Metadata erc20) external view returns (bool); } interface TestIBroker is IBroker { @@ -86,5 +89,10 @@ interface TestIBroker is IBroker { function setDutchAuctionLength(uint48 newAuctionLength) external; - function setDisabled(bool disabled_) external; + function setBatchTradeDisabled(bool disabled) external; + + function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external; + + // only present on pre-3.0.0 Brokers; used by EasyAuction regression test + function disabled() external view returns (bool); } diff --git a/contracts/interfaces/IDistributor.sol b/contracts/interfaces/IDistributor.sol index e3c7de6ace..5f5c76c5a2 100644 --- a/contracts/interfaces/IDistributor.sol +++ b/contracts/interfaces/IDistributor.sol @@ -28,13 +28,13 @@ interface IDistributor is IComponent { /// @param dest The address set to receive the distribution /// @param rTokenDist The distribution of RToken that should go to `dest` /// @param rsrDist The distribution of RSR that should go to `dest` - event DistributionSet(address dest, uint16 rTokenDist, uint16 rsrDist); + event DistributionSet(address indexed dest, uint16 rTokenDist, uint16 rsrDist); /// Emitted when revenue is distributed /// @param erc20 The token being distributed, either RSR or the RToken itself /// @param source The address providing the revenue /// @param amount The amount of the revenue - event RevenueDistributed(IERC20 indexed erc20, address indexed source, uint256 indexed amount); + event RevenueDistributed(IERC20 indexed erc20, address indexed source, uint256 amount); // Initialization function init(IMain main_, RevenueShare memory dist) external; diff --git a/contracts/interfaces/IFurnace.sol b/contracts/interfaces/IFurnace.sol index 25c754aacf..170cb4c554 100644 --- a/contracts/interfaces/IFurnace.sol +++ b/contracts/interfaces/IFurnace.sol @@ -15,7 +15,7 @@ interface IFurnace is IComponent { /// Emitted when the melting ratio is changed /// @param oldRatio The old ratio /// @param newRatio The new ratio - event RatioSet(uint192 indexed oldRatio, uint192 indexed newRatio); + event RatioSet(uint192 oldRatio, uint192 newRatio); function ratio() external view returns (uint192); diff --git a/contracts/interfaces/IMain.sol b/contracts/interfaces/IMain.sol index 00bb261de9..f282be1479 100644 --- a/contracts/interfaces/IMain.sol +++ b/contracts/interfaces/IMain.sol @@ -49,27 +49,27 @@ interface IAuth is IAccessControlUpgradeable { /// Emitted when `unfreezeAt` is changed /// @param oldVal The old value of `unfreezeAt` /// @param newVal The new value of `unfreezeAt` - event UnfreezeAtSet(uint48 indexed oldVal, uint48 indexed newVal); + event UnfreezeAtSet(uint48 oldVal, uint48 newVal); /// Emitted when the short freeze duration governance param is changed /// @param oldDuration The old short freeze duration /// @param newDuration The new short freeze duration - event ShortFreezeDurationSet(uint48 indexed oldDuration, uint48 indexed newDuration); + event ShortFreezeDurationSet(uint48 oldDuration, uint48 newDuration); /// Emitted when the long freeze duration governance param is changed /// @param oldDuration The old long freeze duration /// @param newDuration The new long freeze duration - event LongFreezeDurationSet(uint48 indexed oldDuration, uint48 indexed newDuration); + event LongFreezeDurationSet(uint48 oldDuration, uint48 newDuration); /// Emitted when the system is paused or unpaused for trading /// @param oldVal The old value of `tradingPaused` /// @param newVal The new value of `tradingPaused` - event TradingPausedSet(bool indexed oldVal, bool indexed newVal); + event TradingPausedSet(bool oldVal, bool newVal); /// Emitted when the system is paused or unpaused for issuance /// @param oldVal The old value of `issuancePaused` /// @param newVal The new value of `issuancePaused` - event IssuancePausedSet(bool indexed oldVal, bool indexed newVal); + event IssuancePausedSet(bool oldVal, bool newVal); /** * Trading Paused: Disable everything except for OWNER actions, RToken.issue, RToken.redeem, @@ -118,39 +118,39 @@ interface IComponentRegistry { function rToken() external view returns (IRToken); - event StRSRSet(IStRSR indexed oldVal, IStRSR indexed newVal); + event StRSRSet(IStRSR oldVal, IStRSR newVal); function stRSR() external view returns (IStRSR); - event AssetRegistrySet(IAssetRegistry indexed oldVal, IAssetRegistry indexed newVal); + event AssetRegistrySet(IAssetRegistry oldVal, IAssetRegistry newVal); function assetRegistry() external view returns (IAssetRegistry); - event BasketHandlerSet(IBasketHandler indexed oldVal, IBasketHandler indexed newVal); + event BasketHandlerSet(IBasketHandler oldVal, IBasketHandler newVal); function basketHandler() external view returns (IBasketHandler); - event BackingManagerSet(IBackingManager indexed oldVal, IBackingManager indexed newVal); + event BackingManagerSet(IBackingManager oldVal, IBackingManager newVal); function backingManager() external view returns (IBackingManager); - event DistributorSet(IDistributor indexed oldVal, IDistributor indexed newVal); + event DistributorSet(IDistributor oldVal, IDistributor newVal); function distributor() external view returns (IDistributor); - event RSRTraderSet(IRevenueTrader indexed oldVal, IRevenueTrader indexed newVal); + event RSRTraderSet(IRevenueTrader oldVal, IRevenueTrader newVal); function rsrTrader() external view returns (IRevenueTrader); - event RTokenTraderSet(IRevenueTrader indexed oldVal, IRevenueTrader indexed newVal); + event RTokenTraderSet(IRevenueTrader oldVal, IRevenueTrader newVal); function rTokenTrader() external view returns (IRevenueTrader); - event FurnaceSet(IFurnace indexed oldVal, IFurnace indexed newVal); + event FurnaceSet(IFurnace oldVal, IFurnace newVal); function furnace() external view returns (IFurnace); - event BrokerSet(IBroker indexed oldVal, IBroker indexed newVal); + event BrokerSet(IBroker oldVal, IBroker newVal); function broker() external view returns (IBroker); } diff --git a/contracts/interfaces/IRToken.sol b/contracts/interfaces/IRToken.sol index 3111563053..9528ab2efd 100644 --- a/contracts/interfaces/IRToken.sol +++ b/contracts/interfaces/IRToken.sol @@ -25,7 +25,7 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea event Issuance( address indexed issuer, address indexed recipient, - uint256 indexed amount, + uint256 amount, uint192 baskets ); @@ -38,7 +38,7 @@ interface IRToken is IComponent, IERC20MetadataUpgradeable, IERC20PermitUpgradea event Redemption( address indexed redeemer, address indexed recipient, - uint256 indexed amount, + uint256 amount, uint192 baskets ); diff --git a/contracts/interfaces/IRevenueTrader.sol b/contracts/interfaces/IRevenueTrader.sol index 4b07ff70bc..8ab78078e1 100644 --- a/contracts/interfaces/IRevenueTrader.sol +++ b/contracts/interfaces/IRevenueTrader.sol @@ -25,6 +25,10 @@ interface IRevenueTrader is IComponent, ITrading { /// @custom:interaction function distributeTokenToBuy() external; + /// Return registered ERC20s to the BackingManager if distribution for tokenToBuy is 0 + /// @custom:interaction + function returnTokens(IERC20[] memory erc20s) external; + /// Process some number of tokens /// If the tokenToBuy is included in erc20s, RevenueTrader will distribute it at end of the tx /// @param erc20s The ERC20s to manage; can be tokenToBuy or anything registered diff --git a/contracts/interfaces/IRewardable.sol b/contracts/interfaces/IRewardable.sol index 90563bad51..48da999850 100644 --- a/contracts/interfaces/IRewardable.sol +++ b/contracts/interfaces/IRewardable.sol @@ -11,7 +11,7 @@ import "./IMain.sol"; */ interface IRewardable { /// Emitted whenever a reward token balance is claimed - event RewardsClaimed(IERC20 indexed erc20, uint256 indexed amount); + event RewardsClaimed(IERC20 indexed erc20, uint256 amount); /// Claim rewards earned by holding a balance of the ERC20 token /// Must emit `RewardsClaimed` for each token rewards are claimed for diff --git a/contracts/interfaces/IStRSR.sol b/contracts/interfaces/IStRSR.sol index 45b7db9227..b0279ef220 100644 --- a/contracts/interfaces/IStRSR.sol +++ b/contracts/interfaces/IStRSR.sol @@ -29,7 +29,7 @@ interface IStRSR is IERC20MetadataUpgradeable, IERC20PermitUpgradeable, ICompone uint256 indexed era, address indexed staker, uint256 rsrAmount, - uint256 indexed stRSRAmount + uint256 stRSRAmount ); /// Emitted when an unstaking is started @@ -83,19 +83,19 @@ interface IStRSR is IERC20MetadataUpgradeable, IERC20PermitUpgradeable, ICompone ); /// Emitted whenever the exchange rate changes - event ExchangeRateSet(uint192 indexed oldVal, uint192 indexed newVal); + event ExchangeRateSet(uint192 oldVal, uint192 newVal); /// Emitted whenever RSR are paids out - event RewardsPaid(uint256 indexed rsrAmt); + event RewardsPaid(uint256 rsrAmt); /// Emitted if all the RSR in the staking pool is seized and all balances are reset to zero. event AllBalancesReset(uint256 indexed newEra); /// Emitted if all the RSR in the unstakin pool is seized, and all ongoing unstaking is voided. event AllUnstakingReset(uint256 indexed newEra); - event UnstakingDelaySet(uint48 indexed oldVal, uint48 indexed newVal); - event RewardRatioSet(uint192 indexed oldVal, uint192 indexed newVal); - event WithdrawalLeakSet(uint192 indexed oldVal, uint192 indexed newVal); + event UnstakingDelaySet(uint48 oldVal, uint48 newVal); + event RewardRatioSet(uint192 oldVal, uint192 newVal); + event WithdrawalLeakSet(uint192 oldVal, uint192 newVal); // Initialization function init( @@ -134,6 +134,10 @@ interface IStRSR is IERC20MetadataUpgradeable, IERC20PermitUpgradeable, ICompone /// @custom:protected function seizeRSR(uint256 amount) external; + /// Reset all stakes and advance era + /// @custom:governance + function resetStakes() external; + /// Return the maximum valid value of endId such that withdraw(endId) should immediately work function endIdForWithdraw(address account) external view returns (uint256 endId); diff --git a/contracts/interfaces/ITrading.sol b/contracts/interfaces/ITrading.sol index 4c40bce0e0..b0bed9bad3 100644 --- a/contracts/interfaces/ITrading.sol +++ b/contracts/interfaces/ITrading.sol @@ -13,8 +13,8 @@ import "./IRewardable.sol"; * @notice Common events and refresher function for all Trading contracts */ interface ITrading is IComponent, IRewardableComponent { - event MaxTradeSlippageSet(uint192 indexed oldVal, uint192 indexed newVal); - event MinTradeVolumeSet(uint192 indexed oldVal, uint192 indexed newVal); + event MaxTradeSlippageSet(uint192 oldVal, uint192 newVal); + event MinTradeVolumeSet(uint192 oldVal, uint192 newVal); /// Emitted when a trade is started /// @param trade The one-time-use trade contract that was just deployed diff --git a/contracts/libraries/NetworkConfigLib.sol b/contracts/libraries/NetworkConfigLib.sol index b347bec484..c07aa392c0 100644 --- a/contracts/libraries/NetworkConfigLib.sol +++ b/contracts/libraries/NetworkConfigLib.sol @@ -15,7 +15,7 @@ library NetworkConfigLib { // untestable: // most of the branches will be shown as uncovered, because we only run coverage // on local Ethereum PoS network (31337). Manual testing was performed. - if (chainId == 1 || chainId == 5 || chainId == 31337) { + if (chainId == 1 || chainId == 3 || chainId == 5 || chainId == 31337) { return 12; // Ethereum PoS, Goerli, HH (tests) } else if (chainId == 8453 || chainId == 84531) { return 2; // Base, Base Goerli diff --git a/contracts/p0/AssetRegistry.sol b/contracts/p0/AssetRegistry.sol index 223c691154..ebdf5fd8b1 100644 --- a/contracts/p0/AssetRegistry.sol +++ b/contracts/p0/AssetRegistry.sol @@ -144,6 +144,13 @@ contract AssetRegistryP0 is ComponentP0, IAssetRegistry { /// Register an asset, unregistering any previous asset with the same ERC20. function _registerIgnoringCollisions(IAsset asset) private returns (bool swapped) { + if (asset.isCollateral()) { + require( + ICollateral(address(asset)).status() == CollateralStatus.SOUND, + "collateral not sound" + ); + } + if (_erc20s.contains(address(asset.erc20())) && assets[asset.erc20()] == asset) return false; diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 0bdc2249c7..c22df732c7 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -124,7 +124,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { BasketRange memory basketsHeld = main.basketHandler().basketsHeldBy(address(this)); (bool doTrade, TradeRequest memory req, TradePrices memory prices) = TradingLibP0 - .prepareRecollateralizationTrade(this, basketsHeld); + .prepareRecollateralizationTrade(this, basketsHeld); if (doTrade) { // Seize RSR if needed diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 602ec43339..357b0a7251 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -516,8 +516,8 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { // {tok} = {BU} * {ref/BU} / {ref/tok} quantities[i] = amount - .safeMulDiv(refAmtsAll[i], collsAll[i].refPerTok(), FLOOR) - .shiftl_toUint(int8(collsAll[i].erc20Decimals()), FLOOR); + .safeMulDiv(refAmtsAll[i], collsAll[i].refPerTok(), FLOOR) + .shiftl_toUint(int8(collsAll[i].erc20Decimals()), FLOOR); // marginally more penalizing than its sibling calculation that uses _quantity() // because does not intermediately CEIL as part of the division } diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index c1c7da1452..41584907d2 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/proxy/Clones.sol"; import "../plugins/trading/DutchTrade.sol"; import "../plugins/trading/GnosisTrade.sol"; import "../interfaces/IBroker.sol"; @@ -24,7 +25,7 @@ contract BrokerP0 is ComponentP0, IBroker { uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration -1 week // solhint-disable-next-line var-name-mixedcase - uint48 public immutable MIN_AUCTION_LENGTH; // {s} 2 blocks based on network + uint48 public immutable MIN_AUCTION_LENGTH; // {s} 20 blocks, based on network // Added for interface compatibility with P1 ITrade public batchTradeImplementation; @@ -37,10 +38,12 @@ contract BrokerP0 is ComponentP0, IBroker { uint48 public batchAuctionLength; // {s} the length of a Gnosis EasyAuction uint48 public dutchAuctionLength; // {s} the length of a Dutch Auction - bool public disabled; + bool public batchTradeDisabled; + + mapping(IERC20Metadata => bool) public dutchTradeDisabled; constructor() { - MIN_AUCTION_LENGTH = NetworkConfigLib.blocktime() * 2; + MIN_AUCTION_LENGTH = NetworkConfigLib.blocktime() * 20; } function init( @@ -68,7 +71,6 @@ contract BrokerP0 is ComponentP0, IBroker { TradeRequest memory req, TradePrices memory prices ) external returns (ITrade) { - require(!disabled, "broker disabled"); assert(req.sellAmount > 0); address caller = _msgSender(); @@ -92,8 +94,23 @@ contract BrokerP0 is ComponentP0, IBroker { /// @custom:protected function reportViolation() external notTradingPausedOrFrozen { require(trades[_msgSender()], "unrecognized trade contract"); - emit DisabledSet(disabled, true); - disabled = true; + ITrade trade = ITrade(_msgSender()); + TradeKind kind = trade.KIND(); + + if (kind == TradeKind.BATCH_AUCTION) { + emit BatchTradeDisabledSet(batchTradeDisabled, true); + batchTradeDisabled = true; + } else if (kind == TradeKind.DUTCH_AUCTION) { + IERC20Metadata sell = trade.sell(); + emit DutchTradeDisabledSet(sell, dutchTradeDisabled[sell], true); + dutchTradeDisabled[sell] = true; + + IERC20Metadata buy = trade.buy(); + emit DutchTradeDisabledSet(buy, dutchTradeDisabled[buy], true); + dutchTradeDisabled[buy] = true; + } else { + revert("unrecognized trade kind"); + } } /// @param maxTokensAllowed {qTok} The max number of sell tokens allowed by the trading platform @@ -173,8 +190,9 @@ contract BrokerP0 is ComponentP0, IBroker { // === Private === function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { + require(!batchTradeDisabled, "batch auctions disabled"); require(batchAuctionLength > 0, "batch auctions not enabled"); - GnosisTrade trade = new GnosisTrade(); + GnosisTrade trade = GnosisTrade(Clones.clone(address(batchTradeImplementation))); trades[address(trade)] = true; // Apply Gnosis EasyAuction-specific resizing of req, if needed: Ensure that @@ -201,8 +219,12 @@ contract BrokerP0 is ComponentP0, IBroker { TradePrices memory prices, ITrading caller ) private returns (ITrade) { + require( + !dutchTradeDisabled[req.sell.erc20()] && !dutchTradeDisabled[req.buy.erc20()], + "dutch auctions disabled for token pair" + ); require(dutchAuctionLength > 0, "dutch auctions not enabled"); - DutchTrade trade = new DutchTrade(); + DutchTrade trade = DutchTrade(Clones.clone(address(dutchTradeImplementation))); trades[address(trade)] = true; IERC20Metadata(address(req.sell.erc20())).safeTransferFrom( @@ -216,8 +238,14 @@ contract BrokerP0 is ComponentP0, IBroker { } /// @custom:governance - function setDisabled(bool disabled_) external governance { - emit DisabledSet(disabled, disabled_); - disabled = disabled_; + function setBatchTradeDisabled(bool disabled) external governance { + emit BatchTradeDisabledSet(batchTradeDisabled, disabled); + batchTradeDisabled = disabled; + } + + /// @custom:governance + function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external governance { + emit DutchTradeDisabledSet(erc20, dutchTradeDisabled[erc20], disabled); + dutchTradeDisabled[erc20] = disabled; } } diff --git a/contracts/p0/Deployer.sol b/contracts/p0/Deployer.sol index 8c8d124d2e..0b1ff0324a 100644 --- a/contracts/p0/Deployer.sol +++ b/contracts/p0/Deployer.sol @@ -4,6 +4,8 @@ pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../plugins/assets/Asset.sol"; import "../plugins/assets/RTokenAsset.sol"; +import "../plugins/trading/DutchTrade.sol"; +import "../plugins/trading/GnosisTrade.sol"; import "./AssetRegistry.sol"; import "./BackingManager.sol"; import "./BasketHandler.sol"; @@ -113,9 +115,9 @@ contract DeployerP0 is IDeployer, Versioned { main.broker().init( main, gnosis, - ITrade(address(1)), + ITrade(address(new GnosisTrade())), params.batchAuctionLength, - ITrade(address(1)), + ITrade(address(new DutchTrade())), params.dutchAuctionLength ); diff --git a/contracts/p0/Furnace.sol b/contracts/p0/Furnace.sol index 869a3eff76..aa99a8140c 100644 --- a/contracts/p0/Furnace.sol +++ b/contracts/p0/Furnace.sol @@ -58,8 +58,14 @@ contract FurnaceP0 is ComponentP0, IFurnace { /// Ratio setting /// @custom:governance function setRatio(uint192 ratio_) public governance { - // solhint-disable-next-line no-empty-blocks - try this.melt() {} catch {} + if (lastPayout > 0) { + // solhint-disable-next-line no-empty-blocks + try this.melt() {} catch { + uint48 numPeriods = uint48((block.timestamp) - lastPayout) / PERIOD; + lastPayout += numPeriods * PERIOD; + lastPayoutBal = main.rToken().balanceOf(address(this)); + } + } require(ratio_ <= MAX_RATIO, "invalid ratio"); // The ratio can safely be set to 0, though it is not recommended emit RatioSet(ratio, ratio_); diff --git a/contracts/p0/RToken.sol b/contracts/p0/RToken.sol index a943324371..e7ce86a0b2 100644 --- a/contracts/p0/RToken.sol +++ b/contracts/p0/RToken.sol @@ -214,8 +214,8 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { } (address[] memory erc20s, uint256[] memory amounts) = main - .basketHandler() - .quoteCustomRedemption(basketNonces, portions, basketsRedeemed); + .basketHandler() + .quoteCustomRedemption(basketNonces, portions, basketsRedeemed); // === Save initial recipient balances === @@ -337,6 +337,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { require(params.amtRate >= MIN_THROTTLE_RATE_AMT, "issuance amtRate too small"); require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "issuance amtRate too big"); require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "issuance pctRate too big"); + issuanceThrottle.useAvailable(totalSupply(), 0); emit IssuanceThrottleSet(issuanceThrottle.params, params); issuanceThrottle.params = params; } @@ -346,6 +347,7 @@ contract RTokenP0 is ComponentP0, ERC20PermitUpgradeable, IRToken { require(params.amtRate >= MIN_THROTTLE_RATE_AMT, "redemption amtRate too small"); require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "redemption amtRate too big"); require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "redemption pctRate too big"); + redemptionThrottle.useAvailable(totalSupply(), 0); emit RedemptionThrottleSet(redemptionThrottle.params, params); redemptionThrottle.params = params; } diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index 4196cc7916..2f380927fe 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -50,6 +50,26 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { _distributeTokenToBuy(); } + /// Return registered ERC20s to the BackingManager if distribution for tokenToBuy is 0 + /// @custom:interaction + function returnTokens(IERC20[] memory erc20s) external notTradingPausedOrFrozen { + RevenueTotals memory revTotals = main.distributor().totals(); + if (tokenToBuy == main.rsr()) { + require(revTotals.rsrTotal == 0, "rsrTotal > 0"); + } else if (address(tokenToBuy) == address(main.rToken())) { + require(revTotals.rTokenTotal == 0, "rTokenTotal > 0"); + } else { + revert("invalid tokenToBuy"); + } + + // Return ERC20s to the BackingManager + for (uint256 i = 0; i < erc20s.length; i++) { + require(main.assetRegistry().isRegistered(erc20s[i]), "unregistered erc20"); + address backingManager = address(main.backingManager()); + erc20s[i].safeTransfer(backingManager, erc20s[i].balanceOf(address(this))); + } + } + /// Process some number of tokens /// @param erc20s The ERC20s to manage; can be tokenToBuy or anything registered /// @param kinds The kinds of auctions to launch: DUTCH_AUCTION | BATCH_AUCTION diff --git a/contracts/p0/StRSR.sol b/contracts/p0/StRSR.sol index aca576a471..fe3676dcef 100644 --- a/contracts/p0/StRSR.sol +++ b/contracts/p0/StRSR.sol @@ -98,6 +98,10 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { // Min exchange rate {qRSR/qStRSR} (compile-time constant) uint192 private constant MIN_EXCHANGE_RATE = uint192(1e9); // 1e-9 + // stake rate under/over which governance can reset all stakes + uint192 private constant MAX_SAFE_STAKE_RATE = 1e6 * FIX_ONE; // 1e6 + uint192 private constant MIN_SAFE_STAKE_RATE = uint192(1e12); // 1e-6 + // Withdrawal Leak uint192 private leaked; // {1} stake fraction that has withdrawn without a refresh uint48 private lastWithdrawRefresh; // {s} timestamp of last refresh() during withdraw() @@ -378,6 +382,20 @@ contract StRSRP0 is IStRSR, ComponentP0, EIP712Upgradeable { address account = accounts.at(i); delete withdrawals[account]; } + emit AllUnstakingReset(era); + } + + /// @custom:governance + /// Reset all stakes and advance era + function resetStakes() external governance { + uint192 stakeRate = divuu(totalStaked, rsrBacking); + require( + stakeRate <= MIN_SAFE_STAKE_RATE || stakeRate >= MAX_SAFE_STAKE_RATE, + "rate still safe" + ); + + bankruptStakers(); + bankruptWithdrawals(); } /// Refresh if too much RSR has exited since the last refresh occurred diff --git a/contracts/p0/mixins/Trading.sol b/contracts/p0/mixins/Trading.sol index 4df64445c8..b49aee3f11 100644 --- a/contracts/p0/mixins/Trading.sol +++ b/contracts/p0/mixins/Trading.sol @@ -67,7 +67,6 @@ abstract contract TradingP0 is RewardableP0, ITrading { ) internal returns (ITrade trade) { IBroker broker = main.broker(); assert(address(trades[req.sell.erc20()]) == address(0)); - require(!broker.disabled(), "broker disabled"); req.sell.erc20().safeApprove(address(broker), 0); req.sell.erc20().safeApprove(address(broker), req.sellAmount); diff --git a/contracts/p1/AssetRegistry.sol b/contracts/p1/AssetRegistry.sol index 3474d171f8..098e47f93a 100644 --- a/contracts/p1/AssetRegistry.sol +++ b/contracts/p1/AssetRegistry.sol @@ -98,6 +98,8 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { } /// Unregister an asset, requiring that it is already registered + /// Rewards are NOT claimed by default when unregistering due to security concerns. + /// If the collateral is secure, governance should claim rewards before unregistering. /// @custom:governance // checks: assets[asset.erc20()] == asset // effects: assets' = assets - {asset.erc20():_} + {asset.erc20(), asset} @@ -186,6 +188,13 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { // effects: assets' = assets.set(asset.erc20(), asset) // returns: assets[asset.erc20()] != asset function _registerIgnoringCollisions(IAsset asset) private returns (bool swapped) { + if (asset.isCollateral()) { + require( + ICollateral(address(asset)).status() == CollateralStatus.SOUND, + "collateral not sound" + ); + } + IERC20Metadata erc20 = asset.erc20(); if (_erc20s.contains(address(erc20))) { if (assets[erc20] == asset) return false; diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index b16d4a1677..a64ff3f412 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -82,15 +82,14 @@ contract BackingManagerP1 is TradingP1, IBackingManager { /// Settle a single trade. If the caller is the trade, try chaining into rebalance() /// While this function is not nonReentrant, its two subsets each individually are + /// If the caller is a trade contract, initiate the next trade. + /// This is done in order to better align incentives, + /// and have the last bidder be the one to start the next auction. + /// This behaviour currently only happens for Dutch Trade. /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) - public - override(ITrading, TradingP1) - notTradingPausedOrFrozen - returns (ITrade trade) - { + function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { trade = super.settleTrade(sell); // nonReentrant // if the settler is the trade contract itself, try chaining with another rebalance() diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index b684b8c3e2..387055fc63 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -255,7 +255,10 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { for (uint256 i = 0; i < size; ++i) { CollateralStatus s = assetRegistry.toColl(basket.erc20s[i]).status(); - if (s.worseThan(status_)) status_ = s; + if (s.worseThan(status_)) { + if (s == CollateralStatus.DISABLED) return CollateralStatus.DISABLED; + status_ = s; + } } } @@ -356,6 +359,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } /// Return the current issuance/redemption value of `amount` BUs + /// Any approvals needed to issue RTokens should be set to the values returned by this function /// @dev Subset of logic of quoteCustomRedemption; more gas efficient for current nonce /// @param amount {BU} /// @return erc20s The backing collateral erc20s @@ -376,11 +380,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // {qTok} = {tok/BU} * {BU} * {tok} * {qTok/tok} quantities[i] = _quantity(basket.erc20s[i], coll) - .safeMul(amount, rounding) - .shiftl_toUint( - int8(IERC20Metadata(address(basket.erc20s[i])).decimals()), - rounding - ); + .safeMul(amount, rounding) + .shiftl_toUint(int8(IERC20Metadata(address(basket.erc20s[i])).decimals()), rounding); } } @@ -461,8 +462,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // {tok} = {BU} * {ref/BU} / {ref/tok} quantities[i] = amount - .safeMulDiv(refAmtsAll[i], collsAll[i].refPerTok(), FLOOR) - .shiftl_toUint(int8(collsAll[i].erc20Decimals()), FLOOR); + .safeMulDiv(refAmtsAll[i], collsAll[i].refPerTok(), FLOOR) + .shiftl_toUint(int8(collsAll[i].erc20Decimals()), FLOOR); // marginally more penalizing than its sibling calculation that uses _quantity() // because does not intermediately CEIL as part of the division } @@ -608,9 +609,9 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { // {tok} = {BU} * {ref/BU} / {ref/tok} quantities[i] = b - .refAmts[erc20s[i]] - .safeDiv(ICollateral(address(asset)).refPerTok(), FLOOR) - .shiftl_toUint(int8(asset.erc20Decimals()), FLOOR); + .refAmts[erc20s[i]] + .safeDiv(ICollateral(address(asset)).refPerTok(), FLOOR) + .shiftl_toUint(int8(asset.erc20Decimals()), FLOOR); } catch (bytes memory errData) { // untested: // OOG pattern tested in other contracts, cost to test here is high diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index f248cb3fcd..98e52120e4 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -26,7 +26,7 @@ contract BrokerP1 is ComponentP1, IBroker { uint48 public constant MAX_AUCTION_LENGTH = 604800; // {s} max valid duration - 1 week /// @custom:oz-upgrades-unsafe-allow state-variable-immutable // solhint-disable-next-line var-name-mixedcase - uint48 public immutable MIN_AUCTION_LENGTH; // {s} 2 blocks based on network + uint48 public immutable MIN_AUCTION_LENGTH; // {s} 20 blocks, based on network IBackingManager private backingManager; IRevenueTrader private rsrTrader; @@ -43,9 +43,10 @@ contract BrokerP1 is ComponentP1, IBroker { // {s} the length of a Gnosis EasyAuction. Governance parameter. uint48 public batchAuctionLength; - // Whether trading is disabled. - // Initially false. Settable by OWNER. A trade clone can set it to true via reportViolation() - bool public disabled; + // Whether Batch Auctions are disabled. + // Initially false. Settable by OWNER. + // A GnosisTrade clone can set it to true via reportViolation() + bool public batchTradeDisabled; // The set of ITrade (clone) addresses this contract has created mapping(address => bool) private trades; @@ -58,12 +59,15 @@ contract BrokerP1 is ComponentP1, IBroker { // {s} the length of a Dutch Auction. Governance parameter. uint48 public dutchAuctionLength; + // Whether Dutch Auctions are currently disabled, per ERC20 + mapping(IERC20Metadata => bool) public dutchTradeDisabled; + // ==== Invariant ==== // (trades[addr] == true) iff this contract has created an ITrade clone at addr /// @custom:oz-upgrades-unsafe-allow constructor constructor() { - MIN_AUCTION_LENGTH = NetworkConfigLib.blocktime() * 2; + MIN_AUCTION_LENGTH = NetworkConfigLib.blocktime() * 20; } // effects: initial parameters are set @@ -93,7 +97,6 @@ contract BrokerP1 is ComponentP1, IBroker { /// @dev Requires setting an allowance in advance /// @custom:protected and @custom:interaction CEI // checks: - // not disabled, paused (trading), or frozen // caller is a system Trader // effects: // Deploys a new trade clone, `trade` @@ -106,8 +109,6 @@ contract BrokerP1 is ComponentP1, IBroker { TradeRequest memory req, TradePrices memory prices ) external returns (ITrade) { - require(!disabled, "broker disabled"); - address caller = _msgSender(); require( caller == address(backingManager) || @@ -129,8 +130,23 @@ contract BrokerP1 is ComponentP1, IBroker { // effects: disabled' = true function reportViolation() external notTradingPausedOrFrozen { require(trades[_msgSender()], "unrecognized trade contract"); - emit DisabledSet(disabled, true); - disabled = true; + ITrade trade = ITrade(_msgSender()); + TradeKind kind = trade.KIND(); + + if (kind == TradeKind.BATCH_AUCTION) { + emit BatchTradeDisabledSet(batchTradeDisabled, true); + batchTradeDisabled = true; + } else if (kind == TradeKind.DUTCH_AUCTION) { + IERC20Metadata sell = trade.sell(); + emit DutchTradeDisabledSet(sell, dutchTradeDisabled[sell], true); + dutchTradeDisabled[sell] = true; + + IERC20Metadata buy = trade.buy(); + emit DutchTradeDisabledSet(buy, dutchTradeDisabled[buy], true); + dutchTradeDisabled[buy] = true; + } else { + revert("unrecognized trade kind"); + } } // === Setters === @@ -188,14 +204,21 @@ contract BrokerP1 is ComponentP1, IBroker { } /// @custom:governance - function setDisabled(bool disabled_) external governance { - emit DisabledSet(disabled, disabled_); - disabled = disabled_; + function setBatchTradeDisabled(bool disabled) external governance { + emit BatchTradeDisabledSet(batchTradeDisabled, disabled); + batchTradeDisabled = disabled; + } + + /// @custom:governance + function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external governance { + emit DutchTradeDisabledSet(erc20, dutchTradeDisabled[erc20], disabled); + dutchTradeDisabled[erc20] = disabled; } // === Private === function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { + require(!batchTradeDisabled, "batch auctions disabled"); require(batchAuctionLength > 0, "batch auctions not enabled"); GnosisTrade trade = GnosisTrade(address(batchTradeImplementation).clone()); trades[address(trade)] = true; @@ -224,6 +247,10 @@ contract BrokerP1 is ComponentP1, IBroker { TradePrices memory prices, ITrading caller ) private returns (ITrade) { + require( + !dutchTradeDisabled[req.sell.erc20()] && !dutchTradeDisabled[req.buy.erc20()], + "dutch auctions disabled for token pair" + ); require(dutchAuctionLength > 0, "dutch auctions not enabled"); DutchTrade trade = DutchTrade(address(dutchTradeImplementation).clone()); trades[address(trade)] = true; @@ -244,5 +271,5 @@ contract BrokerP1 is ComponentP1, IBroker { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[43] private __gap; + uint256[42] private __gap; } diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index 4373a9a9bc..ca818f5a14 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -104,6 +104,9 @@ contract DistributorP1 is ComponentP1, IDistributor { Transfer[] memory transfers = new Transfer[](destinations.length()); uint256 numTransfers; + address furnaceAddr = furnace; // gas-saver + address stRSRAddr = stRSR; // gas-saver + for (uint256 i = 0; i < destinations.length(); ++i) { address addrTo = destinations.at(i); @@ -114,9 +117,9 @@ contract DistributorP1 is ComponentP1, IDistributor { uint256 transferAmt = tokensPerShare * numberOfShares; if (addrTo == FURNACE) { - addrTo = furnace; + addrTo = furnaceAddr; } else if (addrTo == ST_RSR) { - addrTo = stRSR; + addrTo = stRSRAddr; } transfers[numTransfers] = Transfer({ diff --git a/contracts/p1/Furnace.sol b/contracts/p1/Furnace.sol index 3259e48fd2..923ba33737 100644 --- a/contracts/p1/Furnace.sol +++ b/contracts/p1/Furnace.sol @@ -90,8 +90,14 @@ contract FurnaceP1 is ComponentP1, IFurnace { /// Ratio setting /// @custom:governance function setRatio(uint192 ratio_) public governance { - // solhint-disable-next-line no-empty-blocks - if (lastPayout > 0) try this.melt() {} catch {} + if (lastPayout > 0) { + // solhint-disable-next-line no-empty-blocks + try this.melt() {} catch { + uint48 numPeriods = uint48((block.timestamp) - lastPayout) / PERIOD; + lastPayout += numPeriods * PERIOD; + lastPayoutBal = rToken.balanceOf(address(this)); + } + } require(ratio_ <= MAX_RATIO, "invalid ratio"); // The ratio can safely be set to 0 to turn off payouts, though it is not recommended emit RatioSet(ratio, ratio_); diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 942e2d2501..77f15bef54 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -87,6 +87,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { } /// Issue an RToken on the current basket + /// Do no use inifite approvals. Instead, use BasketHandler.quote() to determine the amount + /// of backing tokens to approve. /// @param amount {qTok} The quantity of RToken to issue /// @custom:interaction nearly CEI, but see comments around handling of refunds function issue(uint256 amount) public { @@ -94,6 +96,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { } /// Issue an RToken on the current basket, to a particular recipient + /// Do no use inifite approvals. Instead, use BasketHandler.quote() to determine the amount + /// of backing tokens to approve. /// @param recipient The address to receive the issued RTokens /// @param amount {qRTok} The quantity of RToken to issue /// @custom:interaction RCEI @@ -179,7 +183,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // == Refresh == assetRegistry.refresh(); // solhint-disable-next-line no-empty-blocks - try main.furnace().melt() {} catch {} // nice for the redeemer, but not necessary + try furnace.melt() {} catch {} // nice for the redeemer, but not necessary // == Checks and Effects == @@ -251,7 +255,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // == Refresh == assetRegistry.refresh(); // solhint-disable-next-line no-empty-blocks - try main.furnace().melt() {} catch {} // nice for the redeemer, but not necessary + try furnace.melt() {} catch {} // nice for the redeemer, but not necessary // == Checks and Effects == @@ -395,8 +399,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // Note: These are D18s, even though they are uint256s. This is because // we cannot assume we stay inside our valid range here, as that is what // we are checking in the first place - uint256 low = (FIX_ONE_256 * basketsNeeded) / supply; // D18{BU/rTok} - uint256 high = (FIX_ONE_256 * basketsNeeded + (supply - 1)) / supply; // D18{BU/rTok} + uint256 low = (FIX_ONE_256 * basketsNeeded_) / supply; // D18{BU/rTok} + uint256 high = (FIX_ONE_256 * basketsNeeded_ + (supply - 1)) / supply; // D18{BU/rTok} // here we take advantage of an implicit upcast from uint192 exchange rates require(low >= MIN_EXCHANGE_RATE && high <= MAX_EXCHANGE_RATE, "BU rate out of range"); @@ -422,8 +426,7 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { /// @return available {qRTok} The maximum redemption that can be performed in the current block function redemptionAvailable() external view returns (uint256 available) { uint256 supply = totalSupply(); - uint256 hourlyLimit = redemptionThrottle.hourlyLimit(supply); - available = redemptionThrottle.currentlyAvailable(hourlyLimit); + available = redemptionThrottle.currentlyAvailable(redemptionThrottle.hourlyLimit(supply)); if (supply < available) available = supply; } @@ -442,6 +445,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { require(params.amtRate >= MIN_THROTTLE_RATE_AMT, "issuance amtRate too small"); require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "issuance amtRate too big"); require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "issuance pctRate too big"); + issuanceThrottle.useAvailable(totalSupply(), 0); + emit IssuanceThrottleSet(issuanceThrottle.params, params); issuanceThrottle.params = params; } @@ -451,6 +456,8 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { require(params.amtRate >= MIN_THROTTLE_RATE_AMT, "redemption amtRate too small"); require(params.amtRate <= MAX_THROTTLE_RATE_AMT, "redemption amtRate too big"); require(params.pctRate <= MAX_THROTTLE_PCT_AMT, "redemption pctRate too big"); + redemptionThrottle.useAvailable(totalSupply(), 0); + emit RedemptionThrottleSet(redemptionThrottle.params, params); redemptionThrottle.params = params; } diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 9c7346edef..fe7b409b50 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -22,6 +22,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { IBackingManager private backingManager; IFurnace private furnace; IRToken private rToken; + IERC20 private rsr; function init( IMain main_, @@ -43,18 +44,14 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { backingManager = main.backingManager(); furnace = main.furnace(); rToken = main.rToken(); + rsr = main.rsr(); } /// Settle a single trade + distribute revenue /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) - public - override(ITrading, TradingP1) - notTradingPausedOrFrozen - returns (ITrade trade) - { + function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { trade = super.settleTrade(sell); // nonReentrant _distributeTokenToBuy(); // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely @@ -67,6 +64,27 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { _distributeTokenToBuy(); } + /// Return registered ERC20s to the BackingManager if distribution for tokenToBuy is 0 + /// @custom:interaction + function returnTokens(IERC20[] memory erc20s) external notTradingPausedOrFrozen { + RevenueTotals memory revTotals = distributor.totals(); + if (tokenToBuy == rsr) { + require(revTotals.rsrTotal == 0, "rsrTotal > 0"); + } else if (address(tokenToBuy) == address(rToken)) { + require(revTotals.rTokenTotal == 0, "rTokenTotal > 0"); + } else { + // untestable: tokenToBuy is always the RSR or RToken + revert("invalid tokenToBuy"); + } + + // Return ERC20s to the BackingManager + uint256 len = erc20s.length; + for (uint256 i = 0; i < len; ++i) { + require(assetRegistry.isRegistered(erc20s[i]), "unregistered erc20"); + erc20s[i].safeTransfer(address(backingManager), erc20s[i].balanceOf(address(this))); + } + } + /// Process some number of tokens /// If the tokenToBuy is included in erc20s, RevenueTrader will distribute it at end of the tx /// @param erc20s The ERC20s to manage; can be tokenToBuy or anything registered @@ -169,5 +187,5 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[44] private __gap; + uint256[43] private __gap; } diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index f6babc8eb6..ffe8d0669f 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -163,6 +163,10 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab uint48 private lastWithdrawRefresh; // {s} timestamp of last refresh() during withdraw() uint192 public withdrawalLeak; // {1} gov param -- % RSR that can be withdrawn without refresh + // stake rate under/over which governance can reset all stakes + uint192 private constant MAX_SAFE_STAKE_RATE = 1e6 * FIX_ONE; // 1e6 D18{qStRSR/qRSR} + uint192 private constant MIN_SAFE_STAKE_RATE = uint192(1e12); // 1e-6 D18{qStRSR/qRSR} + // ====================== /// @custom:oz-upgrades-unsafe-allow constructor @@ -482,6 +486,19 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab IERC20Upgradeable(address(rsr)).safeTransfer(_msgSender(), seizedRSR); } + /// @custom:governance + /// Reset all stakes and advance era + function resetStakes() external { + requireGovernanceOnly(); + require( + stakeRate <= MIN_SAFE_STAKE_RATE || stakeRate >= MAX_SAFE_STAKE_RATE, + "rate still safe" + ); + + beginEra(); + beginDraftEra(); + } + /// @return D18{qRSR/qStRSR} The exchange rate between RSR and StRSR function exchangeRate() public view returns (uint192) { // D18{qRSR/qStRSR} = D18 * D18 / D18{qStRSR/qRSR} diff --git a/contracts/p1/mixins/BasketLib.sol b/contracts/p1/mixins/BasketLib.sol index 0ade4b65e8..6480d77055 100644 --- a/contracts/p1/mixins/BasketLib.sol +++ b/contracts/p1/mixins/BasketLib.sol @@ -76,8 +76,9 @@ library BasketLibP1 { empty(self); uint256 length = other.erc20s.length; for (uint256 i = 0; i < length; ++i) { - self.erc20s.push(other.erc20s[i]); - self.refAmts[other.erc20s[i]] = other.refAmts[other.erc20s[i]]; + IERC20 _erc20 = other.erc20s[i]; // gas-saver + self.erc20s.push(_erc20); + self.refAmts[_erc20] = other.refAmts[_erc20]; } } @@ -165,7 +166,9 @@ library BasketLibP1 { IAssetRegistry assetRegistry ) external returns (bool) { // targetNames := {} - while (targetNames.length() > 0) targetNames.remove(targetNames.at(0)); + while (targetNames.length() > 0) { + targetNames.remove(targetNames.at(targetNames.length() - 1)); + } // newBasket := {} newBasket.empty(); @@ -193,30 +196,28 @@ library BasketLibP1 { for (uint256 i = 0; i < config.erc20s.length; ++i) { // Find collateral's targetName index uint256 targetIndex; + IERC20 _erc20 = config.erc20s[i]; // gas-saver for (targetIndex = 0; targetIndex < targetsLength; ++targetIndex) { - if (targetNames.at(targetIndex) == config.targetNames[config.erc20s[i]]) break; + if (targetNames.at(targetIndex) == config.targetNames[_erc20]) break; } assert(targetIndex < targetsLength); // now, targetNames[targetIndex] == config.targetNames[erc20] // Set basket weights for good, prime collateral, // and accumulate the values of goodWeights and targetWeights - uint192 targetWeight = config.targetAmts[config.erc20s[i]]; + uint192 targetWeight = config.targetAmts[_erc20]; totalWeights[targetIndex] = totalWeights[targetIndex].plus(targetWeight); if ( - goodCollateral( - config.targetNames[config.erc20s[i]], - config.erc20s[i], - assetRegistry - ) && targetWeight.gt(FIX_ZERO) + goodCollateral(config.targetNames[_erc20], _erc20, assetRegistry) && + targetWeight.gt(FIX_ZERO) ) { goodWeights[targetIndex] = goodWeights[targetIndex].plus(targetWeight); newBasket.add( - config.erc20s[i], + _erc20, targetWeight.div( // this div is safe: targetPerRef() > 0: goodCollateral check - assetRegistry.toColl(config.erc20s[i]).targetPerRef(), + assetRegistry.toColl(_erc20).targetPerRef(), CEIL ) ); @@ -237,16 +238,17 @@ library BasketLibP1 { // backup basket for tgt to make up that weight: for (uint256 i = 0; i < targetsLength; ++i) { if (totalWeights[i].lte(goodWeights[i])) continue; // Don't need any backup weight + bytes32 _targetName = targetNames.at(i); // "tgt" = targetNames[i] // Now, unsoundPrimeWt(tgt) > 0 uint256 size = 0; // backup basket size - BackupConfig storage backup = config.backups[targetNames.at(i)]; + BackupConfig storage backup = config.backups[_targetName]; // Find the backup basket size: min(backup.max, # of good backup collateral) for (uint256 j = 0; j < backup.erc20s.length && size < backup.max; ++j) { - if (goodCollateral(targetNames.at(i), backup.erc20s[j], assetRegistry)) size++; + if (goodCollateral(_targetName, backup.erc20s[j], assetRegistry)) size++; } // Now, size = len(backups(tgt)). If empty, fail. @@ -257,17 +259,16 @@ library BasketLibP1 { // Loop: for erc20 in backups(tgt)... for (uint256 j = 0; j < backup.erc20s.length && assigned < size; ++j) { - if (goodCollateral(targetNames.at(i), backup.erc20s[j], assetRegistry)) { + if (goodCollateral(_targetName, backup.erc20s[j], assetRegistry)) { + uint192 backupWeight = totalWeights[i].minus(goodWeights[i]).div( + // this div is safe: targetPerRef > 0: goodCollateral check + assetRegistry.toColl(backup.erc20s[j]).targetPerRef().mulu(size), + CEIL + ); + // Across this .add(), targetWeight(newBasket',erc20) // = targetWeight(newBasket,erc20) + unsoundPrimeWt(tgt) / len(backups(tgt)) - newBasket.add( - backup.erc20s[j], - totalWeights[i].minus(goodWeights[i]).div( - // this div is safe: targetPerRef > 0: goodCollateral check - assetRegistry.toColl(backup.erc20s[j]).targetPerRef().mulu(size), - CEIL - ) - ); + BasketLibP1.add(newBasket, backup.erc20s[j], backupWeight); assigned++; } } diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index 8a9e0990f3..dd86e45ca1 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -181,6 +181,9 @@ library RecollateralizationLibP1 { int256 deltaTop; // D18{BU} even though this is int256, it is D18 // not required for range.bottom + // to minimize total operations, range.bottom is calculated from a summed UoA + uint192 uoaBottom; // {UoA} pessimistic UoA estimate of balances above basketsHeld.bottom + // (no space on the stack to cache erc20s.length) for (uint256 i = 0; i < reg.erc20s.length; ++i) { // Exclude RToken balances to avoid double counting value @@ -193,15 +196,14 @@ library RecollateralizationLibP1 { bal = bal.plus(reg.assets[i].bal(address(ctx.stRSR))); } - { + if (ctx.quantities[i] == 0) { // Skip over dust-balance assets not in the basket (uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/tok} // Intentionally include value of IFFY/DISABLED collateral - if ( - ctx.quantities[i] == 0 && - !TradeLib.isEnoughToSell(reg.assets[i], bal, lotLow, ctx.minTradeVolume) - ) continue; + if (!TradeLib.isEnoughToSell(reg.assets[i], bal, lotLow, ctx.minTradeVolume)) { + continue; + } } (uint192 low, uint192 high) = reg.assets[i].price(); // {UoA/tok} @@ -238,7 +240,7 @@ library RecollateralizationLibP1 { // {tok} = {tok/BU} * {BU} uint192 anchor = ctx.quantities[i].mul(ctx.basketsHeld.bottom, FLOOR); - // (1) Sell tokens at low price + // (1) Sum token value at low price // {UoA} = {UoA/tok} * {tok} uint192 val = low.mul(bal - anchor, FLOOR); @@ -249,12 +251,7 @@ library RecollateralizationLibP1 { // in the calculation we have already calculated the UoA amount corresponding to // the excess token balance based on its low price, so we are already set up // to straightforwardly deduct the minTradeVolume before trying to buy BUs. - val = (val < ctx.minTradeVolume) ? 0 : val - ctx.minTradeVolume; - - // (3) Buy BUs at their high price with the remaining value - // (4) Assume maximum slippage in trade - // {BU} = {UoA} * {1} / {UoA/BU} - range.bottom += val.mulDiv(FIX_ONE.minus(ctx.maxTradeSlippage), buPriceHigh, FLOOR); + uoaBottom += (val < ctx.minTradeVolume) ? 0 : val - ctx.minTradeVolume; } } @@ -271,7 +268,12 @@ library RecollateralizationLibP1 { } // range.bottom - range.bottom += ctx.basketsHeld.bottom; + // (3) Buy BUs at their high price with the remaining value + // (4) Assume maximum slippage in trade + // {BU} = {UoA} * {1} / {UoA/BU} + range.bottom = + ctx.basketsHeld.bottom + + uoaBottom.mulDiv(FIX_ONE.minus(ctx.maxTradeSlippage), buPriceHigh, FLOOR); // reverting on overflow is appropriate here // ==== (3/3) Enforce (range.bottom <= range.top <= basketsNeeded) ==== diff --git a/contracts/p1/mixins/Trading.sol b/contracts/p1/mixins/Trading.sol index 1cfa49c191..863f7ab113 100644 --- a/contracts/p1/mixins/Trading.sol +++ b/contracts/p1/mixins/Trading.sol @@ -117,7 +117,6 @@ abstract contract TradingP1 is Multicall, ComponentP1, ReentrancyGuardUpgradeabl TradeRequest memory req, TradePrices memory prices ) internal returns (ITrade trade) { - /* */ IERC20 sell = req.sell.erc20(); assert(address(trades[sell]) == address(0)); diff --git a/contracts/plugins/assets/OracleLib.sol b/contracts/plugins/assets/OracleLib.sol index e15605f881..495186e36c 100644 --- a/contracts/plugins/assets/OracleLib.sol +++ b/contracts/plugins/assets/OracleLib.sol @@ -6,6 +6,10 @@ import "../../libraries/Fixed.sol"; error StalePrice(); +interface EACAggregatorProxy { + function aggregator() external view returns (address); +} + /// Used by asset plugins to price their collateral library OracleLib { /// @dev Use for on-the-fly calculations that should revert @@ -16,8 +20,13 @@ library OracleLib { view returns (uint192) { + // If the aggregator is not set, the chainlink feed has been deprecated + if (EACAggregatorProxy(address(chainlinkFeed)).aggregator() == address(0)) { + revert StalePrice(); + } + (uint80 roundId, int256 p, , uint256 updateTime, uint80 answeredInRound) = chainlinkFeed - .latestRoundData(); + .latestRoundData(); if (updateTime == 0 || answeredInRound < roundId) { revert StalePrice(); diff --git a/contracts/plugins/assets/aave/StaticATokenLM.sol b/contracts/plugins/assets/aave/StaticATokenLM.sol index b24a3c14b5..f6637e1369 100644 --- a/contracts/plugins/assets/aave/StaticATokenLM.sol +++ b/contracts/plugins/assets/aave/StaticATokenLM.sol @@ -39,7 +39,7 @@ contract StaticATokenLM is using RayMathNoRounding for uint256; /// Emitted whenever a reward token balance is claimed - event RewardsClaimed(IERC20 indexed erc20, uint256 indexed amount); + event RewardsClaimed(IERC20 indexed erc20, uint256 amount); bytes public constant EIP712_REVISION = bytes("1"); bytes32 internal constant EIP712_DOMAIN = diff --git a/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol b/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol index aa83bacdf8..fb6b12065a 100644 --- a/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol +++ b/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol @@ -101,7 +101,7 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { emit OwnershipTransferred(address(0), owner); (address _lptoken, address _token, , address _rewards, , ) = IBooster(convexBooster) - .poolInfo(_poolId); + .poolInfo(_poolId); curveToken = _lptoken; convexToken = _token; convexPool = _rewards; @@ -275,10 +275,8 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { } } else { reward.claimable_reward[_accounts[u]] = reward - .claimable_reward[_accounts[u]] - .add( - _balances[u].mul(uint256(reward.reward_integral).sub(userI)).div(1e20) - ); + .claimable_reward[_accounts[u]] + .add(_balances[u].mul(uint256(reward.reward_integral).sub(userI)).div(1e20)); } reward.reward_integral_for[_accounts[u]] = reward.reward_integral; } @@ -375,8 +373,8 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { } uint256 newlyClaimable = _getDepositedBalance(_account) - .mul(I.sub(reward.reward_integral_for[_account])) - .div(1e20); + .mul(I.sub(reward.reward_integral_for[_account])) + .div(1e20); claimable[i].amount = claimable[i].amount.add( reward.claimable_reward[_account].add(newlyClaimable) ); @@ -395,8 +393,8 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { ); } newlyClaimable = _getDepositedBalance(_account) - .mul(I.sub(reward.reward_integral_for[_account])) - .div(1e20); + .mul(I.sub(reward.reward_integral_for[_account])) + .div(1e20); claimable[CVX_INDEX].amount = CvxMining.ConvertCrvToCvx(newlyClaimable); claimable[CVX_INDEX].token = cvx; } diff --git a/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol b/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol index bc4debb54e..778725866c 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol @@ -18,7 +18,6 @@ import { shiftl_toFix, FIX_ONE } from "../../../libraries/Fixed.sol"; contract MorphoFiatCollateral is AppreciatingFiatCollateral { using OracleLib for AggregatorV3Interface; - MorphoTokenisedDeposit public immutable vault; uint256 private immutable oneShare; int8 private immutable refDecimals; @@ -29,13 +28,17 @@ contract MorphoFiatCollateral is AppreciatingFiatCollateral { AppreciatingFiatCollateral(config, revenueHiding) { require(address(config.erc20) != address(0), "missing erc20"); - vault = MorphoTokenisedDeposit(address(config.erc20)); + MorphoTokenisedDeposit vault = MorphoTokenisedDeposit(address(config.erc20)); oneShare = 10**vault.decimals(); refDecimals = int8(uint8(IERC20Metadata(vault.asset()).decimals())); } /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens function _underlyingRefPerTok() internal view override returns (uint192) { - return shiftl_toFix(vault.convertToAssets(oneShare), -refDecimals); + return + shiftl_toFix( + MorphoTokenisedDeposit(address(erc20)).convertToAssets(oneShare), + -refDecimals + ); } } diff --git a/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol b/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol index 0e39fb12d6..b37db03207 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol @@ -48,7 +48,7 @@ contract MorphoNonFiatCollateral is MorphoFiatCollateral { uint192 pegPrice ) { - // {tar/ref} Get current market peg ({btc/wbtc}) + // {tar/ref} Get current market peg pegPrice = targetUnitChainlinkFeed.price(targetUnitOracleTimeout); // {UoA/tok} = {UoA/ref} * {ref/tok} diff --git a/contracts/plugins/mocks/ChainlinkMock.sol b/contracts/plugins/mocks/ChainlinkMock.sol index db794d7ec6..6e0fee6785 100644 --- a/contracts/plugins/mocks/ChainlinkMock.sol +++ b/contracts/plugins/mocks/ChainlinkMock.sol @@ -23,6 +23,7 @@ contract MockV3Aggregator is AggregatorV3Interface { // Additional variable to be able to test invalid behavior uint256 public latestAnsweredRound; + address public aggregator; mapping(uint256 => int256) public getAnswer; mapping(uint256 => uint256) public getTimestamp; @@ -30,9 +31,14 @@ contract MockV3Aggregator is AggregatorV3Interface { constructor(uint8 _decimals, int256 _initialAnswer) { decimals = _decimals; + aggregator = address(this); updateAnswer(_initialAnswer); } + function deprecate() external { + aggregator = address(0); + } + function updateAnswer(int256 _answer) public { latestAnswer = _answer; latestTimestamp = block.timestamp; @@ -80,6 +86,12 @@ contract MockV3Aggregator is AggregatorV3Interface { uint80 answeredInRound ) { + if (aggregator == address(0)) { + // solhint-disable-next-line no-inline-assembly + assembly { + revert(0, 0) + } + } return ( _roundId, getAnswer[_roundId], @@ -102,6 +114,12 @@ contract MockV3Aggregator is AggregatorV3Interface { uint80 answeredInRound ) { + if (aggregator == address(0)) { + // solhint-disable-next-line no-inline-assembly + assembly { + revert(0, 0) + } + } return ( uint80(latestRound), getAnswer[latestRound], diff --git a/contracts/plugins/mocks/EasyAuction.sol b/contracts/plugins/mocks/EasyAuction.sol index ef52004075..8acf1a9ec9 100644 --- a/contracts/plugins/mocks/EasyAuction.sol +++ b/contracts/plugins/mocks/EasyAuction.sol @@ -336,8 +336,8 @@ contract EasyAuction is Ownable { atStageSolutionSubmission(auctionId) { (, , uint96 auctioneerSellAmount) = auctionData[auctionId] - .initialAuctionOrder - .decodeOrder(); + .initialAuctionOrder + .decodeOrder(); uint256 sumBidAmount = auctionData[auctionId].interimSumBidAmount; bytes32 iterOrder = auctionData[auctionId].interimOrder; @@ -438,7 +438,7 @@ contract EasyAuction is Ownable { // Auction fully filled via partial match of currentOrder uint256 sellAmountClearingOrder = sellAmountOfIter.sub(uncoveredBids); auctionData[auctionId].volumeClearingPriceOrder = sellAmountClearingOrder - .toUint96(); + .toUint96(); currentBidSum = currentBidSum.sub(uncoveredBids); clearingOrder = currentOrder; } else { @@ -474,9 +474,9 @@ contract EasyAuction is Ownable { ); fillVolumeOfAuctioneerOrder = currentBidSum - .mul(fullAuctionedAmount) - .div(minAuctionedBuyAmount) - .toUint96(); + .mul(fullAuctionedAmount) + .div(minAuctionedBuyAmount) + .toUint96(); } } auctionData[auctionId].clearingPriceOrder = clearingOrder; @@ -517,8 +517,8 @@ contract EasyAuction is Ownable { } AuctionData memory auction = auctionData[auctionId]; (, uint96 priceNumerator, uint96 priceDenominator) = auction - .clearingPriceOrder - .decodeOrder(); + .clearingPriceOrder + .decodeOrder(); (uint64 userId, , ) = orders[0].decodeOrder(); bool minFundingThresholdNotReached = auctionData[auctionId].minFundingThresholdNotReached; for (uint256 i = 0; i < orders.length; i++) { @@ -568,8 +568,8 @@ contract EasyAuction is Ownable { } else { //[11] (, uint96 priceNumerator, uint96 priceDenominator) = auctionData[auctionId] - .clearingPriceOrder - .decodeOrder(); + .clearingPriceOrder + .decodeOrder(); uint256 unsettledAuctionTokens = fullAuctionedAmount.sub(fillVolumeOfAuctioneerOrder); uint256 auctioningTokenAmount = unsettledAuctionTokens.add( feeAmount.mul(unsettledAuctionTokens).div(fullAuctionedAmount) diff --git a/contracts/plugins/mocks/InvalidBrokerMock.sol b/contracts/plugins/mocks/InvalidBrokerMock.sol index 525d1f7559..7b19993c94 100644 --- a/contracts/plugins/mocks/InvalidBrokerMock.sol +++ b/contracts/plugins/mocks/InvalidBrokerMock.sol @@ -22,7 +22,9 @@ contract InvalidBrokerMock is ComponentP0, IBroker { uint48 public batchAuctionLength; // {s} the length of a batch auction uint48 public dutchAuctionLength; // {s} the length of a dutch auction - bool public disabled = false; + bool public batchTradeDisabled = false; + + mapping(IERC20Metadata => bool) public dutchTradeDisabled; function init( IMain main_, @@ -44,8 +46,6 @@ contract InvalidBrokerMock is ComponentP0, IBroker { TradeRequest memory, TradePrices memory ) external view notTradingPausedOrFrozen returns (ITrade) { - require(!disabled, "broker disabled"); - // Revert when opening trades revert("Failure opening trade"); } @@ -64,5 +64,9 @@ contract InvalidBrokerMock is ComponentP0, IBroker { /// Dummy implementation /* solhint-disable no-empty-blocks */ - function setDisabled(bool disabled_) external governance {} + function setBatchTradeDisabled(bool disabled) external governance {} + + /// Dummy implementation + /* solhint-disable no-empty-blocks */ + function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external governance {} } diff --git a/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol b/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol index 3ad165f9ab..aca54d543c 100644 --- a/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol +++ b/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol @@ -68,6 +68,11 @@ contract InvalidRefPerTokCollateralMock is AppreciatingFiatCollateral { refPerTokRevert = on; } + // Setter for status + function setStatus(CollateralStatus _status) external { + markStatus(_status); + } + function refPerTok() public view virtual override returns (uint192) { if (refPerTokRevert) revert(); // Revert with no reason return rateMock; diff --git a/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol b/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol index 64b3904230..ccb2b0f31e 100644 --- a/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol +++ b/contracts/plugins/mocks/InvalidRevTraderP1Mock.sol @@ -20,6 +20,7 @@ contract RevenueTraderP1InvalidReverts is TradingP1, IRevenueTrader { IBackingManager private backingManager; IFurnace private furnace; IRToken private rToken; + IERC20 private rsr; function init( IMain main_, @@ -35,13 +36,33 @@ contract RevenueTraderP1InvalidReverts is TradingP1, IRevenueTrader { } /// Distribute tokenToBuy to its destinations - function distributeTokenToBuy() public { + function distributeTokenToBuy() public notTradingPausedOrFrozen { uint256 bal = tokenToBuy.balanceOf(address(this)); tokenToBuy.safeApprove(address(main.distributor()), 0); tokenToBuy.safeApprove(address(main.distributor()), bal); main.distributor().distribute(tokenToBuy, bal); } + /// Return registered ERC20s to the BackingManager if distribution for tokenToBuy is 0 + /// @custom:interaction + function returnTokens(IERC20[] memory erc20s) external notTradingPausedOrFrozen { + RevenueTotals memory revTotals = distributor.totals(); + if (tokenToBuy == rsr) { + require(revTotals.rsrTotal == 0, "rsrTotal > 0"); + } else if (address(tokenToBuy) == address(rToken)) { + require(revTotals.rTokenTotal == 0, "rTokenTotal > 0"); + } else { + revert("invalid tokenToBuy"); + } + + // Return ERC20s to the BackingManager + uint256 len = erc20s.length; + for (uint256 i = 0; i < len; ++i) { + require(assetRegistry.isRegistered(erc20s[i]), "erc20 unregistered"); + erc20s[i].safeTransfer(address(backingManager), erc20s[i].balanceOf(address(this))); + } + } + /// Processes a single token; unpermissioned /// Reverts for testing purposes function manageTokens(IERC20[] memory, TradeKind[] memory) external notTradingPausedOrFrozen { @@ -55,5 +76,6 @@ contract RevenueTraderP1InvalidReverts is TradingP1, IRevenueTrader { backingManager = main.backingManager(); furnace = main.furnace(); rToken = main.rToken(); + rsr = main.rsr(); } } diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 86160bcedc..002af6b057 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -9,36 +9,65 @@ import "../../interfaces/IAsset.sol"; import "../../interfaces/IBroker.sol"; import "../../interfaces/ITrade.sol"; -uint192 constant FORTY_PERCENT = 4e17; // {1} 0.4 -uint192 constant SIXTY_PERCENT = 6e17; // {1} 0.6 - -// Exponential price decay with base (999999/1000000). Price starts at 1000x and decays to <1x -// A 30-minute auction on a chain with a 12-second blocktime has a ~10.87% price drop per block -// during the geometric/exponential period and a 0.05% drop per block during the linear period. -// 30-minutes is the recommended length of auction for a chain with 12-second blocktimes, but -// longer and shorter times can be used as well. The pricing method does not degrade -// beyond the degree to which less overall blocktime means necessarily larger price drops. -uint192 constant MAX_EXP = 6907752 * FIX_ONE; // {1} (1000000/999999)^6907752 = ~1000x +// A dutch auction in 4 parts: +// 1. 0% - 20%: Geometric decay from 1000x the bestPrice to ~1.5x the bestPrice +// 2. 20% - 45%: Linear decay from ~1.5x the bestPrice to the bestPrice +// 3. 45% - 95%: Linear decay from the bestPrice to the worstPrice +// 4. 95% - 100%: Constant at the worstPrice +// +// For a trade between 2 assets with 1% oracleError: +// A 30-minute auction on a chain with a 12-second blocktime has a ~20% price drop per block +// during the 1st period, ~0.8% during the 2nd period, and ~0.065% during the 3rd period. +// +// 30-minutes is the recommended length of auction for a chain with 12-second blocktimes. +// 6 minutes, 7.5 minutes, 15 minutes, 1.5 minutes for each pariod respectively. +// +// Longer and shorter times can be used as well. The pricing method does not degrade +// beyond the degree to which less overall blocktime means less overall precision. + +uint192 constant FIVE_PERCENT = 5e16; // {1} 0.05 +uint192 constant TWENTY_PERCENT = 20e16; // {1} 0.2 +uint192 constant TWENTY_FIVE_PERCENT = 25e16; // {1} 0.25 +uint192 constant FORTY_FIVE_PERCENT = 45e16; // {1} 0.45 +uint192 constant FIFTY_PERCENT = 50e16; // {1} 0.5 +uint192 constant NINETY_FIVE_PERCENT = 95e16; // {1} 0.95 + +uint192 constant MAX_EXP = 6502287e18; // {1} (1000000/999999)^6502287 = ~666.6667 uint192 constant BASE = 999999e12; // {1} (999999/1000000) +uint192 constant ONE_POINT_FIVE = 150e16; // {1} 1.5 /** * @title DutchTrade - * @notice Implements a wholesale dutch auction via a piecewise falling-price mechansim. - * Over the first 40% of the auction the price falls from ~1000x the best plausible price - * down to the best plausible price in a geometric series. The price decreases by the same % - * each time. At 30 minutes the decreases are 10.87% per block. Longer auctions have - * smaller price decreases, and shorter auctions have larger price decreases. + * @notice Implements a wholesale dutch auction via a 4-piecewise falling-price mechansim. + * The overall idea is to handle 4 cases: + * 1. Price manipulation of the exchange rate up to 1000x (eg: via a read-only reentrancy) + * 2. Price movement of up to 50% during the auction + * 3. Typical case: no significant price movement; clearing price within expected range + * 4. No bots online; manual human doing bidding; additional time for tx clearing + * + * Case 1: Over the first 20% of the auction the price falls from ~1000x the best plausible + * price down to 1.5x the best plausible price in a geometric series. * This period DOES NOT expect to receive a bid; it just defends against manipulated prices. + * If a bid occurs during this period, a violation is reported to the Broker. + * This is still safe for the protocol since other trades, with price discovery, can occur. + * + * Case 2: Over the next 20% of the auction the price falls from 1.5x the best plausible price + * to the best plausible price, linearly. No violation is reported if a bid occurs. This case + * exists to handle cases where prices change after the auction is started, naturally. * - * Over the last 60% of the auction the price falls from the best plausible price to the worst - * price, linearly. The worst price is further discounted by the maxTradeSlippage as a fraction - * of how far from minTradeVolume to maxTradeVolume the trade lies. + * Case 3: Over the next 50% of the auction the price falls from the best plausible price to the + * worst price, linearly. The worst price is further discounted by the maxTradeSlippage as a + * fraction of how far from minTradeVolume to maxTradeVolume the trade lies. * At maxTradeVolume, no additonal discount beyond the oracle errors is applied. + * This is the phase of the auction where bids will typically occur. + * + * Case 4: Lastly the price stays at the worst price for the final 5% of the auction to allow + * a bid to occur if no bots are online and the only bidders are humans. * * To bid: - * 1. Call `bidAmount()` view to check prices at various timestamps + * 1. Call `bidAmount()` view to check prices at various blocks. * 2. Provide approval of sell tokens for precisely the `bidAmount()` desired - * 3. Wait until a desirable block is reached (hopefully not in the first 40% of the auction) + * 3. Wait until the desired block is reached (hopefully not in the first 20% of the auction) * 4. Call bid() */ contract DutchTrade is ITrade { @@ -52,6 +81,7 @@ contract DutchTrade is ITrade { TradeStatus public status; // reentrancy protection + IBroker public broker; // The Broker that cloned this contract into existence ITrading public origin; // the address that initialized the contract // === Auction === @@ -59,13 +89,14 @@ contract DutchTrade is ITrade { IERC20Metadata public buy; uint192 public sellAmount; // {sellTok} - // The auction runs from [startTime, endTime], inclusive - uint48 public startTime; // {s} when the dutch auction begins (one block after init()) - uint48 public endTime; // {s} when the dutch auction ends if no bids are received + // The auction runs from [startBlock, endTime], inclusive + uint256 public startBlock; // {block} when the dutch auction begins (one block after init()) + uint256 public endBlock; // {block} when the dutch auction ends if no bids are received + uint48 public endTime; // {s} not used in this contract; needed on interface - // highPrice is always 1000x the middlePrice, so we don't need to track it explicitly - uint192 public middlePrice; // {buyTok/sellTok} The price at which the function is piecewise - uint192 public lowPrice; // {buyTok/sellTok} The price the auction ends at + uint192 public bestPrice; // {buyTok/sellTok} The best plausible price based on oracle data + uint192 public worstPrice; // {buyTok/sellTok} The worst plausible price based on oracle data + // and further discounted by a fraction of maxTradeSlippage based on auction volume. // === Bid === address public bidder; @@ -80,24 +111,26 @@ contract DutchTrade is ITrade { status = end; } - // === Public Bid Helper === - - /// Calculates how much buy token is needed to purchase the lot, at a particular timestamp - /// @param timestamp {s} The block timestamp to get price for - /// @return {qBuyTok} The amount of buy tokens required to purchase the lot - function bidAmount(uint48 timestamp) public view returns (uint256) { - require(timestamp >= startTime, "auction not started"); - require(timestamp <= endTime, "auction over"); + // === Auction Sizing Views === - // {buyTok/sellTok} - uint192 price = _price(timestamp); + /// @return {qSellTok} The size of the lot being sold, in token quanta + function lot() public view returns (uint256) { + return sellAmount.shiftl_toUint(int8(sell.decimals())); + } - // {qBuyTok} = {sellTok} * {buyTok/sellTok} * {qBuyTok/buyTok} - return sellAmount.mul(price, CEIL).shiftl_toUint(int8(buy.decimals()), CEIL); + /// Calculates how much buy token is needed to purchase the lot at a particular block + /// @param blockNumber {block} The block number of the bid + /// @return {qBuyTok} The amount of buy tokens required to purchase the lot + function bidAmount(uint256 blockNumber) external view returns (uint256) { + return _bidAmount(_price(blockNumber)); } + // ==== Constructor === + constructor() { ONE_BLOCK = NetworkConfigLib.blocktime(); + + status = TradeStatus.CLOSED; } // === External === @@ -118,21 +151,28 @@ contract DutchTrade is ITrade { assert( address(sell_) != address(0) && address(buy_) != address(0) && - auctionLength >= 2 * ONE_BLOCK + auctionLength >= 20 * ONE_BLOCK ); // misuse by caller // Only start dutch auctions under well-defined prices - require(prices.sellLow > 0 && prices.sellHigh < FIX_MAX, "bad sell pricing"); - require(prices.buyLow > 0 && prices.buyHigh < FIX_MAX, "bad buy pricing"); + require(prices.sellLow > 0 && prices.sellHigh < FIX_MAX / 1000, "bad sell pricing"); + require(prices.buyLow > 0 && prices.buyHigh < FIX_MAX / 1000, "bad buy pricing"); + broker = IBroker(msg.sender); origin = origin_; sell = sell_.erc20(); buy = buy_.erc20(); require(sellAmount_ <= sell.balanceOf(address(this)), "unfunded trade"); sellAmount = shiftl_toFix(sellAmount_, -int8(sell.decimals())); // {sellTok} - startTime = uint48(block.timestamp) + ONE_BLOCK; // start in the next block - endTime = startTime + auctionLength; + + uint256 _startBlock = block.number + 1; // start in the next block + startBlock = _startBlock; // gas-saver + + uint256 _endBlock = _startBlock + auctionLength / ONE_BLOCK; // FLOOR; endBlock is inclusive + endBlock = _endBlock; // gas-saver + + endTime = uint48(block.timestamp + ONE_BLOCK * (_endBlock - _startBlock)); // {1} uint192 slippage = _slippage( @@ -142,11 +182,11 @@ contract DutchTrade is ITrade { ); // {buyTok/sellTok} = {UoA/sellTok} * {1} / {UoA/buyTok} - lowPrice = prices.sellLow.mulDiv(FIX_ONE - slippage, prices.buyHigh, FLOOR); - middlePrice = prices.sellHigh.div(prices.buyLow, CEIL); // no additional slippage - // highPrice = 1000 * middlePrice - - assert(lowPrice <= middlePrice); + uint192 _worstPrice = prices.sellLow.mulDiv(FIX_ONE - slippage, prices.buyHigh, FLOOR); + uint192 _bestPrice = prices.sellHigh.div(prices.buyLow, CEIL); // no additional slippage + assert(_worstPrice <= _bestPrice); + worstPrice = _worstPrice; // gas-saver + bestPrice = _bestPrice; // gas-saver } /// Bid for the auction lot at the current price; settling atomically via a callback @@ -155,16 +195,24 @@ contract DutchTrade is ITrade { function bid() external returns (uint256 amountIn) { require(bidder == address(0), "bid already received"); + // {buyTok/sellTok} + uint192 price = _price(block.number); // enforces auction ongoing + // {qBuyTok} - amountIn = bidAmount(uint48(block.timestamp)); // enforces auction ongoing + amountIn = _bidAmount(price); // Transfer in buy tokens bidder = msg.sender; - buy.safeTransferFrom(bidder, address(this), amountIn); + buy.safeTransferFrom(msg.sender, address(this), amountIn); // status must begin OPEN assert(status == TradeStatus.OPEN); + // reportViolation if auction cleared in geometric phase + if (price > bestPrice.mul(ONE_POINT_FIVE, CEIL)) { + broker.reportViolation(); + } + // settle() via callback origin.settleTrade(sell); @@ -184,18 +232,16 @@ contract DutchTrade is ITrade { // Received bid if (bidder != address(0)) { - sell.safeTransfer(bidder, sellAmount); + soldAmt = lot(); // {qSellTok} + sell.safeTransfer(bidder, soldAmt); // {qSellTok} } else { - require(block.timestamp >= endTime, "auction not over"); + require(block.number > endBlock, "auction not over"); } - uint256 sellBal = sell.balanceOf(address(this)); - soldAmt = sellAmount > sellBal ? sellAmount - sellBal : 0; - boughtAmt = buy.balanceOf(address(this)); - - // Transfer balances back to origin - buy.safeTransfer(address(origin), boughtAmt); - sell.safeTransfer(address(origin), sellBal); + // Transfer remaining balances back to origin + boughtAmt = buy.balanceOf(address(this)); // {qBuyTok} + buy.safeTransfer(address(origin), boughtAmt); // {qBuyTok} + sell.safeTransfer(address(origin), sell.balanceOf(address(this))); // {qSellTok} } /// Anyone can transfer any ERC20 back to the origin after the trade has been closed @@ -209,12 +255,7 @@ contract DutchTrade is ITrade { /// @return true iff the trade can be settled. // Guaranteed to be true some time after init(), until settle() is called function canSettle() external view returns (bool) { - return status == TradeStatus.OPEN && (bidder != address(0) || block.timestamp > endTime); - } - - /// @return {qSellTok} The size of the lot being sold, in token quanta - function lot() external view returns (uint256) { - return sellAmount.shiftl_toUint(int8(sell.decimals())); + return status == TradeStatus.OPEN && (bidder != address(0) || block.number > endBlock); } // === Private === @@ -244,29 +285,61 @@ contract DutchTrade is ITrade { } /// Return the price of the auction at a particular timestamp - /// @param timestamp {s} The block timestamp + /// @param blockNumber {block} The block number to get price for /// @return {buyTok/sellTok} - function _price(uint48 timestamp) private view returns (uint192) { + function _price(uint256 blockNumber) private view returns (uint192) { + uint256 _startBlock = startBlock; // gas savings + uint256 _endBlock = endBlock; // gas savings + require(blockNumber >= _startBlock, "auction not started"); + require(blockNumber <= _endBlock, "auction over"); + /// Price Curve: - /// - first 40%: geometrically decrease the price from 1000x the middlePrice to 1x - /// - last 60: decrease linearly from middlePrice to lowPrice + /// - first 20%: geometrically decrease the price from 1000x the bestPrice to 1.5x it + /// - next 25%: linearly decrease the price from 1.5x the bestPrice to 1x it + /// - next 50%: linearly decrease the price from bestPrice to worstPrice + /// - last 5%: constant at worstPrice - uint192 progression = divuu(timestamp - startTime, endTime - startTime); // {1} + uint192 progression = divuu(blockNumber - _startBlock, _endBlock - _startBlock); // {1} - // Fast geometric decay -- 0%-40% of auction - if (progression < FORTY_PERCENT) { - uint192 exp = MAX_EXP.mulDiv(FORTY_PERCENT - progression, FORTY_PERCENT, ROUND); + // Fast geometric decay -- 0%-20% of auction + if (progression < TWENTY_PERCENT) { + uint192 exp = MAX_EXP.mulDiv(TWENTY_PERCENT - progression, TWENTY_PERCENT, ROUND); - // middlePrice * ((1000000/999999) ^ exp) = middlePrice / ((999999/1000000) ^ exp) - // safe uint48 downcast: exp is at-most 6907752 + // bestPrice * ((1000000/999999) ^ exp) = bestPrice / ((999999/1000000) ^ exp) + // safe uint48 downcast: exp is at-most 6502287 // {buyTok/sellTok} = {buyTok/sellTok} / {1} ^ {1} - return middlePrice.div(BASE.powu(uint48(exp.toUint(ROUND))), CEIL); - // this reverts for middlePrice >= 6.21654046e36 * FIX_ONE + return bestPrice.mulDiv(ONE_POINT_FIVE, BASE.powu(uint48(exp.toUint(ROUND))), CEIL); + // this reverts for bestPrice >= 6.21654046e36 * FIX_ONE + } else if (progression < FORTY_FIVE_PERCENT) { + // First linear decay -- 20%-45% of auction + // 1.5x -> 1x the bestPrice + + uint192 _bestPrice = bestPrice; // gas savings + // {buyTok/sellTok} = {buyTok/sellTok} * {1} + uint192 highPrice = _bestPrice.mul(ONE_POINT_FIVE, CEIL); + return + highPrice - + (highPrice - _bestPrice).mulDiv(progression - TWENTY_PERCENT, TWENTY_FIVE_PERCENT); + } else if (progression < NINETY_FIVE_PERCENT) { + // Second linear decay -- 45%-95% of auction + // bestPrice -> worstPrice + + uint192 _bestPrice = bestPrice; // gas savings + // {buyTok/sellTok} = {buyTok/sellTok} * {1} + return + _bestPrice - + (_bestPrice - worstPrice).mulDiv(progression - FORTY_FIVE_PERCENT, FIFTY_PERCENT); } - // Slow linear decay -- 40%-100% of auction - return - middlePrice - - (middlePrice - lowPrice).mulDiv(progression - FORTY_PERCENT, SIXTY_PERCENT); + // Constant price -- 95%-100% of auction + return worstPrice; + } + + /// Calculates how much buy token is needed to purchase the lot at a particular price + /// @param price {buyTok/sellTok} + /// @return {qBuyTok} The amount of buy tokens required to purchase the lot + function _bidAmount(uint192 price) public view returns (uint256) { + // {qBuyTok} = {sellTok} * {buyTok/sellTok} * {qBuyTok/buyTok} + return sellAmount.mul(price, CEIL).shiftl_toUint(int8(buy.decimals()), CEIL); } } diff --git a/contracts/plugins/trading/GnosisTrade.sol b/contracts/plugins/trading/GnosisTrade.sol index ab954dbe8a..e8413c5fea 100644 --- a/contracts/plugins/trading/GnosisTrade.sol +++ b/contracts/plugins/trading/GnosisTrade.sol @@ -58,6 +58,10 @@ contract GnosisTrade is ITrade { status = end; } + constructor() { + status = TradeStatus.CLOSED; + } + /// Constructor function, can only be called once /// @dev Expects sell tokens to already be present /// @custom:interaction reentrancy-safe b/c state-locking @@ -180,11 +184,18 @@ contract GnosisTrade is ITrade { // Transfer balances to origin uint256 sellBal = sell.balanceOf(address(this)); + + // As raised in C4's review, this balance can be manupulated by a frontrunner + // It won't really affect the outcome of the trade, as protocol still gets paid + // and it just gets a better clearing price than expected. + // Fixing it would require some complex logic, as SimpleAuction does not expose + // the amount of tokens bought by the auction after the tokens are settled. + // So we will live with this for now. Worst case, there will be a mismatch between + // the trades recorded by the IDO contracts and on our side. boughtAmt = buy.balanceOf(address(this)); if (sellBal > 0) IERC20Upgradeable(address(sell)).safeTransfer(origin, sellBal); if (boughtAmt > 0) IERC20Upgradeable(address(buy)).safeTransfer(origin, boughtAmt); - // Check clearing prices if (sellBal < initBal) { soldAmt = initBal - sellBal; diff --git a/docs/build-on-top.md b/docs/build-on-top.md new file mode 100644 index 0000000000..8a777f5168 --- /dev/null +++ b/docs/build-on-top.md @@ -0,0 +1,17 @@ +# Building on Top of Reserve + +TODO -- this document is a work in progress. + +## Overview + +Reserve uses a long-lived Tenderly fork as the main testing environment. Since the Reserve Protocol is a meta-protocol it relies on functional building blocks that are not all present on testnets such as Goerli or Sepolia. For this reason it makes the most sense to use a fork of mainnet. + +Unfortunately it would be bad practice to share the RPC publicly. Please reach out at protocol.eng@reserve.org to request we share the RPC privately with you. + +## Chain + +We re-use the chain ID of 3 (previously: ropsten) for the Tenderly fork in order to separate book-keeping between mainnet and the fork environment. + +- [Core Contracts](../scripts/addresses/3-tmp-deployments.json) +- [Collateral Plugins](../scripts/addresses/3-tmp-assets-collateral.json) + - Note that oracles require special logic in order to be refreshed and for these plugins to function correctly. diff --git a/docs/recollateralization.md b/docs/recollateralization.md index 67c9067592..aecb345c94 100644 --- a/docs/recollateralization.md +++ b/docs/recollateralization.md @@ -8,6 +8,8 @@ Recollateralization takes place in the central loop of [`BackingManager.rebalanc The trading algorithm is isolated in [RecollateralizationLib.sol](../contracts/p1/mixins/RecollateralizationLib.sol). This document describes the algorithm implemented by the library at a high-level, as well as the concepts required to evaluate the correctness of the implementation. +Note: In case of an upwards default, as in a token is worth _more_ than what it is supposed to be, the token redemption is worth more than the peg during recollateralization process. This will continue to be the case until the rebalancing process is complete. This is a good thing, and the protocol should be able to take advantage of this. + ## High-level overview ```solidity diff --git a/docs/system-design.md b/docs/system-design.md index 23d3c0491b..8e8ba41292 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -33,9 +33,8 @@ Some of the core contracts in our system regularly own ERC20 tokens. In each cas ### RToken Lifecycle -1. During SlowIssuance, the `RToken` transfers collateral tokens from the caller's address into itself. -2. At vesting time, the `RToken` contract mints new RToken to the recipient and transfers the held collateral to the `BackingManager`. If the `BasketHandler` has updated the basket since issuance began, then the collateral is instead returned to the recipient and no RToken is minted. -3. During redemption, RToken is burnt from the redeemer's account and they are transferred a prorata share of backing collateral from the `BackingManager`. +1. During minting, the `RToken` transfers collateral tokens from the caller's address into itself and mints new RToken to the caller's address. Minting amount must be less than the current throttle limit, or the transaction will revert. +2. During redemption, RToken is burnt from the redeemer's account and they are transferred a prorata share of backing collateral from the `BackingManager`. ## Protocol Assumptions diff --git a/hardhat.config.ts b/hardhat.config.ts index f45506c31d..e9364399e1 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -79,13 +79,13 @@ const config: HardhatUserConfig = { gasMultiplier: 1.05, // 5% buffer; seen failures on RToken deployment and asset refreshes otherwise }, tenderly: { - chainId: 1, + chainId: 3, url: TENDERLY_RPC_URL, accounts: { mnemonic: MNEMONIC, }, // gasPrice: 10_000_000_000, - gasMultiplier: 1.015, // 1.5% buffer; seen failures on RToken deployment and asset refreshes + gasMultiplier: 1.05, // 5% buffer; seen failures on RToken deployment and asset refreshes otherwise }, }, solidity: { @@ -117,6 +117,7 @@ const config: HardhatUserConfig = { mocha: { timeout: TIMEOUT, slow: 1000, + retries: 3, }, contractSizer: { alphaSort: false, diff --git a/package.json b/package.json index 7d9411dee6..976df23cc0 100644 --- a/package.json +++ b/package.json @@ -15,19 +15,19 @@ "deploy:run": "hardhat run scripts/deploy.ts", "deploy:confirm": "hardhat run scripts/confirm.ts", "deploy:verify_etherscan": "hardhat run scripts/verify_etherscan.ts", - "test:extreme": "EXTREME=1 PROTO_IMPL=1 npx hardhat test test/{Furnace,RTokenExtremes,ZTradingExtremes,ZZStRSR}.test.ts", + "test:extreme": "EXTREME=1 PROTO_IMPL=1 npx hardhat test test/{Broker,Furnace,RTokenExtremes,ZTradingExtremes,ZZStRSR}.test.ts", "test:extreme:integration": "FORK=1 EXTREME=1 PROTO_IMPL=1 npx hardhat test test/integration/**/*.test.ts", "test:unit": "yarn test:plugins && yarn test:p0 && yarn test:p1", "test:fast": "bash tools/fast-test.sh", "test:p0": "PROTO_IMPL=0 hardhat test test/*.test.ts", - "test:p1": "PROTO_IMPL=1 hardhat test test/*.test.ts", + "test:p1": "PROTO_IMPL=1 hardhat test test/*.test.ts ", "test:plugins": "hardhat test test/{libraries,plugins}/*.test.ts", "test:plugins:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/plugins/individual-collateral/**/*.test.ts", "test:integration": "PROTO_IMPL=1 FORK=1 hardhat test test/integration/**/*.test.ts", "test:scenario": "PROTO_IMPL=1 hardhat test test/scenario/*.test.ts", - "test:gas": "yarn test:gas:protocol && yarn test:gas:integration", + "test:gas": "yarn test:gas:protocol && yarn test:gas:collateral && yarn test:gas:integration", "test:gas:protocol": "REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/{libraries,plugins,scenario}/*.test.ts test/*.test.ts", - "test:gas:integration": "FORK=1 REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/integration/**/*.test.ts", + "test:gas:collateral": "FORK=1 REPORT_GAS=1 PROTO_IMPL=1 hardhat test test/plugins/individual-collateral/**/*.test.ts", "test:coverage": "PROTO_IMPL=1 hardhat coverage --testfiles 'test/{libraries,plugins,scenario}/*.test.ts test/*.test.ts'", "test:unit:coverage": "PROTO_IMPL=1 SLOW= hardhat coverage --testfiles 'test/*.test.ts test/libraries/*.test.ts test/plugins/*.test.ts'", "eslint": "eslint test/", @@ -36,8 +36,8 @@ "size": "hardhat size-contracts", "slither": "python3 tools/slither.py", "prepare": "husky install", - "run:backtests": "npx hardhat run scripts/ci_backtest_plugin.ts", - "run:4bytes": "npx hardhat run scripts/4bytes.ts" + "run:backtests": "hardhat run scripts/ci_backtest_plugin.ts", + "run:4byte": "hardhat run scripts/4byte.ts" }, "repository": { "type": "git", @@ -68,21 +68,21 @@ "@types/lodash": "^4.14.177", "@types/mocha": "^9.0.0", "@types/node": "^12.20.37", - "@typescript-eslint/eslint-plugin": "^5.17.0", - "@typescript-eslint/parser": "^5.17.0", + "@typescript-eslint/eslint-plugin": "5.17.0", + "@typescript-eslint/parser": "5.17.0", "axios": "^0.24.0", "bignumber.js": "^9.1.1", "caip": "^1.1.0", "chai": "^4.3.4", "decimal.js": "^10.4.3", "dotenv": "^16.0.0", - "eslint": "^8.14.0", - "eslint-config-prettier": "^8.5.0", - "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-promise": "^6.0.0", + "eslint": "8.14.0", + "eslint-config-prettier": "8.5.0", + "eslint-config-standard": "16.0.3", + "eslint-plugin-import": "2.25.4", + "eslint-plugin-node": "11.1.0", + "eslint-plugin-prettier": "4.0.0", + "eslint-plugin-promise": "6.0.0", "eth-permit": "^0.2.1", "ethers": "^5.7.2", "fast-check": "^2.24.0", @@ -96,9 +96,9 @@ "lodash.get": "^4.4.2", "mocha-chai-jest-snapshot": "^1.1.3", "prettier": "2.5.1", - "prettier-plugin-solidity": "^1.0.0-beta.13", - "solhint": "^3.3.6", - "solhint-plugin-prettier": "^0.0.5", + "prettier-plugin-solidity": "1.0.0-beta.13", + "solhint": "3.3.6", + "solhint-plugin-prettier": "0.0.5", "solidity-coverage": "^0.8.2", "ts-node": "^10.4.0", "tsconfig-paths": "^4.1.0", diff --git a/scripts/4byte.ts b/scripts/4byte.ts new file mode 100644 index 0000000000..aef7bee418 --- /dev/null +++ b/scripts/4byte.ts @@ -0,0 +1,51 @@ +import axios from 'axios' +import hre from 'hardhat' + +async function main() { + await hre.run('compile') + + const allArtifactNames = await hre.artifacts.getAllFullyQualifiedNames() + const fullComposite = await Promise.all( + allArtifactNames.map((fullName) => hre.artifacts.readArtifact(fullName).then((e) => e.abi)) + ) + .then((e) => e.flat()) + .then((e) => e.map((v) => [JSON.stringify(v), v] as const)) + .then((e) => [...new Map(e).values()]) + + const parsedComposite = fullComposite + .filter((e) => ['function', 'event', 'error'].includes(e.type)) + .map((e) => { + if (e.type === 'error') { + // errors are same as functions + e.type = 'function' + e.outputs = [] + } + + return e + }) + + if (parsedComposite.length === 0) { + return console.log('Nothing to sync!') + } + + await axios + .post('https://www.4byte.directory/api/v1/import-abi/', { + contract_abi: JSON.stringify(parsedComposite), + }) + .then(({ data }) => { + console.log( + `Processed ${data.num_processed} unique items from ${allArtifactNames.length} individual ABIs adding ${data.num_imported} new selectors to database with ${data.num_duplicates} duplicates and ${data.num_ignored} ignored items.` + ) + }) + .catch((error) => { + throw Error(`Sync failed with code ${error.response.status}!`) + }) + + console.log('Done!') +} + +main().catch((error) => { + console.error(error) + + process.exitCode = 1 +}) diff --git a/scripts/4bytes-syncced.json b/scripts/4bytes-syncced.json deleted file mode 100644 index ccb75c42d6..0000000000 --- a/scripts/4bytes-syncced.json +++ /dev/null @@ -1,1126 +0,0 @@ -{ - "functions": [ - "allowance(address,address)", - "approve(address,uint256)", - "balanceOf(address)", - "totalSupply()", - "transfer(address,uint256)", - "transferFrom(address,address,uint256)", - "decimals()", - "name()", - "symbol()", - "burn(address,address,uint256,uint256)", - "getScaledUserBalanceAndSupply(address)", - "mint(address,uint256,uint256)", - "mintToTreasury(uint256,uint256)", - "scaledBalanceOf(address)", - "scaledTotalSupply()", - "transferOnLiquidation(address,address,uint256)", - "transferUnderlyingTo(address,uint256)", - "handleAction(address,uint256,uint256)", - "borrow(address,uint256,uint256,uint16,address)", - "deposit(address,uint256,address,uint16)", - "finalizeTransfer(address,address,address,uint256,uint256,uint256)", - "flashLoan(address,address[],uint256[],uint256[],address,bytes,uint16)", - "getAddressesProvider()", - "getConfiguration(address)", - "getReserveData(address)", - "getReserveNormalizedIncome(address)", - "getReserveNormalizedVariableDebt(address)", - "getReservesList()", - "getUserAccountData(address)", - "getUserConfiguration(address)", - "initReserve(address,address,address,address,address)", - "liquidationCall(address,address,address,uint256,bool)", - "paused()", - "rebalanceStableBorrowRate(address,address)", - "repay(address,uint256,uint256,address)", - "setConfiguration(address,uint256)", - "setPause(bool)", - "setReserveInterestRateStrategyAddress(address,address)", - "setUserUseReserveAsCollateral(address,bool)", - "swapBorrowRateMode(address,uint256)", - "withdraw(address,uint256,address)", - "getAddress(bytes32)", - "getEmergencyAdmin()", - "getLendingPool()", - "getLendingPoolCollateralManager()", - "getLendingPoolConfigurator()", - "getLendingRateOracle()", - "getMarketId()", - "getPoolAdmin()", - "getPriceOracle()", - "setAddress(bytes32,address)", - "setAddressAsProxy(bytes32,address)", - "setEmergencyAdmin(address)", - "setLendingPoolCollateralManager(address)", - "setLendingPoolConfiguratorImpl(address)", - "setLendingPoolImpl(address)", - "setLendingRateOracle(address)", - "setMarketId(string)", - "setPoolAdmin(address)", - "setPriceOracle(address)", - "BORROW_ALLOWANCE_NOT_ENOUGH()", - "CALLER_NOT_POOL_ADMIN()", - "CT_CALLER_MUST_BE_LENDING_POOL()", - "CT_CANNOT_GIVE_ALLOWANCE_TO_HIMSELF()", - "CT_INVALID_BURN_AMOUNT()", - "CT_INVALID_MINT_AMOUNT()", - "CT_TRANSFER_AMOUNT_NOT_GT_0()", - "LPAPR_INVALID_ADDRESSES_PROVIDER_ID()", - "LPAPR_PROVIDER_NOT_REGISTERED()", - "LPCM_COLLATERAL_CANNOT_BE_LIQUIDATED()", - "LPCM_HEALTH_FACTOR_NOT_BELOW_THRESHOLD()", - "LPCM_NOT_ENOUGH_LIQUIDITY_TO_LIQUIDATE()", - "LPCM_NO_ERRORS()", - "LPCM_SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER()", - "LPC_CALLER_NOT_EMERGENCY_ADMIN()", - "LPC_INVALID_ADDRESSES_PROVIDER_ID()", - "LPC_INVALID_ATOKEN_POOL_ADDRESS()", - "LPC_INVALID_CONFIGURATION()", - "LPC_INVALID_STABLE_DEBT_TOKEN_POOL_ADDRESS()", - "LPC_INVALID_STABLE_DEBT_TOKEN_UNDERLYING_ADDRESS()", - "LPC_INVALID_VARIABLE_DEBT_TOKEN_POOL_ADDRESS()", - "LPC_INVALID_VARIABLE_DEBT_TOKEN_UNDERLYING_ADDRESS()", - "LPC_RESERVE_LIQUIDITY_NOT_0()", - "LP_CALLER_MUST_BE_AN_ATOKEN()", - "LP_CALLER_NOT_LENDING_POOL_CONFIGURATOR()", - "LP_FAILED_COLLATERAL_SWAP()", - "LP_FAILED_REPAY_WITH_COLLATERAL()", - "LP_INCONSISTENT_FLASHLOAN_PARAMS()", - "LP_INCONSISTENT_PARAMS_LENGTH()", - "LP_INCONSISTENT_PROTOCOL_ACTUAL_BALANCE()", - "LP_INTEREST_RATE_REBALANCE_CONDITIONS_NOT_MET()", - "LP_INVALID_EQUAL_ASSETS_TO_SWAP()", - "LP_INVALID_FLASHLOAN_MODE()", - "LP_INVALID_FLASH_LOAN_EXECUTOR_RETURN()", - "LP_IS_PAUSED()", - "LP_LIQUIDATION_CALL_FAILED()", - "LP_NOT_CONTRACT()", - "LP_NOT_ENOUGH_LIQUIDITY_TO_BORROW()", - "LP_NOT_ENOUGH_STABLE_BORROW_BALANCE()", - "LP_NO_MORE_RESERVES_ALLOWED()", - "LP_REENTRANCY_NOT_ALLOWED()", - "LP_REQUESTED_AMOUNT_TOO_SMALL()", - "MATH_ADDITION_OVERFLOW()", - "MATH_DIVISION_BY_ZERO()", - "MATH_MULTIPLICATION_OVERFLOW()", - "RC_INVALID_DECIMALS()", - "RC_INVALID_LIQ_BONUS()", - "RC_INVALID_LIQ_THRESHOLD()", - "RC_INVALID_LTV()", - "RC_INVALID_RESERVE_FACTOR()", - "RL_LIQUIDITY_INDEX_OVERFLOW()", - "RL_LIQUIDITY_RATE_OVERFLOW()", - "RL_RESERVE_ALREADY_INITIALIZED()", - "RL_STABLE_BORROW_RATE_OVERFLOW()", - "RL_VARIABLE_BORROW_INDEX_OVERFLOW()", - "RL_VARIABLE_BORROW_RATE_OVERFLOW()", - "SDT_BURN_EXCEEDS_BALANCE()", - "SDT_STABLE_DEBT_OVERFLOW()", - "UL_INVALID_INDEX()", - "VL_AMOUNT_BIGGER_THAN_MAX_LOAN_SIZE_STABLE()", - "VL_BORROWING_NOT_ENABLED()", - "VL_COLLATERAL_BALANCE_IS_0()", - "VL_COLLATERAL_CANNOT_COVER_NEW_BORROW()", - "VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY()", - "VL_CURRENT_AVAILABLE_LIQUIDITY_NOT_ENOUGH()", - "VL_DEPOSIT_ALREADY_IN_USE()", - "VL_HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD()", - "VL_INCONSISTENT_FLASHLOAN_PARAMS()", - "VL_INVALID_AMOUNT()", - "VL_INVALID_INTEREST_RATE_MODE_SELECTED()", - "VL_NOT_ENOUGH_AVAILABLE_USER_BALANCE()", - "VL_NO_ACTIVE_RESERVE()", - "VL_NO_DEBT_OF_SELECTED_TYPE()", - "VL_NO_EXPLICIT_AMOUNT_TO_REPAY_ON_BEHALF()", - "VL_NO_STABLE_RATE_LOAN_IN_RESERVE()", - "VL_NO_VARIABLE_RATE_LOAN_IN_RESERVE()", - "VL_RESERVE_FROZEN()", - "VL_STABLE_BORROWING_NOT_ENABLED()", - "VL_TRANSFER_NOT_ALLOWED()", - "VL_UNDERLYING_BALANCE_NOT_GREATER_THAN_0()", - "ATOKEN_REVISION()", - "DOMAIN_SEPARATOR()", - "EIP712_REVISION()", - "PERMIT_TYPEHASH()", - "POOL()", - "RESERVE_TREASURY_ADDRESS()", - "UINT_MAX_VALUE()", - "UNDERLYING_ASSET_ADDRESS()", - "_nonces(address)", - "decreaseAllowance(address,uint256)", - "increaseAllowance(address,uint256)", - "initialize(uint8,string,string)", - "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)", - "description()", - "getRoundData(uint80)", - "latestRoundData()", - "version()", - "DEFAULT_ADMIN_ROLE()", - "getRoleAdmin(bytes32)", - "grantRole(bytes32,address)", - "hasRole(bytes32,address)", - "renounceRole(bytes32,address)", - "revokeRole(bytes32,address)", - "supportsInterface(bytes4)", - "owner()", - "renounceOwnership()", - "transferOwnership(address)", - "delegate(address)", - "delegateBySig(address,uint256,uint256,uint8,bytes32,bytes32)", - "delegates(address)", - "getPastTotalSupply(uint256)", - "getPastVotes(address,uint256)", - "getVotes(address)", - "isValidSignature(bytes32,bytes)", - "proxiableUUID()", - "implementation()", - "upgradeTo(address)", - "upgradeToAndCall(address,bytes)", - "nonces(address)", - "BALLOT_TYPEHASH()", - "COUNTING_MODE()", - "EXTENDED_BALLOT_TYPEHASH()", - "castVote(uint256,uint8)", - "castVoteBySig(uint256,uint8,uint8,bytes32,bytes32)", - "castVoteWithReason(uint256,uint8,string)", - "castVoteWithReasonAndParams(uint256,uint8,string,bytes)", - "castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32)", - "execute(address[],uint256[],bytes[],bytes32)", - "getVotes(address,uint256)", - "getVotesWithParams(address,uint256,bytes)", - "hasVoted(uint256,address)", - "hashProposal(address[],uint256[],bytes[],bytes32)", - "onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)", - "onERC1155Received(address,address,uint256,uint256,bytes)", - "onERC721Received(address,address,uint256,bytes)", - "proposalDeadline(uint256)", - "proposalSnapshot(uint256)", - "proposalThreshold()", - "propose(address[],uint256[],bytes[],string)", - "quorum(uint256)", - "relay(address,uint256,bytes)", - "state(uint256)", - "votingDelay()", - "votingPeriod()", - "CANCELLER_ROLE()", - "EXECUTOR_ROLE()", - "PROPOSER_ROLE()", - "TIMELOCK_ADMIN_ROLE()", - "cancel(bytes32)", - "execute(address,uint256,bytes,bytes32,bytes32)", - "executeBatch(address[],uint256[],bytes[],bytes32,bytes32)", - "getMinDelay()", - "getTimestamp(bytes32)", - "hashOperation(address,uint256,bytes,bytes32,bytes32)", - "hashOperationBatch(address[],uint256[],bytes[],bytes32,bytes32)", - "isOperation(bytes32)", - "isOperationDone(bytes32)", - "isOperationPending(bytes32)", - "isOperationReady(bytes32)", - "schedule(address,uint256,bytes,bytes32,bytes32,uint256)", - "scheduleBatch(address[],uint256[],bytes[],bytes32,bytes32,uint256)", - "updateDelay(uint256)", - "proposalVotes(uint256)", - "setProposalThreshold(uint256)", - "setVotingDelay(uint256)", - "setVotingPeriod(uint256)", - "proposalEta(uint256)", - "queue(address[],uint256[],bytes[],bytes32)", - "timelock()", - "updateTimelock(address)", - "token()", - "quorumDenominator()", - "quorumNumerator(uint256)", - "quorumNumerator()", - "updateQuorumNumerator(uint256)", - "admin()", - "changeAdmin(address)", - "multicall(bytes[])", - "ENS()", - "deployments(string)", - "latestDeployment()", - "register(string,address,bool)", - "unregister(string)", - "canRunRecollateralizationAuctions(address)", - "claimRewards(address)", - "getActCalldata(address)", - "getRevenueAuctionERC20s(address)", - "runRevenueAuctions(address,address[],address[])", - "getTradesForBackingManager(address)", - "getTradesForRevenueTraders(address)", - "auctionsSettleable(address)", - "backingOverview(address)", - "backupConfig(address,bytes32)", - "basketBreakdown(address)", - "basketTokens(address)", - "issue(address,uint256)", - "maxIssuable(address,address)", - "pendingUnstakings(address,address)", - "price(address)", - "primeBasket(address)", - "redeem(address,uint256,uint48)", - "stToken(address)", - "traderBalances(address,address)", - "runAuctionsForAllTraders(address)", - "totalAssetValue(address)", - "wholeBasketsHeldBy(address,address)", - "deployRToken((string,string,string,((uint16,uint16),uint192,uint192,uint48,uint48,uint192,uint48,uint48,uint48,uint192,uint192,(uint256,uint192),(uint256,uint192))),(address[],address[],uint192[],(bytes32,uint256,address[])[],(address,(uint16,uint16))[]))", - "deployer()", - "setupGovernance(address,bool,bool,(uint256,uint256,uint256,uint256,uint256),address,address,address)", - "bal(address)", - "claimRewards()", - "erc20()", - "erc20Decimals()", - "isCollateral()", - "lotPrice()", - "maxTradeVolume()", - "price()", - "refresh()", - "refPerTok()", - "status()", - "targetName()", - "targetPerRef()", - "chainlinkFeed()", - "oracleError()", - "oracleTimeout()", - "priceTimeout()", - "delayUntilDefault()", - "whenDefault()", - "erc20s()", - "getRegistry()", - "init(address,address[])", - "isRegistered(address)", - "main()", - "register(address)", - "swapRegistered(address)", - "toAsset(address)", - "toColl(address)", - "unregister(address)", - "claimRewardsSingle(address)", - "grantRTokenAllowance(address)", - "init(address,uint48,uint192,uint192,uint192)", - "manageTokens(address[])", - "manageTokensSortedOrder(address[])", - "maxTradeSlippage()", - "minTradeVolume()", - "mulDivCeil(uint192,uint192,uint192)", - "settleTrade(address)", - "trades(address)", - "tradesOpen()", - "backingBuffer()", - "setBackingBuffer(uint192)", - "setMaxTradeSlippage(uint192)", - "setMinTradeVolume(uint192)", - "setTradingDelay(uint48)", - "tradingDelay()", - "basketsHeldBy(address)", - "disableBasket()", - "fullyCollateralized()", - "init(address)", - "nonce()", - "quantity(address)", - "quantityUnsafe(address,address)", - "quote(uint192,uint8)", - "refreshBasket()", - "setBackupConfig(bytes32,uint256,address[])", - "setPrimeBasket(address[],uint192[])", - "timestamp()", - "disabled()", - "init(address,address,address,uint48)", - "openTrade((address,address,uint256,uint256))", - "reportViolation()", - "auctionLength()", - "gnosis()", - "setAuctionLength(uint48)", - "setDisabled(bool)", - "setGnosis(address)", - "setTradeImplementation(address)", - "tradeImplementation()", - "deploy(string,string,string,address,((uint16,uint16),uint192,uint192,uint48,uint48,uint192,uint48,uint48,uint48,uint192,uint192,(uint256,uint192),(uint256,uint192)))", - "rsr()", - "rsrAsset()", - "distribute(address,uint256)", - "init(address,(uint16,uint16))", - "setDistribution(address,(uint16,uint16))", - "totals()", - "FURNACE()", - "ST_RSR()", - "distribution(address)", - "init(address,uint192)", - "melt()", - "ratio()", - "setRatio(uint192)", - "lastPayout()", - "lastPayoutBal()", - "auctionData(uint256)", - "feeNumerator()", - "initiateAuction(address,address,uint256,uint256,uint96,uint96,uint256,uint256,bool,address,bytes)", - "settleAuction(uint256)", - "freezeForever()", - "freezeLong()", - "freezeShort()", - "frozen()", - "longFreeze()", - "pause()", - "pausedOrFrozen()", - "shortFreeze()", - "unfreeze()", - "unpause()", - "assetRegistry()", - "backingManager()", - "basketHandler()", - "broker()", - "distributor()", - "furnace()", - "rToken()", - "rTokenTrader()", - "rsrTrader()", - "stRSR()", - "init((address,address,address,address,address,address,address,address,address,address),address,uint48,uint48)", - "poke()", - "longFreezes(address)", - "setLongFreeze(uint48)", - "setShortFreeze(uint48)", - "basketsNeeded()", - "init(address,string,string,string,(uint256,uint192),(uint256,uint192))", - "issuanceAvailable()", - "issue(uint256)", - "issueTo(address,uint256)", - "melt(uint256)", - "mint(address,uint256)", - "redeem(uint256,uint48)", - "redeemTo(address,uint256,uint48)", - "redemptionAvailable()", - "setBasketsNeeded(uint192)", - "issuanceThrottleParams()", - "monetizeDonations(address)", - "redemptionThrottleParams()", - "setIssuanceThrottleParams((uint256,uint192))", - "setRedemptionThrottleParams((uint256,uint192))", - "init(address,address,uint192,uint192)", - "manageToken(address)", - "tokenToBuy()", - "endIdForWithdraw(address)", - "exchangeRate()", - "init(address,string,string,uint48,uint192)", - "payoutRewards()", - "seizeRSR(uint256)", - "stake(uint256)", - "unstake(uint256)", - "withdraw(address,uint256)", - "rewardRatio()", - "setRewardRatio(uint192)", - "setUnstakingDelay(uint48)", - "unstakingDelay()", - "currentEra()", - "getPastEra(uint256)", - "buy()", - "canSettle()", - "endTime()", - "sell()", - "settle()", - "allUnique(address[])", - "sortedAndAllUnique(address[])", - "abs_(int256)", - "div(uint192,uint192)", - "divFix_(uint256,uint192)", - "divRnd(uint192,uint192,uint8)", - "divrnd_(uint256,uint256,uint8)", - "divu(uint192,uint256)", - "divuRnd(uint192,uint256,uint8)", - "divuu_(uint256,uint256)", - "eq(uint192,uint192)", - "fixMax_(uint192,uint192)", - "fixMin_(uint192,uint192)", - "fullMul_(uint256,uint256)", - "gt(uint192,uint192)", - "gte(uint192,uint192)", - "lt(uint192,uint192)", - "lte(uint192,uint192)", - "minus(uint192,uint192)", - "minusu(uint192,uint256)", - "mul(uint192,uint192)", - "mulDiv(uint192,uint192,uint192)", - "mulDiv256Rnd_(uint256,uint256,uint256,uint8)", - "mulDiv256_(uint256,uint256,uint256)", - "mulDivRnd(uint192,uint192,uint192,uint8)", - "mulRnd(uint192,uint192,uint8)", - "mul_toUint(uint192,uint192)", - "mul_toUintRnd(uint192,uint192,uint8)", - "mulu(uint192,uint256)", - "muluDivu(uint192,uint256,uint256)", - "muluDivuRnd(uint192,uint256,uint256,uint8)", - "mulu_toUint(uint192,uint256)", - "mulu_toUintRnd(uint192,uint256,uint8)", - "near(uint192,uint192,uint192)", - "neq(uint192,uint192)", - "plus(uint192,uint192)", - "plusu(uint192,uint256)", - "powu(uint192,uint48)", - "safeMul_(uint192,uint192,uint8)", - "shiftl(uint192,int8)", - "shiftlRnd(uint192,int8,uint8)", - "shiftl_toFix_(uint256,int8)", - "shiftl_toFix_Rnd(uint256,int8,uint8)", - "shiftl_toUint(uint192,int8)", - "shiftl_toUintRnd(uint192,int8,uint8)", - "toFix_(uint256)", - "toUint(uint192)", - "toUintRnd(uint192,uint8)", - "toLower(string)", - "LONG_FREEZER_ROLE()", - "OWNER_ROLE()", - "PAUSER_ROLE()", - "SHORT_FREEZER_ROLE()", - "unfreezeAt()", - "GAS_TO_RESERVE()", - "MAX_BACKING_BUFFER()", - "MAX_TRADE_SLIPPAGE()", - "MAX_TRADE_VOLUME()", - "MAX_TRADING_DELAY()", - "MAX_TARGET_AMT()", - "MAX_AUCTION_LENGTH()", - "MIN_BID_SHARE_OF_TOTAL_SUPPLY()", - "MAX_DESTINATIONS_ALLOWED()", - "MAX_RATIO()", - "PERIOD()", - "MAX_EXCHANGE_RATE()", - "MAX_THROTTLE_PCT_AMT()", - "MAX_THROTTLE_RATE_AMT()", - "MIN_EXCHANGE_RATE()", - "MIN_THROTTLE_RATE_AMT()", - "mandate()", - "MAX_REWARD_RATIO()", - "MAX_UNSTAKING_DELAY()", - "MIN_UNSTAKING_DELAY()", - "withdrawals(address,uint256)", - "prepareRecollateralizationTrade(IBackingManager,(uint192,uint192))", - "getBackupConfig(bytes32)", - "getPrimeBasket()", - "implementations()", - "delegationNonces(address)", - "draftQueueLen(uint256,address)", - "draftQueues(uint256,address,uint256)", - "draftRate()", - "firstRemainingDraft(uint256,address)", - "getDraftRSR()", - "getStakeRSR()", - "getTotalDrafts()", - "payoutLastPaid()", - "stakeRate()", - "checkpoints(address,uint48)", - "numCheckpoints(address)", - "exposedReferencePrice()", - "lastSave()", - "pegBottom()", - "pegTop()", - "revenueShowing()", - "savedHighPrice()", - "savedLowPrice()", - "tryPrice()", - "targetUnitChainlinkFeed()", - "targetUnitOracleTimeout()", - "REWARD_TOKEN()", - "claimRewardsToSelf(bool)", - "rate()", - "getIncentivesController()", - "handleRepayment(address,uint256)", - "PRECISION()", - "claimRewards(address[],uint256,address)", - "claimRewardsOnBehalf(address[],uint256,address,address)", - "configureAssets(address[],uint256[])", - "getAssetData(address)", - "getClaimer(address)", - "getRewardsBalance(address[],address)", - "getUserAssetData(address,address)", - "getUserUnclaimedRewards(address)", - "setClaimer(address,address)", - "ASSET()", - "ATOKEN()", - "INCENTIVES_CONTROLLER()", - "LENDING_POOL()", - "claimRewards(address,bool)", - "claimRewardsOnBehalf(address,address,bool)", - "collectAndUpdateRewards()", - "deposit(address,uint256,uint16,bool)", - "dynamicBalanceOf(address)", - "dynamicToStaticAmount(uint256)", - "getAccRewardsPerToken()", - "getClaimableRewards(address)", - "getDomainSeparator()", - "getLastRewardBlock()", - "getLifetimeRewards()", - "getLifetimeRewardsClaimed()", - "getTotalClaimableRewards()", - "getUnclaimedRewards(address)", - "metaDeposit(address,address,uint256,uint16,bool,uint256,(uint8,bytes32,bytes32))", - "metaWithdraw(address,address,uint256,uint256,bool,uint256,(uint8,bytes32,bytes32))", - "staticToDynamicAmount(uint256)", - "withdraw(address,uint256,bool)", - "withdrawDynamicAmount(address,uint256,bool)", - "INVALID_CLAIMER()", - "INVALID_DEPOSITOR()", - "INVALID_EXPIRATION()", - "INVALID_OWNER()", - "INVALID_RECIPIENT()", - "INVALID_SIGNATURE()", - "ONLY_ONE_AMOUNT_FORMAT_ALLOWED()", - "METADEPOSIT_TYPEHASH()", - "METAWITHDRAWAL_TYPEHASH()", - "STATIC_ATOKEN_LM_REVISION()", - "updateRatio(uint256)", - "comptroller()", - "referenceERC20Decimals()", - "exchangeRateCurrent()", - "exchangeRateStored()", - "mint(uint256)", - "redeem(uint256)", - "underlying()", - "claimComp(address)", - "getCompAddress()", - "comet()", - "reservesThresholdIffy()", - "rewardERC20()", - "BASE_SCALE()", - "EXP_SCALE()", - "RESCALE_FACTOR()", - "TRACKING_INDEX_SCALE()", - "accrue()", - "accrueAccount(address)", - "allow(address,bool)", - "baseTrackingAccrued(address)", - "baseTrackingIndex(address)", - "claimTo(address,address)", - "convertDynamicToStatic(uint256)", - "convertStaticToDynamic(uint104)", - "deposit(uint256)", - "depositFrom(address,address,uint256)", - "depositTo(address,uint256)", - "getRewardOwed(address)", - "hasPermission(address,address)", - "isAllowed(address,address)", - "rewardsAddr()", - "rewardsClaimed(address)", - "underlyingBalanceOf(address)", - "underlyingComet()", - "withdraw(uint256)", - "withdrawFrom(address,address,uint256)", - "withdrawTo(address,uint256)", - "allowBySig(address,address,bool,uint256,uint256,uint8,bytes32,bytes32)", - "baseAccrualScale()", - "baseIndexScale()", - "collateralBalanceOf(address,address)", - "factorScale()", - "maxAssets()", - "priceScale()", - "totalsBasic()", - "absorb(address,address[])", - "approveThis(address,address,uint256)", - "baseBorrowMin()", - "baseMinForRewards()", - "baseScale()", - "baseToken()", - "baseTokenPriceFeed()", - "baseTrackingBorrowSpeed()", - "baseTrackingSupplySpeed()", - "borrowBalanceOf(address)", - "borrowPerSecondInterestRateBase()", - "borrowPerSecondInterestRateSlopeHigh()", - "borrowPerSecondInterestRateSlopeLow()", - "buyCollateral(address,uint256,uint256,address)", - "extensionDelegate()", - "getAssetInfo(uint8)", - "getAssetInfoByAddress(address)", - "getBorrowRate(uint256)", - "getPrice(address)", - "getReserves()", - "getSupplyRate(uint256)", - "getUtilization()", - "governor()", - "initializeStorage()", - "isAbsorbPaused()", - "isBorrowCollateralized(address)", - "isBuyPaused()", - "isLiquidatable(address)", - "isSupplyPaused()", - "isTransferPaused()", - "isWithdrawPaused()", - "numAssets()", - "pause(bool,bool,bool,bool,bool)", - "pauseGuardian()", - "quoteCollateral(address,uint256)", - "storeFrontPriceFactor()", - "supply(address,uint256)", - "supplyFrom(address,address,address,uint256)", - "supplyKink()", - "supplyPerSecondInterestRateBase()", - "supplyPerSecondInterestRateSlopeHigh()", - "supplyPerSecondInterestRateSlopeLow()", - "supplyTo(address,address,uint256)", - "targetReserves()", - "totalBorrow()", - "trackingIndexScale()", - "transferAsset(address,address,uint256)", - "transferAssetFrom(address,address,address,uint256)", - "userBasic(address)", - "withdrawFrom(address,address,address,uint256)", - "withdrawReserves(address,uint256)", - "withdrawTo(address,address,uint256)", - "setBaseTrackingSupplySpeed(address,uint64)", - "deploy(address)", - "deployAndUpgradeTo(address,address)", - "claim(address,address,bool)", - "claimTo(address,address,address,bool)", - "getRewardOwed(address,address)", - "rewardConfig(address)", - "curvePool()", - "lpToken()", - "tokenPrice(uint8)", - "metapoolToken()", - "pairedToken()", - "pairedTokenPegBottom()", - "pairedTokenPegTop()", - "tryPairedPrice()", - "balances(uint256)", - "base_coins(uint256)", - "coins(uint256)", - "exchange(int128,int128,uint256,uint256)", - "get_virtual_price()", - "underlying_coins(uint256)", - "deposit(uint256,bool)", - "lockIncentive()", - "claim_rewards()", - "lp_token()", - "reward_tokens(uint256)", - "rewarded_token()", - "create_lock(uint256,uint256)", - "increase_amount(uint256)", - "increase_unlock_time(uint256)", - "smart_wallet_checker()", - "withdraw()", - "claimRewards(uint256,address)", - "isShutdown()", - "poolInfo(uint256)", - "rewardArbitrator()", - "rewardClaimed(uint256,address,uint256)", - "setGaugeRedirect(uint256)", - "withdrawTo(uint256,uint256,address)", - "claim()", - "mint(address)", - "addPool(address,address,uint256)", - "forceAddPool(address,address,uint256)", - "gaugeMap(address)", - "poolLength()", - "setPoolManager(address)", - "shutdownPool(uint256)", - "gauge_controller()", - "get_address(uint256)", - "get_gauges(address)", - "get_lp_token(address)", - "get_registry()", - "CreateCrvRewards(uint256,address)", - "CreateTokenRewards(address,address,address)", - "activeRewardCount(address)", - "addActiveReward(address,uint256)", - "removeActiveReward(address,uint256)", - "setAccess(address,bool)", - "addExtraReward(address)", - "earned(address)", - "exit(address)", - "getReward(address)", - "notifyRewardAmount(uint256)", - "queueNewRewards(uint256)", - "rewardToken()", - "stake(address,uint256)", - "stakeFor(address,uint256)", - "stakingToken()", - "balanceOfPool(address)", - "claimCrv(address)", - "claimFees(address,address)", - "createLock(uint256,uint256)", - "deposit(address,address)", - "execute(address,uint256,bytes)", - "increaseAmount(uint256)", - "increaseTime(uint256)", - "operator()", - "release()", - "setStashAccess(address,bool)", - "vote(uint256,address,bool)", - "voteGaugeWeight(address,uint256)", - "withdraw(address)", - "withdraw(address,address,uint256)", - "withdrawAll(address,address)", - "initialize(uint256,address,address,address,address)", - "processStash()", - "stashRewards()", - "CreateStash(uint256,address,address,uint256)", - "CreateDepositToken(address)", - "burn(address,uint256)", - "fund(address[],uint256[])", - "getVote(uint256)", - "vote(uint256,bool,bool)", - "vote_for_gauge_weights(address,uint256)", - "check(address)", - "addRewards()", - "collateralVault()", - "convexBooster()", - "convexPool()", - "convexPoolId()", - "convexToken()", - "crv()", - "curveToken()", - "cvx()", - "deposit(uint256,address)", - "earnedView(address)", - "getReward(address,address)", - "initialize(uint256)", - "isInit()", - "registeredRewards(address)", - "rewardLength()", - "rewards(uint256)", - "setApprovals()", - "shutdown()", - "stake(uint256,address)", - "totalBalanceOf(address)", - "user_checkpoint(address)", - "withdrawAndUnwrap(uint256)", - "deposit(uint256,uint256,bool)", - "deposit(uint256,bool,address)", - "ConvertCrvToCvx(uint256)", - "maxSupply()", - "reductionPerCliff()", - "totalCliffs()", - "extraRewards(uint256)", - "extraRewardsLength()", - "getReward()", - "getReward(address,bool)", - "withdrawAndUnwrap(uint256,bool)", - "submit()", - "submitAndDeposit(address)", - "convertToAssets(uint256)", - "pricePerShare()", - "rewardsCycleEnd()", - "syncRewards()", - "getBeaconStat()", - "handleOracleReport(uint256,uint256)", - "stEthPerToken()", - "targetPerRefChainlinkFeed()", - "targetPerRefChainlinkTimeout()", - "getExchangeRate()", - "getTotalETHBalance()", - "setUint(bytes32,uint256)", - "refPerTokChainlinkFeed()", - "refPerTokChainlinkTimeout()", - "ONE_HUNDRED_PERCENT()", - "cancel(address[],uint256[],bytes[],bytes32)", - "adminApprove(address,address,uint256)", - "aaveBalances(address)", - "aaveToken()", - "setAaveToken(address)", - "setExchangeRate(uint192)", - "setRewards(address,uint256)", - "setNormalizedIncome(address,uint256)", - "WETH()", - "getAssetPrice(address)", - "checkHardDefault()", - "checkSoftDefault()", - "setHardDefaultCheck(bool)", - "setSoftDefaultCheck(bool)", - "censored(address)", - "revertDecimals()", - "setCensored(address,bool)", - "borrowKink()", - "withdraw(uint256,bool)", - "setRevertDecimals(bool)", - "setTransferFee(uint192)", - "transferFee()", - "getAnswer(uint256)", - "getTimestamp(uint256)", - "latestAnswer()", - "latestAnsweredRound()", - "latestRound()", - "latestTimestamp()", - "setInvalidAnsweredRound()", - "setInvalidTimestamp()", - "updateAnswer(int256)", - "updateRoundData(uint80,int256,uint256,uint256)", - "externalDelegate()", - "setReserves(int256)", - "compBalances(address)", - "compToken()", - "setCompToken(address)", - "setBalances(uint256[])", - "setVirtualPrice(uint256)", - "setMockExchangeRate(bool,uint256)", - "hasAccess(address,bytes)", - "acceptOwnership()", - "aggregator()", - "confirmAggregator(address)", - "phaseAggregators(uint16)", - "phaseId()", - "proposeAggregator(address)", - "proposedAggregator()", - "proposedGetRoundData(uint80)", - "proposedLatestRoundData()", - "accessController()", - "setController(address)", - "__getAnswer(uint256)", - "__getTimestamp(uint256)", - "__latestAnswer()", - "__latestAnsweredRound()", - "__latestRound()", - "__latestTimestamp()", - "counter()", - "approvalsOn()", - "disableApprovals()", - "enableApprovals()", - "FEE_DENOMINATOR()", - "auctionAccessData(uint256)", - "auctionAccessManager(uint256)", - "auctionCounter()", - "cancelSellOrders(uint256,bytes32[])", - "claimFromParticipantOrder(uint256,bytes32[])", - "containsOrder(uint256,bytes32)", - "feeReceiverUserId()", - "getSecondsRemainingInBatch(uint256)", - "getUserId(address)", - "numUsers()", - "placeSellOrders(uint256,uint96[],uint96[],bytes32[],bytes)", - "placeSellOrdersOnBehalf(uint256,uint96[],uint96[],bytes32[],bytes,address)", - "precalculateSellAmountSum(uint256,uint256)", - "registerUser(address)", - "setFeeParameters(uint256,address)", - "settleAuctionAtomically(uint256,uint96[],uint96[],bytes32[],bytes)", - "infiniteLoop()", - "revertRefPerTok()", - "setRevertRefPerTok(bool)", - "auctions(uint256)", - "bids(uint256)", - "numAuctions()", - "placeBid(uint256,(address,uint256,uint256))", - "reenterOnInit()", - "reenterOnSettle()", - "setReenterOnInit(bool)", - "setReenterOnSettle(bool)", - "setSimplyRevert(bool)", - "simplyRevert()", - "rateMock()", - "refPerTokRevert()", - "setRate(uint192)", - "setRefPerTokRevert(bool)", - "setTargetPerRef(uint192)", - "priceable()", - "destroyAndTransfer(address)", - "setPricePerShare(uint256)", - "mockRefPerTok()", - "setUnpriced(bool)", - "unpriced()", - "deposit()", - "newValue()", - "setNewValue(uint256)", - "isAllowed(address,uint256,bytes)", - "contains(bytes32)", - "decodeOrder(bytes32)", - "encodeOrder(uint64,uint96,uint96)", - "first()", - "initializeEmptyList()", - "insert(bytes32)", - "insertAt(bytes32,bytes32)", - "isEmpty()", - "next(bytes32)", - "nextMap(bytes32)", - "prevMap(bytes32)", - "remove(bytes32)", - "removeKeepHistory(bytes32)", - "smallerThan(bytes32,bytes32)", - "DEFAULT_MIN_BID()", - "MAX_ORDERS()", - "auctionId()", - "init(address,address,address,uint48,(address,address,uint256,uint256))", - "initBal()", - "origin()", - "transferToOriginAfterTradeComplete(address)", - "worstCasePrice()" - ], - "events": [ - "Approval(address,address,uint256)", - "Transfer(address,address,uint256)", - "BalanceTransfer(address,address,uint256,uint256)", - "Burn(address,address,uint256,uint256)", - "Mint(address,uint256,uint256)", - "Borrow(address,address,address,uint256,uint256,uint256,uint16)", - "Deposit(address,address,address,uint256,uint16)", - "FlashLoan(address,address,address,uint256,uint256,uint16)", - "LiquidationCall(address,address,address,uint256,uint256,address,bool)", - "Paused()", - "RebalanceStableBorrowRate(address,address)", - "Repay(address,address,address,uint256)", - "ReserveDataUpdated(address,uint256,uint256,uint256,uint256,uint256)", - "ReserveUsedAsCollateralDisabled(address,address)", - "ReserveUsedAsCollateralEnabled(address,address)", - "Swap(address,address,uint256)", - "Unpaused()", - "Withdraw(address,address,address,uint256)", - "AddressSet(bytes32,address,bool)", - "ConfigurationAdminUpdated(address)", - "EmergencyAdminUpdated(address)", - "LendingPoolCollateralManagerUpdated(address)", - "LendingPoolConfiguratorUpdated(address)", - "LendingPoolUpdated(address)", - "LendingRateOracleUpdated(address)", - "MarketIdSet(string)", - "PriceOracleUpdated(address)", - "ProxyCreated(bytes32,address)", - "Initialized(uint8)", - "RoleAdminChanged(bytes32,bytes32,bytes32)", - "RoleGranted(bytes32,address,address)", - "RoleRevoked(bytes32,address,address)", - "OwnershipTransferred(address,address)", - "DelegateChanged(address,address,address)", - "DelegateVotesChanged(address,uint256,uint256)", - "AdminChanged(address,address)", - "BeaconUpgraded(address)", - "Upgraded(address)", - "ProposalCanceled(uint256)", - "ProposalCreated(uint256,address,address[],uint256[],string[],bytes[],uint256,uint256,string)", - "ProposalExecuted(uint256)", - "VoteCast(address,uint256,uint8,uint256,string)", - "VoteCastWithParams(address,uint256,uint8,uint256,string,bytes)", - "CallExecuted(bytes32,uint256,address,uint256,bytes)", - "CallScheduled(bytes32,uint256,address,uint256,bytes,bytes32,uint256)", - "Cancelled(bytes32)", - "MinDelayChange(uint256,uint256)", - "ProposalThresholdSet(uint256,uint256)", - "VotingDelaySet(uint256,uint256)", - "VotingPeriodSet(uint256,uint256)", - "ProposalQueued(uint256,uint256)", - "TimelockChange(address,address)", - "QuorumNumeratorUpdated(uint256,uint256)", - "DeploymentRegistered(string,address)", - "DeploymentUnregistered(string,address)", - "LatestChanged(string,address)", - "GovernanceCreated(address,address,address)", - "RewardsClaimed(address,uint256)", - "CollateralStatusChanged(uint8,uint8)", - "AssetRegistered(address,address)", - "AssetUnregistered(address,address)", - "BackingBufferSet(uint192,uint192)", - "MaxTradeSlippageSet(uint192,uint192)", - "MinTradeVolumeSet(uint192,uint192)", - "TradeSettled(address,address,address,uint256,uint256)", - "TradeStarted(address,address,address,uint256,uint256)", - "TradingDelaySet(uint48,uint48)", - "BackupConfigSet(bytes32,uint256,address[])", - "BasketSet(uint256,address[],uint192[],bool)", - "PrimeBasketSet(address[],uint192[],bytes32[])", - "AuctionLengthSet(uint48,uint48)", - "DisabledSet(bool,bool)", - "GnosisSet(address,address)", - "TradeImplementationSet(address,address)", - "RTokenCreated(address,address,address,address,string)", - "DistributionSet(address,uint16,uint16)", - "RevenueDistributed(address,address,uint256)", - "RatioSet(uint192,uint192)", - "LongFreezeDurationSet(uint48,uint48)", - "PausedSet(bool,bool)", - "ShortFreezeDurationSet(uint48,uint48)", - "UnfreezeAtSet(uint48,uint48)", - "AssetRegistrySet(address,address)", - "BackingManagerSet(address,address)", - "BasketHandlerSet(address,address)", - "BrokerSet(address,address)", - "DistributorSet(address,address)", - "FurnaceSet(address,address)", - "RSRTraderSet(address,address)", - "RTokenSet(address,address)", - "RTokenTraderSet(address,address)", - "StRSRSet(address,address)", - "MainInitialized()", - "BasketsNeededChanged(uint192,uint192)", - "Issuance(address,address,uint256,uint192)", - "IssuanceThrottleSet((uint256,uint192),(uint256,uint192))", - "Melted(uint256)", - "Redemption(address,address,uint256,uint192)", - "RedemptionThrottleSet((uint256,uint192),(uint256,uint192))", - "AllBalancesReset(uint256)", - "AllUnstakingReset(uint256)", - "ExchangeRateSet(uint192,uint192)", - "RewardRatioSet(uint192,uint192)", - "RewardsPaid(uint256)", - "Staked(uint256,address,uint256,uint256)", - "UnstakingCompleted(uint256,uint256,uint256,address,uint256)", - "UnstakingDelaySet(uint48,uint48)", - "UnstakingStarted(uint256,uint256,address,uint256,uint256,uint256)", - "ClaimerSet(address,address)", - "RewardsAccrued(address,uint256)", - "RewardsClaimed(address,address,uint256)", - "RewardsClaimed(address,address,address,uint256)", - "RewardClaimed(address,address,address,uint256)", - "AbsorbCollateral(address,address,address,uint256,uint256)", - "AbsorbDebt(address,address,uint256,uint256)", - "BuyCollateral(address,address,uint256,uint256)", - "PauseAction(bool,bool,bool,bool,bool)", - "Supply(address,address,uint256)", - "SupplyCollateral(address,address,address,uint256)", - "TransferCollateral(address,address,address,uint256)", - "Withdraw(address,address,uint256)", - "WithdrawCollateral(address,address,address,uint256)", - "WithdrawReserves(address,uint256)", - "Deposited(address,address,uint256,bool)", - "Withdrawn(address,uint256,bool)", - "AnswerUpdated(int256,uint256,uint256)", - "NewRound(uint256,address,uint256)", - "OwnershipTransferRequested(address,address)", - "AuctionCleared(uint256,uint96,uint96,bytes32)", - "CancellationSellOrder(uint256,uint64,uint96,uint96)", - "ClaimedFromOrder(uint256,uint64,uint96,uint96)", - "NewAuction(uint256,address,address,uint256,uint256,uint64,uint96,uint96,uint256,uint256,address,bytes)", - "NewSellOrder(uint256,uint64,uint96,uint96)", - "NewUser(uint64,address)", - "UserRegistration(address,uint64)", - "Deposit(address,uint256)", - "Withdrawal(address,uint256)", - "Empty()", - "OutOfBounds()", - "UIntOutOfBounds()", - "StalePrice()", - "InvalidInt256()", - "InvalidUInt104()", - "InvalidUInt64()", - "NegativeNumber()", - "BadAmount()", - "ExceedsBalance(uint256)", - "Unauthorized()", - "ZeroAddress()", - "BadNonce()", - "BadSignatory()", - "InvalidValueS()", - "InvalidValueV()", - "SignatureExpired()", - "Absurd()", - "AlreadyInitialized()", - "BadAsset()", - "BadDecimals()", - "BadDiscount()", - "BadMinimum()", - "BadPrice()", - "BorrowCFTooLarge()", - "BorrowTooSmall()", - "InsufficientReserves()", - "LiquidateCFTooLarge()", - "NoSelfTransfer()", - "NotCollateralized()", - "NotForSale()", - "NotLiquidatable()", - "SupplyCapExceeded()", - "TimestampTooLarge()", - "TooManyAssets()", - "TooMuchSlippage()", - "TransferInFailed()", - "TransferOutFailed()", - "NoToken(uint8)", - "WrongIndex(uint8)" - ] -} diff --git a/scripts/4bytes.ts b/scripts/4bytes.ts deleted file mode 100644 index e0bdc74421..0000000000 --- a/scripts/4bytes.ts +++ /dev/null @@ -1,105 +0,0 @@ -import hre from 'hardhat' -import fs from 'fs' -import fetch from 'isomorphic-fetch' -import previousSync from './4bytes-syncced.json' -/** - * This script will sync any event and function we have with www.4byte.directory - * The script saves all processed signatures with 4bytes-syncced.json as it succcesses - * this way we avoid syncing the same signature twice. - * */ - -const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) - -async function main() { - const artifacts = await hre.artifacts.getAllFullyQualifiedNames() - const artifactsWithAbi = ( - await Promise.all(artifacts.map((name) => hre.artifacts.readArtifact(name))) - ).filter((artifact) => artifact.abi.length !== 0) - const prevFunctions = new Set(previousSync.functions) - const prevEvents = new Set(previousSync.events) - const newErrorSignatures = new Set() - const newFunctionSignatures = new Set() - const newEventSignatures = new Set() - for (const { abi } of artifactsWithAbi) { - const abiInterface = new hre.ethers.utils.Interface(abi) - // Events and Errors seem to be the same thing for 4bytes - Object.keys(abiInterface.events) - .filter((e) => !prevEvents.has(e)) - .forEach((e) => newEventSignatures.add(e)) - Object.keys(abiInterface.errors) - .filter((e) => !prevEvents.has(e)) - .forEach((e) => newEventSignatures.add(e)) - - Object.keys(abiInterface.functions) - .filter((e) => !prevFunctions.has(e)) - .forEach((e) => newFunctionSignatures.add(e)) - } - const total = newErrorSignatures.size + newFunctionSignatures.size + newEventSignatures.size - if (total === 0) { - console.log('All up to date!') - return - } - - console.log('Will sync ' + total + ' signatures with 4bytes...') - - const save = () => { - fs.writeFileSync('./scripts/4bytes-syncced.json', JSON.stringify(previousSync, null, 2)) - } - console.log('----- Synccing functions ----- ') - for (const sig of newFunctionSignatures) { - for (let i = 0; i < 3; i++) { - const resp = await fetch('https://www.4byte.directory/api/v1/signatures/', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - text_signature: sig, - }), - }) - if (resp.status === 400 || resp.status === 201) { - console.log('function', sig, resp.status, await resp.text()) - previousSync.functions.push(sig) - save() - break - } - if (i === 2) { - console.log('Failed to sync function', sig, 'after 3 attempts') - } else { - await sleep(1000) - } - } - } - console.log('----- Synccing events ----- ') - for (const sig of newEventSignatures) { - for (let i = 0; i < 3; i++) { - const resp = await fetch('https://www.4byte.directory/api/v1/event-signatures/', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - text_signature: sig, - }), - }) - if (resp.status === 400 || resp.status === 201) { - console.log('event', sig, resp.status, await resp.text()) - previousSync.events.push(sig) - save() - break - } - - if (i === 2) { - console.log('Failed to sync event', sig, 'after 3 attempts') - } else { - await sleep(1000) - } - } - } - console.log('Done!') -} - -main().catch((error) => { - console.error(error) - process.exitCode = 1 -}) diff --git a/scripts/addresses/3-tmp-assets-collateral.json b/scripts/addresses/3-tmp-assets-collateral.json new file mode 100644 index 0000000000..a462576f94 --- /dev/null +++ b/scripts/addresses/3-tmp-assets-collateral.json @@ -0,0 +1,85 @@ +{ + "assets": { + "stkAAVE": "0x72BA23683CBc1a3Fa5b3129b1335327A32c2CE8C", + "COMP": "0x70c635Bf4972259F2358Db5e431DB9592a2745a2", + "CRV": "0x66a3b432F77123E418cDbeD35fBaDdB0Eb9576B0", + "CVX": "0x0F53Aba2a7354C86B64dcaEe0ab9BF852846bAa5" + }, + "collateral": { + "DAI": "0x97C75046CE7Ea5253d20A35B3138699865E8813f", + "USDC": "0x256b89658bD831CC40283F42e85B1fa8973Db0c9", + "USDT": "0x743063E627d375f0A21bB92D07598Edc7D6F3a2d", + "USDP": "0x22d28452B506eFD909E9FC1d446a27061C4BDA7B", + "TUSD": "0xAd76B12aeEe90B745F0C62110cf1E261Fc5a06bb", + "BUSD": "0xe639d53Aa860757D7fe9cD4ebF9C8b92b8DedE7D", + "aDAI": "0x80A574cC2B369dc496af6655f57a16a4f180BfAF", + "aUSDC": "0x3043be171e846c33D5f06864Cc045d9Fc799aF52", + "aUSDT": "0xf579F9885f1AEa0d3F8bE0F18AfED28c92a43022", + "aBUSD": "0x4Be33630F92661afD646081BC29079A38b879aA0", + "aUSDP": "0xF69c995129CC16d0F577C303091a400cC1879fFa", + "cDAI": "0xF2A309bc36A504c772B416a4950d5d0021219745", + "cUSDC": "0xbF6E8F64547Bdec55bc3FBb0664722465FCC2F0F", + "cUSDT": "0x87A959e0377C68A50b08a91ae5ab3aFA7F41ACA4", + "cUSDP": "0x11C9ca7a43B76a5d9604e7441EB41a49e2084723", + "cWBTC": "0xC1E16AD7844Da1AEFFa6c3932AD02b823DE12d3F", + "cETH": "0x3700b22C742980be9D22740933d4a041A64f7314", + "WBTC": "0x1FFA5955D64Ee32cB1BF7104167b81bb085b0c8d", + "WETH": "0x2837f952c1FD773B3Ce02631A90f95E4b9ce2cF7", + "EURT": "0x5c83CA710E72D130E3B74aEC5b739676ef5737c2", + "wstETH": "0xE1fcCf8e23713Ed0497ED1a0E6Ae2b19ED443eCd", + "rETH": "0x55590a1Bf90fbf7352A46c4af652A231AA5CbF13", + "fUSDC": "0x7e1e077b289c0153b5ceAD9F264d66215341c9Ab", + "fUSDT": "0xaBd7E7a5C846eD497681a590feBED99e7157B6a3", + "fDAI": "0x3C8cD9FCa9925780598eB097D9718dF3da482C2F", + "fFRAX": "0x890FAa00C16EAD6AA76F18A1A7fe9C40838F9122", + "cUSDCv3": "0xc9291eF2f81dBc9B412381aBe83b28954220565E", + "cvx3Pool": "0xdEBe74dc2A415e00bE8B4b9d1e6e0007153D006a", + "cvxeUSDFRAXBP": "0x0240E29Be6cBbB178543fF27EA4AaC8F8b870b44", + "cvxMIM3Pool": "0xe8461dB45A7430AA7aB40346E68821284980FdFD", + "crveUSDFRAXBP": "0xa9F0eca90B5d4f213f8119834E0920785bb70F46", + "crvMIM3Pool": "0xaA91d24c2F7DBb6487f61869cD8cd8aFd5c5Cab2", + "sDAI": "0xE2b16e14dB6216e33082D5A8Be1Ef01DF7511bBb", + "cbETH": "0x291ed25eB61fcc074156eE79c5Da87e5DA94198F" + }, + "erc20s": { + "stkAAVE": "0x4da27a545c0c5B758a6BA100e3a049001de870f5", + "COMP": "0xc00e94Cb662C3520282E6f5717214004A7f26888", + "CRV": "0xD533a949740bb3306d119CC777fa900bA034cd52", + "CVX": "0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B", + "DAI": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "USDC": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "USDT": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "USDP": "0x8E870D67F660D95d5be530380D0eC0bd388289E1", + "TUSD": "0x0000000000085d4780B73119b644AE5ecd22b376", + "BUSD": "0x4Fabb145d64652a948d72533023f6E7A623C7C53", + "aDAI": "0x2a00A95Dc311EB96fEf922B8418c998f86D2c464", + "aUSDC": "0x9E8C96d86F1c85BC43200B8093159cf47e0CB921", + "aUSDT": "0x6356F6876D781795660cD2F6410b5a78636Df5C1", + "aBUSD": "0xfa21CD6EEde080Fdb1C79c1bdBC0C593c8CD08A5", + "aUSDP": "0x6446189FD250D96517C119DD9929c24bF825fb4e", + "cDAI": "0x22018D85BFdA9e2673FB4101e957562a1e952Cdf", + "cUSDC": "0x1142Ad5E5A082077A7d79d211726c1bd39b0D5FA", + "cUSDT": "0x35E6756B92daf6aE2CF2156d479e8a806898971B", + "cUSDP": "0x6B87142C7e6cA80aa3E6ead0351673C45c8990e3", + "cWBTC": "0xe352b0aE3114c57f56258F73277F825E643268d0", + "cETH": "0x0E6D6cBdA4629Fb2D82b4b4Af0D5c887f21F3BC7", + "WBTC": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "EURT": "0xC581b735A1688071A1746c968e0798D642EDE491", + "wstETH": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + "rETH": "0xae78736Cd615f374D3085123A210448E74Fc6393", + "fUSDC": "0xb3dCcEf35647A8821C76f796bE8B5426Cc953412", + "fUSDT": "0x7906238833Bb9e4Fec24a1735C94f47cb194f678", + "fDAI": "0x62C394620f674e85768a7618a6C202baE7fB8Dd1", + "fFRAX": "0x5A4f2FfC4aD066152B344Ceb2fc2275275b1a9C7", + "cUSDCv3": "0x9FF9c353136e86EFe02ADD177E7c9769f8a5A77F", + "cvx3Pool": "0xAdfB9BCdA981136c83076a52Ef8fE4D8B2b520e7", + "cvxeUSDFRAXBP": "0xFdb9F465C56933ab91f341C966DB517f975de5c1", + "cvxMIM3Pool": "0xC87CDFFD680D57BF50De4C364BF4277B8A90098E", + "crv3Pool": "0x1bc463270b8d3D797F59Fe639eDF5ae130f35FF3", + "crveUSDFRAXBP": "0xDe0e2F0c9792617D3908D92A024cAa846354CEa2", + "crvMIM3Pool": "0x9b2A9bAeB8F1930fC2AF9b7Fa473edF2B8c3B549", + "sDAI": "0x83f20f44975d03b1b09e64809b757c47f942beea", + "cbETH": "0xBe9895146f7AF43049ca1c1AE358B0541Ea49704" + } +} diff --git a/scripts/addresses/3-tmp-deployments.json b/scripts/addresses/3-tmp-deployments.json new file mode 100644 index 0000000000..e53597cd9f --- /dev/null +++ b/scripts/addresses/3-tmp-deployments.json @@ -0,0 +1,35 @@ +{ + "prerequisites": { + "RSR": "0x320623b8e4ff03373931769a31fc52a4e78b5d70", + "RSR_FEED": "0x759bBC1be8F90eE6457C44abc7d443842a976d02", + "GNOSIS_EASY_AUCTION": "0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101" + }, + "tradingLib": "0x1cCa3FBB11C4b734183f997679d52DeFA74b613A", + "cvxMiningLib": "0xC98eaFc9F249D90e3E35E729e3679DD75A899c10", + "facadeRead": "0x4024c00bBD0C420E719527D88781bc1543e63dd5", + "facadeAct": "0xBE9D23040fe22E8Bd8A88BF5101061557355cA04", + "facadeWriteLib": "0xf7d1C6eE4C0D84C6B530D53A897daa1E9eB56833", + "basketLib": "0xb6f01Aa21defA4a4DE33Bed16BcC06cfd23b6A6F", + "facadeWrite": "0xb3Be23A0cEFfd1814DC4F1FdcDc1200b39922bCc", + "deployer": "0xDeC1B73754449166cB270AC83F4b536e738b1351", + "rsrAsset": "0x45B950AF443281c5F67c2c7A1d9bBc325ECb8eEA", + "implementations": { + "main": "0x773cf50adCF1730964D4A9b664BaEd4b9FFC2450", + "trading": { + "gnosisTrade": "0x7edD40933DfdA0ecEe1ad3E61a5044962284e1A6", + "dutchTrade": "0x339c1509b980D80A0b50858518531eDbe2940dA1" + }, + "components": { + "assetRegistry": "0x81b9Ae0740CcA7cDc5211b2737de735FBC4BeB3C", + "backingManager": "0x1BD20253c49515D348dad1Af70ff2c0473FEa358", + "basketHandler": "0x0776Ad71Ae99D759354B3f06fe17454b94837B0D", + "broker": "0xDAacEE75C863a79f07699b094DB07793D3A52D6D", + "distributor": "0xc3E9E42DE399F50C5Fc2BC971f0b8D10A631688D", + "furnace": "0x6647c880Eb8F57948AF50aB45fca8FE86C154D24", + "rsrTrader": "0x089848C5228ADe0DF18883197Cd82628b32E95BA", + "rTokenTrader": "0x089848C5228ADe0DF18883197Cd82628b32E95BA", + "rToken": "0xCFA67f42A0fDe4F0Fb612ea5e66170B0465B84c1", + "stRSR": "0x5a5eb5d26871e26645bD6d006671ec0887aeca69" + } + } +} diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts index 72fa5d3dea..429eac9944 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts @@ -11,7 +11,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { priceTimeout, oracleTimeout } from '../../utils' +import { priceTimeout, oracleTimeout, combinedError } from '../../utils' async function main() { // ==== Read Configuration ==== @@ -36,14 +36,16 @@ async function main() { const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) const deployedCollateral: string[] = [] - const revenueHiding = fp('1e-6').toString() // revenueHiding = 0.0001% + const revenueHiding = fp('1e-6') // revenueHiding = 0.0001% /******** Deploy Morpho - AaveV2 **************************/ /******** Morpho token vaults **************************/ console.log(`Deploying morpho token vaults to network ${hre.network.name} (${chainId}) with burner account: ${deployer.address}`) - const MorphoTokenisedDepositFactory = await ethers.getContractFactory("MorphoAaveV2TokenisedDeposit") + const MorphoTokenisedDepositFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDeposit' + ) const maUSDT = await MorphoTokenisedDepositFactory.deploy({ morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, @@ -61,6 +63,7 @@ async function main() { poolToken: networkConfig[chainId].tokens.aUSDC!, rewardToken: networkConfig[chainId].tokens.MORPHO!, }) + const maDAI = await MorphoTokenisedDepositFactory.deploy({ morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, @@ -70,74 +73,174 @@ async function main() { rewardToken: networkConfig[chainId].tokens.MORPHO!, }) + const maWBTC = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, + underlyingERC20: networkConfig[chainId].tokens.WBTC!, + poolToken: networkConfig[chainId].tokens.aWBTC!, + rewardToken: networkConfig[chainId].tokens.MORPHO!, + }) + + const maWETH = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, + underlyingERC20: networkConfig[chainId].tokens.WETH!, + poolToken: networkConfig[chainId].tokens.aWETH!, + rewardToken: networkConfig[chainId].tokens.MORPHO!, + }) + + const maStETH = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, + underlyingERC20: networkConfig[chainId].tokens.stETH!, + poolToken: networkConfig[chainId].tokens.astETH!, + rewardToken: networkConfig[chainId].tokens.MORPHO!, + }) + await maUSDT.deployed() await maUSDC.deployed() await maDAI.deployed() + await maWBTC.deployed() + await maWETH.deployed() + await maStETH.deployed() assetCollDeployments.erc20s.maUSDT = maUSDT.address assetCollDeployments.erc20s.maUSDC = maUSDC.address assetCollDeployments.erc20s.maDAI = maDAI.address + assetCollDeployments.erc20s.maWBTC = maWBTC.address + assetCollDeployments.erc20s.maWETH = maWETH.address + assetCollDeployments.erc20s.maStETH = maStETH.address /******** Morpho collateral **************************/ - const FiatCollateralFactory = await hre.ethers.getContractFactory( - "MorphoFiatCollateral" + const FiatCollateralFactory = await hre.ethers.getContractFactory('MorphoFiatCollateral') + const NonFiatCollateralFactory = await hre.ethers.getContractFactory('MorphoNonFiatCollateral') + const SelfReferentialFactory = await hre.ethers.getContractFactory( + 'MorphoSelfReferentialCollateral' ) const stablesOracleError = fp('0.0025') // 0.25% + const baseStableConfig = { + priceTimeout: priceTimeout.toString(), + oracleError: stablesOracleError.toString(), + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 1 hr + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: stablesOracleError.add(fp('0.01')), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + } + { - const collateral = await FiatCollateralFactory.connect(deployer).deploy({ - priceTimeout: priceTimeout.toString(), - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDT!, - oracleError: stablesOracleError.toString(), - erc20: maUSDT.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 1 hr - targetName: ethers.utils.formatBytes32String("USD"), - defaultThreshold: stablesOracleError.add(fp("0.01")), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - }, + const collateral = await FiatCollateralFactory.connect(deployer).deploy( + { + ...baseStableConfig, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDT!, + erc20: maUSDT.address, + }, revenueHiding - ); + ) assetCollDeployments.collateral.maUSDT = collateral.address deployedCollateral.push(collateral.address.toString()) - } { - - const collateral = await FiatCollateralFactory.connect(deployer).deploy({ - priceTimeout: priceTimeout.toString(), - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, - oracleError: stablesOracleError.toString(), - erc20: maUSDC.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 1 hr - targetName: ethers.utils.formatBytes32String("USD"), - defaultThreshold: stablesOracleError.add(fp("0.01")), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - }, + const collateral = await FiatCollateralFactory.connect(deployer).deploy( + { + ...baseStableConfig, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, + erc20: maUSDC.address, + }, revenueHiding - ); + ) assetCollDeployments.collateral.maUSDC = collateral.address deployedCollateral.push(collateral.address.toString()) } { - const collateral = await FiatCollateralFactory.connect(deployer).deploy({ - priceTimeout: priceTimeout.toString(), - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI!, - oracleError: stablesOracleError.toString(), - erc20: maDAI.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr - targetName: ethers.utils.formatBytes32String("USD"), - defaultThreshold: stablesOracleError.add(fp("0.01")), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - }, + const collateral = await FiatCollateralFactory.connect(deployer).deploy( + { + ...baseStableConfig, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI!, + erc20: maDAI.address, + }, revenueHiding - ); + ) assetCollDeployments.collateral.maDAI = collateral.address deployedCollateral.push(collateral.address.toString()) } + { + const wbtcOracleError = fp('0.02') // 2% + const btcOracleError = fp('0.005') // 0.5% + const combinedBTCWBTCError = combinedError(wbtcOracleError, btcOracleError) + const collateral = await NonFiatCollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout, + oracleError: combinedBTCWBTCError, + maxTradeVolume: fp('1e6'), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + targetName: ethers.utils.formatBytes32String('BTC'), + defaultThreshold: fp('0.01').add(combinedBTCWBTCError), // ~3.5% + delayUntilDefault: bn('86400'), // 24h + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.WBTC!, + erc20: maWBTC.address, + }, + revenueHiding, + networkConfig[chainId].chainlinkFeeds.wBTCBTC!, + oracleTimeout(chainId, '86400').toString() // 1 hr + ) + assetCollDeployments.collateral.maWBTC = collateral.address + deployedCollateral.push(collateral.address.toString()) + } + + { + const collateral = await SelfReferentialFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout, + oracleError: fp('0.005'), + maxTradeVolume: fp('1e6'), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + targetName: ethers.utils.formatBytes32String('ETH'), + defaultThreshold: fp('0.05'), // 5% + delayUntilDefault: bn('86400'), // 24h + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, + erc20: maWBTC.address, + }, + revenueHiding + ) + assetCollDeployments.collateral.maWETH = collateral.address + deployedCollateral.push(collateral.address.toString()) + } + + { + const ethStEthOracleError = fp('0.005') // 0.5% + const ethOracleError = fp('0.005') // 0.5% + + const combinedOracleErrors = combinedError(ethStEthOracleError, ethOracleError) + + // TAR: ETH + // REF: stETH + // TOK: maETH + const collateral = await NonFiatCollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout, + oracleError: combinedOracleErrors, + maxTradeVolume: fp('1e6'), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + targetName: ethers.utils.formatBytes32String('ETH'), + defaultThreshold: fp('0.01').add(combinedOracleErrors), // ~1.5% + delayUntilDefault: bn('86400'), // 24h + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, + erc20: maStETH.address, + }, + revenueHiding, + networkConfig[chainId].chainlinkFeeds.stETHETH!, // {target/ref} + oracleTimeout(chainId, '86400').toString() // 1 hr + ) + assetCollDeployments.collateral.maWBTC = collateral.address + deployedCollateral.push(collateral.address.toString()) + } + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) console.log(`Deployed collateral to ${hre.network.name} (${chainId}) diff --git a/tasks/testing/upgrade-checker-utils/constants.ts b/tasks/testing/upgrade-checker-utils/constants.ts index fe5640245c..a58d60a7fe 100644 --- a/tasks/testing/upgrade-checker-utils/constants.ts +++ b/tasks/testing/upgrade-checker-utils/constants.ts @@ -17,7 +17,7 @@ export const whales: { [key: string]: string } = { [networkConfig['1'].tokens.stETH!.toLowerCase()]: '0x176F3DAb24a159341c0509bB36B833E7fdd0a132', [networkConfig['1'].tokens.WETH!.toLowerCase()]: '0x8EB8a3b98659Cce290402893d0123abb75E3ab28', [networkConfig['1'].tokens.DAI!.toLowerCase()]: '0x8EB8a3b98659Cce290402893d0123abb75E3ab28', - [networkConfig['1'].tokens.CRV!.toLowerCase()]: "0xf977814e90da44bfa03b6295a0616a897441acec" + [networkConfig['1'].tokens.CRV!.toLowerCase()]: '0xf977814e90da44bfa03b6295a0616a897441acec', } export const collateralToUnderlying: { [key: string]: string } = { diff --git a/tasks/testing/upgrade-checker-utils/trades.ts b/tasks/testing/upgrade-checker-utils/trades.ts index 4db79d0220..17082bcabc 100644 --- a/tasks/testing/upgrade-checker-utils/trades.ts +++ b/tasks/testing/upgrade-checker-utils/trades.ts @@ -117,4 +117,4 @@ const mintAToken = async ( await underlying.connect(usdtSigner).approve(collateral.address, amount) await collateral.connect(usdtSigner).deposit(recipient, amount, 0, true) }) -} \ No newline at end of file +} diff --git a/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts b/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts index 6b36c96ec1..c5398e5744 100644 --- a/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts +++ b/tasks/testing/upgrade-checker-utils/upgrades/2_1_0.ts @@ -127,12 +127,13 @@ export default async ( let parsedLog: LogDescription | undefined try { parsedLog = iface.parseLog(event) - } catch { } + } catch {} if (parsedLog && parsedLog.name == 'TradeStarted') { console.log( `\n====== Trade Started: sell ${logToken(parsedLog.args.sell)} / buy ${logToken( parsedLog.args.buy - )} ======\n\tmbuyAmount: ${parsedLog.args.minBuyAmount}\n\tsellAmount: ${parsedLog.args.sellAmount + )} ======\n\tmbuyAmount: ${parsedLog.args.minBuyAmount}\n\tsellAmount: ${ + parsedLog.args.sellAmount }` ) // @@ -187,7 +188,6 @@ export default async ( console.log('Trying again...') } } - }) const lastTimestamp = await getLatestBlockTimestamp(hre) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 4c47b54176..61398dba6f 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -1,4 +1,4 @@ -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { loadFixture, setStorageAt } from '@nomicfoundation/hardhat-network-helpers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' import { expect } from 'chai' @@ -6,6 +6,7 @@ import { BigNumber, ContractFactory } from 'ethers' import { ethers, upgrades } from 'hardhat' import { IConfig, MAX_AUCTION_LENGTH } from '../common/configuration' import { + MAX_UINT48, MAX_UINT96, MAX_UINT192, TradeKind, @@ -13,7 +14,7 @@ import { ZERO_ADDRESS, ONE_ADDRESS, } from '../common/constants' -import { bn, fp, divCeil, toBNDecimals } from '../common/numbers' +import { bn, fp, divCeil, shortString, toBNDecimals } from '../common/numbers' import { DutchTrade, ERC20Mock, @@ -21,15 +22,19 @@ import { GnosisMock, GnosisMockReentrant, GnosisTrade, + GnosisTrade__factory, IAssetRegistry, TestIBackingManager, + TestIBasketHandler, TestIBroker, TestIMain, TestIRevenueTrader, + TestIRToken, USDCMock, ZeroDecimalMock, } from '../typechain' import { whileImpersonating } from './utils/impersonation' +import { cartesianProduct } from './utils/cases' import { Collateral, DefaultFixture, @@ -39,15 +44,26 @@ import { ORACLE_ERROR, ORACLE_TIMEOUT, PRICE_TIMEOUT, + SLOW, } from './fixtures' import snapshotGasCost from './utils/snapshotGasCost' -import { advanceTime, advanceToTimestamp, getLatestBlockTimestamp } from './utils/time' +import { + advanceBlocks, + advanceTime, + advanceToTimestamp, + getLatestBlockTimestamp, + getLatestBlockNumber, +} from './utils/time' import { ITradeRequest } from './utils/trades' import { useEnv } from '#/utils/env' +import { parseUnits } from 'ethers/lib/utils' const DEFAULT_THRESHOLD = fp('0.01') // 1% const DELAY_UNTIL_DEFAULT = bn('86400') // 24h +const describeExtreme = + IMPLEMENTATION == Implementation.P1 && useEnv('EXTREME') ? describe.only : describe.skip + const describeGas = IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip @@ -76,8 +92,10 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { let main: TestIMain let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager + let basketHandler: TestIBasketHandler let rsrTrader: TestIRevenueTrader let rTokenTrader: TestIRevenueTrader + let rToken: TestIRToken let basket: Collateral[] let collateral: Collateral[] @@ -93,10 +111,12 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { main, assetRegistry, backingManager, + basketHandler, broker, gnosis, rsrTrader, rTokenTrader, + rToken, collateral, } = await loadFixture(defaultFixture)) @@ -116,7 +136,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { it('Should setup Broker correctly', async () => { expect(await broker.gnosis()).to.equal(gnosis.address) expect(await broker.batchAuctionLength()).to.equal(config.batchAuctionLength) - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) expect(await broker.main()).to.equal(main.address) }) @@ -142,9 +163,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { main.address, gnosis.address, ZERO_ADDRESS, - bn('100'), + bn('1000'), ZERO_ADDRESS, - bn('100') + bn('1000') ) ).to.be.revertedWith('invalid batchTradeImplementation address') await expect( @@ -152,9 +173,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { main.address, gnosis.address, ONE_ADDRESS, - bn('100'), + bn('1000'), ZERO_ADDRESS, - bn('100') + bn('1000') ) ).to.be.revertedWith('invalid dutchTradeImplementation address') }) @@ -351,42 +372,70 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await broker.dutchAuctionLength()).to.equal(bn(0)) }) - it('Should allow to update disabled if Owner', async () => { + it('Should allow to update batchTradeDisabled/dutchTradeDisabled if Owner', async () => { // Check existing value - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) // If not owner cannot update - await expect(broker.connect(other).setDisabled(true)).to.be.revertedWith('governance only') + await expect(broker.connect(other).setBatchTradeDisabled(true)).to.be.revertedWith( + 'governance only' + ) + await expect( + broker.connect(other).setDutchTradeDisabled(token0.address, true) + ).to.be.revertedWith('governance only') // Check value did not change - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) - // Update with owner - await expect(broker.connect(owner).setDisabled(true)) - .to.emit(broker, 'DisabledSet') + // Update batchTradeDisabled with owner + await expect(broker.connect(owner).setBatchTradeDisabled(true)) + .to.emit(broker, 'BatchTradeDisabledSet') .withArgs(false, true) // Check value was updated - expect(await broker.disabled()).to.equal(true) + expect(await broker.batchTradeDisabled()).to.equal(true) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) // Update back to false - await expect(broker.connect(owner).setDisabled(false)) - .to.emit(broker, 'DisabledSet') + await expect(broker.connect(owner).setBatchTradeDisabled(false)) + .to.emit(broker, 'BatchTradeDisabledSet') .withArgs(true, false) // Check value was updated - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) + + // Update dutchTradeDisabled with owner + await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, true)) + .to.emit(broker, 'DutchTradeDisabledSet') + .withArgs(token0.address, false, true) + + // Check value was updated + expect(await broker.batchTradeDisabled()).to.equal(false) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(true) + expect(await broker.dutchTradeDisabled(token1.address)).to.equal(false) + + // Update back to false + await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, false)) + .to.emit(broker, 'DutchTradeDisabledSet') + .withArgs(token0.address, true, false) + + // Check value was updated + expect(await broker.batchTradeDisabled()).to.equal(false) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(token1.address)).to.equal(false) }) }) describe('Trade Management', () => { - it('Should not allow to open trade if Disabled', async () => { - // Disable Broker - await expect(broker.connect(owner).setDisabled(true)) - .to.emit(broker, 'DisabledSet') + it('Should not allow to open Batch trade if Disabled', async () => { + // Disable Broker Batch Auctions + await expect(broker.connect(owner).setBatchTradeDisabled(true)) + .to.emit(broker, 'BatchTradeDisabledSet') .withArgs(false, true) - // Attempt to open trade const tradeRequest: ITradeRequest = { sell: collateral0.address, buy: collateral1.address, @@ -394,13 +443,59 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { minBuyAmount: bn('0'), } + // Batch Auction openTrade should fail await whileImpersonating(backingManager.address, async (bmSigner) => { await expect( broker.connect(bmSigner).openTrade(TradeKind.BATCH_AUCTION, tradeRequest, prices) - ).to.be.revertedWith('broker disabled') + ).to.be.revertedWith('batch auctions disabled') + }) + }) + + it('Should not allow to open Dutch trade if Disabled for either token', async () => { + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: bn('100e18'), + minBuyAmount: bn('0'), + } + await whileImpersonating(backingManager.address, async (bmSigner) => { + await token0.mint(backingManager.address, tradeRequest.sellAmount) + await token0.connect(bmSigner).approve(broker.address, tradeRequest.sellAmount) + + // Should succeed in callStatic + await broker + .connect(bmSigner) + .callStatic.openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) + + // Disable Broker Dutch Auctions for token0 + await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, true)) + .to.emit(broker, 'DutchTradeDisabledSet') + .withArgs(token0.address, false, true) + + // Dutch Auction openTrade should fail now await expect( broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) - ).to.be.revertedWith('broker disabled') + ).to.be.revertedWith('dutch auctions disabled for token pair') + + // Re-enable Dutch Auctions for token0 + await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, false)) + .to.emit(broker, 'DutchTradeDisabledSet') + .withArgs(token0.address, true, false) + + // Should succeed in callStatic + await broker + .connect(bmSigner) + .callStatic.openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) + + // Disable Broker Dutch Auctions for token1 + await expect(broker.connect(owner).setDutchTradeDisabled(token1.address, true)) + .to.emit(broker, 'DutchTradeDisabledSet') + .withArgs(token1.address, false, true) + + // Dutch Auction openTrade should fail now + await expect( + broker.connect(bmSigner).openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) + ).to.be.revertedWith('dutch auctions disabled for token pair') }) }) @@ -454,7 +549,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { it('Should not allow to report violation if not trade contract', async () => { // Check not disabled - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) // Should not allow to report violation from any address await expect(broker.connect(addr1).reportViolation()).to.be.revertedWith( @@ -469,12 +564,12 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { }) // Check nothing changed - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) }) it('Should not allow to report violation if paused or frozen', async () => { // Check not disabled - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) await main.connect(owner).pauseTrading() @@ -491,13 +586,13 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ) // Check nothing changed - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) }) }) describe('Trades', () => { context('GnosisTrade', () => { - const amount = bn('100e18') + const amount = fp('100.0') let trade: GnosisTrade beforeEach(async () => { @@ -505,6 +600,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { const TradeFactory: ContractFactory = await ethers.getContractFactory('GnosisTrade') trade = await TradeFactory.deploy() + await setStorageAt(trade.address, 0, 0) + // Check state expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) expect(await trade.canSettle()).to.equal(false) @@ -759,6 +856,98 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await trade.canSettle()).to.equal(false) }) + it('Settle frontrun regression check - should be OK', async () => { + // Initialize trade - simulate from backingManager + // token0 18 decimals + // token1 6 decimals + const tradeRequest: ITradeRequest = { + sell: collateral0.address, + buy: collateral1.address, + sellAmount: fp('100.0'), + minBuyAmount: parseUnits('95.0', 6), + } + + // Fund trade and initialize + await token0.connect(owner).mint(backingManager.address, tradeRequest.sellAmount) + + let newTradeAddress = '' + await whileImpersonating(backingManager.address, async (bmSigner) => { + await token0.connect(bmSigner).approve(broker.address, tradeRequest.sellAmount) + const brokerWithBM = broker.connect(bmSigner) + newTradeAddress = await brokerWithBM.callStatic.openTrade( + TradeKind.BATCH_AUCTION, + tradeRequest, + prices + ) + await brokerWithBM.openTrade(TradeKind.BATCH_AUCTION, tradeRequest, prices) + }) + trade = GnosisTrade__factory.connect(newTradeAddress, owner) + + await advanceTime(config.batchAuctionLength.div(10).toString()) + + // Place minimum bid + const bid = { + bidder: addr1.address, + sellAmount: tradeRequest.sellAmount, + buyAmount: tradeRequest.minBuyAmount, + } + await token1.connect(owner).mint(addr1.address, bid.buyAmount) + await token1.connect(addr1).approve(gnosis.address, bid.buyAmount) + await gnosis.placeBid(0, bid) + + // Advance time till trade can be settled + await advanceTime(config.batchAuctionLength.add(100).toString()) + + await whileImpersonating(backingManager.address, async (bmSigner) => { + const tradeWithBm = GnosisTrade__factory.connect(newTradeAddress, bmSigner) + + const normalValues = await tradeWithBm.callStatic.settle() + + expect(normalValues.boughtAmt).to.eq(tradeRequest.minBuyAmount) + expect(normalValues.soldAmt).to.eq(tradeRequest.sellAmount) + + // Simulate someone frontrunning settlement and adding more funds to the trade + await token0.connect(owner).mint(tradeWithBm.address, fp('10')) + await token1.connect(owner).mint(tradeWithBm.address, parseUnits('1', 6)) + + // Simulate settlement after manipulating the trade + let frontRunnedValues = await tradeWithBm.callStatic.settle() + expect(frontRunnedValues.boughtAmt).to.eq( + tradeRequest.minBuyAmount.add(parseUnits('1', 6)) + ) + expect(frontRunnedValues.soldAmt).to.eq(tradeRequest.sellAmount.sub(fp('10'))) + // We can manipulate boughtAmt up and soldAmt down. + // So we're unable to manipualte the clearing price down and force a violation. + + // uint192 clearingPrice = shiftl_toFix(adjustedBuyAmt, -int8(buy.decimals())).div( + // shiftl_toFix(adjustedSoldAmt, -int8(sell.decimals())) + // ); + // if (clearingPrice.lt(worstCasePrice)) { + // broker.reportViolation(); + // } + await token0.connect(owner).mint(tradeWithBm.address, fp('10')) + await token1.connect(owner).mint(tradeWithBm.address, parseUnits('1', 6)) + frontRunnedValues = await tradeWithBm.callStatic.settle() + expect(frontRunnedValues.boughtAmt).to.eq( + tradeRequest.minBuyAmount.add(parseUnits('2', 6)) + ) + expect(frontRunnedValues.soldAmt).to.eq(tradeRequest.sellAmount.sub(fp('20'))) + + expect(await broker.batchTradeDisabled()).to.be.false + await tradeWithBm.settle() + expect(await broker.batchTradeDisabled()).to.be.false + }) + + // Check status + expect(await trade.status()).to.equal(TradeStatus.CLOSED) + expect(await trade.canSettle()).to.equal(false) + + // It's potentially possible to prevent the reportViolation call to be called + // if (sellBal < initBal) { + // if sellBal get's set to initBal, then the GnosisTrade will ignore the boughtAmt + // But it's unknown if this could be exploited + }) + it('Should protect against reentrancy when settling GnosisTrade', async () => { // Create a Reetrant Gnosis const GnosisReentrantFactory: ContractFactory = await ethers.getContractFactory( @@ -935,6 +1124,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await token0.balanceOf(trade.address)).to.equal(0) expect(await token0.balanceOf(backingManager.address)).to.equal(amount.add(newFunds)) }) + + // There is no test here for the reportViolation case; that is in Revenues.test.ts }) context('DutchTrade', () => { @@ -948,6 +1139,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { const TradeFactory: ContractFactory = await ethers.getContractFactory('DutchTrade') trade = await TradeFactory.deploy() + await setStorageAt(trade.address, 0, 0) + // Check state expect(await trade.status()).to.equal(TradeStatus.NOT_STARTED) expect(await trade.canSettle()).to.equal(false) @@ -974,14 +1167,15 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await trade.sell()).to.equal(token0.address) expect(await trade.buy()).to.equal(token1.address) expect(await trade.sellAmount()).to.equal(amount) - expect(await trade.startTime()).to.equal((await getLatestBlockTimestamp()) + 12) + expect(await trade.startBlock()).to.equal((await getLatestBlockNumber()) + 1) + const tradeLen = (await trade.endBlock()).sub(await trade.startBlock()) expect(await trade.endTime()).to.equal( - (await trade.startTime()) + config.dutchAuctionLength.toNumber() + tradeLen.mul(12).add(await getLatestBlockTimestamp()) ) - expect(await trade.middlePrice()).to.equal( + expect(await trade.bestPrice()).to.equal( divCeil(prices.sellHigh.mul(fp('1')), prices.buyLow) ) - expect(await trade.lowPrice()).to.equal(prices.sellLow.mul(fp('1')).div(prices.buyHigh)) + expect(await trade.worstPrice()).to.equal(prices.sellLow.mul(fp('1')).div(prices.buyHigh)) expect(await trade.canSettle()).to.equal(false) // Attempt to initialize again @@ -1046,57 +1240,14 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ).to.not.be.reverted // Check trade values - expect(await trade.middlePrice()).to.equal( - divCeil(prices.sellHigh.mul(fp('1')), prices.buyLow) - ) - const withoutSlippage = prices.sellLow.mul(fp('1')).div(prices.buyHigh) - const withSlippage = withoutSlippage.sub( - withoutSlippage.mul(config.maxTradeSlippage).div(fp('1')) - ) - expect(await trade.lowPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) - }) - - it('Should apply full maxTradeSlippage with low maxTradeVolume', async () => { - // Set low maxTradeVolume for collateral - const FiatCollateralFactory = await ethers.getContractFactory('FiatCollateral') - const newCollateral0: FiatCollateral = await FiatCollateralFactory.deploy({ - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: await collateral0.chainlinkFeed(), - oracleError: ORACLE_ERROR, - erc20: token0.address, - maxTradeVolume: bn(500), - oracleTimeout: ORACLE_TIMEOUT, - targetName: ethers.utils.formatBytes32String('USD'), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - }) - - // Refresh and swap collateral - await newCollateral0.refresh() - await assetRegistry.connect(owner).swapRegistered(newCollateral0.address) - - // Fund trade and initialize - await token0.connect(owner).mint(trade.address, amount) - await expect( - trade.init( - backingManager.address, - newCollateral0.address, - collateral1.address, - amount, - config.dutchAuctionLength, - prices - ) - ).to.not.be.reverted - - // Check trade values - expect(await trade.middlePrice()).to.equal( + expect(await trade.bestPrice()).to.equal( divCeil(prices.sellHigh.mul(fp('1')), prices.buyLow) ) const withoutSlippage = prices.sellLow.mul(fp('1')).div(prices.buyHigh) const withSlippage = withoutSlippage.sub( withoutSlippage.mul(config.maxTradeSlippage).div(fp('1')) ) - expect(await trade.lowPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) + expect(await trade.worstPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) }) it('Should apply full maxTradeSlippage with low maxTradeVolume', async () => { @@ -1132,14 +1283,14 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ).to.not.be.reverted // Check trade values - expect(await trade.middlePrice()).to.equal( + expect(await trade.bestPrice()).to.equal( divCeil(prices.sellHigh.mul(fp('1')), prices.buyLow) ) const withoutSlippage = prices.sellLow.mul(fp('1')).div(prices.buyHigh) const withSlippage = withoutSlippage.sub( withoutSlippage.mul(config.maxTradeSlippage).div(fp('1')) ) - expect(await trade.lowPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) + expect(await trade.worstPrice()).to.be.closeTo(withSlippage, withSlippage.div(bn('1e9'))) }) it('Should not allow to initialize an unfunded trade', async () => { @@ -1176,8 +1327,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { await expect(trade.connect(bmSigner).settle()).to.be.revertedWith('auction not over') }) - // Advance time till trade can be settled - await advanceTime(config.dutchAuctionLength.add(100).toString()) + // Advance blocks til trade can be settled + const tradeLen = (await trade.endBlock()).sub(await getLatestBlockNumber()) + await advanceBlocks(tradeLen.add(1)) // Settle trade expect(await trade.canSettle()).to.equal(true) @@ -1214,8 +1366,9 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { 'only after trade is closed' ) - // Advance time till trade can be settled - await advanceTime(config.dutchAuctionLength.add(100).toString()) + // Advance blocks til trade can be settled + const tradeLen = (await trade.endBlock()).sub(await getLatestBlockNumber()) + await advanceBlocks(tradeLen.add(1)) // Settle trade await whileImpersonating(backingManager.address, async (bmSigner) => { @@ -1246,6 +1399,149 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await token0.balanceOf(trade.address)).to.equal(0) expect(await token0.balanceOf(backingManager.address)).to.equal(amount.add(newFunds)) }) + + // There is no test here for the reportViolation case; that is in Revenues.test.ts + }) + }) + + describeExtreme(`Extreme Values ${SLOW ? 'slow mode' : 'fast mode'}`, () => { + if (!(Implementation.P1 && useEnv('EXTREME'))) return // prevents bunch of skipped tests + + async function runScenario([ + sellTokDecimals, + buyTokDecimals, + auctionSellAmt, + progression, + ]: BigNumber[]) { + // Factories + const ERC20Factory = await ethers.getContractFactory('ERC20MockDecimals') + const CollFactory = await ethers.getContractFactory('FiatCollateral') + const sellTok = await ERC20Factory.deploy('Sell Token', 'SELL', sellTokDecimals) + const buyTok = await ERC20Factory.deploy('Buy Token', 'BUY', buyTokDecimals) + const sellColl = await CollFactory.deploy({ + priceTimeout: MAX_UINT48, + chainlinkFeed: await collateral0.chainlinkFeed(), + oracleError: bn('1'), // minimize + erc20: sellTok.address, + maxTradeVolume: MAX_UINT192, + oracleTimeout: MAX_UINT48, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01'), // shouldn't matter + delayUntilDefault: bn('604800'), // shouldn't matter + }) + await assetRegistry.connect(owner).register(sellColl.address) + const buyColl = await CollFactory.deploy({ + priceTimeout: MAX_UINT48, + chainlinkFeed: await collateral0.chainlinkFeed(), + oracleError: bn('1'), // minimize + erc20: buyTok.address, + maxTradeVolume: MAX_UINT192, + oracleTimeout: MAX_UINT48, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01'), // shouldn't matter + delayUntilDefault: bn('604800'), // shouldn't matter + }) + await assetRegistry.connect(owner).register(buyColl.address) + + // Set basket + await basketHandler + .connect(owner) + .setPrimeBasket([sellTok.address, buyTok.address], [fp('0.5'), fp('0.5')]) + await basketHandler.connect(owner).refreshBasket() + + const MAX_ERC20_SUPPLY = bn('1e48') // from docs/solidity-style.md + + // Max out throttles + const issuanceThrottleParams = { amtRate: MAX_ERC20_SUPPLY, pctRate: 0 } + const redemptionThrottleParams = { amtRate: MAX_ERC20_SUPPLY, pctRate: 0 } + await rToken.connect(owner).setIssuanceThrottleParams(issuanceThrottleParams) + await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + await advanceTime(3600) + + // Mint coll tokens to addr1 + await buyTok.connect(owner).mint(addr1.address, MAX_ERC20_SUPPLY) + await sellTok.connect(owner).mint(addr1.address, MAX_ERC20_SUPPLY) + + // Issue RToken + await buyTok.connect(addr1).approve(rToken.address, MAX_ERC20_SUPPLY) + await sellTok.connect(addr1).approve(rToken.address, MAX_ERC20_SUPPLY) + await rToken.connect(addr1).issue(MAX_ERC20_SUPPLY.div(2)) + + // Burn buyTok from backingManager and send extra sellTok + const burnAmount = divCeil( + auctionSellAmt.mul(bn(10).pow(buyTokDecimals)), + bn(10).pow(sellTokDecimals) + ) + await buyTok.burn(backingManager.address, burnAmount) + await sellTok.connect(addr1).transfer(backingManager.address, auctionSellAmt.mul(10)) + + // Rebalance should cause backingManager to trade about auctionSellAmt, though not exactly + await backingManager.setMaxTradeSlippage(bn('0')) + await backingManager.setMinTradeVolume(bn('0')) + await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)) + .to.emit(backingManager, 'TradeStarted') + .withArgs(anyValue, sellTok.address, buyTok.address, anyValue, anyValue) + + // Get Trade + const tradeAddr = await backingManager.trades(sellTok.address) + await buyTok.connect(addr1).approve(tradeAddr, MAX_ERC20_SUPPLY) + const trade = await ethers.getContractAt('DutchTrade', tradeAddr) + const currentBlock = bn(await getLatestBlockNumber()) + const toAdvance = progression + .mul((await trade.endBlock()).sub(currentBlock)) + .div(fp('1')) + .sub(1) + if (toAdvance.gt(0)) await advanceBlocks(toAdvance) + + // Bid + const sellAmt = await trade.lot() + const bidBlock = bn('1').add(await getLatestBlockNumber()) + const bidAmt = await trade.bidAmount(bidBlock) + expect(bidAmt).to.be.gt(0) + const buyBalBefore = await buyTok.balanceOf(backingManager.address) + const sellBalBefore = await sellTok.balanceOf(addr1.address) + await expect(trade.connect(addr1).bid()) + .to.emit(backingManager, 'TradeSettled') + .withArgs(anyValue, sellTok.address, buyTok.address, sellAmt, bidAmt) + + // Check balances + expect(await sellTok.balanceOf(addr1.address)).to.equal(sellBalBefore.add(sellAmt)) + expect(await buyTok.balanceOf(backingManager.address)).to.equal(buyBalBefore.add(bidAmt)) + expect(await sellTok.balanceOf(trade.address)).to.equal(0) + expect(await buyTok.balanceOf(trade.address)).to.equal(0) + + // Check disabled status + const shouldDisable = progression.lt(fp('0.2')) + expect(await broker.dutchTradeDisabled(sellTok.address)).to.equal(shouldDisable) + expect(await broker.dutchTradeDisabled(buyTok.address)).to.equal(shouldDisable) + } + + // ==== Generate the tests ==== + + // applied to both buy and sell tokens + const decimals = [bn('1'), bn('6'), bn('8'), bn('9'), bn('18')] + + // auction sell amount + const auctionSellAmts = [bn('2'), bn('1595439874635'), bn('987321984732198435645846513')] + + // auction progression %: these will get rounded to blocks later + const progression = [fp('0'), fp('0.321698432589749813'), fp('0.798138321987329646'), fp('1')] + + // total cases is 5 * 5 * 3 * 4 = 300 + + if (SLOW) { + progression.push(fp('0.176334768961354965'), fp('0.523449931646439834')) + + // total cases is 5 * 5 * 3 * 6 = 450 + } + + const paramList = cartesianProduct(decimals, decimals, auctionSellAmts, progression) + + const numCases = paramList.length.toString() + paramList.forEach((params, index) => { + it(`case ${index + 1} of ${numCases}: ${params.map(shortString).join(' ')}`, async () => { + await runScenario(params) + }) }) }) @@ -1275,6 +1571,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // Create a new trade TradeFactory = await ethers.getContractFactory('GnosisTrade') newTrade = await TradeFactory.deploy() + await setStorageAt(newTrade.address, 0, 0) }) it('Open Trade ', async () => { @@ -1318,6 +1615,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ) }) + // Bidding tested in Revenues.test.ts + it('Settle Trade ', async () => { // Fund trade and initialize await token0.connect(owner).mint(newTrade.address, amount) @@ -1368,6 +1667,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // Create a new trade TradeFactory = await ethers.getContractFactory('DutchTrade') newTrade = await TradeFactory.deploy() + await setStorageAt(newTrade.address, 0, 0) }) it('Open Trade ', async () => { @@ -1412,6 +1712,8 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ) }) + // Bidding tested in Revenues.test.ts + it('Settle Trade ', async () => { // Fund trade and initialize await token0.connect(owner).mint(newTrade.address, amount) @@ -1425,7 +1727,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ) // Advance time till trade can be settled - await advanceTime(config.dutchAuctionLength.add(100).toString()) + await advanceBlocks((await newTrade.endBlock()).sub(await getLatestBlockNumber())) // Settle trade await whileImpersonating(backingManager.address, async (bmSigner) => { diff --git a/test/Facade.test.ts b/test/Facade.test.ts index 1ebc9145e4..5415dd39d6 100644 --- a/test/Facade.test.ts +++ b/test/Facade.test.ts @@ -46,7 +46,7 @@ import { defaultFixture, ORACLE_ERROR, } from './fixtures' -import { getLatestBlockTimestamp, setNextBlockTimestamp } from './utils/time' +import { advanceBlocks, getLatestBlockTimestamp, setNextBlockTimestamp } from './utils/time' import { CollateralStatus, TradeKind, MAX_UINT256, ZERO_ADDRESS } from '#/common/constants' import { expectTrade } from './utils/trades' import { mintCollaterals } from './utils/tokens' @@ -605,8 +605,8 @@ describe('FacadeRead + FacadeAct contracts', () => { // Nothing should be settleable expect((await facade.auctionsSettleable(trader.address)).length).to.equal(0) - // Advance time till auction ended - await advanceTime(auctionLength + 13) + // Advance time till auction is over + await advanceBlocks(2 + auctionLength / 12) // Now should be settleable const settleable = await facade.auctionsSettleable(trader.address) @@ -1097,7 +1097,7 @@ describe('FacadeRead + FacadeAct contracts', () => { expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(0) // Advance time till auction ended - await advanceTime(auctionLength + 13) + await advanceBlocks(1 + auctionLength / 12) // Settle and start new auction - Will retry await expectEvents( @@ -1161,7 +1161,7 @@ describe('FacadeRead + FacadeAct contracts', () => { expect((await facade.auctionsSettleable(rTokenTrader.address)).length).to.equal(0) // Advance time till auction ended - await advanceTime(auctionLength + 13) + await advanceBlocks(1 + auctionLength / 12) // Upgrade components to V2 await backingManager.connect(owner).upgradeTo(backingManagerV2.address) @@ -1196,7 +1196,7 @@ describe('FacadeRead + FacadeAct contracts', () => { await rTokenTrader.connect(owner).upgradeTo(revTraderV1.address) // Advance time till auction ended - await advanceTime(auctionLength + 13) + await advanceBlocks(1 + auctionLength / 12) // Settle and start new auction - Will retry again await expectEvents( diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index de3a602612..15776210be 100644 --- a/test/Furnace.test.ts +++ b/test/Furnace.test.ts @@ -446,6 +446,45 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { expect(diff).to.be.lte(expectedDiff) }) + + it('Regression test -- C4 June 2023 Issue #29', async () => { + // https://github.com/code-423n4/2023-06-reserve-findings/issues/29 + + // Transfer to Furnace and do first melt + await rToken.connect(addr1).transfer(furnace.address, bn('10e18')) + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) + await furnace.melt() + + // Should have updated lastPayout + lastPayoutBal + expect(await furnace.lastPayout()).to.be.closeTo(await getLatestBlockTimestamp(), 12) + expect(await furnace.lastPayout()).to.be.lte(await getLatestBlockTimestamp()) + expect(await furnace.lastPayoutBal()).to.equal(bn('10e18')) + + // Advance 99 periods -- should melt at old ratio + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 99 * Number(ONE_PERIOD)) + + // Freeze and change ratio + await main.connect(owner).freezeForever() + const maxRatio = bn('1e14') + await expect(furnace.connect(owner).setRatio(maxRatio)) + .to.emit(furnace, 'RatioSet') + .withArgs(config.rewardRatio, maxRatio) + + // Should have updated lastPayout + lastPayoutBal + expect(await furnace.lastPayout()).to.be.closeTo(await getLatestBlockTimestamp(), 12) + expect(await furnace.lastPayout()).to.be.lte(await getLatestBlockTimestamp()) + expect(await furnace.lastPayoutBal()).to.equal(bn('10e18')) // no change + + // Unfreeze and advance 1 period + await main.connect(owner).unfreeze() + await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) + await expect(furnace.melt()).to.emit(rToken, 'Melted') + + // Should have updated lastPayout + lastPayoutBal + expect(await furnace.lastPayout()).to.be.closeTo(await getLatestBlockTimestamp(), 12) + expect(await furnace.lastPayout()).to.be.lte(await getLatestBlockTimestamp()) + expect(await furnace.lastPayoutBal()).to.equal(bn('9.999e18')) + }) }) describeExtreme('Extreme Bounds', () => { diff --git a/test/Main.test.ts b/test/Main.test.ts index 591fe6d095..b8613d40a0 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -1668,6 +1668,21 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { // Basket is now disabled expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) }) + + it('Recognizes Sound Collateral', async () => { + expect(await collateral1.status()).to.equal(CollateralStatus.SOUND) + await expect(assetRegistry.register(collateral1.address)).not.be.reverted + + await revertCollateral.setStatus(CollateralStatus.DISABLED) + expect(await revertCollateral.status()).to.equal(CollateralStatus.DISABLED) + + await expect( + assetRegistry.connect(owner).register(revertCollateral.address) + ).be.revertedWith('collateral not sound') + await expect( + assetRegistry.connect(owner).swapRegistered(revertCollateral.address) + ).be.revertedWith('collateral not sound') + }) }) }) diff --git a/test/RToken.test.ts b/test/RToken.test.ts index c13c235db7..4652dec1c9 100644 --- a/test/RToken.test.ts +++ b/test/RToken.test.ts @@ -224,6 +224,26 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ).to.be.revertedWith('issuance pctRate too big') }) + it('Should account for accrued value when updating issuance throttle parameters', async () => { + await advanceTime(12 * 5 * 60) // 60 minutes, charge fully + const issuanceThrottleParams = { amtRate: fp('60'), pctRate: fp('0.1') } + + await rToken.connect(owner).setIssuanceThrottleParams(issuanceThrottleParams) + const params = await rToken.issuanceThrottleParams() + expect(params[0]).to.equal(issuanceThrottleParams.amtRate) + expect(params[1]).to.equal(issuanceThrottleParams.pctRate) + + await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, initialBal))) + await rToken.connect(addr1).issue(fp('20')) + expect(await rToken.issuanceAvailable()).to.equal(fp('40')) + + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12 * 5 * 10) // 10 minutes + + issuanceThrottleParams.amtRate = fp('100') + await rToken.connect(owner).setIssuanceThrottleParams(issuanceThrottleParams) + expect(await rToken.issuanceAvailable()).to.equal(fp('50')) + }) + it('Should allow to update redemption throttle if Owner and perform validations', async () => { const redemptionThrottleParams = { amtRate: fp('1'), pctRate: fp('0.1') } await expect( @@ -262,6 +282,30 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { ).to.be.revertedWith('redemption pctRate too big') }) + it('Should account for accrued value when updating redemption throttle parameters', async () => { + await advanceTime(12 * 5 * 60) // 60 minutes, charge fully + const issuanceThrottleParams = { amtRate: fp('100'), pctRate: fp('0.1') } + const redemptionThrottleParams = { amtRate: fp('60'), pctRate: fp('0.1') } + + await rToken.connect(owner).setIssuanceThrottleParams(issuanceThrottleParams) + await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + const params = await rToken.redemptionThrottleParams() + expect(params[0]).to.equal(redemptionThrottleParams.amtRate) + expect(params[1]).to.equal(redemptionThrottleParams.pctRate) + + await Promise.all(tokens.map((t) => t.connect(addr1).approve(rToken.address, initialBal))) + await rToken.connect(addr1).issue(fp('100')) + expect(await rToken.redemptionAvailable()).to.equal(fp('60')) + await rToken.connect(addr1).redeem(fp('30')) + expect(await rToken.redemptionAvailable()).to.equal(fp('30')) + + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + 12 * 5 * 10) // 10 minutes + + redemptionThrottleParams.amtRate = fp('100') + await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + expect(await rToken.redemptionAvailable()).to.equal(fp('40')) + }) + it('Should return a price of 0 if the assets become unregistered', async () => { const startPrice = await basketHandler.price() @@ -367,6 +411,8 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await Promise.all( tokens.map((t) => t.connect(addr1).approve(rToken.address, MAX_THROTTLE_AMT_RATE)) ) + // advance time + await advanceTime(12 * 5 * 60) // 60 minutes, charge fully await rToken.connect(addr1).issue(MAX_THROTTLE_AMT_RATE) expect(await rToken.totalSupply()).to.equal(MAX_THROTTLE_AMT_RATE) expect(await rToken.basketsNeeded()).to.equal(MAX_THROTTLE_AMT_RATE) @@ -1370,6 +1416,9 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { redemptionThrottleParams.pctRate = bn(0) await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + // advance time + await advanceTime(12 * 5 * 60) // 60 minutes, charge fully + // Check redemption throttle expect(await rToken.redemptionAvailable()).to.equal(redemptionThrottleParams.amtRate) @@ -2183,6 +2232,9 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { redemptionThrottleParams.pctRate = bn(0) await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + // advance time + await advanceTime(12 * 5 * 60) // 60 minutes, charge fully + // Check redemption throttle expect(await rToken.redemptionAvailable()).to.equal(redemptionThrottleParams.amtRate) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 9f707e1d1a..5b7718a225 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -37,7 +37,12 @@ import { TestIStRSR, USDCMock, } from '../typechain' -import { advanceTime, advanceToTimestamp, getLatestBlockTimestamp } from './utils/time' +import { + advanceTime, + advanceBlocks, + getLatestBlockTimestamp, + getLatestBlockNumber, +} from './utils/time' import { Collateral, defaultFixture, @@ -1009,20 +1014,6 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) }) - it('Should not settle trades if trading paused', async () => { - await main.connect(owner).pauseTrading() - await expect(backingManager.settleTrade(token0.address)).to.be.revertedWith( - 'frozen or trading paused' - ) - }) - - it('Should not settle trades if frozen', async () => { - await main.connect(owner).freezeShort() - await expect(backingManager.settleTrade(token0.address)).to.be.revertedWith( - 'frozen or trading paused' - ) - }) - it('Should not recollateralize when switching basket if all assets are UNPRICED', async () => { // Set price to use lot price await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) @@ -3293,7 +3284,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ) // Check the empty buffer block as well - await advanceToTimestamp((await getLatestBlockTimestamp()) + auctionLength + 12) + await advanceBlocks(1) await expect(backingManager.rebalance(TradeKind.DUTCH_AUCTION)).to.be.revertedWith( 'already rebalancing' ) @@ -3310,29 +3301,30 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ) await token1.connect(addr1).approve(trade.address, initialBal) - const start = await trade.startTime() - const end = await trade.endTime() - await advanceToTimestamp(start) + const start = await trade.startBlock() + const end = await trade.endBlock() // Simulate 30 minutes of blocks, should swap at right price each time - for (let now = await getLatestBlockTimestamp(); now <= end; now += 12) { + let now = bn(await getLatestBlockNumber()) + while (now.lt(end)) { const actual = await trade.connect(addr1).bidAmount(now) const expected = divCeil( await dutchBuyAmount( - fp(now - start).div(end - start), - rTokenAsset.address, + fp(now.sub(start)).div(end.sub(start)), + collateral1.address, collateral0.address, issueAmount, config.minTradeVolume, config.maxTradeSlippage ), - bn('1e12') // fix for decimals + bn('1e12') ) expect(actual).to.be.closeTo(expected, expected.div(bn('1e15'))) const staticResult = await trade.connect(addr1).callStatic.bid() - expect(staticResult).to.equal(expected) - await advanceToTimestamp((await getLatestBlockTimestamp()) + 12) + expect(staticResult).to.equal(actual) + await advanceBlocks(1) + now = bn(await getLatestBlockNumber()) } }) @@ -3343,9 +3335,10 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await backingManager.trades(token0.address) ) await token1.connect(addr1).approve(trade.address, initialBal) - await advanceToTimestamp((await trade.endTime()) + 1) + + await advanceBlocks((await trade.endBlock()).sub(await getLatestBlockNumber()).add(1)) await expect( - trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + trade.connect(addr1).bidAmount(await getLatestBlockNumber()) ).to.be.revertedWith('auction over') await expect(trade.connect(addr1).bid()).be.revertedWith('auction over') @@ -3372,7 +3365,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await token1.connect(addr1).approve(trade1.address, initialBal) // Snipe auction at 0s left - await advanceToTimestamp((await trade1.endTime()) - 1) + await advanceBlocks((await trade1.endBlock()).sub(await getLatestBlockNumber()).sub(1)) await trade1.connect(addr1).bid() expect(await trade1.canSettle()).to.equal(false) expect(await trade1.status()).to.equal(2) // Status.CLOSED @@ -3381,7 +3374,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { const expected = divCeil( await dutchBuyAmount( - fp(auctionLength).div(auctionLength), // last possible second + fp('1'), // last block collateral0.address, collateral1.address, issueAmount, @@ -3411,8 +3404,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { it('even under worst-possible bids', async () => { await token1.connect(addr1).approve(trade2.address, initialBal) - // Advance to final second of auction - await advanceToTimestamp((await trade2.endTime()) - 1) + // Advance to final block of auction + await advanceBlocks((await trade2.endBlock()).sub(await getLatestBlockNumber()).sub(1)) expect(await trade2.status()).to.equal(1) // TradeStatus.OPEN expect(await trade2.canSettle()).to.equal(false) @@ -3421,8 +3414,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { }) it('via fallback to Batch Auction', async () => { - // Advance past auction timeout - await advanceToTimestamp((await trade2.endTime()) + 1) + // Advance past auction end block + await advanceBlocks((await trade2.endBlock()).sub(await getLatestBlockNumber()).add(1)) expect(await trade2.status()).to.equal(1) // TradeStatus.OPEN expect(await trade2.canSettle()).to.equal(true) @@ -5197,17 +5190,16 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { let tradeAddr = await backingManager.trades(token2.address) let trade = await ethers.getContractAt('DutchTrade', tradeAddr) await backupToken1.connect(addr1).approve(trade.address, initialBal) - await advanceToTimestamp((await trade.endTime()) - 1) + await advanceBlocks((await trade.endBlock()).sub(await getLatestBlockNumber()).sub(1)) await snapshotGasCost(trade.connect(addr1).bid()) - // Expect new trade started -- bid in first block at ~1000x price + // Expect new trade started -- bid in last block expect(await backingManager.tradesOpen()).to.equal(1) expect(await backingManager.trades(token2.address)).to.equal(ZERO_ADDRESS) expect(await backingManager.trades(rsr.address)).to.not.equal(ZERO_ADDRESS) tradeAddr = await backingManager.trades(rsr.address) trade = await ethers.getContractAt('DutchTrade', tradeAddr) await backupToken1.connect(addr1).approve(trade.address, initialBal) - await advanceToTimestamp((await trade.startTime()) - 1) await snapshotGasCost(trade.connect(addr1).bid()) // No new trade diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 746815c077..6b4dad02c8 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -46,9 +46,9 @@ import { whileImpersonating } from './utils/impersonation' import snapshotGasCost from './utils/snapshotGasCost' import { advanceTime, - advanceToTimestamp, + advanceBlocks, + getLatestBlockNumber, getLatestBlockTimestamp, - setNextBlockTimestamp, } from './utils/time' import { withinQuad } from './utils/matchers' import { @@ -428,20 +428,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(balAfter).to.equal(rewardAmt) }) - it('Should not settle trade if paused', async () => { - await main.connect(owner).pauseTrading() - await expect(rTokenTrader.settleTrade(ZERO_ADDRESS)).to.be.revertedWith( - 'frozen or trading paused' - ) - }) - - it('Should not settle trade if frozen', async () => { - await main.connect(owner).freezeShort() - await expect(rTokenTrader.settleTrade(ZERO_ADDRESS)).to.be.revertedWith( - 'frozen or trading paused' - ) - }) - it('Should still launch revenue auction if IFFY', async () => { // Depeg one of the underlying tokens - Reducing price 30% await setOraclePrice(collateral0.address, bn('7e7')) @@ -650,6 +636,112 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rsr.balanceOf(stRSR.address)).to.be.closeTo(expectedAmount, 100) }) + it('Should return tokens to BackingManager correctly - rsrTrader.returnTokens()', async () => { + // Mint tokens + await rsr.connect(owner).mint(rsrTrader.address, issueAmount) + await token0.connect(owner).mint(rsrTrader.address, issueAmount.add(1)) + await token1.connect(owner).mint(rsrTrader.address, issueAmount.add(2)) + + // Should fail when trading paused or frozen + await main.connect(owner).pauseIssuance() + await main.connect(owner).pauseTrading() + await main.connect(owner).freezeForever() + await expect( + rsrTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('frozen or trading paused') + await main.connect(owner).unfreeze() + await expect( + rsrTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('frozen or trading paused') + await main.connect(owner).unpauseTrading() + + // Should fail when distribution is nonzero + await expect( + rsrTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('rsrTotal > 0') + await distributor.setDistribution(STRSR_DEST, { rTokenDist: bn('0'), rsrDist: bn('0') }) + + // Should fail for unregistered token + await assetRegistry.connect(owner).unregister(collateral1.address) + await expect( + rsrTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('unregistered erc20') + + // Succeed on just token0 + rsr + await expectEvents(rsrTrader.returnTokens([rsr.address, token0.address]), [ + { + contract: rsr, + name: 'Transfer', + args: [rsrTrader.address, backingManager.address, issueAmount], + emitted: true, + }, + { + contract: token0, + name: 'Transfer', + args: [rsrTrader.address, backingManager.address, issueAmount.add(1)], + emitted: true, + }, + { + contract: token1, + name: 'Transfer', + emitted: false, + }, + ]) + }) + + it('Should return tokens to BackingManager correctly - rTokenTrader.returnTokens()', async () => { + // Mint tokens + await rsr.connect(owner).mint(rTokenTrader.address, issueAmount) + await token0.connect(owner).mint(rTokenTrader.address, issueAmount.add(1)) + await token1.connect(owner).mint(rTokenTrader.address, issueAmount.add(2)) + + // Should fail when trading paused or frozen + await main.connect(owner).pauseIssuance() + await main.connect(owner).pauseTrading() + await main.connect(owner).freezeForever() + await expect( + rTokenTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('frozen or trading paused') + await main.connect(owner).unfreeze() + await expect( + rTokenTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('frozen or trading paused') + await main.connect(owner).unpauseTrading() + + // Should fail when distribution is nonzero + await expect( + rTokenTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('rTokenTotal > 0') + await distributor.setDistribution(FURNACE_DEST, { rTokenDist: bn('0'), rsrDist: bn('0') }) + + // Should fail for unregistered token + await assetRegistry.connect(owner).unregister(collateral1.address) + await expect( + rTokenTrader.returnTokens([rsr.address, token0.address, token1.address]) + ).to.be.revertedWith('unregistered erc20') + + // Succeed on just token0 + rsr + await expectEvents(rTokenTrader.returnTokens([rsr.address, token0.address]), [ + { + contract: rsr, + name: 'Transfer', + args: [rTokenTrader.address, backingManager.address, issueAmount], + emitted: true, + }, + { + contract: token0, + name: 'Transfer', + args: [rTokenTrader.address, backingManager.address, issueAmount.add(1)], + emitted: true, + }, + { + contract: token1, + name: 'Transfer', + emitted: false, + }, + ]) + }) + it('Should launch multiple auctions -- has tokenToBuy', async () => { // Mint AAVE, token0, and RSR to the RSRTrader await aaveToken.connect(owner).mint(rsrTrader.address, issueAmount) @@ -1995,7 +2087,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader) }) - it('Should report violation when auction behaves incorrectly', async () => { + it('Should report violation when Batch Auction behaves incorrectly', async () => { + // This test needs to be in this file and not Broker.test.ts because settleTrade() + // requires the BackingManager _actually_ started the trade + rewardAmountAAVE = bn('0.5e18') // AAVE Rewards @@ -2094,7 +2189,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ { contract: broker, - name: 'DisabledSet', + name: 'BatchTradeDisabledSet', args: [false, true], emitted: true, }, @@ -2133,7 +2228,233 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(furnace.address)).to.be.closeTo(minBuyAmtRToken.sub(10), 50) }) - it('Should not perform auction if Broker is disabled', async () => { + it('Should report violation when Dutch Auction clears in geometric phase', async () => { + // This test needs to be in this file and not Broker.test.ts because settleTrade() + // requires the BackingManager _actually_ started the trade + + rewardAmountAAVE = bn('0.5e18') + + // AAVE Rewards + await token2.setRewards(backingManager.address, rewardAmountAAVE) + + // Collect revenue + // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) + const sellAmt: BigNumber = rewardAmountAAVE.mul(60).div(100) // due to f = 60% + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + const sellAmtRToken: BigNumber = rewardAmountAAVE.sub(sellAmt) // Remainder + const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) + + // Claim rewards + + await expectEvents(facadeTest.claimRewards(rToken.address), [ + { + contract: token3, + name: 'RewardsClaimed', + args: [compToken.address, bn(0)], + emitted: true, + }, + { + contract: token2, + name: 'RewardsClaimed', + args: [aaveToken.address, rewardAmountAAVE], + emitted: true, + }, + ]) + + // Check status of destinations at this point + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + + // Run auctions + await expectEvents( + facadeTest.runAuctionsForAllTradersForKind(rToken.address, TradeKind.DUTCH_AUCTION), + [ + { + contract: rsrTrader, + name: 'TradeStarted', + args: [anyValue, aaveToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + args: [ + anyValue, + aaveToken.address, + rToken.address, + sellAmtRToken, + withinQuad(minBuyAmtRToken), + ], + emitted: true, + }, + ] + ) + + // Check auctions registered + // AAVE -> RSR Auction + const rsrTrade = await ethers.getContractAt( + 'DutchTrade', + ( + await getTrade(rsrTrader, aaveToken.address) + ).address + ) + expect(await rsrTrade.sell()).to.equal(aaveToken.address) + expect(await rsrTrade.buy()).to.equal(rsr.address) + expect(await rsrTrade.sellAmount()).to.equal(sellAmt) + + // AAVE -> RToken Auction + const rTokenTrade = await ethers.getContractAt( + 'DutchTrade', + ( + await getTrade(rTokenTrader, aaveToken.address) + ).address + ) + expect(await rTokenTrade.sell()).to.equal(aaveToken.address) + expect(await rTokenTrade.buy()).to.equal(rToken.address) + expect(await rTokenTrade.sellAmount()).to.equal(sellAmtRToken) + + // Should not be disabled to start + expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rToken.address)).to.equal(false) + + // Advance time near end of geometric phase + await advanceBlocks(config.dutchAuctionLength.div(12).div(5).sub(5)) + + // Should settle RSR auction + await rsr.connect(addr1).approve(rsrTrade.address, sellAmt.mul(10)) + await expect(rsrTrade.connect(addr1).bid()) + .to.emit(rsrTrader, 'TradeSettled') + .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, anyValue) + expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(true) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(true) + + // Should still be able to settle RToken auction, even though aaveToken is now disabled + await rToken.connect(addr1).approve(rTokenTrade.address, sellAmtRToken.mul(10)) + await expect(rTokenTrade.connect(addr1).bid()) + .to.emit(rTokenTrader, 'TradeSettled') + .withArgs(anyValue, aaveToken.address, rToken.address, sellAmtRToken, anyValue) + + // Check all 3 tokens are disabled for dutch auctions + expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(true) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(true) + expect(await broker.dutchTradeDisabled(rToken.address)).to.equal(true) + }) + + it('Should not report violation when Dutch Auction clears in first linear phase', async () => { + // This test needs to be in this file and not Broker.test.ts because settleTrade() + // requires the BackingManager _actually_ started the trade + + rewardAmountAAVE = bn('0.5e18') + + // AAVE Rewards + await token2.setRewards(backingManager.address, rewardAmountAAVE) + + // Collect revenue + // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) + const sellAmt: BigNumber = rewardAmountAAVE.mul(60).div(100) // due to f = 60% + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + const sellAmtRToken: BigNumber = rewardAmountAAVE.sub(sellAmt) // Remainder + const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) + + // Claim rewards + + await expectEvents(facadeTest.claimRewards(rToken.address), [ + { + contract: token3, + name: 'RewardsClaimed', + args: [compToken.address, bn(0)], + emitted: true, + }, + { + contract: token2, + name: 'RewardsClaimed', + args: [aaveToken.address, rewardAmountAAVE], + emitted: true, + }, + ]) + + // Check status of destinations at this point + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + + // Run auctions + await expectEvents( + facadeTest.runAuctionsForAllTradersForKind(rToken.address, TradeKind.DUTCH_AUCTION), + [ + { + contract: rsrTrader, + name: 'TradeStarted', + args: [anyValue, aaveToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + args: [ + anyValue, + aaveToken.address, + rToken.address, + sellAmtRToken, + withinQuad(minBuyAmtRToken), + ], + emitted: true, + }, + ] + ) + + // Check auctions registered + // AAVE -> RSR Auction + const rsrTrade = await ethers.getContractAt( + 'DutchTrade', + ( + await getTrade(rsrTrader, aaveToken.address) + ).address + ) + expect(await rsrTrade.sell()).to.equal(aaveToken.address) + expect(await rsrTrade.buy()).to.equal(rsr.address) + expect(await rsrTrade.sellAmount()).to.equal(sellAmt) + + // AAVE -> RToken Auction + const rTokenTrade = await ethers.getContractAt( + 'DutchTrade', + ( + await getTrade(rTokenTrader, aaveToken.address) + ).address + ) + expect(await rTokenTrade.sell()).to.equal(aaveToken.address) + expect(await rTokenTrade.buy()).to.equal(rToken.address) + expect(await rTokenTrade.sellAmount()).to.equal(sellAmtRToken) + + // Should not be disabled to start + expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rToken.address)).to.equal(false) + + // Advance time to middle of first linear phase + await advanceBlocks(config.dutchAuctionLength.div(12).div(3)) + + // Should settle RSR auction + await rsr.connect(addr1).approve(rsrTrade.address, sellAmt.mul(10)) + await expect(rsrTrade.connect(addr1).bid()) + .to.emit(rsrTrader, 'TradeSettled') + .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, anyValue) + + // Should settle RToken auction + await rToken.connect(addr1).approve(rTokenTrade.address, sellAmtRToken.mul(10)) + await expect(rTokenTrade.connect(addr1).bid()) + .to.emit(rTokenTrader, 'TradeSettled') + .withArgs(anyValue, aaveToken.address, rToken.address, sellAmtRToken, anyValue) + + // Should not have disabled anything + expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rToken.address)).to.equal(false) + }) + + it('Should not perform auction if Batch Trades are disabled', async () => { rewardAmountAAVE = bn('0.5e18') // AAVE Rewards @@ -2163,7 +2484,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) // Disable broker - await broker.connect(owner).setDisabled(true) + await broker.connect(owner).setBatchTradeDisabled(true) // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) const sellAmt: BigNumber = rewardAmountAAVE.mul(60).div(100) // due to f = 60% @@ -2173,10 +2494,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await backingManager.forwardRevenue([aaveToken.address]) await expect( rsrTrader.manageTokens([aaveToken.address], [TradeKind.BATCH_AUCTION]) - ).to.be.revertedWith('broker disabled') + ).to.be.revertedWith('batch auctions disabled') await expect( rTokenTrader.manageTokens([aaveToken.address], [TradeKind.BATCH_AUCTION]) - ).to.be.revertedWith('broker disabled') + ).to.be.revertedWith('batch auctions disabled') // Check funds - remain in traders expect(await rsr.balanceOf(stRSR.address)).to.equal(0) @@ -2563,20 +2884,17 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Cannot get bid amount yet await expect( - trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + trade.connect(addr1).bidAmount(await getLatestBlockNumber()) ).to.be.revertedWith('auction not started') - // Advance to start time - const start = await trade.startTime() - await advanceToTimestamp(start) - - // Now we can get bid amount - const actual = await trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + // Can get bid amount in following block + await advanceBlocks(1) + const actual = await trade.connect(addr1).bidAmount(await getLatestBlockNumber()) expect(actual).to.be.gt(bn(0)) }) it('Should allow one bidder', async () => { - await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount.div(2)) + await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount.div(2000)) await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) const trade = await ethers.getContractAt( @@ -2584,55 +2902,8 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await rTokenTrader.trades(token0.address) ) - // Advance to auction on-going - await advanceToTimestamp((await trade.endTime()) - 1000) - // Bid - await rToken.connect(addr1).approve(trade.address, initialBal) - await trade.connect(addr1).bid() - expect(await trade.bidder()).to.equal(addr1.address) - - // Cannot bid once is settled - await expect(trade.connect(addr1).bid()).to.be.revertedWith('bid already received') - }) - - it('Should not return bid amount before auction starts', async () => { - await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) - await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) - - const trade = await ethers.getContractAt( - 'DutchTrade', - await rTokenTrader.trades(token0.address) - ) - - // Cannot get bid amount yet - await expect( - trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) - ).to.be.revertedWith('auction not started') - - // Advance to start time - const start = await trade.startTime() - await advanceToTimestamp(start) - - // Now we can get bid amount - const actual = await trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) - expect(actual).to.be.gt(bn(0)) - }) - - it('Should allow one bidder', async () => { - await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount.div(2)) - await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) - - const trade = await ethers.getContractAt( - 'DutchTrade', - await rTokenTrader.trades(token0.address) - ) - - // Advance to auction on-going - await advanceToTimestamp((await trade.endTime()) - 1000) - - // Bid - await rToken.connect(addr1).approve(trade.address, initialBal) + await rToken.connect(addr1).approve(trade.address, issueAmount) await trade.connect(addr1).bid() expect(await trade.bidder()).to.equal(addr1.address) @@ -2650,15 +2921,15 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) await rToken.connect(addr1).approve(trade.address, initialBal) - const start = await trade.startTime() - const end = await trade.endTime() - await advanceToTimestamp(start) + const start = await trade.startBlock() + const end = await trade.endBlock() // Simulate 30 minutes of blocks, should swap at right price each time - for (let now = await getLatestBlockTimestamp(); now <= end; now += 12) { + let now = bn(await getLatestBlockNumber()) + while (now.lt(end)) { const actual = await trade.connect(addr1).bidAmount(now) const expected = await dutchBuyAmount( - fp(now - start).div(end - start), + fp(now.sub(start)).div(end.sub(start)), rTokenAsset.address, collateral0.address, issueAmount, @@ -2669,7 +2940,8 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const staticResult = await trade.connect(addr1).callStatic.bid() expect(staticResult).to.equal(actual) - await advanceToTimestamp((await getLatestBlockTimestamp()) + 12) + await advanceBlocks(1) + now = bn(await getLatestBlockNumber()) } }) @@ -2682,9 +2954,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) await rToken.connect(addr1).approve(trade.address, initialBal) - await advanceToTimestamp((await trade.endTime()) + 1) + await advanceBlocks((await trade.endBlock()).sub(await getLatestBlockNumber()).add(1)) await expect( - trade.connect(addr1).bidAmount(await getLatestBlockTimestamp()) + trade.connect(addr1).bidAmount(await getLatestBlockNumber()) ).to.be.revertedWith('auction over') await expect(trade.connect(addr1).bid()).be.revertedWith('auction over') @@ -2698,7 +2970,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await backingManager.tradesOpen()).to.equal(0) }) - it('Should bid at exactly endTime() and not launch another auction', async () => { + it('Should bid at exactly endBlock() and not launch another auction', async () => { await token0.connect(addr1).transfer(rTokenTrader.address, issueAmount) await rTokenTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) const trade = await ethers.getContractAt( @@ -2706,12 +2978,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { await rTokenTrader.trades(token0.address) ) await rToken.connect(addr1).approve(trade.address, initialBal) + await expect(trade.bidAmount(await trade.endBlock())).to.not.be.reverted // Snipe auction at 0s left - await advanceToTimestamp((await trade.endTime()) - 1) - await expect(trade.bidAmount(await trade.endTime())).to.not.be.reverted - // Set timestamp to be exactly endTime() - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 1) + await advanceBlocks((await trade.endBlock()).sub(await getLatestBlockNumber()).sub(1)) await trade.connect(addr1).bid() expect(await trade.canSettle()).to.equal(false) expect(await trade.status()).to.equal(2) // Status.CLOSED diff --git a/test/Upgradeability.test.ts b/test/Upgradeability.test.ts index 3a6e62135c..0c94eec20c 100644 --- a/test/Upgradeability.test.ts +++ b/test/Upgradeability.test.ts @@ -284,7 +284,9 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { expect(await newBroker.gnosis()).to.equal(gnosis.address) expect(await newBroker.batchAuctionLength()).to.equal(config.batchAuctionLength) expect(await newBroker.dutchAuctionLength()).to.equal(config.dutchAuctionLength) - expect(await newBroker.disabled()).to.equal(false) + expect(await newBroker.batchTradeDisabled()).to.equal(false) + expect(await newBroker.dutchTradeDisabled(rToken.address)).to.equal(false) + expect(await newBroker.dutchTradeDisabled(rsr.address)).to.equal(false) expect(await newBroker.main()).to.equal(main.address) }) @@ -553,7 +555,9 @@ describeP1(`Upgradeability - P${IMPLEMENTATION}`, () => { // Check state is preserved expect(await brokerV2.gnosis()).to.equal(gnosis.address) expect(await brokerV2.batchAuctionLength()).to.equal(config.batchAuctionLength) - expect(await brokerV2.disabled()).to.equal(false) + expect(await brokerV2.batchTradeDisabled()).to.equal(false) + expect(await brokerV2.dutchTradeDisabled(rToken.address)).to.equal(false) + expect(await brokerV2.dutchTradeDisabled(rsr.address)).to.equal(false) expect(await brokerV2.main()).to.equal(main.address) // Check new version is implemented diff --git a/test/ZTradingExtremes.test.ts b/test/ZTradingExtremes.test.ts index 537ba81761..77ca05227d 100644 --- a/test/ZTradingExtremes.test.ts +++ b/test/ZTradingExtremes.test.ts @@ -480,6 +480,9 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, const noThrottle = { amtRate: MAX_THROTTLE_AMT_RATE, pctRate: 0 } await rToken.setIssuanceThrottleParams(noThrottle) await rToken.setRedemptionThrottleParams(noThrottle) + + await advanceTime(12 * 5 * 60) // 60 minutes, charge fully + await rToken.connect(addr1).issue(rTokenSupply) expect(await rToken.balanceOf(addr1.address)).to.equal(rTokenSupply) @@ -651,6 +654,9 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, const noThrottle = { amtRate: MAX_THROTTLE_AMT_RATE, pctRate: 0 } await rToken.setIssuanceThrottleParams(noThrottle) await rToken.setRedemptionThrottleParams(noThrottle) + + await advanceTime(12 * 5 * 60) // 60 minutes, charge fully + await rToken.connect(addr1).issue(rTokenSupply) expect(await rToken.balanceOf(addr1.address)).to.equal(rTokenSupply) diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts index 30444cd379..927858718f 100644 --- a/test/ZZStRSR.test.ts +++ b/test/ZZStRSR.test.ts @@ -2145,6 +2145,126 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { }) }) + describe('Reset Stakes - Governance', () => { + it('Should not allow to reset stakes if not governance', async () => { + await expect(stRSR.connect(other).resetStakes()).to.be.revertedWith('governance only') + }) + + it('Should reset stakes and perform validations on rate - MAX', async () => { + const stakeAmt: BigNumber = bn('100e18') + const seizeAmt: BigNumber = bn('1e18') + + // Stake + await rsr.connect(addr1).approve(stRSR.address, stakeAmt) + await stRSR.connect(addr1).stake(stakeAmt) + + expect(await stRSR.exchangeRate()).to.equal(fp('1')) + expect(await stRSR.totalSupply()).to.equal(stakeAmt) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmt) + + // Cannot reset stakes with this rate + await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rate still safe') + + // Seize small portion of RSR to increase stake rate - still safe + await whileImpersonating(backingManager.address, async (signer) => { + await expect(stRSR.connect(signer).seizeRSR(seizeAmt)).to.emit(stRSR, 'ExchangeRateSet') + }) + + // new rate: new strsr supply / RSR backing that strsr supply + let expectedRate = fp(stakeAmt.sub(seizeAmt)).div(stakeAmt) + expect(await stRSR.exchangeRate()).to.be.closeTo(expectedRate, 1) + expect(await stRSR.totalSupply()).to.equal(stakeAmt) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmt) + + // Attempt to reset stakes, still not possible + await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rate still safe') + + // New Seizure - rate will be unsafe + const rsrRemaining = stakeAmt.sub(seizeAmt) + const seizeAmt2 = rsrRemaining.sub(1e13) + await whileImpersonating(backingManager.address, async (signer) => { + await expect(stRSR.connect(signer).seizeRSR(seizeAmt2)).to.emit(stRSR, 'ExchangeRateSet') + }) + + // check new rate + expectedRate = fp(stakeAmt.sub(seizeAmt).sub(seizeAmt2)).div(stakeAmt) + expect(await stRSR.exchangeRate()).to.be.closeTo(expectedRate, 1) + expect(await stRSR.exchangeRate()).to.be.lte(fp('1e-6')) + expect(await stRSR.exchangeRate()).to.be.gte(fp('1e-9')) + + // Now governance can reset stakes + await expect(stRSR.connect(owner).resetStakes()).to.emit(stRSR, 'AllBalancesReset') + + // All stakes reset + expect(await stRSR.exchangeRate()).to.equal(fp('1')) + expect(await stRSR.totalSupply()).to.equal(bn(0)) + expect(await stRSR.balanceOf(addr1.address)).to.equal(bn(0)) + }) + + it('Should reset stakes and perform validations on rate - MIN', async () => { + const stakeAmt: BigNumber = bn('1000e18') + const addAmt1: BigNumber = bn('100e18') + const addAmt2: BigNumber = bn('10e30') + + // Stake + await rsr.connect(addr1).approve(stRSR.address, stakeAmt) + await stRSR.connect(addr1).stake(stakeAmt) + + expect(await stRSR.exchangeRate()).to.equal(fp('1')) + expect(await stRSR.totalSupply()).to.equal(stakeAmt) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmt) + + // Cannot reset stakes with this rate + await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rate still safe') + + // Add RSR to decrease stake rate - still safe + await rsr.connect(owner).transfer(stRSR.address, addAmt1) + + // Advance to the end of noop period + await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp()))) + await stRSR.payoutRewards() + + // Calculate payout amount + const decayFn = makeDecayFn(await stRSR.rewardRatio()) + const addedRSRStake = addAmt1.sub(decayFn(addAmt1, 1)) // 1 round + const newRate: BigNumber = fp(stakeAmt.add(addedRSRStake)).div(stakeAmt) + + // Payout rewards - Advance to get 1 round of rewards + await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp()))) + await expect(stRSR.payoutRewards()).to.emit(stRSR, 'ExchangeRateSet') + expect(await stRSR.exchangeRate()).to.be.closeTo(newRate, 1) + expect(await stRSR.totalSupply()).to.equal(stakeAmt) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmt) + + // Attempt to reset stakes, still not possible + await expect(stRSR.connect(owner).resetStakes()).to.be.revertedWith('rate still safe') + + // Add a large amount of funds - rate will be unsafe + await rsr.connect(owner).mint(owner.address, addAmt2) + await rsr.connect(owner).transfer(stRSR.address, addAmt2) + + // Advance to the end of noop period + await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp()))) + await stRSR.payoutRewards() + + // Payout rewards - Advance time - rate will be unsafe + await setNextBlockTimestamp(Number(ONE_PERIOD.mul(100).add(await getLatestBlockTimestamp()))) + await expect(stRSR.payoutRewards()).to.emit(stRSR, 'ExchangeRateSet') + expect(await stRSR.exchangeRate()).to.be.gte(fp('1e6')) + expect(await stRSR.exchangeRate()).to.be.lte(fp('1e9')) + expect(await stRSR.totalSupply()).to.equal(stakeAmt) + expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmt) + + // Now governance can reset stakes + await expect(stRSR.connect(owner).resetStakes()).to.emit(stRSR, 'AllBalancesReset') + + // All stakes reset + expect(await stRSR.exchangeRate()).to.equal(fp('1')) + expect(await stRSR.totalSupply()).to.equal(bn(0)) + expect(await stRSR.balanceOf(addr1.address)).to.equal(bn(0)) + }) + }) + describe('Transfers #fast', () => { let amount: BigNumber diff --git a/test/__snapshots__/Broker.test.ts.snap b/test/__snapshots__/Broker.test.ts.snap index 8189fb7987..889471b056 100644 --- a/test/__snapshots__/Broker.test.ts.snap +++ b/test/__snapshots__/Broker.test.ts.snap @@ -1,21 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `233492`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `259526`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `338119`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `368768`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `340233`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `370883`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `342371`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `373021`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63421`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63333`; exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Initialize Trade 1`] = `451427`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 1`] = `539971`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 1`] = `541418`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 2`] = `527809`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 2`] = `529256`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 3`] = `529947`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 3`] = `531394`; exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Settle Trade 1`] = `113056`; diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index 63df8acefd..857bff5b7e 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `9070064`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8355533`; -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464714`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464235`; exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Without governance 1`] = `114895`; diff --git a/test/__snapshots__/Furnace.test.ts.snap b/test/__snapshots__/Furnace.test.ts.snap index fbfe5b16dd..af06969a2f 100644 --- a/test/__snapshots__/Furnace.test.ts.snap +++ b/test/__snapshots__/Furnace.test.ts.snap @@ -1,35 +1,35 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 1`] = `83925`; +exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 1`] = `83931`; -exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 2`] = `89814`; +exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 2`] = `89820`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 1`] = `83925`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 1`] = `83931`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 2`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 2`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 3`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 3`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 4`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 4`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 5`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 5`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 6`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 6`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 7`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 7`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 8`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 8`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 9`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 9`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 10`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 10`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 11`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 11`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 1`] = `64025`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 1`] = `64031`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 2`] = `80657`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 2`] = `80663`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 3`] = `78297`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 3`] = `78303`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 4`] = `40755`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 4`] = `40761`; diff --git a/test/__snapshots__/Main.test.ts.snap b/test/__snapshots__/Main.test.ts.snap index 1861862025..40f226bf7b 100644 --- a/test/__snapshots__/Main.test.ts.snap +++ b/test/__snapshots__/Main.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `341625`; +exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `361898`; -exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `192993`; +exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `196758`; -exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `192993`; +exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `196758`; -exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `164144`; +exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `167914`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80416`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80532`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `69928`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `70044`; diff --git a/test/__snapshots__/RToken.test.ts.snap b/test/__snapshots__/RToken.test.ts.snap index 45f7c82958..eb047d1e78 100644 --- a/test/__snapshots__/RToken.test.ts.snap +++ b/test/__snapshots__/RToken.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `770967`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `791646`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `597971`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `618650`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `573851`; +exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `593423`; exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56658`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index b48be609f9..f7e606d149 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1305108`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1390775`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1445319`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1516167`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `649717`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `744609`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1675046`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1685173`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `184816`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1624723`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1616603`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `184816`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1714111`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1704288`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `212916`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `202908`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index 3408dca024..3b77878520 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -1,27 +1,27 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 1`] = `165168`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 1`] = `164974`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `165110`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `165027`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `165110`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `165027`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `208818`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `208624`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229460`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229377`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212360`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212277`; -exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `916913`; +exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1034418`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `748182`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `777373`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1141155`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1188577`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `319722`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `311446`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `274788`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `266512`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `731082`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `743173`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `250582`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `242306`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index d3c38d5241..263e512679 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`StRSRP1 contract Gas Reporting Stake 1`] = `152134`; +exports[`StRSRP1 contract Gas Reporting Stake 1`] = `139717`; -exports[`StRSRP1 contract Gas Reporting Stake 2`] = `147334`; +exports[`StRSRP1 contract Gas Reporting Stake 2`] = `134917`; -exports[`StRSRP1 contract Gas Reporting Transfer 1`] = `63389`; +exports[`StRSRP1 contract Gas Reporting Transfer 1`] = `63409`; -exports[`StRSRP1 contract Gas Reporting Transfer 2`] = `41489`; +exports[`StRSRP1 contract Gas Reporting Transfer 2`] = `41509`; -exports[`StRSRP1 contract Gas Reporting Transfer 3`] = `58601`; +exports[`StRSRP1 contract Gas Reporting Transfer 3`] = `58621`; exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `222609`; exports[`StRSRP1 contract Gas Reporting Unstake 2`] = `139758`; -exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `555617`; +exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `576204`; -exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `509621`; +exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `530208`; diff --git a/test/fixtures.ts b/test/fixtures.ts index 2f20d2a669..15944a43b8 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -1,4 +1,4 @@ -import { BigNumber, ContractFactory } from 'ethers' +import { ContractFactory } from 'ethers' import { expect } from 'chai' import hre, { ethers } from 'hardhat' import { getChainId } from '../common/blockchain-utils' @@ -149,19 +149,12 @@ async function gnosisFixture(): Promise { } } -interface CollateralFixture { - erc20s: ERC20Mock[] // all erc20 addresses - collateral: Collateral[] // all collateral - basket: Collateral[] // only the collateral actively backing the RToken - basketsNeededAmts: BigNumber[] // reference amounts -} - async function collateralFixture( compToken: ERC20Mock, comptroller: ComptrollerMock, aaveToken: ERC20Mock, config: IConfig -): Promise { +) { const ERC20: ContractFactory = await ethers.getContractFactory('ERC20Mock') const USDC: ContractFactory = await ethers.getContractFactory('USDCMock') const ATokenMockFactory: ContractFactory = await ethers.getContractFactory('StaticATokenMock') @@ -349,7 +342,7 @@ async function collateralFixture( ausdt[0], abusd[0], zcoin[0], - ] + ] as ERC20Mock[] const collateral = [ dai[1], usdc[1], @@ -374,9 +367,25 @@ async function collateralFixture( collateral, basket, basketsNeededAmts, + bySymbol: { + dai, + usdc, + usdt, + busd, + cdai, + cusdc, + cusdt, + adai, + ausdc, + ausdt, + abusd, + zcoin, + }, } } +type CollateralFixture = Awaited> + type RSRAndCompAaveAndCollateralAndModuleFixture = RSRFixture & COMPAAVEFixture & CollateralFixture & @@ -663,7 +672,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = const stRSR: TestIStRSR = await ethers.getContractAt('TestIStRSR', await main.stRSR()) // Deploy collateral for Main - const { erc20s, collateral, basket, basketsNeededAmts } = await collateralFixture( + const { erc20s, collateral, basket, basketsNeededAmts, bySymbol } = await collateralFixture( compToken, compoundMock, aaveToken, @@ -742,5 +751,6 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = facadeTest, rsrTrader, rTokenTrader, + bySymbol, } } diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 808a21eca9..16798523be 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -188,7 +188,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function afterEach(async () => { // Should not trigger a de-listing of the auction platform - expect(await broker.disabled()).to.equal(false) + expect(await broker.batchTradeDisabled()).to.equal(false) // Should not be able to re-bid in auction await token0.connect(addr2).approve(easyAuction.address, buyAmt) @@ -715,10 +715,14 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function const CollFactory = await ethers.getContractFactory('FiatCollateral') const MainFactory = await ethers.getContractFactory('MainP0') const BrokerFactory = await ethers.getContractFactory('BrokerP0') + const GnosisTradeFactory = await ethers.getContractFactory('GnosisTrade') + const DutchTradeFactory = await ethers.getContractFactory('DutchTrade') // Deployments const main = await MainFactory.deploy() const broker = await BrokerFactory.deploy() + const gnosisTradeImpl = await GnosisTradeFactory.deploy() + const dutchTradeImpl = await DutchTradeFactory.deploy() await main.init( { rToken: ONE_ADDRESS, @@ -743,9 +747,9 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await broker.init( main.address, easyAuction.address, - ONE_ADDRESS, + gnosisTradeImpl.address, config.batchAuctionLength, - ONE_ADDRESS, + dutchTradeImpl.address, config.dutchAuctionLength ) const sellTok = await ERC20Factory.deploy('Sell Token', 'SELL', sellTokDecimals) @@ -836,8 +840,8 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await advanceTime(config.batchAuctionLength.add(100).toString()) // End Auction - await expect(trade.connect(addr1).settle()).to.not.emit(broker, 'DisabledSet') - expect(await broker.disabled()).to.equal(false) + await expect(trade.connect(addr1).settle()).to.not.emit(broker, 'BatchTradeDisabledSet') + expect(await broker.batchTradeDisabled()).to.equal(false) } // ==== Generate the tests ==== @@ -883,7 +887,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function }) describe('Regression Tests', () => { - it('Passes Test: 12/03/2023 - Broker Disabled on Trade Settlement with one less token', async () => { + it('Passes Test: 12/03/2023 - Batch Auctions on Trade Settlement with one less token', async () => { // TX: 0xb5fc3d61d46e41b79bd333583448e6d4c186ca49206f8a0e7dde05f2700e0965 // This set the broker to false since it was one token short. // This test is to make sure that the broker is not disabled in this case. diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index 2a6f946314..3d9661263f 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -3,7 +3,12 @@ import { expect } from 'chai' import { Wallet, ContractFactory } from 'ethers' import { ethers } from 'hardhat' import { IConfig } from '../../common/configuration' -import { advanceBlocks, advanceTime, getLatestBlockTimestamp } from '../utils/time' +import { + advanceBlocks, + advanceTime, + getLatestBlockTimestamp, + setNextBlockTimestamp, +} from '../utils/time' import { ZERO_ADDRESS, ONE_ADDRESS, MAX_UINT192 } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { @@ -34,11 +39,18 @@ import { import { Collateral, defaultFixture, + IMPLEMENTATION, + Implementation, ORACLE_TIMEOUT, ORACLE_ERROR, PRICE_TIMEOUT, VERSION, } from '../fixtures' +import { useEnv } from '#/utils/env' +import snapshotGasCost from '../utils/snapshotGasCost' + +const describeGas = + IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip const DEFAULT_THRESHOLD = fp('0.01') // 1% const DELAY_UNTIL_DEFAULT = bn('86400') // 24h @@ -664,4 +676,30 @@ describe('Assets contracts #fast', () => { ) }) }) + + describeGas('Gas Reporting', () => { + context('refresh()', () => { + afterEach(async () => { + await snapshotGasCost(rsrAsset.refresh()) + await snapshotGasCost(rsrAsset.refresh()) // 2nd refresh can be different than 1st + }) + + it('refresh() during SOUND', async () => { + // pass + }) + + it('refresh() after oracle timeout', async () => { + const oracleTimeout = await rsrAsset.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(bn(oracleTimeout).div(12)) + }) + + it('refresh() after full price timeout', async () => { + await advanceTime((await rsrAsset.priceTimeout()) + (await rsrAsset.oracleTimeout())) + const lotP = await rsrAsset.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) + }) + }) + }) }) diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index d2fdbc0177..ea5db3b17e 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -29,9 +29,13 @@ import { UnpricedAppreciatingFiatCollateralMock, USDCMock, WETH9, - UnpricedAppreciatingFiatCollateralMock, } from '../../typechain' -import { advanceTime, getLatestBlockTimestamp, setNextBlockTimestamp } from '../utils/time' +import { + advanceBlocks, + advanceTime, + getLatestBlockTimestamp, + setNextBlockTimestamp, +} from '../utils/time' import snapshotGasCost from '../utils/snapshotGasCost' import { expectPrice, @@ -2164,45 +2168,69 @@ describe('Collateral contracts', () => { }) describeGas('Gas Reporting', () => { - it('Force Updates - Soft Default', async function () { - const delayUntilDefault: BigNumber = bn(await tokenCollateral.delayUntilDefault()) + context('refresh()', () => { + it('during SOUND', async () => { + await snapshotGasCost(tokenCollateral.refresh()) + await snapshotGasCost(usdcCollateral.refresh()) + }) - // Depeg one of the underlying tokens - Reducing price 20% - // Should also impact on the aToken and cToken - await setOraclePrice(tokenCollateral.address, bn('7e7')) + it('during + after soft default', async function () { + const delayUntilDefault: BigNumber = bn(await tokenCollateral.delayUntilDefault()) - // Force updates - Should update whenDefault and status - await snapshotGasCost(tokenCollateral.refresh()) - expect(await tokenCollateral.status()).to.equal(CollateralStatus.IFFY) + // Depeg one of the underlying tokens - Reducing price 20% + // Should also impact on the aToken and cToken + await setOraclePrice(tokenCollateral.address, bn('7e7')) - // Adance half the delay - await advanceTime(Number(delayUntilDefault.div(2)) + 1) + // Force updates - Should update whenDefault and status + await snapshotGasCost(tokenCollateral.refresh()) + expect(await tokenCollateral.status()).to.equal(CollateralStatus.IFFY) - // Force updates - Nothing occurs - await snapshotGasCost(tokenCollateral.refresh()) - await snapshotGasCost(usdcCollateral.refresh()) - expect(await usdcCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await tokenCollateral.status()).to.equal(CollateralStatus.IFFY) + // Adance half the delay + await advanceTime(Number(delayUntilDefault.div(2)) + 1) - // Adance the other half - await advanceTime(Number(delayUntilDefault.div(2)) + 1) + // Force updates - Nothing occurs + await snapshotGasCost(tokenCollateral.refresh()) + await snapshotGasCost(usdcCollateral.refresh()) + expect(await usdcCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await tokenCollateral.status()).to.equal(CollateralStatus.IFFY) - // Move time forward past delayUntilDefault - expect(await tokenCollateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await usdcCollateral.status()).to.equal(CollateralStatus.SOUND) - }) + // Adance the other half + await advanceTime(Number(delayUntilDefault.div(2)) + 1) - it('Force Updates - Hard Default - ATokens/CTokens', async function () { - // Decrease rate for AToken and CToken, will disable collateral immediately - await aToken.setExchangeRate(fp('0.99')) - await cToken.setExchangeRate(fp('0.95')) + // Move time forward past delayUntilDefault + expect(await tokenCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await usdcCollateral.status()).to.equal(CollateralStatus.SOUND) + await snapshotGasCost(tokenCollateral.refresh()) + await snapshotGasCost(usdcCollateral.refresh()) + }) - // Force updates - Should update whenDefault and status for Atokens/CTokens - await snapshotGasCost(aTokenCollateral.refresh()) - expect(await aTokenCollateral.status()).to.equal(CollateralStatus.DISABLED) + it('after hard default', async function () { + // Decrease rate for AToken and CToken, will disable collateral immediately + await aToken.setExchangeRate(fp('0.99')) + await cToken.setExchangeRate(fp('0.95')) - await snapshotGasCost(cTokenCollateral.refresh()) - expect(await cTokenCollateral.status()).to.equal(CollateralStatus.DISABLED) + // Force updates - Should update whenDefault and status for Atokens/CTokens + await snapshotGasCost(aTokenCollateral.refresh()) + expect(await aTokenCollateral.status()).to.equal(CollateralStatus.DISABLED) + + await snapshotGasCost(cTokenCollateral.refresh()) + expect(await cTokenCollateral.status()).to.equal(CollateralStatus.DISABLED) + }) + + it('after oracle timeout', async () => { + const oracleTimeout = await tokenCollateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(bn(oracleTimeout).div(12)) + }) + + it('after full price timeout', async () => { + await advanceTime( + (await tokenCollateral.priceTimeout()) + (await tokenCollateral.oracleTimeout()) + ) + const lotP = await tokenCollateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) + }) }) }) }) diff --git a/test/plugins/OracleDeprecation.test.ts b/test/plugins/OracleDeprecation.test.ts new file mode 100644 index 0000000000..d76d16df0e --- /dev/null +++ b/test/plugins/OracleDeprecation.test.ts @@ -0,0 +1,64 @@ +import { Wallet } from 'ethers' +import { ethers } from 'hardhat' +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { fp } from '../../common/numbers' +import { ERC20Mock, TestIRToken } from '../../typechain' +import { Collateral, DefaultFixture, defaultFixture } from '../fixtures' +import { expect } from 'chai' + +describe('Chainlink Oracle', () => { + // Tokens + let rsr: ERC20Mock + let compToken: ERC20Mock + let aaveToken: ERC20Mock + let rToken: TestIRToken + + // Assets + let basket: Collateral[] + + let wallet: Wallet + + const amt = fp('1e4') + let fixture: DefaultFixture + + before('create fixture loader', async () => { + ;[wallet] = (await ethers.getSigners()) as unknown as Wallet[] + }) + + beforeEach(async () => { + // Deploy fixture + fixture = await loadFixture(defaultFixture) + ;({ rsr, compToken, aaveToken, basket, rToken } = fixture) + + // Get collateral tokens + await rsr.connect(wallet).mint(wallet.address, amt) + await compToken.connect(wallet).mint(wallet.address, amt) + await aaveToken.connect(wallet).mint(wallet.address, amt) + + // Issue RToken to enable RToken.price + for (let i = 0; i < basket.length; i++) { + const tok = await ethers.getContractAt('ERC20Mock', await basket[i].erc20()) + await tok.connect(wallet).mint(wallet.address, amt) + await tok.connect(wallet).approve(rToken.address, amt) + } + await rToken.connect(wallet).issue(amt) + }) + + describe('Chainlink deprecates an asset', () => { + it('Refresh should mark the asset as IFFY', async () => { + const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') + const [, aUSDCCollateral] = fixture.bySymbol.ausdc + const chainLinkOracle = MockV3AggregatorFactory.attach(await aUSDCCollateral.chainlinkFeed()) + await aUSDCCollateral.refresh() + await aUSDCCollateral.tryPrice() + expect(await aUSDCCollateral.status()).to.equal(0) + await chainLinkOracle.deprecate() + await aUSDCCollateral.refresh() + expect(await aUSDCCollateral.status()).to.equal(1) + await expect(aUSDCCollateral.tryPrice()).to.be.revertedWithCustomError( + aUSDCCollateral, + 'StalePrice' + ) + }) + }) +}) diff --git a/test/plugins/__snapshots__/Asset.test.ts.snap b/test/plugins/__snapshots__/Asset.test.ts.snap new file mode 100644 index 0000000000..95905a05a0 --- /dev/null +++ b/test/plugins/__snapshots__/Asset.test.ts.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Assets contracts #fast Gas Reporting refresh() refresh() after full price timeout 1`] = `39474`; + +exports[`Assets contracts #fast Gas Reporting refresh() refresh() after full price timeout 2`] = `39474`; + +exports[`Assets contracts #fast Gas Reporting refresh() refresh() after oracle timeout 1`] = `39474`; + +exports[`Assets contracts #fast Gas Reporting refresh() refresh() after oracle timeout 2`] = `39474`; + +exports[`Assets contracts #fast Gas Reporting refresh() refresh() during SOUND 1`] = `51879`; + +exports[`Assets contracts #fast Gas Reporting refresh() refresh() during SOUND 2`] = `51879`; diff --git a/test/plugins/__snapshots__/Collateral.test.ts.snap b/test/plugins/__snapshots__/Collateral.test.ts.snap index cc9cc3ac12..9c98a9ffd2 100644 --- a/test/plugins/__snapshots__/Collateral.test.ts.snap +++ b/test/plugins/__snapshots__/Collateral.test.ts.snap @@ -1,11 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral contracts Gas Reporting Force Updates - Hard Default - ATokens/CTokens 1`] = `69709`; +exports[`Collateral contracts Gas Reporting refresh() after hard default 1`] = `72844`; -exports[`Collateral contracts Gas Reporting Force Updates - Hard Default - ATokens/CTokens 2`] = `70845`; +exports[`Collateral contracts Gas Reporting refresh() after hard default 2`] = `73980`; -exports[`Collateral contracts Gas Reporting Force Updates - Soft Default 1`] = `59399`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 1`] = `62534`; -exports[`Collateral contracts Gas Reporting Force Updates - Soft Default 2`] = `52131`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 2`] = `55266`; -exports[`Collateral contracts Gas Reporting Force Updates - Soft Default 3`] = `51849`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 3`] = `54984`; + +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 4`] = `23429`; + +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 5`] = `54984`; + +exports[`Collateral contracts Gas Reporting refresh() during SOUND 1`] = `54984`; + +exports[`Collateral contracts Gas Reporting refresh() during SOUND 2`] = `54984`; diff --git a/test/plugins/__snapshots__/RewardableERC20.test.ts.snap b/test/plugins/__snapshots__/RewardableERC20.test.ts.snap new file mode 100644 index 0000000000..195b28a702 --- /dev/null +++ b/test/plugins/__snapshots__/RewardableERC20.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Gas Reporting RewardableERC20WrapperTest claimRewards 1`] = `174078`; + +exports[`Gas Reporting RewardableERC20WrapperTest claimRewards 2`] = `105678`; + +exports[`Gas Reporting RewardableERC20WrapperTest deposit 1`] = `119303`; + +exports[`Gas Reporting RewardableERC20WrapperTest deposit 2`] = `86643`; + +exports[`Gas Reporting RewardableERC20WrapperTest withdraw 1`] = `96090`; + +exports[`Gas Reporting RewardableERC20WrapperTest withdraw 2`] = `64590`; + +exports[`Gas Reporting RewardableERC4626VaultTest claimRewards 1`] = `174047`; + +exports[`Gas Reporting RewardableERC4626VaultTest claimRewards 2`] = `105647`; + +exports[`Gas Reporting RewardableERC4626VaultTest deposit 1`] = `121596`; + +exports[`Gas Reporting RewardableERC4626VaultTest deposit 2`] = `88936`; + +exports[`Gas Reporting RewardableERC4626VaultTest withdraw 1`] = `101629`; + +exports[`Gas Reporting RewardableERC4626VaultTest withdraw 2`] = `70129`; diff --git a/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap b/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap deleted file mode 100644 index 5d104c5e65..0000000000 --- a/test/plugins/__snapshots__/RewardableERC20Vault.test.ts.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Gas Reporting RewardableERC4626Vault claimRewards 1`] = `159832`; - -exports[`Gas Reporting RewardableERC4626Vault claimRewards 2`] = `83066`; - -exports[`Gas Reporting RewardableERC4626Vault deposit 1`] = `119033`; - -exports[`Gas Reporting RewardableERC4626Vault deposit 2`] = `85387`; - -exports[`Gas Reporting RewardableERC4626Vault withdraw 1`] = `98068`; - -exports[`Gas Reporting RewardableERC4626Vault withdraw 2`] = `66568`; diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index 38853a79c9..b74fda69de 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -3,7 +3,13 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { BigNumber, ContractFactory } from 'ethers' import hre, { ethers } from 'hardhat' -import { IMPLEMENTATION, ORACLE_ERROR, PRICE_TIMEOUT, REVENUE_HIDING } from '../../../fixtures' +import { + IMPLEMENTATION, + Implementation, + ORACLE_ERROR, + PRICE_TIMEOUT, + REVENUE_HIDING, +} from '../../../fixtures' import { DefaultFixture, Fixture, getDefaultFixture, ORACLE_TIMEOUT } from '../fixtures' import { getChainId } from '../../../../common/blockchain-utils' import forkBlockNumber from '../../../integration/fork-block-numbers' @@ -26,7 +32,12 @@ import { expectUnpriced, setOraclePrice, } from '../../../utils/oracles' -import { advanceBlocks, advanceTime, getLatestBlockTimestamp } from '../../../utils/time' +import { + advanceBlocks, + advanceTime, + getLatestBlockTimestamp, + setNextBlockTimestamp, +} from '../../../utils/time' import { Asset, ATokenFiatCollateral, @@ -50,6 +61,10 @@ import { } from '../../../../typechain' import { useEnv } from '#/utils/env' import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' +import snapshotGasCost from '../../../utils/snapshotGasCost' + +const describeGas = + IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip // Setup test environment const setup = async (blockNumber: number) => { @@ -709,7 +724,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Test for soft default it('Updates status in case of soft default', async () => { // Redeploy plugin using a Chainlink mock feed where we can change the price - const newCDaiCollateral: ATokenFiatCollateral = await ( + const newADaiCollateral: ATokenFiatCollateral = await ( await ethers.getContractFactory('ATokenFiatCollateral') ).deploy( { @@ -727,36 +742,36 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi ) // Check initial state - expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await newCDaiCollateral.whenDefault()).to.equal(MAX_UINT48) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newADaiCollateral.whenDefault()).to.equal(MAX_UINT48) // Depeg one of the underlying tokens - Reducing price 20% - await setOraclePrice(newCDaiCollateral.address, bn('8e7')) // -20% + await setOraclePrice(newADaiCollateral.address, bn('8e7')) // -20% // Force updates - Should update whenDefault and status - await expect(newCDaiCollateral.refresh()) - .to.emit(newCDaiCollateral, 'CollateralStatusChanged') + await expect(newADaiCollateral.refresh()) + .to.emit(newADaiCollateral, 'CollateralStatusChanged') .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) - expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.IFFY) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.IFFY) const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()).add( delayUntilDefault ) - expect(await newCDaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + expect(await newADaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) // Move time forward past delayUntilDefault await advanceTime(Number(delayUntilDefault)) - expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.DISABLED) // Nothing changes if attempt to refresh after default // CToken - const prevWhenDefault: BigNumber = await newCDaiCollateral.whenDefault() - await expect(newCDaiCollateral.refresh()).to.not.emit( - newCDaiCollateral, + const prevWhenDefault: BigNumber = await newADaiCollateral.whenDefault() + await expect(newADaiCollateral.refresh()).to.not.emit( + newADaiCollateral, 'CollateralStatusChanged' ) - expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) - expect(await newCDaiCollateral.whenDefault()).to.equal(prevWhenDefault) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await newADaiCollateral.whenDefault()).to.equal(prevWhenDefault) }) // Test for hard default @@ -774,7 +789,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await saDaiMock.setExchangeRate(fp('0.02')) // Redeploy plugin using the new aDai mock - const newCDaiCollateral: ATokenFiatCollateral = await ( + const newADaiCollateral: ATokenFiatCollateral = await ( await ethers.getContractFactory('ATokenFiatCollateral') ).deploy( { @@ -790,23 +805,23 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi }, REVENUE_HIDING ) - await newCDaiCollateral.refresh() + await newADaiCollateral.refresh() // Check initial state - expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.SOUND) - expect(await newCDaiCollateral.whenDefault()).to.equal(MAX_UINT48) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newADaiCollateral.whenDefault()).to.equal(MAX_UINT48) // Decrease rate for aDAI, will disable collateral immediately await saDaiMock.setExchangeRate(fp('0.019')) // Force updates - Should update whenDefault and status for Atokens/aTokens - await expect(newCDaiCollateral.refresh()) - .to.emit(newCDaiCollateral, 'CollateralStatusChanged') + await expect(newADaiCollateral.refresh()) + .to.emit(newADaiCollateral, 'CollateralStatusChanged') .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) - expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.DISABLED) const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) - expect(await newCDaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + expect(await newADaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) }) it('Reverts if oracle reverts or runs out of gas, maintains status', async () => { @@ -845,4 +860,152 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(await invalidCTokenCollateral.status()).to.equal(CollateralStatus.SOUND) }) }) + + describeGas('Gas Reporting', () => { + context('refresh()', () => { + it('during SOUND', async () => { + await snapshotGasCost(aDaiCollateral.refresh()) + await snapshotGasCost(aDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after hard default', async () => { + const sATokenMockFactory: ContractFactory = await ethers.getContractFactory( + 'StaticATokenMock' + ) + const aDaiErc20 = await ethers.getContractAt('ERC20Mock', aDai.address) + const symbol = await aDaiErc20.symbol() + const saDaiMock: StaticATokenMock = ( + await sATokenMockFactory.deploy(symbol + ' Token', symbol, dai.address) + ) + // Set initial exchange rate to the new aDai Mock + await saDaiMock.setExchangeRate(fp('0.02')) + + // Redeploy plugin using the new aDai mock + const newADaiCollateral: ATokenFiatCollateral = await ( + await ethers.getContractFactory('ATokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await aDaiCollateral.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: saDaiMock.address, + maxTradeVolume: await aDaiCollateral.maxTradeVolume(), + oracleTimeout: await aDaiCollateral.oracleTimeout(), + targetName: await aDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await aDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + await newADaiCollateral.refresh() + + // Decrease rate for aDAI, will disable collateral immediately + await saDaiMock.setExchangeRate(fp('0.019')) + await snapshotGasCost(newADaiCollateral.refresh()) + await snapshotGasCost(newADaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('during soft default', async () => { + // Redeploy plugin using a Chainlink mock feed where we can change the price + const newADaiCollateral: ATokenFiatCollateral = await ( + await ethers.getContractFactory('ATokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: await aDaiCollateral.erc20(), + maxTradeVolume: await aDaiCollateral.maxTradeVolume(), + oracleTimeout: await aDaiCollateral.oracleTimeout(), + targetName: await aDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await aDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + + // Check initial state + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newADaiCollateral.whenDefault()).to.equal(MAX_UINT48) + + // Depeg one of the underlying tokens - Reducing price 20% + await setOraclePrice(newADaiCollateral.address, bn('8e7')) // -20% + await snapshotGasCost(newADaiCollateral.refresh()) + await snapshotGasCost(newADaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after soft default', async () => { + // Redeploy plugin using a Chainlink mock feed where we can change the price + const newADaiCollateral: ATokenFiatCollateral = await ( + await ethers.getContractFactory('ATokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: await aDaiCollateral.erc20(), + maxTradeVolume: await aDaiCollateral.maxTradeVolume(), + oracleTimeout: await aDaiCollateral.oracleTimeout(), + targetName: await aDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await aDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + + // Check initial state + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newADaiCollateral.whenDefault()).to.equal(MAX_UINT48) + + // Depeg one of the underlying tokens - Reducing price 20% + await setOraclePrice(newADaiCollateral.address, bn('8e7')) // -20% + + // Force updates - Should update whenDefault and status + await expect(newADaiCollateral.refresh()) + .to.emit(newADaiCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.IFFY) + + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()).add( + delayUntilDefault + ) + expect(await newADaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + + // Move time forward past delayUntilDefault + await advanceTime(Number(delayUntilDefault)) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + + // Nothing changes if attempt to refresh after default + // CToken + const prevWhenDefault: BigNumber = await newADaiCollateral.whenDefault() + await expect(newADaiCollateral.refresh()).to.not.emit( + newADaiCollateral, + 'CollateralStatusChanged' + ) + expect(await newADaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await newADaiCollateral.whenDefault()).to.equal(prevWhenDefault) + await snapshotGasCost(newADaiCollateral.refresh()) + await snapshotGasCost(newADaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after oracle timeout', async () => { + const oracleTimeout = await aDaiCollateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(bn(oracleTimeout).div(12)) + await snapshotGasCost(aDaiCollateral.refresh()) + await snapshotGasCost(aDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after full price timeout', async () => { + await advanceTime( + (await aDaiCollateral.priceTimeout()) + (await aDaiCollateral.oracleTimeout()) + ) + const lotP = await aDaiCollateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) + await snapshotGasCost(aDaiCollateral.refresh()) + await snapshotGasCost(aDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + }) + }) }) diff --git a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap new file mode 100644 index 0000000000..62146c7325 --- /dev/null +++ b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `75367`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `73699`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `73922`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `30918`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `75367`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `73699`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `51728`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `51728`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `93195`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `93269`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `128341`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `92399`; diff --git a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap new file mode 100644 index 0000000000..efb00072cb --- /dev/null +++ b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61342`; + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `56873`; + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `79535`; + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `37508`; + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61342`; + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `56873`; + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `71849`; + +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `71849`; diff --git a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap new file mode 100644 index 0000000000..67c84c6cc5 --- /dev/null +++ b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `60830`; + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `56361`; + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `98131`; + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `36971`; + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `81946`; + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77477`; + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `90445`; + +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `90445`; diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 55052779fc..00891b0076 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -26,9 +26,14 @@ import { CollateralStatus, } from './pluginTestTypes' import { expectPrice } from '../../utils/oracles' +import snapshotGasCost from '../../utils/snapshotGasCost' +import { IMPLEMENTATION, Implementation } from '../../fixtures' const describeFork = useEnv('FORK') ? describe : describe.skip +const describeGas = + IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip + export default function fn( fixtures: CollateralTestSuiteFixtures ) { @@ -490,6 +495,52 @@ export default function fn( }) describe('collateral-specific tests', collateralSpecificStatusTests) + + describeGas('Gas Reporting', () => { + if (IMPLEMENTATION != Implementation.P1 || !useEnv('REPORT_GAS')) return // hide pending + + context('refresh()', () => { + afterEach(async () => { + await snapshotGasCost(collateral.refresh()) + await snapshotGasCost(collateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('during SOUND', async () => { + // pass + }) + + itChecksRefPerTokDefault('after hard default', async () => { + await reduceRefPerTok(ctx, 5) + }) + + itChecksTargetPerRefDefault('during soft default', async () => { + await reduceTargetPerRef(ctx, 20) + }) + + itChecksTargetPerRefDefault('after soft default', async () => { + await reduceTargetPerRef(ctx, 20) + await expect(collateral.refresh()) + .to.emit(collateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) + await advanceTime(await collateral.delayUntilDefault()) + }) + + it('after oracle timeout', async () => { + const oracleTimeout = await collateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(oracleTimeout / 12) + }) + + it('after full price timeout', async () => { + await advanceTime( + (await collateral.priceTimeout()) + (await collateral.oracleTimeout()) + ) + const lotP = await collateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) + }) + }) + }) }) }) } diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 2170b61b45..2a16daceb9 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -3,7 +3,13 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import { BigNumber, ContractFactory } from 'ethers' import hre, { ethers } from 'hardhat' -import { IMPLEMENTATION, ORACLE_ERROR, PRICE_TIMEOUT, REVENUE_HIDING } from '../../../fixtures' +import { + IMPLEMENTATION, + Implementation, + ORACLE_ERROR, + PRICE_TIMEOUT, + REVENUE_HIDING, +} from '../../../fixtures' import { DefaultFixture, Fixture, getDefaultFixture, ORACLE_TIMEOUT } from '../fixtures' import { getChainId } from '../../../../common/blockchain-utils' import forkBlockNumber from '../../../integration/fork-block-numbers' @@ -26,7 +32,12 @@ import { expectUnpriced, setOraclePrice, } from '../../../utils/oracles' -import { advanceBlocks, advanceTime, getLatestBlockTimestamp } from '../../../utils/time' +import { + advanceBlocks, + advanceTime, + getLatestBlockTimestamp, + setNextBlockTimestamp, +} from '../../../utils/time' import { Asset, BadERC20, @@ -51,6 +62,10 @@ import { } from '../../../../typechain' import { useEnv } from '#/utils/env' import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' +import snapshotGasCost from '../../../utils/snapshotGasCost' + +const describeGas = + IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip // Setup test environment const setup = async (blockNumber: number) => { @@ -865,4 +880,160 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(await invalidCTokenCollateral.status()).to.equal(CollateralStatus.SOUND) }) }) + + describeGas('Gas Reporting', () => { + context('refresh()', () => { + it('during SOUND', async () => { + await snapshotGasCost(cDaiCollateral.refresh()) + await snapshotGasCost(cDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after hard default', async () => { + // Note: In this case requires to use a CToken mock to be able to change the rate + const CTokenMockFactory: ContractFactory = await ethers.getContractFactory('CTokenMock') + const symbol = await cDai.symbol() + const cDaiMock: CTokenMock = ( + await CTokenMockFactory.deploy(symbol + ' Token', symbol, dai.address) + ) + // Set initial exchange rate to the new cDai Mock + await cDaiMock.setExchangeRate(fp('0.02')) + + const cDaiVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenWrapper') + cDaiVault = ( + await cDaiVaultFactory.deploy( + cDaiMock.address, + 'cDAI RToken Vault', + 'rv_cDAI', + comptroller.address + ) + ) + + // Redeploy plugin using the new cDai mock + const newCDaiCollateral: CTokenFiatCollateral = await ( + await ethers.getContractFactory('CTokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await cDaiCollateral.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: cDaiVault.address, + maxTradeVolume: await cDaiCollateral.maxTradeVolume(), + oracleTimeout: await cDaiCollateral.oracleTimeout(), + targetName: await cDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await cDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + await newCDaiCollateral.refresh() + + // Decrease rate for aDAI, will disable collateral immediately + await cDaiMock.setExchangeRate(fp('0.019')) + await snapshotGasCost(newCDaiCollateral.refresh()) + await snapshotGasCost(newCDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('during soft default', async () => { + // Redeploy plugin using a Chainlink mock feed where we can change the price + const newCDaiCollateral: CTokenFiatCollateral = await ( + await ethers.getContractFactory('CTokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: await cDaiCollateral.erc20(), + maxTradeVolume: await cDaiCollateral.maxTradeVolume(), + oracleTimeout: await cDaiCollateral.oracleTimeout(), + targetName: await cDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await cDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + + // Check initial state + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newCDaiCollateral.whenDefault()).to.equal(MAX_UINT48) + + // Depeg one of the underlying tokens - Reducing price 20% + await setOraclePrice(newCDaiCollateral.address, bn('8e7')) // -20% + await snapshotGasCost(newCDaiCollateral.refresh()) + await snapshotGasCost(newCDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after soft default', async () => { + // Redeploy plugin using a Chainlink mock feed where we can change the price + const newCDaiCollateral: CTokenFiatCollateral = await ( + await ethers.getContractFactory('CTokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: mockChainlinkFeed.address, + oracleError: ORACLE_ERROR, + erc20: await cDaiCollateral.erc20(), + maxTradeVolume: await cDaiCollateral.maxTradeVolume(), + oracleTimeout: await cDaiCollateral.oracleTimeout(), + targetName: await cDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await cDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + + // Check initial state + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newCDaiCollateral.whenDefault()).to.equal(MAX_UINT48) + + // Depeg one of the underlying tokens - Reducing price 20% + await setOraclePrice(newCDaiCollateral.address, bn('8e7')) // -20% + + // Force updates - Should update whenDefault and status + await expect(newCDaiCollateral.refresh()) + .to.emit(newCDaiCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.IFFY) + + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()).add( + delayUntilDefault + ) + expect(await newCDaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + + // Move time forward past delayUntilDefault + await advanceTime(Number(delayUntilDefault)) + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + + // Nothing changes if attempt to refresh after default + // CToken + const prevWhenDefault: BigNumber = await newCDaiCollateral.whenDefault() + await expect(newCDaiCollateral.refresh()).to.not.emit( + newCDaiCollateral, + 'CollateralStatusChanged' + ) + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + expect(await newCDaiCollateral.whenDefault()).to.equal(prevWhenDefault) + await snapshotGasCost(newCDaiCollateral.refresh()) + await snapshotGasCost(newCDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after oracle timeout', async () => { + const oracleTimeout = await cDaiCollateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(bn(oracleTimeout).div(12)) + await snapshotGasCost(cDaiCollateral.refresh()) + await snapshotGasCost(cDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('after full price timeout', async () => { + await advanceTime( + (await cDaiCollateral.priceTimeout()) + (await cDaiCollateral.oracleTimeout()) + ) + const lotP = await cDaiCollateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) + await snapshotGasCost(cDaiCollateral.refresh()) + await snapshotGasCost(cDaiCollateral.refresh()) // 2nd refresh can be different than 1st + }) + }) + }) }) diff --git a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap new file mode 100644 index 0000000000..00de61ac71 --- /dev/null +++ b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `120330`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `118662`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `75058`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `31842`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `120330`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `118662`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `96669`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `96669`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `140788`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `140788`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `175860`; + +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `139992`; diff --git a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap new file mode 100644 index 0000000000..c760e4bbb0 --- /dev/null +++ b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `110087`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `105350`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `135455`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `70661`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `110087`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `105350`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `73461`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `70661`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `127769`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `127769`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `135320`; + +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `128051`; diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index 17cb3f6881..14aff54151 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -19,6 +19,11 @@ import { getLatestBlockTimestamp, setNextBlockTimestamp, } from '#/test/utils/time' +import snapshotGasCost from '../../../utils/snapshotGasCost' +import { IMPLEMENTATION, Implementation } from '../../../fixtures' + +const describeGas = + IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip const describeFork = useEnv('FORK') ? describe : describe.skip @@ -636,6 +641,57 @@ export default function fn( describe('collateral-specific tests', collateralSpecificStatusTests) }) + + describeGas('Gas Reporting', () => { + if (IMPLEMENTATION != Implementation.P1 || !useEnv('REPORT_GAS')) return // hide pending + + context('refresh()', () => { + afterEach(async () => { + await snapshotGasCost(ctx.collateral.refresh()) + await snapshotGasCost(ctx.collateral.refresh()) // 2nd refresh can be different than 1st + }) + + it('during SOUND', async () => { + // pass + }) + + it('after hard default', async () => { + const currentExchangeRate = await ctx.curvePool.get_virtual_price() + await ctx.curvePool.setVirtualPrice(currentExchangeRate.sub(1e3)).then((e) => e.wait()) + }) + + it('during soft default', async () => { + // Depeg first feed - Reducing price by 20% from 1 to 0.8 + const updateAnswerTx = await ctx.feeds[0].updateAnswer(bn('8e7')) + await updateAnswerTx.wait() + }) + + it('after soft default', async () => { + // Depeg first feed - Reducing price by 20% from 1 to 0.8 + const updateAnswerTx = await ctx.feeds[0].updateAnswer(bn('8e7')) + await updateAnswerTx.wait() + await expect(ctx.collateral.refresh()) + .to.emit(ctx.collateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.IFFY) + await advanceTime(await ctx.collateral.delayUntilDefault()) + }) + + it('after oracle timeout', async () => { + const oracleTimeout = await ctx.collateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(oracleTimeout / 12) + }) + + it('after full price timeout', async () => { + await advanceTime( + (await ctx.collateral.priceTimeout()) + (await ctx.collateral.oracleTimeout()) + ) + const lotP = await ctx.collateral.lotPrice() + expect(lotP[0]).to.equal(0) + expect(lotP[1]).to.equal(0) + }) + }) + }) }) }) } diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap new file mode 100644 index 0000000000..6aaba60bdb --- /dev/null +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `82121`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `77653`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `259238`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `82121`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77653`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `254353`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `254353`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `261931`; + +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `254663`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap new file mode 100644 index 0000000000..2b5d33eea0 --- /dev/null +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `99018`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `94550`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `232924`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `104078`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `99610`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `228039`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `228039`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `213786`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `206518`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap new file mode 100644 index 0000000000..ffe45ebe78 --- /dev/null +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62898`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `58430`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `205141`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62898`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58430`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `200256`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `200256`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `185611`; + +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `178343`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap new file mode 100644 index 0000000000..cf655d93a7 --- /dev/null +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvVolatileTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `65346`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `60878`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `241371`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `65346`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `60878`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `236486`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `236486`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `242740`; + +exports[`Collateral: CurveVolatileCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `235472`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap new file mode 100644 index 0000000000..6a295e365d --- /dev/null +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `82121`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `77653`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `259238`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `82121`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `77653`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `254353`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `254353`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `261931`; + +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `254663`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap new file mode 100644 index 0000000000..49b74a184d --- /dev/null +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `99018`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `94550`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `232924`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29804`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `104078`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `99610`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29804`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29804`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `228039`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `228039`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `213786`; + +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `206518`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap new file mode 100644 index 0000000000..53d7c649fe --- /dev/null +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `62898`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `58430`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `205141`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `62898`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58430`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `200256`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `200256`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `185611`; + +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `178343`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap new file mode 100644 index 0000000000..562706cdba --- /dev/null +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxVolatileTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `65346`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `60878`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `241371`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `29793`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `65346`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `60878`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `29793`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `29793`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `236486`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `236486`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `242740`; + +exports[`Collateral: CurveVolatileCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `235472`; diff --git a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts index 4b50a53796..7ee5c7dc01 100644 --- a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts @@ -123,11 +123,17 @@ const mintCollateralTo: MintCollateralFunc = async ( await mintSDAI(ctx.tok, user, amount, recipient) } -// eslint-disable-next-line @typescript-eslint/no-empty-function -const reduceTargetPerRef = async () => {} +const reduceTargetPerRef = async (ctx: CollateralFixtureContext, pctDecrease: BigNumberish) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) +} -// eslint-disable-next-line @typescript-eslint/no-empty-function -const increaseTargetPerRef = async () => {} +const increaseTargetPerRef = async (ctx: CollateralFixtureContext, pctIncrease: BigNumberish) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) +} // prettier-ignore const reduceRefPerTok = async ( @@ -199,13 +205,14 @@ const opts = { increaseRefPerTok, getExpectedPrice, itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, + itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, resetFork, collateralName: 'SDaiCollateral', chainlinkDefaultAnswer, + itIsPricedByPeg: true, } collateralTests(opts) diff --git a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap new file mode 100644 index 0000000000..0d82e1ea60 --- /dev/null +++ b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117757`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `109445`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `132246`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `90061`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117576`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `109445`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `93388`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `90061`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `124283`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `124283`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `132111`; + +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `124565`; diff --git a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts index 9367ee577c..8b51724dcc 100644 --- a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts @@ -1,16 +1,10 @@ +import { setStorageAt } from '@nomicfoundation/hardhat-network-helpers' import collateralTests from '../collateralTests' -import { - CollateralFixtureContext, - CollateralStatus, - CollateralOpts, - MintCollateralFunc, -} from '../pluginTestTypes' +import { CollateralFixtureContext, CollateralOpts, MintCollateralFunc } from '../pluginTestTypes' import { ethers } from 'hardhat' import { ContractFactory, BigNumberish } from 'ethers' import { CTokenWrapper, - CTokenWrapperMock, - ICToken, MockV3Aggregator, MockV3Aggregator__factory, TestICollateral, @@ -20,7 +14,6 @@ import { bn, fp } from '../../../../common/numbers' import { ZERO_ADDRESS } from '../../../../common/constants' import { expect } from 'chai' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { advanceBlocks } from '../../../utils/time' import { USDC_HOLDER, USDT_HOLDER, @@ -192,59 +185,6 @@ all.forEach((curr: FTokenEnumeration) => { return makeCollateralFixtureContext } - const deployCollateralMockContext = async ( - opts: FTokenCollateralOpts = {} - ): Promise => { - const collateralOpts = { ...defaultCollateralOpts, ...opts } - - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) - - const comptroller = await ethers.getContractAt('ComptrollerMock', collateralOpts.comptroller!) - - const chainlinkFeed = await MockV3AggregatorFactory.deploy(6, bn('1e6')) - collateralOpts.chainlinkFeed = chainlinkFeed.address - - const FTokenMockFactory = await ethers.getContractFactory('CTokenMock') - - const underlyingFToken = await FTokenMockFactory.deploy( - 'Mock FToken', - 'Mock Ftk', - curr.underlying - ) - - const CTokenWrapperMockFactory: ContractFactory = await ethers.getContractFactory( - 'CTokenWrapperMock' - ) - - let compAddress = ZERO_ADDRESS - try { - compAddress = await comptroller.getCompAddress() - // eslint-disable-next-line no-empty - } catch {} - - const fTokenVault = ( - await CTokenWrapperMockFactory.deploy( - await underlyingFToken.name(), - await underlyingFToken.symbol(), - underlyingFToken.address, - compAddress, - collateralOpts.comptroller! - ) - ) - - collateralOpts.erc20 = fTokenVault.address - - const collateral = await deployCollateral(collateralOpts) - - return { - collateral, - chainlinkFeed, - tok: fTokenVault, - } - } - /* Define helper functions */ @@ -261,41 +201,45 @@ all.forEach((curr: FTokenEnumeration) => { await mintFToken(underlying, curr.holderUnderlying, fToken, tok, amount, recipient) } - const increaseRefPerTok = async (ctx: CollateralFixtureContext) => { - await advanceBlocks(1) - await (ctx.tok as ICToken).exchangeRateCurrent() + const reduceTargetPerRef = async (ctx: CollateralFixtureContext, pctDecrease: BigNumberish) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } + + const increaseTargetPerRef = async (ctx: CollateralFixtureContext, pctIncrease: BigNumberish) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } + + const increaseRefPerTok = async (ctx: CollateralFixtureContext, pctIncrease: BigNumberish) => { + const tok = ctx.tok as CTokenWrapper + const fToken = await ethers.getContractAt('ICToken', await tok.underlying()) + const totalSupply = await fToken.totalSupply() + await setStorageAt( + fToken.address, + 13, // interesting, the storage slot is 13 for fTokens and 14 for cTokens + totalSupply.sub(totalSupply.mul(pctIncrease).div(100)) + ) // expand supply by pctDecrease, since it's denominator of exchange rate calculation + } + + const reduceRefPerTok = async (ctx: CollateralFixtureContext, pctDecrease: BigNumberish) => { + const tok = ctx.tok as CTokenWrapper + const fToken = await ethers.getContractAt('ICToken', await tok.underlying()) + const totalSupply = await fToken.totalSupply() + await setStorageAt( + fToken.address, + 13, // interesting, the storage slot is 13 for fTokens and 14 for cTokens + totalSupply.add(totalSupply.mul(pctDecrease).div(100)) + ) // expand supply by pctDecrease, since it's denominator of exchange rate calculation } // eslint-disable-next-line @typescript-eslint/no-empty-function const collateralSpecificConstructorTests = () => {} - const collateralSpecificStatusTests = () => { - it('does revenue hiding correctly', async () => { - const { collateral, tok } = await deployCollateralMockContext({ revenueHiding: fp('0.01') }) - - const rate = fp('2') - const rateAsRefPerTok = rate.div(50) - await (tok as CTokenWrapperMock).setExchangeRate(rate) // above current - await collateral.refresh() - const before = await collateral.refPerTok() - expect(before).to.equal(rateAsRefPerTok.mul(fp('0.99')).div(fp('1'))) - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - // Should be SOUND if drops just under 1% - await (tok as CTokenWrapperMock).setExchangeRate(rate.mul(fp('0.99001')).div(fp('1'))) - await collateral.refresh() - let after = await collateral.refPerTok() - expect(before).to.eq(after) - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - // Should be DISABLED if drops just over 1% - await (tok as CTokenWrapperMock).setExchangeRate(before.mul(fp('0.98999')).div(fp('1'))) - await collateral.refresh() - after = await collateral.refPerTok() - expect(before).to.be.gt(after) - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - }) - } + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificStatusTests = () => {} const getExpectedPrice = async (ctx: CollateralFixtureContext) => { const initRefPerTok = await ctx.collateral.refPerTok() @@ -324,19 +268,20 @@ all.forEach((curr: FTokenEnumeration) => { beforeEachRewardsTest: emptyFn, makeCollateralFixtureContext, mintCollateralTo, - reduceTargetPerRef: emptyFn, - increaseTargetPerRef: emptyFn, - reduceRefPerTok: emptyFn, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, increaseRefPerTok, getExpectedPrice, itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, - itChecksRefPerTokDefault: it.skip, + itChecksTargetPerRefDefault: it, + itChecksRefPerTokDefault: it, itChecksPriceChanges: it, - itHasRevenueHiding: it.skip, // in this file + itHasRevenueHiding: it, resetFork, collateralName: curr.testName, chainlinkDefaultAnswer: bn('1e8'), + itIsPricedByPeg: true, } collateralTests(opts) diff --git a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap new file mode 100644 index 0000000000..f2f98569c7 --- /dev/null +++ b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap @@ -0,0 +1,97 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118331`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116663`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `141925`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `97011`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118331`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `116663`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `97011`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `97011`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `140108`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `140108`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `142132`; + +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140464`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `118523`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `116855`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `142181`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `97203`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `118523`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `116855`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `97203`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `97203`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `140364`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `140364`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `142388`; + +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `140646`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `126813`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `125145`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `150963`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `105493`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `126813`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `125145`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `105493`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `105493`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `149146`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `149146`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `151026`; + +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `149428`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `121461`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `119793`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `145257`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `100141`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `121461`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `119793`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `100141`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `100141`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `143510`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `143440`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `145460`; + +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `143792`; diff --git a/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap new file mode 100644 index 0000000000..40c07ca09a --- /dev/null +++ b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59998`; + +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55261`; + +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `60698`; + +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `59029`; + +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `74794`; + +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `74794`; diff --git a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap new file mode 100644 index 0000000000..9764e5e036 --- /dev/null +++ b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `89047`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `84578`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `134690`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `65149`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `89047`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `84578`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `65149`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `65149`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `127004`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `127004`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `131755`; + +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `127286`; diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts index f03e2b6c1d..6ecee49770 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts @@ -32,302 +32,345 @@ interface MAFiatCollateralOpts extends CollateralOpts { defaultRefPerTok?: BigNumberish } -export const deployCollateral = async ( - opts: MAFiatCollateralOpts = {} -): Promise => { - opts = { ...defaultCollateralOpts, ...opts } - - const MorphoAAVECollateralFactory: ContractFactory = await ethers.getContractFactory( - 'MorphoFiatCollateral' - ) - if (opts.erc20 == null) { - const MorphoTokenisedDepositFactory = await ethers.getContractFactory( - 'MorphoAaveV2TokenisedDepositMock' - ) - const wrapperMock = await MorphoTokenisedDepositFactory.deploy({ - morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, - morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, - underlyingERC20: opts.underlyingToken!, - poolToken: opts.poolToken!, - rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, - rewardToken: networkConfig[1].tokens.MORPHO!, - }) - opts.erc20 = wrapperMock.address - } +const makeAaveFiatCollateralTestSuite = ( + collateralName: string, + defaultCollateralOpts: MAFiatCollateralOpts +) => { + const networkConfigToUse = networkConfig[31337] + const deployCollateral = async (opts: MAFiatCollateralOpts = {}): Promise => { + opts = { ...defaultCollateralOpts, ...opts } - const collateral = await MorphoAAVECollateralFactory.deploy( - { - erc20: opts.erc20, - targetName: opts.targetName, - priceTimeout: opts.priceTimeout, - chainlinkFeed: opts.chainlinkFeed, - oracleError: opts.oracleError, - oracleTimeout: opts.oracleTimeout, - maxTradeVolume: opts.maxTradeVolume, - defaultThreshold: opts.defaultThreshold, - delayUntilDefault: opts.delayUntilDefault, - }, - opts.revenueHiding, - { gasLimit: 2000000000 } - ) - await collateral.deployed() - - await expect(collateral.refresh()) - - return collateral -} + const MorphoAAVECollateralFactory: ContractFactory = await ethers.getContractFactory( + 'MorphoFiatCollateral' + ) + if (opts.erc20 == null) { + const MorphoTokenisedDepositFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDepositMock' + ) + const wrapperMock = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfigToUse.MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfigToUse.MORPHO_AAVE_LENS!, + underlyingERC20: opts.underlyingToken!, + poolToken: opts.poolToken!, + rewardsDistributor: networkConfigToUse.MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfigToUse.tokens.MORPHO!, + }) + opts.erc20 = wrapperMock.address + } -type Fixture = () => Promise + const collateral = await MorphoAAVECollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + { gasLimit: 2000000000 } + ) + await collateral.deployed() -const makeCollateralFixtureContext = ( - alice: SignerWithAddress, - inOpts: MAFiatCollateralOpts = {} -): Fixture => { - const makeCollateralFixtureContext = async () => { - const opts = { ...defaultCollateralOpts, ...inOpts } + await expect(collateral.refresh()) - const MorphoTokenisedDepositFactory = await ethers.getContractFactory( - 'MorphoAaveV2TokenisedDepositMock' - ) - const erc20Factory = await ethers.getContractFactory('ERC20Mock') - const underlyingErc20 = await erc20Factory.attach(opts.underlyingToken!) - const wrapperMock = await MorphoTokenisedDepositFactory.deploy({ - morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, - morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, - underlyingERC20: opts.underlyingToken!, - poolToken: opts.poolToken!, - rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, - rewardToken: networkConfig[1].tokens.MORPHO!, - }) + return collateral + } - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) + type Fixture = () => Promise - const chainlinkFeed = ( - await MockV3AggregatorFactory.deploy(8, opts.defaultPrice!) - ) - const collateralOpts = { - ...opts, - erc20: wrapperMock.address, - chainlinkFeed: chainlinkFeed.address, - } + const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + inOpts: MAFiatCollateralOpts = {} + ): Fixture => { + const makeCollateralFixtureContext = async () => { + const opts = { ...defaultCollateralOpts, ...inOpts } - const collateral = await deployCollateral(collateralOpts) + const MorphoTokenisedDepositFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDepositMock' + ) + const erc20Factory = await ethers.getContractFactory('ERC20Mock') + const underlyingErc20 = await erc20Factory.attach(opts.underlyingToken!) + const wrapperMock = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfigToUse.MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfigToUse.MORPHO_AAVE_LENS!, + underlyingERC20: opts.underlyingToken!, + poolToken: opts.poolToken!, + rewardsDistributor: networkConfigToUse.MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfigToUse.tokens.MORPHO!, + }) + + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) - return { - alice, - collateral, - underlyingErc20: underlyingErc20, - chainlinkFeed, - tok: wrapperMock as unknown as ERC20Mock, - morphoWrapper: wrapperMock, - } as MorphoAaveCollateralFixtureContext - } + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, opts.defaultPrice!) + ) + const collateralOpts = { + ...opts, + erc20: wrapperMock.address, + chainlinkFeed: chainlinkFeed.address, + } - return makeCollateralFixtureContext -} + const collateral = await deployCollateral(collateralOpts) -// eslint-disable-next-line @typescript-eslint/no-empty-function -const reduceTargetPerRef = async () => {} + return { + alice, + collateral, + underlyingErc20: underlyingErc20, + chainlinkFeed, + tok: wrapperMock as unknown as ERC20Mock, + morphoWrapper: wrapperMock, + } as MorphoAaveCollateralFixtureContext + } -// eslint-disable-next-line @typescript-eslint/no-empty-function -const increaseTargetPerRef = async () => {} + return makeCollateralFixtureContext + } -const changeRefPerTok = async ( - ctx: MorphoAaveCollateralFixtureContext, - percentChange: BigNumber -) => { - const rate = await ctx.morphoWrapper.getExchangeRate() - await ctx.morphoWrapper.setExchangeRate(rate.add(rate.mul(percentChange).div(bn('100')))) -} + const reduceTargetPerRef = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctDecrease: BigNumberish + ) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } -// prettier-ignore -const reduceRefPerTok = async ( - ctx: MorphoAaveCollateralFixtureContext, - pctDecrease: BigNumberish -) => { - await changeRefPerTok( - ctx, - bn(pctDecrease).mul(-1) - ) -} -// prettier-ignore -const increaseRefPerTok = async ( - ctx: MorphoAaveCollateralFixtureContext, - pctIncrease: BigNumberish -) => { - await changeRefPerTok( - ctx, - bn(pctIncrease) - ) -} -const getExpectedPrice = async (ctx: MorphoAaveCollateralFixtureContext): Promise => { - const clData = await ctx.chainlinkFeed.latestRoundData() - const clDecimals = await ctx.chainlinkFeed.decimals() - - const refPerTok = await ctx.collateral.refPerTok() - return clData.answer - .mul(bn(10).pow(18 - clDecimals)) - .mul(refPerTok) - .div(fp('1')) -} + const increaseTargetPerRef = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctIncrease: BigNumberish + ) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + await ctx.chainlinkFeed.updateAnswer(nextAnswer) + } -/* - Define collateral-specific tests -*/ + const changeRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + percentChange: BigNumber + ) => { + const rate = await ctx.morphoWrapper.getExchangeRate() + await ctx.morphoWrapper.setExchangeRate(rate.add(rate.mul(percentChange).div(bn('100')))) + } -// eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificConstructorTests = () => { - it('tokenised deposits can correctly claim rewards', async () => { - const morphoTokenOwner = '0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa' - const forkBlock = 17574117 - const claimer = '0x05e818959c2Aa4CD05EDAe9A099c38e7Bdc377C6' - const reset = getResetFork(forkBlock) - await reset() - const MorphoTokenisedDepositFactory = await ethers.getContractFactory( - 'MorphoAaveV2TokenisedDeposit' + // prettier-ignore + const reduceRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctDecrease: BigNumberish + ) => { + await changeRefPerTok( + ctx, + bn(pctDecrease).mul(-1) ) - const usdtVault = await MorphoTokenisedDepositFactory.deploy({ - morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, - morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, - underlyingERC20: networkConfig[1].tokens.USDT!, - poolToken: networkConfig[1].tokens.aUSDT!, - rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, - rewardToken: networkConfig[1].tokens.MORPHO!, - }) - const vaultCode = await ethers.provider.getCode(usdtVault.address) - await setCode(claimer, vaultCode) - - const vaultWithClaimableRewards = usdtVault.attach(claimer) - const erc20Factory = await ethers.getContractFactory('ERC20Mock') - const underlyingERC20 = await erc20Factory.attach(networkConfig[1].tokens.USDT!) - const depositAmount = utils.parseUnits('1000', 6) - - const user = hre.ethers.provider.getSigner(0) - const userAddress = await user.getAddress() - - expect( - formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) - ).to.be.equal('0.0') - - await whileImpersonating( - hre, - whales[networkConfig[1].tokens.USDT!.toLowerCase()], - async (whaleSigner) => { - await underlyingERC20.connect(whaleSigner).approve(vaultWithClaimableRewards.address, 0) - await underlyingERC20 - .connect(whaleSigner) - .approve(vaultWithClaimableRewards.address, ethers.constants.MaxUint256) - await vaultWithClaimableRewards.connect(whaleSigner).mint(depositAmount, userAddress) - } + } + // prettier-ignore + const increaseRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctIncrease: BigNumberish + ) => { + await changeRefPerTok( + ctx, + bn(pctIncrease) ) + } + const getExpectedPrice = async (ctx: MorphoAaveCollateralFixtureContext): Promise => { + const clData = await ctx.chainlinkFeed.latestRoundData() + const clDecimals = await ctx.chainlinkFeed.decimals() + + const refPerTok = await ctx.collateral.refPerTok() + return clData.answer + .mul(bn(10).pow(18 - clDecimals)) + .mul(refPerTok) + .div(fp('1')) + } + + /* + Define collateral-specific tests + */ + const collateralSpecificConstructorTests = () => { + it('tokenised deposits can correctly claim rewards', async () => { + const morphoTokenOwner = '0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa' + const forkBlock = 17574117 + const claimer = '0x05e818959c2Aa4CD05EDAe9A099c38e7Bdc377C6' + const reset = getResetFork(forkBlock) + await reset() + const MorphoTokenisedDepositFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDeposit' + ) + const usdtVault = await MorphoTokenisedDepositFactory.deploy({ + morphoController: networkConfigToUse.MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfigToUse.MORPHO_AAVE_LENS!, + underlyingERC20: defaultCollateralOpts.underlyingToken!, + poolToken: defaultCollateralOpts.poolToken!, + rewardsDistributor: networkConfigToUse.MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfigToUse.tokens.MORPHO!, + }) + const vaultCode = await ethers.provider.getCode(usdtVault.address) + await setCode(claimer, vaultCode) + + const vaultWithClaimableRewards = usdtVault.attach(claimer) + const erc20Factory = await ethers.getContractFactory('ERC20Mock') + const underlyingERC20 = erc20Factory.attach(defaultCollateralOpts.underlyingToken!) + const depositAmount = utils.parseUnits('1000', 6) + + const user = hre.ethers.provider.getSigner(0) + const userAddress = await user.getAddress() + + expect( + formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) + ).to.be.equal('0.0') + + await whileImpersonating( + hre, + whales[defaultCollateralOpts.underlyingToken!.toLowerCase()], + async (whaleSigner) => { + await underlyingERC20.connect(whaleSigner).approve(vaultWithClaimableRewards.address, 0) + await underlyingERC20 + .connect(whaleSigner) + .approve(vaultWithClaimableRewards.address, ethers.constants.MaxUint256) + await vaultWithClaimableRewards.connect(whaleSigner).mint(depositAmount, userAddress) + } + ) - expect( - formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) - ).to.be.equal('8.60295466891613') + expect( + formatEther( + await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress) + ).slice(0, '8.60295466891613'.length) + ).to.be.equal('8.60295466891613') + + const morphoRewards = await ethers.getContractAt( + 'IMorphoRewardsDistributor', + networkConfigToUse.MORPHO_REWARDS_DISTRIBUTOR! + ) + await morphoRewards.claim(vaultWithClaimableRewards.address, '14162082619942089266', [ + '0x49bb35f20573d5b927c5b5c15c904839cacdf83c6119450ccb6c2ed0647aa71b', + '0xfb9f4530177774effb7af9c1723c7087f60cd135a0cb5f409ec7bbc792a79235', + '0x16dcb8d895b9520c20f476bfc23125aa8f47b800a3bea63b63f89abe158a16fe', + '0x70b3bcf266272051262da958e86efb68a3621977aab0fa0205a5e47a83f3b129', + '0xc06f6781c002b96e5860094fec5ac0692e6e39b3aafa0e02a2c9f87a993a55cb', + '0x679aafaa2e4772160288874aa86f2f1baf6ab7409109da7ad96d3b6d5cf2c3ee', + '0x5b9f1e5d9dfbdc65ec0166a6f1e2fe4a31396fa31739cce54962f1ed43638ff1', + '0xb2db22839637b4c40c7ecc800df0ed8a205c9c31d7d49c41c3d105a62d1c5526', + '0xa26071ec1b113e9033dcbccd7680617d3e75fa626b9f1c43dbc778f641f162da', + '0x53eb58db4c07b67b3bce54b530c950a4ef0c229a3ed2506c53d7c4e31ecc6bfc', + '0x14c512bd39f8b1d13d4cfaad2b4473c4022d01577249ecc97fbf0a64244378ee', + '0xea8c2ee8d43e37ceb7b0c04d59106eff88afbe3e911b656dec7caebd415ea696', + ]) + + expect( + formatEther( + await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress) + ).slice(0, '14.162082619942089'.length) + ).to.be.equal('14.162082619942089') + + // MORPHO is not a transferable token. + // POST Launch we could ask the Morpho team if our TokenVaults could get permission to transfer the MORPHO tokens. + // Otherwise owners of the TokenVault shares need to wait until the protocol enables the transfer function on the MORPHO token. + + await whileImpersonating(hre, morphoTokenOwner, async (signer) => { + const morphoTokenInst = await ethers.getContractAt( + 'IMorphoToken', + networkConfigToUse.tokens.MORPHO!, + signer + ) + + await morphoTokenInst + .connect(signer) + .setUserRole(vaultWithClaimableRewards.address, 0, true) + }) - const morphoRewards = await ethers.getContractAt( - 'IMorphoRewardsDistributor', - networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR! - ) - await morphoRewards.claim(vaultWithClaimableRewards.address, '14162082619942089266', [ - '0x49bb35f20573d5b927c5b5c15c904839cacdf83c6119450ccb6c2ed0647aa71b', - '0xfb9f4530177774effb7af9c1723c7087f60cd135a0cb5f409ec7bbc792a79235', - '0x16dcb8d895b9520c20f476bfc23125aa8f47b800a3bea63b63f89abe158a16fe', - '0x70b3bcf266272051262da958e86efb68a3621977aab0fa0205a5e47a83f3b129', - '0xc06f6781c002b96e5860094fec5ac0692e6e39b3aafa0e02a2c9f87a993a55cb', - '0x679aafaa2e4772160288874aa86f2f1baf6ab7409109da7ad96d3b6d5cf2c3ee', - '0x5b9f1e5d9dfbdc65ec0166a6f1e2fe4a31396fa31739cce54962f1ed43638ff1', - '0xb2db22839637b4c40c7ecc800df0ed8a205c9c31d7d49c41c3d105a62d1c5526', - '0xa26071ec1b113e9033dcbccd7680617d3e75fa626b9f1c43dbc778f641f162da', - '0x53eb58db4c07b67b3bce54b530c950a4ef0c229a3ed2506c53d7c4e31ecc6bfc', - '0x14c512bd39f8b1d13d4cfaad2b4473c4022d01577249ecc97fbf0a64244378ee', - '0xea8c2ee8d43e37ceb7b0c04d59106eff88afbe3e911b656dec7caebd415ea696', - ]) - - expect( - formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) - ).to.be.equal('14.162082619942089') - - // MORPHO is not a transferable token. - // POST Launch we could ask the Morpho team if our TokenVaults could get permission to transfer the MORPHO tokens. - // Otherwise owners of the TokenVault shares need to wait until the protocol enables the transfer function on the MORPHO token. - - await whileImpersonating(hre, morphoTokenOwner, async (signer) => { const morphoTokenInst = await ethers.getContractAt( 'IMorphoToken', - networkConfig[1].tokens.MORPHO!, - signer + networkConfigToUse.tokens.MORPHO!, + user ) + expect(formatEther(await morphoTokenInst.balanceOf(userAddress))).to.be.equal('0.0') - await morphoTokenInst.connect(signer).setUserRole(vaultWithClaimableRewards.address, 0, true) - }) + await vaultWithClaimableRewards.claimRewards() - const morphoTokenInst = await ethers.getContractAt( - 'IMorphoToken', - networkConfig[1].tokens.MORPHO!, - user - ) - expect(formatEther(await morphoTokenInst.balanceOf(userAddress))).to.be.equal('0.0') + expect( + formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) + ).to.be.equal('0.0') - await vaultWithClaimableRewards.claimRewards() + expect( + formatEther(await morphoTokenInst.balanceOf(userAddress)).slice( + 0, + '14.162082619942089'.length + ) + ).to.be.equal('14.162082619942089') + }) + } - expect( - formatEther(await vaultWithClaimableRewards.callStatic.rewardTokenBalance(userAddress)) - ).to.be.equal('0.0') + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificStatusTests = () => {} + // eslint-disable-next-line @typescript-eslint/no-empty-function + const beforeEachRewardsTest = async () => {} + + const opts = { + deployCollateral, + collateralSpecificConstructorTests: collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itHasRevenueHiding: it, + resetFork: getResetFork(FORK_BLOCK), + collateralName, + chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, + itIsPricedByPeg: true, + } - expect(formatEther(await morphoTokenInst.balanceOf(userAddress))).to.be.equal( - '14.162082619942089' - ) - }) + collateralTests(opts) } -// eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificStatusTests = () => {} -// eslint-disable-next-line @typescript-eslint/no-empty-function -const beforeEachRewardsTest = async () => {} - -export const defaultCollateralOpts: MAFiatCollateralOpts = { - targetName: ethers.utils.formatBytes32String('USDT'), - underlyingToken: networkConfig[1].tokens.USDT!, - poolToken: networkConfig[1].tokens.aUSDT!, - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: networkConfig[1].chainlinkFeeds.USDT!, - oracleTimeout: ORACLE_TIMEOUT, - oracleError: ORACLE_ERROR, - maxTradeVolume: bn(1000000), - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - revenueHiding: fp('0'), - defaultPrice: bn('1e8'), - defaultRefPerTok: fp('1'), +const makeOpts = ( + underlyingToken: string, + poolToken: string, + chainlinkFeed: string +): MAFiatCollateralOpts => { + return { + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: PRICE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT, + oracleError: ORACLE_ERROR, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + maxTradeVolume: bn(1000000), + revenueHiding: fp('0'), + defaultPrice: bn('1e8'), + defaultRefPerTok: fp('1'), + underlyingToken, + poolToken, + chainlinkFeed, + } } /* Run the test suite */ - -const opts = { - deployCollateral, - collateralSpecificConstructorTests, - collateralSpecificStatusTests, - beforeEachRewardsTest, - makeCollateralFixtureContext, - mintCollateralTo, - reduceTargetPerRef, - increaseTargetPerRef, - reduceRefPerTok, - increaseRefPerTok, - getExpectedPrice, - itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, - itChecksRefPerTokDefault: it, - itChecksPriceChanges: it, - itHasRevenueHiding: it, - resetFork: getResetFork(FORK_BLOCK), - collateralName: 'MorphoAAVEV2FiatCollateral', - chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, -} - -collateralTests(opts) +const { tokens, chainlinkFeeds } = networkConfig[31337] +makeAaveFiatCollateralTestSuite( + 'MorphoAAVEV2FiatCollateral - USDT', + makeOpts(tokens.USDT!, tokens.aUSDT!, chainlinkFeeds.USDT!) +) +makeAaveFiatCollateralTestSuite( + 'MorphoAAVEV2FiatCollateral - USDC', + makeOpts(tokens.USDC!, tokens.aUSDC!, chainlinkFeeds.USDC!) +) +makeAaveFiatCollateralTestSuite( + 'MorphoAAVEV2FiatCollateral - DAI', + makeOpts(tokens.DAI!, tokens.aDAI!, chainlinkFeeds.DAI!) +) diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts index 77a2a56e20..d6302abfb0 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts @@ -10,7 +10,7 @@ import { } from '@typechain/index' import { expect } from 'chai' import { BigNumber, BigNumberish } from 'ethers' -import { parseEther, parseUnits } from 'ethers/lib/utils' +import { parseUnits } from 'ethers/lib/utils' import { ethers } from 'hardhat' import collateralTests from '../collateralTests' import { getResetFork } from '../helpers' @@ -24,6 +24,7 @@ import { PRICE_TIMEOUT, } from './constants' import { MorphoAaveCollateralFixtureContext, mintCollateralTo } from './mintCollateralTo' +const configToUse = networkConfig[31337] interface MAFiatCollateralOpts extends CollateralOpts { underlyingToken?: string @@ -34,227 +35,245 @@ interface MAFiatCollateralOpts extends CollateralOpts { targetPrRefFeed?: string refPerTokChainlinkTimeout?: BigNumberish } +const makeAaveNonFiatCollateralTestSuite = ( + collateralName: string, + defaultCollateralOpts: MAFiatCollateralOpts +) => { + const deployCollateral = async (opts: MAFiatCollateralOpts = {}): Promise => { + opts = { ...defaultCollateralOpts, ...opts } -export const deployCollateral = async ( - opts: MAFiatCollateralOpts = {} -): Promise => { - opts = { ...defaultCollateralOpts, ...opts } + const MorphoAAVECollateralFactory: MorphoNonFiatCollateral__factory = + await ethers.getContractFactory('MorphoNonFiatCollateral') + if (opts.erc20 == null) { + const MorphoTokenisedDepositMockFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDepositMock' + ) + const wrapperMock = await MorphoTokenisedDepositMockFactory.deploy({ + morphoController: configToUse.MORPHO_AAVE_CONTROLLER!, + morphoLens: configToUse.MORPHO_AAVE_LENS!, + underlyingERC20: opts.underlyingToken!, + poolToken: opts.poolToken!, + rewardsDistributor: configToUse.MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: configToUse.tokens.MORPHO!, + }) + opts.erc20 = wrapperMock.address + } + const collateral = (await MorphoAAVECollateralFactory.deploy( + { + erc20: opts.erc20!, + targetName: opts.targetName!, + priceTimeout: opts.priceTimeout!, + chainlinkFeed: opts.chainlinkFeed!, + oracleError: opts.oracleError!, + oracleTimeout: opts.oracleTimeout!, + maxTradeVolume: opts.maxTradeVolume!, + defaultThreshold: opts.defaultThreshold!, + delayUntilDefault: opts.delayUntilDefault!, + }, + opts.revenueHiding!, + opts.targetPrRefFeed!, + opts.refPerTokChainlinkTimeout!, + { gasLimit: 2000000000 } + )) as unknown as TestICollateral + await collateral.deployed() - const MorphoAAVECollateralFactory: MorphoNonFiatCollateral__factory = - await ethers.getContractFactory('MorphoNonFiatCollateral') - if (opts.erc20 == null) { - const MorphoTokenisedDepositMockFactory = await ethers.getContractFactory( - 'MorphoAaveV2TokenisedDepositMock' - ) - const wrapperMock = await MorphoTokenisedDepositMockFactory.deploy({ - morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, - morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, - underlyingERC20: opts.underlyingToken!, - poolToken: opts.poolToken!, - rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, - rewardToken: networkConfig[1].tokens.MORPHO!, - }) - opts.erc20 = wrapperMock.address - } - const collateral = (await MorphoAAVECollateralFactory.deploy( - { - erc20: opts.erc20!, - targetName: opts.targetName!, - priceTimeout: opts.priceTimeout!, - chainlinkFeed: opts.chainlinkFeed!, - oracleError: opts.oracleError!, - oracleTimeout: opts.oracleTimeout!, - maxTradeVolume: opts.maxTradeVolume!, - defaultThreshold: opts.defaultThreshold!, - delayUntilDefault: opts.delayUntilDefault!, - }, - opts.revenueHiding!, - opts.targetPrRefFeed!, - opts.refPerTokChainlinkTimeout!, - { gasLimit: 2000000000 } - )) as unknown as TestICollateral - await collateral.deployed() + await expect(collateral.refresh()) - await expect(collateral.refresh()) + return collateral + } - return collateral -} + type Fixture = () => Promise -type Fixture = () => Promise + const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + inOpts: MAFiatCollateralOpts = {} + ): Fixture => { + const makeCollateralFixtureContext = async () => { + const opts = { ...defaultCollateralOpts, ...inOpts } + const MorphoTokenisedDepositMockFactory = await ethers.getContractFactory( + 'MorphoAaveV2TokenisedDepositMock' + ) + const erc20Factory = await ethers.getContractFactory('ERC20Mock') + const underlyingErc20 = erc20Factory.attach(opts.underlyingToken!) + const wrapperMock = await MorphoTokenisedDepositMockFactory.deploy({ + morphoController: configToUse.MORPHO_AAVE_CONTROLLER!, + morphoLens: configToUse.MORPHO_AAVE_LENS!, + underlyingERC20: opts.underlyingToken!, + poolToken: opts.poolToken!, + rewardsDistributor: configToUse.MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: configToUse.tokens.MORPHO!, + }) -const makeCollateralFixtureContext = ( - alice: SignerWithAddress, - inOpts: MAFiatCollateralOpts = {} -): Fixture => { - const makeCollateralFixtureContext = async () => { - const opts = { ...defaultCollateralOpts, ...inOpts } - const MorphoTokenisedDepositMockFactory = await ethers.getContractFactory( - 'MorphoAaveV2TokenisedDepositMock' - ) - const erc20Factory = await ethers.getContractFactory('ERC20Mock') - const underlyingErc20 = erc20Factory.attach(opts.underlyingToken!) - const wrapperMock = await MorphoTokenisedDepositMockFactory.deploy({ - morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, - morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, - underlyingERC20: opts.underlyingToken!, - poolToken: opts.poolToken!, - rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, - rewardToken: networkConfig[1].tokens.MORPHO!, - }) + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) - const MockV3AggregatorFactory = ( - await ethers.getContractFactory('MockV3Aggregator') - ) + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, opts.defaultPrice!) + ) - const chainlinkFeed = ( - await MockV3AggregatorFactory.deploy(8, opts.defaultPrice!) - ) + const targetPrRefFeed = ( + await MockV3AggregatorFactory.deploy(8, opts.defaultRefPerTok!) + ) - const targetPrRefFeed = ( - await MockV3AggregatorFactory.deploy(8, opts.defaultRefPerTok!) - ) + const collateralOpts = { + ...opts, + erc20: wrapperMock.address, + chainlinkFeed: chainlinkFeed.address, + targetPrRefFeed: targetPrRefFeed.address, + } + const collateral = await deployCollateral(collateralOpts) - const collateralOpts = { - ...opts, - erc20: wrapperMock.address, - chainlinkFeed: chainlinkFeed.address, - targetPrRefFeed: targetPrRefFeed.address, + return { + alice, + collateral, + chainlinkFeed, + targetPrRefFeed, + tok: wrapperMock as unknown as ERC20Mock, + morphoWrapper: wrapperMock, + underlyingErc20: underlyingErc20, + } as MorphoAaveCollateralFixtureContext } - const collateral = await deployCollateral(collateralOpts) - return { - alice, - collateral, - chainlinkFeed, - targetPrRefFeed, - tok: wrapperMock as unknown as ERC20Mock, - morphoWrapper: wrapperMock, - underlyingErc20: underlyingErc20, - } as MorphoAaveCollateralFixtureContext + return makeCollateralFixtureContext } - return makeCollateralFixtureContext -} + /* + Define helper functions + */ -/* - Define helper functions -*/ + const reduceTargetPerRef = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctDecrease: BigNumberish + ) => { + const lastRound = await ctx.targetPrRefFeed!.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + await ctx.targetPrRefFeed!.updateAnswer(nextAnswer) + } -// eslint-disable-next-line @typescript-eslint/no-empty-function -const reduceTargetPerRef = async () => {} + const increaseTargetPerRef = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctIncrease: BigNumberish + ) => { + const lastRound = await ctx.targetPrRefFeed!.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + await ctx.targetPrRefFeed!.updateAnswer(nextAnswer) + } -// eslint-disable-next-line @typescript-eslint/no-empty-function -const increaseTargetPerRef = async () => {} + const changeRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + percentChange: BigNumber + ) => { + const rate = await ctx.morphoWrapper.getExchangeRate() + await ctx.morphoWrapper.setExchangeRate(rate.add(rate.mul(percentChange).div(bn('100')))) + } -const changeRefPerTok = async ( - ctx: MorphoAaveCollateralFixtureContext, - percentChange: BigNumber -) => { - const rate = await ctx.morphoWrapper.getExchangeRate() - await ctx.morphoWrapper.setExchangeRate(rate.add(rate.mul(percentChange).div(bn('100')))) + // prettier-ignore + const reduceRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctDecrease: BigNumberish + ) => { + await changeRefPerTok( + ctx, + bn(pctDecrease).mul(-1) + ) + } + // prettier-ignore + const increaseRefPerTok = async ( + ctx: MorphoAaveCollateralFixtureContext, + pctIncrease: BigNumberish + ) => { + await changeRefPerTok( + ctx, + bn(pctIncrease) + ) + } - // { - // const lastRound = await ctx.targetPrRefFeed!.latestRoundData() - // const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) - // await ctx.targetPrRefFeed!.updateAnswer(nextAnswer) - // } + const getExpectedPrice = async (ctx: MorphoAaveCollateralFixtureContext): Promise => { + const clData = await ctx.chainlinkFeed.latestRoundData() + const clDecimals = await ctx.chainlinkFeed.decimals() - // { - // const lastRound = await ctx.chainlinkFeed.latestRoundData() - // const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) - // await ctx.chainlinkFeed.updateAnswer(nextAnswer) - // } -} + const clRptData = await ctx.targetPrRefFeed!.latestRoundData() + const clRptDecimals = await ctx.targetPrRefFeed!.decimals() -// prettier-ignore -const reduceRefPerTok = async ( - ctx: MorphoAaveCollateralFixtureContext, - pctDecrease: BigNumberish -) => { - await changeRefPerTok( - ctx, - bn(pctDecrease).mul(-1) - ) -} -// prettier-ignore -const increaseRefPerTok = async ( - ctx: MorphoAaveCollateralFixtureContext, - pctIncrease: BigNumberish -) => { - await changeRefPerTok( - ctx, - bn(pctIncrease) - ) -} + const expctPrice = clData.answer + .mul(bn(10).pow(18 - clDecimals)) + .mul(clRptData.answer.mul(bn(10).pow(18 - clRptDecimals))) + .div(fp('1')) + return expctPrice + } -const getExpectedPrice = async (ctx: MorphoAaveCollateralFixtureContext): Promise => { - const clData = await ctx.chainlinkFeed.latestRoundData() - const clDecimals = await ctx.chainlinkFeed.decimals() + /* + Define collateral-specific tests + */ - const clRptData = await ctx.targetPrRefFeed!.latestRoundData() - const clRptDecimals = await ctx.targetPrRefFeed!.decimals() + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificConstructorTests = () => {} - const expctPrice = clData.answer - .mul(bn(10).pow(18 - clDecimals)) - .mul(clRptData.answer.mul(bn(10).pow(18 - clRptDecimals))) - .div(fp('1')) - return expctPrice + // eslint-disable-next-line @typescript-eslint/no-empty-function + const collateralSpecificStatusTests = () => {} + // eslint-disable-next-line @typescript-eslint/no-empty-function + const beforeEachRewardsTest = async () => {} + const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itHasRevenueHiding: it, + resetFork: getResetFork(FORK_BLOCK), + collateralName, + chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, + } + + collateralTests(opts) } /* - Define collateral-specific tests + Run the test suite */ - -// eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificConstructorTests = () => {} - -// eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificStatusTests = () => {} -// eslint-disable-next-line @typescript-eslint/no-empty-function -const beforeEachRewardsTest = async () => {} - -export const defaultCollateralOpts: MAFiatCollateralOpts = { +makeAaveNonFiatCollateralTestSuite('MorphoAAVEV2NonFiatCollateral - WBTC', { targetName: ethers.utils.formatBytes32String('BTC'), - underlyingToken: networkConfig[1].tokens.WBTC!, - poolToken: networkConfig[1].tokens.aWBTC!, + underlyingToken: configToUse.tokens.WBTC!, + poolToken: configToUse.tokens.aWBTC!, priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: networkConfig[1].chainlinkFeeds.WBTC!, - targetPrRefFeed: networkConfig[1].chainlinkFeeds.wBTCBTC!, + chainlinkFeed: configToUse.chainlinkFeeds.WBTC!, + targetPrRefFeed: configToUse.chainlinkFeeds.wBTCBTC!, oracleTimeout: ORACLE_TIMEOUT, oracleError: ORACLE_ERROR, - maxTradeVolume: parseEther('100'), + maxTradeVolume: fp('1e6'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, revenueHiding: fp('0'), defaultPrice: parseUnits('30000', 8), defaultRefPerTok: parseUnits('1', 8), refPerTokChainlinkTimeout: PRICE_TIMEOUT, -} - -/* - Run the test suite -*/ - -const opts = { - deployCollateral, - collateralSpecificConstructorTests, - collateralSpecificStatusTests, - beforeEachRewardsTest, - makeCollateralFixtureContext, - mintCollateralTo, - reduceTargetPerRef, - increaseTargetPerRef, - reduceRefPerTok, - increaseRefPerTok, - getExpectedPrice, - itClaimsRewards: it.skip, - itChecksTargetPerRefDefault: it.skip, - itChecksRefPerTokDefault: it, - itChecksPriceChanges: it, - itHasRevenueHiding: it, - resetFork: getResetFork(FORK_BLOCK), - collateralName: 'MorphoAAVEV2NonFiatCollateral', - chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, -} +}) -collateralTests(opts) +makeAaveNonFiatCollateralTestSuite('MorphoAAVEV2NonFiatCollateral - stETH', { + targetName: ethers.utils.formatBytes32String('ETH'), + underlyingToken: configToUse.tokens.stETH!, + poolToken: configToUse.tokens.astETH!, + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: configToUse.chainlinkFeeds.ETH!, + targetPrRefFeed: configToUse.chainlinkFeeds.stETHETH!, + oracleTimeout: ORACLE_TIMEOUT, + oracleError: ORACLE_ERROR, + maxTradeVolume: fp('1e6'), + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: fp('0'), + defaultPrice: parseUnits('1800', 8), + defaultRefPerTok: parseUnits('1', 8), + refPerTokChainlinkTimeout: PRICE_TIMEOUT, +}) diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts index 1544f99966..16dd346ae7 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts @@ -31,9 +31,7 @@ interface MAFiatCollateralOpts extends CollateralOpts { defaultRefPerTok?: BigNumberish } -export const deployCollateral = async ( - opts: MAFiatCollateralOpts = {} -): Promise => { +const deployCollateral = async (opts: MAFiatCollateralOpts = {}): Promise => { if (opts.defaultThreshold == null && opts.delayUntilDefault === 0) { opts.defaultThreshold = fp('0.001') } @@ -191,7 +189,7 @@ const collateralSpecificStatusTests = () => {} // eslint-disable-next-line @typescript-eslint/no-empty-function const beforeEachRewardsTest = async () => {} -export const defaultCollateralOpts: MAFiatCollateralOpts = { +const defaultCollateralOpts: MAFiatCollateralOpts = { targetName: ethers.utils.formatBytes32String('ETH'), underlyingToken: networkConfig[1].tokens.stETH!, poolToken: networkConfig[1].tokens.astETH!, @@ -229,7 +227,7 @@ const opts = { itChecksPriceChanges: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), - collateralName: 'MorphoAAVEV2SelfReferentialCollateral', + collateralName: 'MorphoAAVEV2SelfReferentialCollateral - WETH', chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, } diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts index 1b4122b604..c529655694 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts @@ -8,9 +8,10 @@ import { expect } from 'chai' import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' type ITokenSymbol = keyof ITokens +const networkConfigToUse = networkConfig[31337] const mkToken = (symbol: ITokenSymbol) => ({ - address: networkConfig[1].tokens[symbol]! as string, + address: networkConfigToUse.tokens[symbol]! as string, symbol: symbol, }) const mkTestCase = (symbol: T, amount: string) => ({ @@ -20,6 +21,7 @@ const mkTestCase = (symbol: T, amount: string) => ({ }) const TOKENS_TO_TEST = [ + mkTestCase('USDC', '1000.0'), mkTestCase('USDT', '1000.0'), mkTestCase('DAI', '1000.0'), mkTestCase('WETH', '1.0'), @@ -37,14 +39,14 @@ const execTestForToken = ({ token, poolToken, amount }: ITestSuiteVariant) => { } const instances = { underlying: factories.ERC20Mock.attach(token.address), - morpho: factories.ERC20Mock.attach(networkConfig[1].tokens.MORPHO!), + morpho: factories.ERC20Mock.attach(networkConfigToUse.tokens.MORPHO!), tokenVault: await factories.MorphoTokenisedDeposit.deploy({ underlyingERC20: token.address, poolToken: poolToken.address, - morphoController: networkConfig[1].MORPHO_AAVE_CONTROLLER!, - morphoLens: networkConfig[1].MORPHO_AAVE_LENS!, - rewardsDistributor: networkConfig[1].MORPHO_REWARDS_DISTRIBUTOR!, - rewardToken: networkConfig[1].tokens.MORPHO!, + morphoController: networkConfigToUse.MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfigToUse.MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfigToUse.MORPHO_REWARDS_DISTRIBUTOR!, + rewardToken: networkConfigToUse.tokens.MORPHO!, }), } const underlyingDecimals = await instances.underlying.decimals() diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap new file mode 100644 index 0000000000..637d4b1476 --- /dev/null +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `135022`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 2`] = `130553`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 1`] = `180391`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 2`] = `111194`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `135022`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `130553`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 1`] = `111194`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 2`] = `111194`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `172705`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 2`] = `172705`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 1`] = `180256`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 2`] = `172987`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `135225`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `130756`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `180797`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `111397`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `135225`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `130756`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `111397`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `111397`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `173111`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `173111`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `180662`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `173393`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134378`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129909`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 1`] = `179103`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 2`] = `110550`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134378`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129909`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 1`] = `110550`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 2`] = `110550`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `171417`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 2`] = `171417`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 1`] = `178968`; + +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 2`] = `171699`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap new file mode 100644 index 0000000000..fd7e828651 --- /dev/null +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134445`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129976`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 1`] = `200486`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 2`] = `110550`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `184309`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `179840`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 1`] = `110550`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 2`] = `110550`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `192800`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 2`] = `192800`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 1`] = `197551`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 2`] = `193082`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `168077`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `163608`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 1`] = `239750`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 2`] = `144182`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `223573`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `219104`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 1`] = `144182`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 2`] = `144182`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `232064`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `232064`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 1`] = `236815`; + +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 2`] = `232346`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap new file mode 100644 index 0000000000..a8faf3cbe4 --- /dev/null +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `202160`; + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `197691`; + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 1`] = `218342`; + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 2`] = `144160`; + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `202160`; + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `197691`; + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `210656`; + +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `210656`; diff --git a/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap new file mode 100644 index 0000000000..35bb6dd12a --- /dev/null +++ b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `71915`; + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `67446`; + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 1`] = `109216`; + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 2`] = `48056`; + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `71915`; + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `67446`; + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `101530`; + +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `101530`; diff --git a/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap b/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap new file mode 100644 index 0000000000..a11f838460 --- /dev/null +++ b/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `56267`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `51799`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `69964`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `23429`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `56267`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `51799`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `23429`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `23429`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `67225`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `67225`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `74775`; + +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `67507`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `56267`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `51799`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `69964`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `23429`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `56267`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `51799`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `23429`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `23429`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `67225`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `67225`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `74775`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `67507`; diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index 0e0cede769..3da2c08a10 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `11745060`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12085575`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9481763`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9836953`; -exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2292984`; +exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2281990`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `12996739`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13002663`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `25285711`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20658478`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10751848`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `11092363`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8471463`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8826653`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4535170`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `4481356`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `17723377`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `13292858`; diff --git a/test/utils/trades.ts b/test/utils/trades.ts index 7302e0489f..7c7e75d394 100644 --- a/test/utils/trades.ts +++ b/test/utils/trades.ts @@ -104,21 +104,29 @@ export const dutchBuyAmount = async ( const leftover = slippage1e18.mod(fp('1')) const slippage = slippage1e18.div(fp('1')).add(leftover.gte(fp('0.5')) ? 1 : 0) - const lowPrice = sellLow.mul(fp('1').sub(slippage)).div(buyHigh) - const middlePrice = divCeil(sellHigh.mul(fp('1')), buyLow) - - const FORTY_PERCENT = fp('0.4') // 40% - const SIXTY_PERCENT = fp('0.6') // 60% + const worstPrice = sellLow.mul(fp('1').sub(slippage)).div(buyHigh) + const bestPrice = divCeil(sellHigh.mul(fp('1')), buyLow) + const highPrice = divCeil(sellHigh.mul(fp('1.5')), buyLow) let price: BigNumber - if (progression.lt(FORTY_PERCENT)) { - const exp = divRound(bn('6907752').mul(FORTY_PERCENT.sub(progression)), FORTY_PERCENT) + if (progression.lt(fp('0.2'))) { + const exp = divRound(bn('6502287').mul(fp('0.2').sub(progression)), fp('0.2')) const divisor = new Decimal('999999').div('1000000').pow(exp.toString()) - price = divCeil(middlePrice.mul(fp('1')), fp(divisor.toString())) - } else { - price = middlePrice.sub( - middlePrice.sub(lowPrice).mul(progression.sub(FORTY_PERCENT)).div(SIXTY_PERCENT) + price = divCeil(highPrice.mul(fp('1')), fp(divisor.toString())) + } else if (progression.lt(fp('0.45'))) { + price = highPrice.sub( + highPrice + .sub(bestPrice) + .mul(progression.sub(fp('0.2'))) + .div(fp('0.25')) + ) + } else if (progression.lt(fp('0.95'))) { + price = bestPrice.sub( + bestPrice + .sub(worstPrice) + .mul(progression.sub(fp('0.45'))) + .div(fp('0.5')) ) - } + } else price = worstPrice return divCeil(outAmount.mul(price), fp('1')) } diff --git a/yarn.lock b/yarn.lock index b3f0942254..cd38842265 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,13 @@ __metadata: version: 6 cacheKey: 8 +"@aashutoshrathi/word-wrap@npm:^1.2.3": + version: 1.2.6 + resolution: "@aashutoshrathi/word-wrap@npm:1.2.6" + checksum: ada901b9e7c680d190f1d012c84217ce0063d8f5c5a7725bb91ec3c5ed99bb7572680eb2d2938a531ccbaec39a95422fcd8a6b4a13110c7d98dd75402f66a0cd + languageName: node + linkType: hard + "@aave/protocol-v2@npm:^1.0.1": version: 1.0.1 resolution: "@aave/protocol-v2@npm:1.0.1" @@ -14,13 +21,13 @@ __metadata: languageName: node linkType: hard -"@ampproject/remapping@npm:^2.1.0": - version: 2.2.0 - resolution: "@ampproject/remapping@npm:2.2.0" +"@ampproject/remapping@npm:^2.2.0": + version: 2.2.1 + resolution: "@ampproject/remapping@npm:2.2.1" dependencies: - "@jridgewell/gen-mapping": ^0.1.0 + "@jridgewell/gen-mapping": ^0.3.0 "@jridgewell/trace-mapping": ^0.3.9 - checksum: d74d170d06468913921d72430259424b7e4c826b5a7d39ff839a29d547efb97dc577caa8ba3fb5cf023624e9af9d09651afc3d4112a45e2050328abc9b3a2292 + checksum: 03c04fd526acc64a1f4df22651186f3e5ef0a9d6d6530ce4482ec9841269cf7a11dbb8af79237c282d721c5312024ff17529cd72cc4768c11e999b58e2302079 languageName: node linkType: hard @@ -47,11 +54,12 @@ __metadata: linkType: hard "@aws-sdk/types@npm:^3.1.0": - version: 3.329.0 - resolution: "@aws-sdk/types@npm:3.329.0" + version: 3.378.0 + resolution: "@aws-sdk/types@npm:3.378.0" dependencies: + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 2bbcd8e6ba2f813dc220c60e7ac0fa0d0990d49f88030fd8235479586716016c8f9d7aeaf1f4c1a64694275f33690bd1dd4ed46e127983201061e063da73f426 + checksum: c4c7ebb48a625cb990a1288466f2dd8f0d770078cc77b60d5ee4a803b473ff41df474271dff26d3dadad151d5a016b398167738dd4926266ff1cd04585d4d8e8 languageName: node linkType: hard @@ -64,188 +72,196 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/code-frame@npm:7.18.6" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/code-frame@npm:7.22.5" dependencies: - "@babel/highlight": ^7.18.6 - checksum: 195e2be3172d7684bf95cff69ae3b7a15a9841ea9d27d3c843662d50cdd7d6470fd9c8e64be84d031117e4a4083486effba39f9aef6bbb2c89f7f21bcfba33ba + "@babel/highlight": ^7.22.5 + checksum: cfe804f518f53faaf9a1d3e0f9f74127ab9a004912c3a16fda07fb6a633393ecb9918a053cb71804204c1b7ec3d49e1699604715e2cfb0c9f7bc4933d324ebb6 languageName: node linkType: hard -"@babel/compat-data@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/compat-data@npm:7.18.6" - checksum: fd73a1bd7bc29be5528d2ef78248929ed3ee72e0edb69cef6051e0aad0bf8087594db6cd9e981f0d7f5bfc274fdbb77306d8abea8ceb71e95c18afc3ebd81828 +"@babel/compat-data@npm:^7.22.9": + version: 7.22.9 + resolution: "@babel/compat-data@npm:7.22.9" + checksum: bed77d9044ce948b4327b30dd0de0779fa9f3a7ed1f2d31638714ed00229fa71fc4d1617ae0eb1fad419338d3658d0e9a5a083297451e09e73e078d0347ff808 languageName: node linkType: hard "@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3": - version: 7.18.6 - resolution: "@babel/core@npm:7.18.6" - dependencies: - "@ampproject/remapping": ^2.1.0 - "@babel/code-frame": ^7.18.6 - "@babel/generator": ^7.18.6 - "@babel/helper-compilation-targets": ^7.18.6 - "@babel/helper-module-transforms": ^7.18.6 - "@babel/helpers": ^7.18.6 - "@babel/parser": ^7.18.6 - "@babel/template": ^7.18.6 - "@babel/traverse": ^7.18.6 - "@babel/types": ^7.18.6 + version: 7.22.9 + resolution: "@babel/core@npm:7.22.9" + dependencies: + "@ampproject/remapping": ^2.2.0 + "@babel/code-frame": ^7.22.5 + "@babel/generator": ^7.22.9 + "@babel/helper-compilation-targets": ^7.22.9 + "@babel/helper-module-transforms": ^7.22.9 + "@babel/helpers": ^7.22.6 + "@babel/parser": ^7.22.7 + "@babel/template": ^7.22.5 + "@babel/traverse": ^7.22.8 + "@babel/types": ^7.22.5 convert-source-map: ^1.7.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 - json5: ^2.2.1 - semver: ^6.3.0 - checksum: 711459ebf7afab7b8eff88b7155c3f4a62690545f1c8c2eb6ba5ebaed01abeecb984cf9657847a2151ad24a5645efce765832aa343ce0f0386f311b67b59589a + json5: ^2.2.2 + semver: ^6.3.1 + checksum: 7bf069aeceb417902c4efdaefab1f7b94adb7dea694a9aed1bda2edf4135348a080820529b1a300c6f8605740a00ca00c19b2d5e74b5dd489d99d8c11d5e56d1 languageName: node linkType: hard -"@babel/generator@npm:^7.18.6, @babel/generator@npm:^7.7.2": - version: 7.18.7 - resolution: "@babel/generator@npm:7.18.7" +"@babel/generator@npm:^7.22.7, @babel/generator@npm:^7.22.9, @babel/generator@npm:^7.7.2": + version: 7.22.9 + resolution: "@babel/generator@npm:7.22.9" dependencies: - "@babel/types": ^7.18.7 + "@babel/types": ^7.22.5 "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 jsesc: ^2.5.1 - checksum: aad4b6873130165e9483af2888bce5a3a5ad9cca0757fc90ae11a0396757d0b295a3bff49282c8df8ab01b31972cc855ae88fd9ddc9ab00d9427dc0e01caeea9 + checksum: 7c9d2c58b8d5ac5e047421a6ab03ec2ff5d9a5ff2c2212130a0055e063ac349e0b19d435537d6886c999771aef394832e4f54cd9fc810100a7f23d982f6af06b languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-compilation-targets@npm:7.18.6" +"@babel/helper-compilation-targets@npm:^7.22.9": + version: 7.22.9 + resolution: "@babel/helper-compilation-targets@npm:7.22.9" dependencies: - "@babel/compat-data": ^7.18.6 - "@babel/helper-validator-option": ^7.18.6 - browserslist: ^4.20.2 - semver: ^6.3.0 + "@babel/compat-data": ^7.22.9 + "@babel/helper-validator-option": ^7.22.5 + browserslist: ^4.21.9 + lru-cache: ^5.1.1 + semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0 - checksum: f09ddaddc83c241cb7a040025e2ba558daa1c950ce878604d91230aed8d8a90f10dfd5bb0b67bc5b3db8af1576a0d0dac1d65959a06a17259243dbb5730d0ed1 + checksum: ea0006c6a93759025f4a35a25228ae260538c9f15023e8aac2a6d45ca68aef4cf86cfc429b19af9a402cbdd54d5de74ad3fbcf6baa7e48184dc079f1a791e178 languageName: node linkType: hard -"@babel/helper-environment-visitor@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-environment-visitor@npm:7.18.6" - checksum: 64fce65a26efb50d2496061ab2de669dc4c42175a8e05c82279497127e5c542538ed22b38194f6f5a4e86bed6ef5a4890aed23408480db0555728b4ca660fc9c +"@babel/helper-environment-visitor@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-environment-visitor@npm:7.22.5" + checksum: 248532077d732a34cd0844eb7b078ff917c3a8ec81a7f133593f71a860a582f05b60f818dc5049c2212e5baa12289c27889a4b81d56ef409b4863db49646c4b1 languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-function-name@npm:7.18.6" +"@babel/helper-function-name@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-function-name@npm:7.22.5" dependencies: - "@babel/template": ^7.18.6 - "@babel/types": ^7.18.6 - checksum: bf84c2e0699aa07c3559d4262d199d4a9d0320037c2932efe3246866c3e01ce042c9c2131b5db32ba2409a9af01fb468171052819af759babc8ca93bdc6c9aeb + "@babel/template": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: 6b1f6ce1b1f4e513bf2c8385a557ea0dd7fa37971b9002ad19268ca4384bbe90c09681fe4c076013f33deabc63a53b341ed91e792de741b4b35e01c00238177a languageName: node linkType: hard -"@babel/helper-hoist-variables@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-hoist-variables@npm:7.18.6" +"@babel/helper-hoist-variables@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-hoist-variables@npm:7.22.5" dependencies: - "@babel/types": ^7.18.6 - checksum: fd9c35bb435fda802bf9ff7b6f2df06308a21277c6dec2120a35b09f9de68f68a33972e2c15505c1a1a04b36ec64c9ace97d4a9e26d6097b76b4396b7c5fa20f + "@babel/types": ^7.22.5 + checksum: 394ca191b4ac908a76e7c50ab52102669efe3a1c277033e49467913c7ed6f7c64d7eacbeabf3bed39ea1f41731e22993f763b1edce0f74ff8563fd1f380d92cc languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-module-imports@npm:7.18.6" +"@babel/helper-module-imports@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-module-imports@npm:7.22.5" dependencies: - "@babel/types": ^7.18.6 - checksum: f393f8a3b3304b1b7a288a38c10989de754f01d29caf62ce7c4e5835daf0a27b81f3ac687d9d2780d39685aae7b55267324b512150e7b2be967b0c493b6a1def + "@babel/types": ^7.22.5 + checksum: 9ac2b0404fa38b80bdf2653fbeaf8e8a43ccb41bd505f9741d820ed95d3c4e037c62a1bcdcb6c9527d7798d2e595924c4d025daed73283badc180ada2c9c49ad languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-module-transforms@npm:7.18.6" +"@babel/helper-module-transforms@npm:^7.22.9": + version: 7.22.9 + resolution: "@babel/helper-module-transforms@npm:7.22.9" dependencies: - "@babel/helper-environment-visitor": ^7.18.6 - "@babel/helper-module-imports": ^7.18.6 - "@babel/helper-simple-access": ^7.18.6 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/helper-validator-identifier": ^7.18.6 - "@babel/template": ^7.18.6 - "@babel/traverse": ^7.18.6 - "@babel/types": ^7.18.6 - checksum: 75d90be9ecd314fe2f1b668ce065d7e8b3dff82eddea88480259c5d4bd54f73a909d0998909ffe734a44ba8be85ba233359033071cc800db209d37173bd26db2 + "@babel/helper-environment-visitor": ^7.22.5 + "@babel/helper-module-imports": ^7.22.5 + "@babel/helper-simple-access": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/helper-validator-identifier": ^7.22.5 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 2751f77660518cf4ff027514d6f4794f04598c6393be7b04b8e46c6e21606e11c19f3f57ab6129a9c21bacdf8b3ffe3af87bb401d972f34af2d0ffde02ac3001 languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.8.0": - version: 7.18.6 - resolution: "@babel/helper-plugin-utils@npm:7.18.6" - checksum: 3dbfceb6c10fdf6c78a0e57f24e991ff8967b8a0bd45fe0314fb4a8ccf7c8ad4c3778c319a32286e7b1f63d507173df56b4e69fb31b71e1b447a73efa1ca723e +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.22.5 + resolution: "@babel/helper-plugin-utils@npm:7.22.5" + checksum: c0fc7227076b6041acd2f0e818145d2e8c41968cc52fb5ca70eed48e21b8fe6dd88a0a91cbddf4951e33647336eb5ae184747ca706817ca3bef5e9e905151ff5 languageName: node linkType: hard -"@babel/helper-simple-access@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-simple-access@npm:7.18.6" +"@babel/helper-simple-access@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-simple-access@npm:7.22.5" dependencies: - "@babel/types": ^7.18.6 - checksum: 37cd36eef199e0517845763c1e6ff6ea5e7876d6d707a6f59c9267c547a50aa0e84260ba9285d49acfaf2cfa0a74a772d92967f32ac1024c961517d40b6c16a5 + "@babel/types": ^7.22.5 + checksum: fe9686714caf7d70aedb46c3cce090f8b915b206e09225f1e4dbc416786c2fdbbee40b38b23c268b7ccef749dd2db35f255338fb4f2444429874d900dede5ad2 languageName: node linkType: hard -"@babel/helper-split-export-declaration@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-split-export-declaration@npm:7.18.6" +"@babel/helper-split-export-declaration@npm:^7.22.6": + version: 7.22.6 + resolution: "@babel/helper-split-export-declaration@npm:7.22.6" dependencies: - "@babel/types": ^7.18.6 - checksum: c6d3dede53878f6be1d869e03e9ffbbb36f4897c7cc1527dc96c56d127d834ffe4520a6f7e467f5b6f3c2843ea0e81a7819d66ae02f707f6ac057f3d57943a2b + "@babel/types": ^7.22.5 + checksum: e141cace583b19d9195f9c2b8e17a3ae913b7ee9b8120246d0f9ca349ca6f03cb2c001fd5ec57488c544347c0bb584afec66c936511e447fd20a360e591ac921 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-string-parser@npm:7.22.5" + checksum: 836851ca5ec813077bbb303acc992d75a360267aa3b5de7134d220411c852a6f17de7c0d0b8c8dcc0f567f67874c00f4528672b2a4f1bc978a3ada64c8c78467 languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-validator-identifier@npm:7.18.6" - checksum: e295254d616bbe26e48c196a198476ab4d42a73b90478c9842536cf910ead887f5af6b5c4df544d3052a25ccb3614866fa808dc1e3a5a4291acd444e243c0648 +"@babel/helper-validator-identifier@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-validator-identifier@npm:7.22.5" + checksum: 7f0f30113474a28298c12161763b49de5018732290ca4de13cdaefd4fd0d635a6fe3f6686c37a02905fb1e64f21a5ee2b55140cf7b070e729f1bd66866506aea languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-validator-option@npm:7.18.6" - checksum: f9cc6eb7cc5d759c5abf006402180f8d5e4251e9198197428a97e05d65eb2f8ae5a0ce73b1dfd2d35af41d0eb780627a64edf98a4e71f064eeeacef8de58f2cf +"@babel/helper-validator-option@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-validator-option@npm:7.22.5" + checksum: bbeca8a85ee86990215c0424997438b388b8d642d69b9f86c375a174d3cdeb270efafd1ff128bc7a1d370923d13b6e45829ba8581c027620e83e3a80c5c414b3 languageName: node linkType: hard -"@babel/helpers@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helpers@npm:7.18.6" +"@babel/helpers@npm:^7.22.6": + version: 7.22.6 + resolution: "@babel/helpers@npm:7.22.6" dependencies: - "@babel/template": ^7.18.6 - "@babel/traverse": ^7.18.6 - "@babel/types": ^7.18.6 - checksum: 5dea4fa53776703ae4190cacd3f81464e6e00cf0b6908ea9b0af2b3d9992153f3746dd8c33d22ec198f77a8eaf13a273d83cd8847f7aef983801e7bfafa856ec + "@babel/template": ^7.22.5 + "@babel/traverse": ^7.22.6 + "@babel/types": ^7.22.5 + checksum: 5c1f33241fe7bf7709868c2105134a0a86dca26a0fbd508af10a89312b1f77ca38ebae43e50be3b208613c5eacca1559618af4ca236f0abc55d294800faeff30 languageName: node linkType: hard -"@babel/highlight@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/highlight@npm:7.18.6" +"@babel/highlight@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/highlight@npm:7.22.5" dependencies: - "@babel/helper-validator-identifier": ^7.18.6 + "@babel/helper-validator-identifier": ^7.22.5 chalk: ^2.0.0 js-tokens: ^4.0.0 - checksum: 92d8ee61549de5ff5120e945e774728e5ccd57fd3b2ed6eace020ec744823d4a98e242be1453d21764a30a14769ecd62170fba28539b211799bbaf232bbb2789 + checksum: f61ae6de6ee0ea8d9b5bcf2a532faec5ab0a1dc0f7c640e5047fc61630a0edb88b18d8c92eb06566d30da7a27db841aca11820ecd3ebe9ce514c9350fbed39c4 languageName: node linkType: hard -"@babel/parser@npm:^7.14.7, @babel/parser@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/parser@npm:7.18.6" +"@babel/parser@npm:^7.14.7, @babel/parser@npm:^7.22.5, @babel/parser@npm:^7.22.7": + version: 7.22.7 + resolution: "@babel/parser@npm:7.22.7" bin: parser: ./bin/babel-parser.js - checksum: 533ffc26667b7e2e0d87ae11368d90b6a3a468734d6dfe9c4697c24f48373cf9cc35ee08e416728f087fc56531b68022f752097941feddc60e0223d69a4d4cad + checksum: 02209ddbd445831ee8bf966fdf7c29d189ed4b14343a68eb2479d940e7e3846340d7cc6bd654a5f3d87d19dc84f49f50a58cf9363bee249dc5409ff3ba3dab54 languageName: node linkType: hard @@ -382,52 +398,53 @@ __metadata: linkType: hard "@babel/plugin-syntax-typescript@npm:^7.7.2": - version: 7.18.6 - resolution: "@babel/plugin-syntax-typescript@npm:7.18.6" + version: 7.22.5 + resolution: "@babel/plugin-syntax-typescript@npm:7.22.5" dependencies: - "@babel/helper-plugin-utils": ^7.18.6 + "@babel/helper-plugin-utils": ^7.22.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 2cde73725ec51118ebf410bf02d78781c03fa4d3185993fcc9d253b97443381b621c44810084c5dd68b92eb8bdfae0e5b163e91b32bebbb33852383d1815c05d + checksum: 8ab7718fbb026d64da93681a57797d60326097fd7cb930380c8bffd9eb101689e90142c760a14b51e8e69c88a73ba3da956cb4520a3b0c65743aee5c71ef360a languageName: node linkType: hard -"@babel/template@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/template@npm:7.18.6" +"@babel/template@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/template@npm:7.22.5" dependencies: - "@babel/code-frame": ^7.18.6 - "@babel/parser": ^7.18.6 - "@babel/types": ^7.18.6 - checksum: cb02ed804b7b1938dbecef4e01562013b80681843dd391933315b3dd9880820def3b5b1bff6320d6e4c6a1d63d1d5799630d658ec6b0369c5505e7e4029c38fb + "@babel/code-frame": ^7.22.5 + "@babel/parser": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: c5746410164039aca61829cdb42e9a55410f43cace6f51ca443313f3d0bdfa9a5a330d0b0df73dc17ef885c72104234ae05efede37c1cc8a72dc9f93425977a3 languageName: node linkType: hard -"@babel/traverse@npm:^7.18.6, @babel/traverse@npm:^7.7.2": - version: 7.18.6 - resolution: "@babel/traverse@npm:7.18.6" +"@babel/traverse@npm:^7.22.6, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.7.2": + version: 7.22.8 + resolution: "@babel/traverse@npm:7.22.8" dependencies: - "@babel/code-frame": ^7.18.6 - "@babel/generator": ^7.18.6 - "@babel/helper-environment-visitor": ^7.18.6 - "@babel/helper-function-name": ^7.18.6 - "@babel/helper-hoist-variables": ^7.18.6 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/parser": ^7.18.6 - "@babel/types": ^7.18.6 + "@babel/code-frame": ^7.22.5 + "@babel/generator": ^7.22.7 + "@babel/helper-environment-visitor": ^7.22.5 + "@babel/helper-function-name": ^7.22.5 + "@babel/helper-hoist-variables": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/parser": ^7.22.7 + "@babel/types": ^7.22.5 debug: ^4.1.0 globals: ^11.1.0 - checksum: 5427a9db63984b2600f62b257dab18e3fc057997b69d708573bfc88eb5eacd6678fb24fddba082d6ac050734b8846ce110960be841ea1e461d66e2cde72b6b07 + checksum: a381369bc3eedfd13ed5fef7b884657f1c29024ea7388198149f0edc34bd69ce3966e9f40188d15f56490a5e12ba250ccc485f2882b53d41b054fccefb233e33 languageName: node linkType: hard -"@babel/types@npm:^7.18.6, @babel/types@npm:^7.18.7, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3": - version: 7.18.7 - resolution: "@babel/types@npm:7.18.7" +"@babel/types@npm:^7.20.7, @babel/types@npm:^7.22.5, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3": + version: 7.22.5 + resolution: "@babel/types@npm:7.22.5" dependencies: - "@babel/helper-validator-identifier": ^7.18.6 + "@babel/helper-string-parser": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.5 to-fast-properties: ^2.0.0 - checksum: 3114ce161c4ebcb70271e168aa5af5cecedf3278209161d5ba6124bd3f9cb02e3f3ace587ad1b53f7baa153b6b3714720721c72a9ef3ec451663862f9cc1f014 + checksum: c13a9c1dc7d2d1a241a2f8363540cb9af1d66e978e8984b400a20c4f38ba38ca29f06e26a0f2d49a70bad9e57615dac09c35accfddf1bb90d23cd3e0a0bab892 languageName: node linkType: hard @@ -504,20 +521,20 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^1.3.0": - version: 1.3.0 - resolution: "@eslint/eslintrc@npm:1.3.0" +"@eslint/eslintrc@npm:^1.2.2": + version: 1.4.1 + resolution: "@eslint/eslintrc@npm:1.4.1" dependencies: ajv: ^6.12.4 debug: ^4.3.2 - espree: ^9.3.2 - globals: ^13.15.0 + espree: ^9.4.0 + globals: ^13.19.0 ignore: ^5.2.0 import-fresh: ^3.2.1 js-yaml: ^4.1.0 minimatch: ^3.1.2 strip-json-comments: ^3.1.1 - checksum: a1e734ad31a8b5328dce9f479f185fd4fc83dd7f06c538e1fa457fd8226b89602a55cc6458cd52b29573b01cdfaf42331be8cfc1fec732570086b591f4ed6515 + checksum: cd3e5a8683db604739938b1c1c8b77927dc04fce3e28e0c88e7f2cd4900b89466baf83dfbad76b2b9e4d2746abdd00dd3f9da544d3e311633d8693f327d04cd7 languageName: node linkType: hard @@ -960,19 +977,12 @@ __metadata: languageName: node linkType: hard -"@gar/promisify@npm:^1.1.3": - version: 1.1.3 - resolution: "@gar/promisify@npm:1.1.3" - checksum: 4059f790e2d07bf3c3ff3e0fec0daa8144fe35c1f6e0111c9921bd32106adaa97a4ab096ad7dab1e28ee6a9060083c4d1a4ada42a7f5f3f7a96b8812e2b757c1 - languageName: node - linkType: hard - "@graphql-typed-document-node/core@npm:^3.1.1": - version: 3.1.2 - resolution: "@graphql-typed-document-node/core@npm:3.1.2" + version: 3.2.0 + resolution: "@graphql-typed-document-node/core@npm:3.2.0" peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: a61afa025acdabd7833e4f654a5802fc1a526171f81e0c435c8e651050a5a0682499a2c7a51304ceb61fde36cd69fc7975ce5e1b16b9ba7ea474c649f33eea8b + checksum: fa44443accd28c8cf4cb96aaaf39d144a22e8b091b13366843f4e97d19c7bfeaf609ce3c7603a4aeffe385081eaf8ea245d078633a7324c11c5ec4b2011bb76d languageName: node linkType: hard @@ -994,6 +1004,20 @@ __metadata: languageName: node linkType: hard +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: ^5.1.2 + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: ^7.0.1 + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: ^8.1.0 + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 4a473b9b32a7d4d3cfb7a614226e555091ff0c5a29a1734c28c72a182c2f6699b26fc6b5c2131dfd841e86b185aea714c72201d7c98c2fba5f17709333a67aeb + languageName: node + linkType: hard + "@istanbuljs/load-nyc-config@npm:^1.0.0": version: 1.1.0 resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" @@ -1014,129 +1038,133 @@ __metadata: languageName: node linkType: hard -"@jest/console@npm:^28.1.1": - version: 28.1.1 - resolution: "@jest/console@npm:28.1.1" +"@jest/console@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/console@npm:28.1.3" dependencies: - "@jest/types": ^28.1.1 + "@jest/types": ^28.1.3 "@types/node": "*" chalk: ^4.0.0 - jest-message-util: ^28.1.1 - jest-util: ^28.1.1 + jest-message-util: ^28.1.3 + jest-util: ^28.1.3 slash: ^3.0.0 - checksum: ddf3b9e9b003a99d6686ecd89c263fda8f81303277f64cca6e434106fa3556c456df6023cdba962851df16880e044bfbae264daa5f67f7ac28712144b5f1007e + checksum: fe50d98d26d02ce2901c76dff4bd5429a33c13affb692c9ebf8a578ca2f38a5dd854363d40d6c394f215150791fd1f692afd8e730a4178dda24107c8dfd9750a languageName: node linkType: hard -"@jest/expect-utils@npm:^28.1.1": - version: 28.1.1 - resolution: "@jest/expect-utils@npm:28.1.1" +"@jest/expect-utils@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/expect-utils@npm:28.1.3" dependencies: jest-get-type: ^28.0.2 - checksum: 46a2ad754b10bc649c36a5914f887bea33a43bb868946508892a73f1da99065b17167dc3c0e3e299c7cea82c6be1e9d816986e120d7ae3e1be511f64cfc1d3d3 + checksum: 808ea3a68292a7e0b95490fdd55605c430b4cf209ea76b5b61bfb2a1badcb41bc046810fe4e364bd5fe04663978aa2bd73d8f8465a761dd7c655aeb44cf22987 languageName: node linkType: hard -"@jest/schemas@npm:^28.0.2": - version: 28.0.2 - resolution: "@jest/schemas@npm:28.0.2" +"@jest/schemas@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/schemas@npm:28.1.3" dependencies: - "@sinclair/typebox": ^0.23.3 - checksum: 6a177e97b112c99f377697fe803a34f4489b92cd07949876250c69edc9029c7cbda771fcbb03caebd20ffbcfa89b9c22b4dc9d1e9a7fbc9873185459b48ba780 + "@sinclair/typebox": ^0.24.1 + checksum: 3cf1d4b66c9c4ffda58b246de1ddcba8e6ad085af63dccdf07922511f13b68c0cc480a7bc620cb4f3099a6f134801c747e1df7bfc7a4ef4dceefbdea3e31e1de languageName: node linkType: hard "@jest/test-result@npm:^28.1.1": - version: 28.1.1 - resolution: "@jest/test-result@npm:28.1.1" + version: 28.1.3 + resolution: "@jest/test-result@npm:28.1.3" dependencies: - "@jest/console": ^28.1.1 - "@jest/types": ^28.1.1 + "@jest/console": ^28.1.3 + "@jest/types": ^28.1.3 "@types/istanbul-lib-coverage": ^2.0.0 collect-v8-coverage: ^1.0.0 - checksum: 8812db2649a09ed423ccb33cf76162a996fc781156a489d4fd86e22615b523d72ca026c68b3699a1ea1ea274146234e09db636c49d7ea2516e0e1bb229f3013d + checksum: 957a5dd2fd2e84aabe86698f93c0825e96128ccaa23abf548b159a9b08ac74e4bde7acf4bec48479243dbdb27e4ea1b68c171846d21fb64855c6b55cead9ef27 languageName: node linkType: hard -"@jest/transform@npm:^28.1.2": - version: 28.1.2 - resolution: "@jest/transform@npm:28.1.2" +"@jest/transform@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/transform@npm:28.1.3" dependencies: "@babel/core": ^7.11.6 - "@jest/types": ^28.1.1 + "@jest/types": ^28.1.3 "@jridgewell/trace-mapping": ^0.3.13 babel-plugin-istanbul: ^6.1.1 chalk: ^4.0.0 convert-source-map: ^1.4.0 fast-json-stable-stringify: ^2.0.0 graceful-fs: ^4.2.9 - jest-haste-map: ^28.1.1 + jest-haste-map: ^28.1.3 jest-regex-util: ^28.0.2 - jest-util: ^28.1.1 + jest-util: ^28.1.3 micromatch: ^4.0.4 pirates: ^4.0.4 slash: ^3.0.0 write-file-atomic: ^4.0.1 - checksum: cd8d1bdf1a5831cdf91934dd0af1d29d4d2bcad92feb9bf7555fc0e1152cb01a9206410380af0f6221a623ffc9b6f6e6dded429d01d87b85b0777cf9d4425127 + checksum: dadf618936e0aa84342f07f532801d5bed43cdf95d1417b929e4f8782c872cff1adc84096d5a287a796d0039a2691c06d8450cce5a713a8b52fbb9f872a1e760 languageName: node linkType: hard -"@jest/types@npm:^28.1.1": - version: 28.1.1 - resolution: "@jest/types@npm:28.1.1" +"@jest/types@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/types@npm:28.1.3" dependencies: - "@jest/schemas": ^28.0.2 + "@jest/schemas": ^28.1.3 "@types/istanbul-lib-coverage": ^2.0.0 "@types/istanbul-reports": ^3.0.0 "@types/node": "*" "@types/yargs": ^17.0.8 chalk: ^4.0.0 - checksum: 3c35d3674e08da1e4bb27b8303a59c71fd19a852ff7c7827305462f48ef224b5334aa50e0d547470e1cca1f2dd15a0cff51b46618b8e61e7196908504b29f08f - languageName: node - linkType: hard - -"@jridgewell/gen-mapping@npm:^0.1.0": - version: 0.1.1 - resolution: "@jridgewell/gen-mapping@npm:0.1.1" - dependencies: - "@jridgewell/set-array": ^1.0.0 - "@jridgewell/sourcemap-codec": ^1.4.10 - checksum: 3bcc21fe786de6ffbf35c399a174faab05eb23ce6a03e8769569de28abbf4facc2db36a9ddb0150545ae23a8d35a7cf7237b2aa9e9356a7c626fb4698287d5cc + checksum: 1e258d9c063fcf59ebc91e46d5ea5984674ac7ae6cae3e50aa780d22b4405bf2c925f40350bf30013839eb5d4b5e521d956ddf8f3b7c78debef0e75a07f57350 languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.2": - version: 0.3.2 - resolution: "@jridgewell/gen-mapping@npm:0.3.2" +"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2": + version: 0.3.3 + resolution: "@jridgewell/gen-mapping@npm:0.3.3" dependencies: "@jridgewell/set-array": ^1.0.1 "@jridgewell/sourcemap-codec": ^1.4.10 "@jridgewell/trace-mapping": ^0.3.9 - checksum: 1832707a1c476afebe4d0fbbd4b9434fdb51a4c3e009ab1e9938648e21b7a97049fa6009393bdf05cab7504108413441df26d8a3c12193996e65493a4efb6882 + checksum: 4a74944bd31f22354fc01c3da32e83c19e519e3bbadafa114f6da4522ea77dd0c2842607e923a591d60a76699d819a2fbb6f3552e277efdb9b58b081390b60ab languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:^3.0.3": +"@jridgewell/resolve-uri@npm:3.1.0": version: 3.1.0 resolution: "@jridgewell/resolve-uri@npm:3.1.0" checksum: b5ceaaf9a110fcb2780d1d8f8d4a0bfd216702f31c988d8042e5f8fbe353c55d9b0f55a1733afdc64806f8e79c485d2464680ac48a0d9fcadb9548ee6b81d267 languageName: node linkType: hard -"@jridgewell/set-array@npm:^1.0.0, @jridgewell/set-array@npm:^1.0.1": +"@jridgewell/resolve-uri@npm:^3.0.3": + version: 3.1.1 + resolution: "@jridgewell/resolve-uri@npm:3.1.1" + checksum: f5b441fe7900eab4f9155b3b93f9800a916257f4e8563afbcd3b5a5337b55e52bd8ae6735453b1b745457d9f6cdb16d74cd6220bbdd98cf153239e13f6cbb653 + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.0.1": version: 1.1.2 resolution: "@jridgewell/set-array@npm:1.1.2" checksum: 69a84d5980385f396ff60a175f7177af0b8da4ddb81824cb7016a9ef914eee9806c72b6b65942003c63f7983d4f39a5c6c27185bbca88eb4690b62075602e28e languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10": +"@jridgewell/sourcemap-codec@npm:1.4.14": version: 1.4.14 resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97 languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.4.10": + version: 1.4.15 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" + checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:0.3.9": version: 0.3.9 resolution: "@jridgewell/trace-mapping@npm:0.3.9" @@ -1147,13 +1175,13 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.13, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.14 - resolution: "@jridgewell/trace-mapping@npm:0.3.14" +"@jridgewell/trace-mapping@npm:^0.3.13, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.9": + version: 0.3.18 + resolution: "@jridgewell/trace-mapping@npm:0.3.18" dependencies: - "@jridgewell/resolve-uri": ^3.0.3 - "@jridgewell/sourcemap-codec": ^1.4.10 - checksum: b9537b9630ffb631aef9651a085fe361881cde1772cd482c257fe3c78c8fd5388d681f504a9c9fe1081b1c05e8f75edf55ee10fdb58d92bbaa8dbf6a7bd6b18c + "@jridgewell/resolve-uri": 3.1.0 + "@jridgewell/sourcemap-codec": 1.4.14 + checksum: 0572669f855260808c16fe8f78f5f1b4356463b11d3f2c7c0b5580c8ba1cbf4ae53efe9f627595830856e57dbac2325ac17eb0c3dd0ec42102e6f227cc289c02 languageName: node linkType: hard @@ -1170,24 +1198,17 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.1.1": - version: 1.1.1 - resolution: "@noble/hashes@npm:1.1.1" - checksum: 3bd98d7a6dcc01c5e72478975073e12c79639636f4eb5710b665dd8ac462fcdff5b235d0c3b113ac83e7e56c43eee5ccba3759f9262964edc123bd1713dd2180 - languageName: node - linkType: hard - -"@noble/hashes@npm:~1.1.1": - version: 1.1.2 - resolution: "@noble/hashes@npm:1.1.2" - checksum: 3c2a8cb7c2e053811032f242155d870c5eb98844d924d69702244d48804cb03b42d4a666c49c2b71164420d8229cb9a6f242b972d50d5bb2f1d673b98b041de2 +"@noble/hashes@npm:1.2.0, @noble/hashes@npm:~1.2.0": + version: 1.2.0 + resolution: "@noble/hashes@npm:1.2.0" + checksum: 8ca080ce557b8f40fb2f78d3aedffd95825a415ac8e13d7ffe3643f8626a8c2d99a3e5975b555027ac24316d8b3c02a35b8358567c0c23af681e6573602aa434 languageName: node linkType: hard -"@noble/secp256k1@npm:1.6.0, @noble/secp256k1@npm:~1.6.0": - version: 1.6.0 - resolution: "@noble/secp256k1@npm:1.6.0" - checksum: e99df3b776515e6a8b3193870e69ff3a7d22c6a4733245dceb9d1d229d5b0859bd478b7213f31d556ba3745647ec07262d0f9df845d79204b7ce4ae1648b27c7 +"@noble/secp256k1@npm:1.7.1, @noble/secp256k1@npm:~1.7.0": + version: 1.7.1 + resolution: "@noble/secp256k1@npm:1.7.1" + checksum: d2301f1f7690368d8409a3152450458f27e54df47e3f917292de3de82c298770890c2de7c967d237eff9c95b70af485389a9695f73eb05a43e2bd562d18b18cb languageName: node linkType: hard @@ -1406,8 +1427,8 @@ __metadata: linkType: hard "@nomicfoundation/hardhat-toolbox@npm:^2.0.1": - version: 2.0.1 - resolution: "@nomicfoundation/hardhat-toolbox@npm:2.0.1" + version: 2.0.2 + resolution: "@nomicfoundation/hardhat-toolbox@npm:2.0.2" peerDependencies: "@ethersproject/abi": ^5.4.7 "@ethersproject/providers": ^5.4.7 @@ -1428,94 +1449,94 @@ __metadata: ts-node: ">=8.0.0" typechain: ^8.1.0 typescript: ">=4.5.0" - checksum: 053236c47745c65f0fb79a34e2570a193dc99aee972c8ec667503fd0a8a5da20f21cf060cf85af8c72fba7a601a604fc36828c791bf21239aafb9e7031bbfe1d + checksum: a2eafb709acbabe40de4871c4e8684a03098f045dba4fc6c6e9281358d072f386a668488c109e2a36b8eade01dc4c4f9e8a76fa45c92591857c590c6e19f1ae7 languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-darwin-x64@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-darwin-x64@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-darwin-x64@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-darwin-x64@npm:0.1.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-freebsd-x64@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-freebsd-x64@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-freebsd-x64@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-freebsd-x64@npm:0.1.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-linux-arm64-gnu@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-linux-arm64-gnu@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-linux-arm64-gnu@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-linux-arm64-gnu@npm:0.1.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-linux-arm64-musl@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-linux-arm64-musl@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-linux-arm64-musl@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-linux-arm64-musl@npm:0.1.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-linux-x64-gnu@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-linux-x64-gnu@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-linux-x64-gnu@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-linux-x64-gnu@npm:0.1.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-linux-x64-musl@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-linux-x64-musl@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-linux-x64-musl@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-linux-x64-musl@npm:0.1.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-win32-arm64-msvc@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-win32-arm64-msvc@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-win32-arm64-msvc@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-win32-arm64-msvc@npm:0.1.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-win32-ia32-msvc@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-win32-ia32-msvc@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-win32-ia32-msvc@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-win32-ia32-msvc@npm:0.1.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@nomicfoundation/solidity-analyzer-win32-x64-msvc@npm:0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer-win32-x64-msvc@npm:0.1.0" +"@nomicfoundation/solidity-analyzer-win32-x64-msvc@npm:0.1.1": + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer-win32-x64-msvc@npm:0.1.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@nomicfoundation/solidity-analyzer@npm:^0.1.0": - version: 0.1.0 - resolution: "@nomicfoundation/solidity-analyzer@npm:0.1.0" - dependencies: - "@nomicfoundation/solidity-analyzer-darwin-arm64": 0.1.0 - "@nomicfoundation/solidity-analyzer-darwin-x64": 0.1.0 - "@nomicfoundation/solidity-analyzer-freebsd-x64": 0.1.0 - "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": 0.1.0 - "@nomicfoundation/solidity-analyzer-linux-arm64-musl": 0.1.0 - "@nomicfoundation/solidity-analyzer-linux-x64-gnu": 0.1.0 - "@nomicfoundation/solidity-analyzer-linux-x64-musl": 0.1.0 - "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": 0.1.0 - "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": 0.1.0 - "@nomicfoundation/solidity-analyzer-win32-x64-msvc": 0.1.0 + version: 0.1.1 + resolution: "@nomicfoundation/solidity-analyzer@npm:0.1.1" + dependencies: + "@nomicfoundation/solidity-analyzer-darwin-arm64": 0.1.1 + "@nomicfoundation/solidity-analyzer-darwin-x64": 0.1.1 + "@nomicfoundation/solidity-analyzer-freebsd-x64": 0.1.1 + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": 0.1.1 + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": 0.1.1 + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": 0.1.1 + "@nomicfoundation/solidity-analyzer-linux-x64-musl": 0.1.1 + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": 0.1.1 + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": 0.1.1 + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": 0.1.1 dependenciesMeta: "@nomicfoundation/solidity-analyzer-darwin-arm64": optional: true @@ -1537,7 +1558,7 @@ __metadata: optional: true "@nomicfoundation/solidity-analyzer-win32-x64-msvc": optional: true - checksum: 42dc5ba40e76bf14945fb6a423554bbbc6c99596675065d7d6f3c9a49ec39e37f3f77ecfedcf906fdb1bb33b033a5d92a90c645c886d6ff23334c8af8b14ff67 + checksum: 038cffafd5769e25256b5b8bef88d95cc1c021274a65c020cf84aceb3237752a3b51645fdb0687f5516a2bdfebf166fcf50b08ab64857925100213e0654b266b languageName: node linkType: hard @@ -1552,42 +1573,31 @@ __metadata: linkType: hard "@nomiclabs/hardhat-etherscan@npm:^3.1.0": - version: 3.1.0 - resolution: "@nomiclabs/hardhat-etherscan@npm:3.1.0" + version: 3.1.7 + resolution: "@nomiclabs/hardhat-etherscan@npm:3.1.7" dependencies: "@ethersproject/abi": ^5.1.2 "@ethersproject/address": ^5.0.2 - cbor: ^5.0.2 + cbor: ^8.1.0 chalk: ^2.4.2 debug: ^4.1.1 fs-extra: ^7.0.1 lodash: ^4.17.11 semver: ^6.3.0 table: ^6.8.0 - undici: ^5.4.0 + undici: ^5.14.0 peerDependencies: hardhat: ^2.0.4 - checksum: 3f28abc39edce2936226b6d0087c3be78bffcba68b6935f3f60767f0e10233e940ddc74803dff91f7ddf9464a7199aab00fba08d8b3865dbc2f8936f53a7a5a5 + checksum: 32d74e567e78a940a79cbe49c5dee0eb5cda0a4c0c34a9badfaf13d45e6054d9e717c28b8d2b0b20f29721a484af15a52d391fb60768222c4b13de92ef0f72b3 languageName: node linkType: hard -"@npmcli/fs@npm:^2.1.0": - version: 2.1.0 - resolution: "@npmcli/fs@npm:2.1.0" +"@npmcli/fs@npm:^3.1.0": + version: 3.1.0 + resolution: "@npmcli/fs@npm:3.1.0" dependencies: - "@gar/promisify": ^1.1.3 semver: ^7.3.5 - checksum: 6ec6d678af6da49f9dac50cd882d7f661934dd278972ffbaacde40d9eaa2871292d634000a0cca9510f6fc29855fbd4af433e1adbff90a524ec3eaf140f1219b - languageName: node - linkType: hard - -"@npmcli/move-file@npm:^2.0.0": - version: 2.0.0 - resolution: "@npmcli/move-file@npm:2.0.0" - dependencies: - mkdirp: ^1.0.4 - rimraf: ^3.0.2 - checksum: 1388777b507b0c592d53f41b9d182e1a8de7763bc625fc07999b8edbc22325f074e5b3ec90af79c89d6987fdb2325bc66d59f483258543c14a43661621f841b0 + checksum: a50a6818de5fc557d0b0e6f50ec780a7a02ab8ad07e5ac8b16bf519e0ad60a144ac64f97d05c443c3367235d337182e1d012bbac0eb8dbae8dc7b40b193efd0e languageName: node linkType: hard @@ -1606,9 +1616,9 @@ __metadata: linkType: hard "@openzeppelin/contracts@npm:^4.3.3": - version: 4.8.2 - resolution: "@openzeppelin/contracts@npm:4.8.2" - checksum: 1d362f0b9c880549cb82544e23fb70270fbbbe24a69e10bd5aa07649fd82347686173998ae484defafdc473d04004d519f839e3cd3d3e7733d0895b950622243 + version: 4.9.2 + resolution: "@openzeppelin/contracts@npm:4.9.2" + checksum: 0538b18fe222e5414a5a539c240b155e0bef2a23c5182fb8e137d71a0c390fe899160f2d55701f75b127f54cc61aee4375370acc832475f19829368ac65c1fc6 languageName: node linkType: hard @@ -1619,15 +1629,28 @@ __metadata: languageName: node linkType: hard +"@openzeppelin/defender-base-client@npm:^1.46.0": + version: 1.47.0 + resolution: "@openzeppelin/defender-base-client@npm:1.47.0" + dependencies: + amazon-cognito-identity-js: ^6.0.1 + async-retry: ^1.3.3 + axios: ^1.4.0 + lodash: ^4.17.19 + node-fetch: ^2.6.0 + checksum: 7fcba4417805aa3920cc2870e4dcd74d3c6874e6f38ce6c1a3e370c140daf253a1cd5baab5204ec40730aaf3ed052e2129c64bbd5f1daacf3838339d06b559ab + languageName: node + linkType: hard + "@openzeppelin/hardhat-upgrades@npm:^1.23.0": - version: 1.25.0 - resolution: "@openzeppelin/hardhat-upgrades@npm:1.25.0" + version: 1.28.0 + resolution: "@openzeppelin/hardhat-upgrades@npm:1.28.0" dependencies: - "@openzeppelin/upgrades-core": ^1.26.0 + "@openzeppelin/defender-base-client": ^1.46.0 + "@openzeppelin/platform-deploy-client": ^0.8.0 + "@openzeppelin/upgrades-core": ^1.27.0 chalk: ^4.1.0 debug: ^4.1.1 - defender-admin-client: ^1.39.0 - platform-deploy-client: ^0.3.2 proper-lockfile: ^4.1.1 peerDependencies: "@nomiclabs/hardhat-ethers": ^2.0.0 @@ -1639,22 +1662,45 @@ __metadata: optional: true bin: migrate-oz-cli-project: dist/scripts/migrate-oz-cli-project.js - checksum: 1647917371d2a4316287940f12e471200c0d143f01ddf485aacc309531d6597e0377243f5c7463d56167c0eefbc983964c15fd7228b7852cc1e47545b1a78e87 + checksum: b37a5eb7c3a5c1fb4ae6754f5fe1d6e93eb6bc143861f57babf5c7d66706ee3e44ca7d57db17ce2ec6c7014f09c269d506f62b3b116897407fdb0d1ff68f4925 languageName: node linkType: hard -"@openzeppelin/upgrades-core@npm:^1.26.0": - version: 1.26.0 - resolution: "@openzeppelin/upgrades-core@npm:1.26.0" +"@openzeppelin/platform-deploy-client@npm:^0.8.0": + version: 0.8.0 + resolution: "@openzeppelin/platform-deploy-client@npm:0.8.0" + dependencies: + "@ethersproject/abi": ^5.6.3 + "@openzeppelin/defender-base-client": ^1.46.0 + axios: ^0.21.2 + lodash: ^4.17.19 + node-fetch: ^2.6.0 + checksum: 0ce050e185a812c366ceef7dcfce526815babab9396275d9724f324a548ddfdca92ea9913ce61356dcd8c014fc495890c8e21afab4a197e0e14e761c698cce68 + languageName: node + linkType: hard + +"@openzeppelin/upgrades-core@npm:^1.27.0": + version: 1.27.3 + resolution: "@openzeppelin/upgrades-core@npm:1.27.3" dependencies: cbor: ^8.0.0 chalk: ^4.1.0 - compare-versions: ^5.0.0 + compare-versions: ^6.0.0 debug: ^4.1.1 ethereumjs-util: ^7.0.3 + minimist: ^1.2.7 proper-lockfile: ^4.1.1 solidity-ast: ^0.4.15 - checksum: 7bc90d2157fc887be24f1351ca00904203f28bca138f89ff9f7ac6ac18910feca889f0b59766bd9ff4a8698ec58aec35348c504fd37fe48a4311156b81802d00 + bin: + openzeppelin-upgrades-core: dist/cli/cli.js + checksum: fd6a6624adbd81cce42afa6da9bd670bcf718fa42e72d8bae2195e6784dc31f75b1915bf2c5d1689c65587ef6f19c3d4e2f81222ca289718bc107647648d69c7 + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 6ad6a00fc4f2f2cfc6bff76fb1d88b8ee20bc0601e18ebb01b6d4be583733a860239a521a7fbca73b612e66705078809483549d2b18f370eb346c5155c8e4a0f languageName: node linkType: hard @@ -1665,24 +1711,24 @@ __metadata: languageName: node linkType: hard -"@scure/bip32@npm:1.1.0": - version: 1.1.0 - resolution: "@scure/bip32@npm:1.1.0" +"@scure/bip32@npm:1.1.5": + version: 1.1.5 + resolution: "@scure/bip32@npm:1.1.5" dependencies: - "@noble/hashes": ~1.1.1 - "@noble/secp256k1": ~1.6.0 + "@noble/hashes": ~1.2.0 + "@noble/secp256k1": ~1.7.0 "@scure/base": ~1.1.0 - checksum: e6102ab9038896861fca5628b8a97f3c4cb24a073cc9f333c71c747037d82e4423d1d111fd282ba212efaf73cbc5875702567fb4cf13b5f0eb23a5bab402e37e + checksum: b08494ab0d2b1efee7226d1b5100db5157ebea22a78bb87126982a76a186cb3048413e8be0ba2622d00d048a20acbba527af730de86c132a77de616eb9907a3b languageName: node linkType: hard -"@scure/bip39@npm:1.1.0": - version: 1.1.0 - resolution: "@scure/bip39@npm:1.1.0" +"@scure/bip39@npm:1.1.1": + version: 1.1.1 + resolution: "@scure/bip39@npm:1.1.1" dependencies: - "@noble/hashes": ~1.1.1 + "@noble/hashes": ~1.2.0 "@scure/base": ~1.1.0 - checksum: c4361406f092a45e511dc572c89f497af6665ad81cb3fd7bf78e6772f357f7ae885e129ef0b985cb3496a460b4811318f77bc61634d9b0a8446079a801b6003c + checksum: fbb594c50696fa9c14e891d872f382e50a3f919b6c96c55ef2fb10c7102c546dafb8f099a62bd114c12a00525b595dcf7381846f383f0ddcedeaa6e210747d2f languageName: node linkType: hard @@ -1768,10 +1814,19 @@ __metadata: languageName: node linkType: hard -"@sinclair/typebox@npm:^0.23.3": - version: 0.23.5 - resolution: "@sinclair/typebox@npm:0.23.5" - checksum: c96056d35d9cb862aeb635ff8873e2e7633e668dd544e162aee2690a82c970d0b3f90aa2b3501fe374dfa8e792388559a3e3a86712b23ebaef10061add534f47 +"@sinclair/typebox@npm:^0.24.1": + version: 0.24.51 + resolution: "@sinclair/typebox@npm:0.24.51" + checksum: fd0d855e748ef767eb19da1a60ed0ab928e91e0f358c1dd198d600762c0015440b15755e96d1176e2a0db7e09c6a64ed487828ee10dd0c3e22f61eb09c478cd0 + languageName: node + linkType: hard + +"@smithy/types@npm:^2.0.2": + version: 2.0.2 + resolution: "@smithy/types@npm:2.0.2" + dependencies: + tslib: ^2.5.0 + checksum: 4afdd7c77b212abd9e0770a1489057aa0470f8a59061c4fb2175b1f12e02180db3d85e16f2cd870a95c17bd28a5a4b8ef1dff1ade6852f85eafea12872d9588e languageName: node linkType: hard @@ -1832,9 +1887,9 @@ __metadata: linkType: hard "@tsconfig/node16@npm:^1.0.2": - version: 1.0.3 - resolution: "@tsconfig/node16@npm:1.0.3" - checksum: 3a8b657dd047495b7ad23437d6afd20297ce90380ff0bdee93fc7d39a900dbd8d9e26e53ff6b465e7967ce2adf0b218782590ce9013285121e6a5928fbd6819f + version: 1.0.4 + resolution: "@tsconfig/node16@npm:1.0.4" + checksum: 202319785901f942a6e1e476b872d421baec20cf09f4b266a1854060efbf78cde16a4d256e8bc949d31e6cd9a90f1e8ef8fb06af96a65e98338a2b6b0de0a0ff languageName: node linkType: hard @@ -1869,11 +1924,11 @@ __metadata: linkType: hard "@types/babel__traverse@npm:^7.0.6": - version: 7.17.1 - resolution: "@types/babel__traverse@npm:7.17.1" + version: 7.20.1 + resolution: "@types/babel__traverse@npm:7.20.1" dependencies: - "@babel/types": ^7.3.0 - checksum: 8992d8c1eaaf1c793e9184b930767883446939d2744c40ea4e9591086e79b631189dc519931ed8864f1e016742a189703c217db59b800aca84870b865009d8b4 + "@babel/types": ^7.20.7 + checksum: 58341e23c649c0eba134a1682d4f20d027fad290d92e5740faa1279978f6ed476fc467ae51ce17a877e2566d805aeac64eae541168994367761ec883a4150221 languageName: node linkType: hard @@ -1887,11 +1942,11 @@ __metadata: linkType: hard "@types/bn.js@npm:^5.1.0": - version: 5.1.0 - resolution: "@types/bn.js@npm:5.1.0" + version: 5.1.1 + resolution: "@types/bn.js@npm:5.1.1" dependencies: "@types/node": "*" - checksum: 1dc1cbbd7a1e8bf3614752e9602f558762a901031f499f3055828b5e3e2bba16e5b88c27b3c4152ad795248fbe4086c731a5c4b0f29bb243f1875beeeabee59c + checksum: e50ed2dd3abe997e047caf90e0352c71e54fc388679735217978b4ceb7e336e51477791b715f49fd77195ac26dd296c7bad08a3be9750e235f9b2e1edb1b51c2 languageName: node linkType: hard @@ -1905,9 +1960,9 @@ __metadata: linkType: hard "@types/chai@npm:*, @types/chai@npm:^4.3.0": - version: 4.3.1 - resolution: "@types/chai@npm:4.3.1" - checksum: 2ee246b76c469cd620a7a1876a73bc597074361b67d547b4bd96a0c1adb43597ede2d8589ab626192e14349d83cbb646cc11e2c179eeeb43ff11596de94d82c4 + version: 4.3.5 + resolution: "@types/chai@npm:4.3.5" + checksum: c8f26a88c6b5b53a3275c7f5ff8f107028e3cbb9ff26795fff5f3d9dea07106a54ce9e2dce5e40347f7c4cc35657900aaf0c83934a25a1ae12e61e0f5516e431 languageName: node linkType: hard @@ -1940,11 +1995,11 @@ __metadata: linkType: hard "@types/graceful-fs@npm:^4.1.3": - version: 4.1.5 - resolution: "@types/graceful-fs@npm:4.1.5" + version: 4.1.6 + resolution: "@types/graceful-fs@npm:4.1.6" dependencies: "@types/node": "*" - checksum: d076bb61f45d0fc42dee496ef8b1c2f8742e15d5e47e90e20d0243386e426c04d4efd408a48875ab432f7960b4ce3414db20ed0fbbfc7bcc89d84e574f6e045a + checksum: c3070ccdc9ca0f40df747bced1c96c71a61992d6f7c767e8fd24bb6a3c2de26e8b84135ede000b7e79db530a23e7e88dcd9db60eee6395d0f4ce1dae91369dd4 languageName: node linkType: hard @@ -1981,9 +2036,9 @@ __metadata: linkType: hard "@types/json-schema@npm:^7.0.9": - version: 7.0.11 - resolution: "@types/json-schema@npm:7.0.11" - checksum: 527bddfe62db9012fccd7627794bd4c71beb77601861055d87e3ee464f2217c85fca7a4b56ae677478367bbd248dbde13553312b7d4dbc702a2f2bbf60c4018d + version: 7.0.12 + resolution: "@types/json-schema@npm:7.0.12" + checksum: 00239e97234eeb5ceefb0c1875d98ade6e922bfec39dd365ec6bd360b5c2f825e612ac4f6e5f1d13601b8b30f378f15e6faa805a3a732f4a1bbe61915163d293 languageName: node linkType: hard @@ -1995,9 +2050,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.177": - version: 4.14.182 - resolution: "@types/lodash@npm:4.14.182" - checksum: 7dd137aa9dbabd632408bd37009d984655164fa1ecc3f2b6eb94afe35bf0a5852cbab6183148d883e9c73a958b7fec9a9bcf7c8e45d41195add6a18c34958209 + version: 4.14.196 + resolution: "@types/lodash@npm:4.14.196" + checksum: 201d17c3e62ae02a93c99ec78e024b2be9bd75564dd8fd8c26f6ac51a985ab280d28ce2688c3bcdfe785b0991cd9814edff19ee000234c7b45d9a697f09feb6a languageName: node linkType: hard @@ -2009,9 +2064,9 @@ __metadata: linkType: hard "@types/minimatch@npm:*": - version: 3.0.5 - resolution: "@types/minimatch@npm:3.0.5" - checksum: c41d136f67231c3131cf1d4ca0b06687f4a322918a3a5adddc87ce90ed9dbd175a3610adee36b106ae68c0b92c637c35e02b58c8a56c424f71d30993ea220b92 + version: 5.1.2 + resolution: "@types/minimatch@npm:5.1.2" + checksum: 0391a282860c7cb6fe262c12b99564732401bdaa5e395bee9ca323c312c1a0f45efbf34dce974682036e857db59a5c9b1da522f3d6055aeead7097264c8705a8 languageName: node linkType: hard @@ -2023,9 +2078,9 @@ __metadata: linkType: hard "@types/node@npm:*": - version: 18.0.3 - resolution: "@types/node@npm:18.0.3" - checksum: 5dec59fbbc1186c808b53df1ca717dad034dbd6a901c75f5b052c845618b531b05f27217122c6254db99529a68618e4cfc534ae3dbf4e88754e9e572df80defa + version: 20.4.5 + resolution: "@types/node@npm:20.4.5" + checksum: 36a0304a8dc346a1b2d2edac4c4633eecf70875793d61a5274d0df052d7a7af7a8e34f29884eac4fbd094c4f0201477dcb39c0ecd3307ca141688806538d1138 languageName: node linkType: hard @@ -2060,9 +2115,9 @@ __metadata: linkType: hard "@types/prettier@npm:^2.1.1, @types/prettier@npm:^2.1.5": - version: 2.6.3 - resolution: "@types/prettier@npm:2.6.3" - checksum: e1836699ca189fff6d2a73dc22e028b6a6f693ed1180d5998ac29fa197caf8f85aa92cb38db642e4a370e616b451cb5722ad2395dab11c78e025a1455f37d1f0 + version: 2.7.3 + resolution: "@types/prettier@npm:2.7.3" + checksum: 705384209cea6d1433ff6c187c80dcc0b95d99d5c5ce21a46a9a58060c527973506822e428789d842761e0280d25e3359300f017fbe77b9755bc772ab3dc2f83 languageName: node linkType: hard @@ -2107,26 +2162,26 @@ __metadata: linkType: hard "@types/yargs@npm:^17.0.8": - version: 17.0.10 - resolution: "@types/yargs@npm:17.0.10" + version: 17.0.24 + resolution: "@types/yargs@npm:17.0.24" dependencies: "@types/yargs-parser": "*" - checksum: f0673cbfc08e17239dc58952a88350d6c4db04a027a28a06fbad27d87b670e909f9cd9e66f9c64cebdd5071d1096261e33454a55868395f125297e5c50992ca8 + checksum: 5f3ac4dc4f6e211c1627340160fbe2fd247ceba002190da6cf9155af1798450501d628c9165a183f30a224fc68fa5e700490d740ff4c73e2cdef95bc4e8ba7bf languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^5.17.0": - version: 5.30.5 - resolution: "@typescript-eslint/eslint-plugin@npm:5.30.5" +"@typescript-eslint/eslint-plugin@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/eslint-plugin@npm:5.17.0" dependencies: - "@typescript-eslint/scope-manager": 5.30.5 - "@typescript-eslint/type-utils": 5.30.5 - "@typescript-eslint/utils": 5.30.5 - debug: ^4.3.4 + "@typescript-eslint/scope-manager": 5.17.0 + "@typescript-eslint/type-utils": 5.17.0 + "@typescript-eslint/utils": 5.17.0 + debug: ^4.3.2 functional-red-black-tree: ^1.0.1 - ignore: ^5.2.0 + ignore: ^5.1.8 regexpp: ^3.2.0 - semver: ^7.3.7 + semver: ^7.3.5 tsutils: ^3.21.0 peerDependencies: "@typescript-eslint/parser": ^5.0.0 @@ -2134,105 +2189,105 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: cf763fb091dcdfd6c25843251a220b654ca83968b17266e0f343771f489085c6afc4e41fcf2187b4c72c4d12a787070c64b5e5367069460f95a8174573f48905 + checksum: 62ec611fb384f27fc5b101fc8a0642ae94b2975618d37d3157c2f887cf89b389624e9d476bff303073d038076c05e6c00f3b205af3b2302967e720e99cd18d38 languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.17.0": - version: 5.30.5 - resolution: "@typescript-eslint/parser@npm:5.30.5" +"@typescript-eslint/parser@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/parser@npm:5.17.0" dependencies: - "@typescript-eslint/scope-manager": 5.30.5 - "@typescript-eslint/types": 5.30.5 - "@typescript-eslint/typescript-estree": 5.30.5 - debug: ^4.3.4 + "@typescript-eslint/scope-manager": 5.17.0 + "@typescript-eslint/types": 5.17.0 + "@typescript-eslint/typescript-estree": 5.17.0 + debug: ^4.3.2 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 6c16821e122b891420a538f200f6e576ad1167855a67e87f9a7d3a08c0513fe26006f6411b8ba6f4662a81526bd0339ae37c47dd88fa5943e6f27ff70da9f989 + checksum: 15b855ea84e44371366d44b5add87ed0dc34b856ca8a6949ecc4066faaf3ea3d7e016ea92db06ab97a637530148c472c38c19cc5eff68b691701ff89dc5c1abc languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.30.5": - version: 5.30.5 - resolution: "@typescript-eslint/scope-manager@npm:5.30.5" +"@typescript-eslint/scope-manager@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/scope-manager@npm:5.17.0" dependencies: - "@typescript-eslint/types": 5.30.5 - "@typescript-eslint/visitor-keys": 5.30.5 - checksum: 509bee6d62cca1716e8f4792d9180c189974992ba13d8103ca04423a64006cf184c4b2c606d55c776305458140c798a3a9a414d07a60790b83dd714f56c457b0 + "@typescript-eslint/types": 5.17.0 + "@typescript-eslint/visitor-keys": 5.17.0 + checksum: 8fc28d5742f36994ce05f09b0000f696a600d6f757f39ccae7875c08398b266f21d48ed1dfb027549d9c6692255a1fb3e8482ef94d765bb134371824da7d5ba7 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.30.5": - version: 5.30.5 - resolution: "@typescript-eslint/type-utils@npm:5.30.5" +"@typescript-eslint/type-utils@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/type-utils@npm:5.17.0" dependencies: - "@typescript-eslint/utils": 5.30.5 - debug: ^4.3.4 + "@typescript-eslint/utils": 5.17.0 + debug: ^4.3.2 tsutils: ^3.21.0 peerDependencies: eslint: "*" peerDependenciesMeta: typescript: optional: true - checksum: 080cc1231729c34b778395658374e32d034474056f9b777dbc89d20d15eb93d93d0959328ad47c2a6623d40c6552364ababadce439842a944bce001f55b731b3 + checksum: 9aad46ea7a757ec4584b9d9c995e94543bf40af7d85b2f502d66db08d7f03468c858320fccb4942238b0bb9e2d432df3d9861cf21624b0c57660c88b1d91a7d4 languageName: node linkType: hard -"@typescript-eslint/types@npm:5.30.5": - version: 5.30.5 - resolution: "@typescript-eslint/types@npm:5.30.5" - checksum: c70420618cb875d4e964a20a3fa4cf40cb97a8ad3123e24860e3d829edf3b081c77fa1fe25644700499d27e44aee5783abc7765deee61e2ef59a928db96b2175 +"@typescript-eslint/types@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/types@npm:5.17.0" + checksum: 06ed4c3c3f0a05bee9c23b6cb5eb679336c0f4769beb28848e8ce674f726fec88adba059f20e0b0f7271685d7f5480931b3bcafcf6b60044b93da162e29f3f68 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.30.5": - version: 5.30.5 - resolution: "@typescript-eslint/typescript-estree@npm:5.30.5" +"@typescript-eslint/typescript-estree@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.17.0" dependencies: - "@typescript-eslint/types": 5.30.5 - "@typescript-eslint/visitor-keys": 5.30.5 - debug: ^4.3.4 - globby: ^11.1.0 + "@typescript-eslint/types": 5.17.0 + "@typescript-eslint/visitor-keys": 5.17.0 + debug: ^4.3.2 + globby: ^11.0.4 is-glob: ^4.0.3 - semver: ^7.3.7 + semver: ^7.3.5 tsutils: ^3.21.0 peerDependenciesMeta: typescript: optional: true - checksum: 19dce426c826cddd4aadf2fa15be943c6ad7d2038685cc2665749486a5f44a47819aab5d260b54f8a4babf6acf2500e9f62e709d61fce337b12d5468ff285277 + checksum: 589829b1bb1d7e704de6a35dd9a39c70a3ca54b0885b68aad54a864bc5e5a11ce43f917c3f15f0afe9bc734a250288efdf03dfbed70b8fe0cc12f759e2e1f8ef languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.30.5": - version: 5.30.5 - resolution: "@typescript-eslint/utils@npm:5.30.5" +"@typescript-eslint/utils@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/utils@npm:5.17.0" dependencies: "@types/json-schema": ^7.0.9 - "@typescript-eslint/scope-manager": 5.30.5 - "@typescript-eslint/types": 5.30.5 - "@typescript-eslint/typescript-estree": 5.30.5 + "@typescript-eslint/scope-manager": 5.17.0 + "@typescript-eslint/types": 5.17.0 + "@typescript-eslint/typescript-estree": 5.17.0 eslint-scope: ^5.1.1 eslint-utils: ^3.0.0 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 12f68cb34a150d39708f4e09a54964360f29589885cd50f119a2061660011752ec72eff3d90111f0e597575d32aae7250a6e2c730a84963e5e30352759d5f1f4 + checksum: 88de02eafb7d39950c520c53aa07ffe63c95ca7ef2262c39d2afd3c6aabcd5d717ba61f74314f5bc9c27588b721ff016b45af6fc1de88801c6ac4bf5ebaf8775 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.30.5": - version: 5.30.5 - resolution: "@typescript-eslint/visitor-keys@npm:5.30.5" +"@typescript-eslint/visitor-keys@npm:5.17.0": + version: 5.17.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.17.0" dependencies: - "@typescript-eslint/types": 5.30.5 - eslint-visitor-keys: ^3.3.0 - checksum: c0de9ae48378eec2682b860a059518bed213ea29575aad538d8d2f8137875e7279e375a7f23d38c1c183466fdd9cf1ca1db4ed5a1d374968f9460d83e48b2437 + "@typescript-eslint/types": 5.17.0 + eslint-visitor-keys: ^3.0.0 + checksum: 333468277b50e2fc381ba1b99ccb410046c422e0329c791c51bea62e705edd16ba97f75b668c6945a3ea3dc43b89a1739693ea60bfa241c67ce42e8b474e5048 languageName: node linkType: hard -"abbrev@npm:1": +"abbrev@npm:1, abbrev@npm:^1.0.0": version: 1.1.1 resolution: "abbrev@npm:1.1.1" checksum: a4a97ec07d7ea112c517036882b2ac22f3109b7b19077dc656316d07d308438aac28e4d9746dc4d84bf6b1e75b4a7b0a5f3cb30592419f128ca9a8cee3bcfa17 @@ -2305,19 +2360,19 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.4.1, acorn@npm:^8.7.1": - version: 8.7.1 - resolution: "acorn@npm:8.7.1" +"acorn@npm:^8.4.1, acorn@npm:^8.9.0": + version: 8.10.0 + resolution: "acorn@npm:8.10.0" bin: acorn: bin/acorn - checksum: aca0aabf98826717920ac2583fdcad0a6fbe4e583fdb6e843af2594e907455aeafe30b1e14f1757cd83ce1776773cf8296ffc3a4acf13f0bd3dfebcf1db6ae80 + checksum: 538ba38af0cc9e5ef983aee196c4b8b4d87c0c94532334fa7e065b2c8a1f85863467bb774231aae91613fcda5e68740c15d97b1967ae3394d20faddddd8af61d languageName: node linkType: hard "address@npm:^1.0.1": - version: 1.2.0 - resolution: "address@npm:1.2.0" - checksum: 2ef3aa9d23bbe0f9f2745a634b16f3a2f2b18c43146c0913c7b26c8be410e20d59b8c3808d0bb7fe94d50fc2448b4b91e65dd9f33deb4aed53c14f0dedc3ddd8 + version: 1.2.2 + resolution: "address@npm:1.2.2" + checksum: ace439960c1e3564d8f523aff23a841904bf33a2a7c2e064f7f60a064194075758b9690e65bd9785692a4ef698a998c57eb74d145881a1cecab8ba658ddb1607 languageName: node linkType: hard @@ -2345,13 +2400,13 @@ __metadata: linkType: hard "agentkeepalive@npm:^4.2.1": - version: 4.2.1 - resolution: "agentkeepalive@npm:4.2.1" + version: 4.3.0 + resolution: "agentkeepalive@npm:4.3.0" dependencies: debug: ^4.1.0 - depd: ^1.1.2 + depd: ^2.0.0 humanize-ms: ^1.2.1 - checksum: 39cb49ed8cf217fd6da058a92828a0a84e0b74c35550f82ee0a10e1ee403c4b78ade7948be2279b188b7a7303f5d396ea2738b134731e464bf28de00a4f72a18 + checksum: 982453aa44c11a06826c836025e5162c846e1200adb56f2d075400da7d32d87021b3b0a58768d949d824811f5654223d5a8a3dad120921a2439625eb847c6260 languageName: node linkType: hard @@ -2378,27 +2433,27 @@ __metadata: linkType: hard "ajv@npm:^8.0.1": - version: 8.11.0 - resolution: "ajv@npm:8.11.0" + version: 8.12.0 + resolution: "ajv@npm:8.12.0" dependencies: fast-deep-equal: ^3.1.1 json-schema-traverse: ^1.0.0 require-from-string: ^2.0.2 uri-js: ^4.2.2 - checksum: 5e0ff226806763be73e93dd7805b634f6f5921e3e90ca04acdf8db81eed9d8d3f0d4c5f1213047f45ebbf8047ffe0c840fa1ef2ec42c3a644899f69aa72b5bef + checksum: 4dc13714e316e67537c8b31bc063f99a1d9d9a497eb4bbd55191ac0dcd5e4985bbb71570352ad6f1e76684fb6d790928f96ba3b2d4fd6e10024be9612fe3f001 languageName: node linkType: hard "amazon-cognito-identity-js@npm:^6.0.1": - version: 6.2.0 - resolution: "amazon-cognito-identity-js@npm:6.2.0" + version: 6.3.1 + resolution: "amazon-cognito-identity-js@npm:6.3.1" dependencies: "@aws-crypto/sha256-js": 1.2.2 buffer: 4.9.2 fast-base64-decode: ^1.0.0 isomorphic-unfetch: ^3.0.0 js-cookie: ^2.2.1 - checksum: 9b976ceac2a2648bfa707190d683214168cf1a70083152355b36e5b87b1aedbbb38dab8b71904ee968de25e90ebcf1e86ffaa21d809373d31acd73f62b6c7c1c + checksum: a38d1c809417d2894613a2fba896434cad3514ed2a16ec6be8084b14788440a4829b50c0dd0ecae3c878ff76bcd1f8df1a862b545eeeb0ca219c6e28cdf598fc languageName: node linkType: hard @@ -2467,6 +2522,13 @@ __metadata: languageName: node linkType: hard +"ansi-regex@npm:^6.0.1": + version: 6.0.1 + resolution: "ansi-regex@npm:6.0.1" + checksum: 1ff8b7667cded1de4fa2c9ae283e979fc87036864317da86a2e546725f96406746411d0d85e87a2d12fa5abd715d90006de7fa4fa0477c92321ad3b4c7d4e169 + languageName: node + linkType: hard + "ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" @@ -2492,6 +2554,13 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: ef940f2f0ced1a6347398da88a91da7930c33ecac3c77b72c5905f8b8fe402c52e6fde304ff5347f616e27a742da3f1dc76de98f6866c69251ad0b07a66776d9 + languageName: node + linkType: hard + "antlr4@npm:4.7.1": version: 4.7.1 resolution: "antlr4@npm:4.7.1" @@ -2509,12 +2578,12 @@ __metadata: linkType: hard "anymatch@npm:^3.0.3, anymatch@npm:~3.1.1, anymatch@npm:~3.1.2": - version: 3.1.2 - resolution: "anymatch@npm:3.1.2" + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" dependencies: normalize-path: ^3.0.0 picomatch: ^2.0.4 - checksum: 985163db2292fac9e5a1e072bf99f1b5baccf196e4de25a0b0b81865ebddeb3b3eb4480734ef0a2ac8c002845396b91aa89121f5b84f93981a4658164a9ec6e9 + checksum: 3e044fd6d1d26545f235a9fe4d7a534e2029d8e59fa7fd9f2a6eb21230f6b5380ea1eaf55136e60cbf8e613544b3b766e7a6fa2102e2a3a117505466e3025dc2 languageName: node linkType: hard @@ -2526,12 +2595,12 @@ __metadata: linkType: hard "are-we-there-yet@npm:^3.0.0": - version: 3.0.0 - resolution: "are-we-there-yet@npm:3.0.0" + version: 3.0.1 + resolution: "are-we-there-yet@npm:3.0.1" dependencies: delegates: ^1.0.0 readable-stream: ^3.6.0 - checksum: 348edfdd931b0b50868b55402c01c3f64df1d4c229ab6f063539a5025fd6c5f5bb8a0cab409bbed8d75d34762d22aa91b7c20b4204eb8177063158d9ba792981 + checksum: 52590c24860fa7173bedeb69a4c05fb573473e860197f618b9a28432ee4379049336727ae3a1f9c4cb083114601c1140cee578376164d0e651217a9843f9fe83 languageName: node linkType: hard @@ -2576,6 +2645,16 @@ __metadata: languageName: node linkType: hard +"array-buffer-byte-length@npm:^1.0.0": + version: 1.0.0 + resolution: "array-buffer-byte-length@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + is-array-buffer: ^3.0.1 + checksum: 044e101ce150f4804ad19c51d6c4d4cfa505c5b2577bd179256e4aa3f3f6a0a5e9874c78cd428ee566ac574c8a04d7ce21af9fe52e844abfdccb82b33035a7c3 + languageName: node + linkType: hard + "array-flatten@npm:1.1.1": version: 1.1.1 resolution: "array-flatten@npm:1.1.1" @@ -2584,15 +2663,15 @@ __metadata: linkType: hard "array-includes@npm:^3.1.4": - version: 3.1.5 - resolution: "array-includes@npm:3.1.5" + version: 3.1.6 + resolution: "array-includes@npm:3.1.6" dependencies: call-bind: ^1.0.2 define-properties: ^1.1.4 - es-abstract: ^1.19.5 - get-intrinsic: ^1.1.1 + es-abstract: ^1.20.4 + get-intrinsic: ^1.1.3 is-string: ^1.0.7 - checksum: f6f24d834179604656b7bec3e047251d5cc87e9e87fab7c175c61af48e80e75acd296017abcde21fb52292ab6a2a449ab2ee37213ee48c8709f004d75983f9c5 + checksum: f22f8cd8ba8a6448d91eebdc69f04e4e55085d09232b5216ee2d476dab3ef59984e8d1889e662c6a0ed939dcb1b57fd05b2c0209c3370942fc41b752c82a2ca5 languageName: node linkType: hard @@ -2611,27 +2690,41 @@ __metadata: linkType: hard "array.prototype.flat@npm:^1.2.5": - version: 1.3.0 - resolution: "array.prototype.flat@npm:1.3.0" + version: 1.3.1 + resolution: "array.prototype.flat@npm:1.3.1" dependencies: call-bind: ^1.0.2 - define-properties: ^1.1.3 - es-abstract: ^1.19.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 es-shim-unscopables: ^1.0.0 - checksum: 2a652b3e8dc0bebb6117e42a5ab5738af0203a14c27341d7bb2431467bdb4b348e2c5dc555dfcda8af0a5e4075c400b85311ded73861c87290a71a17c3e0a257 + checksum: 5a8415949df79bf6e01afd7e8839bbde5a3581300e8ad5d8449dea52639e9e59b26a467665622783697917b43bf39940a6e621877c7dd9b3d1c1f97484b9b88b languageName: node linkType: hard -"array.prototype.reduce@npm:^1.0.4": - version: 1.0.4 - resolution: "array.prototype.reduce@npm:1.0.4" +"array.prototype.reduce@npm:^1.0.5": + version: 1.0.5 + resolution: "array.prototype.reduce@npm:1.0.5" dependencies: call-bind: ^1.0.2 - define-properties: ^1.1.3 - es-abstract: ^1.19.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 es-array-method-boxes-properly: ^1.0.0 is-string: ^1.0.7 - checksum: 6a57a1a2d3b77a9543db139cd52211f43a5af8e8271cb3c173be802076e3a6f71204ba8f090f5937ebc0842d5876db282f0f63dffd0e86b153e6e5a45681e4a5 + checksum: f44691395f9202aba5ec2446468d4c27209bfa81464f342ae024b7157dbf05b164e47cca01250b8c7c2a8219953fb57651cca16aab3d16f43b85c0d92c26eef3 + languageName: node + linkType: hard + +"arraybuffer.prototype.slice@npm:^1.0.1": + version: 1.0.1 + resolution: "arraybuffer.prototype.slice@npm:1.0.1" + dependencies: + array-buffer-byte-length: ^1.0.0 + call-bind: ^1.0.2 + define-properties: ^1.2.0 + get-intrinsic: ^1.2.1 + is-array-buffer: ^3.0.2 + is-shared-array-buffer: ^1.0.2 + checksum: e3e9b2a3e988ebfeddce4c7e8f69df730c9e48cb04b0d40ff0874ce3d86b3d1339dd520ffde5e39c02610bc172ecfbd4bc93324b1cabd9554c44a56b131ce0ce languageName: node linkType: hard @@ -2716,6 +2809,13 @@ __metadata: languageName: node linkType: hard +"available-typed-arrays@npm:^1.0.5": + version: 1.0.5 + resolution: "available-typed-arrays@npm:1.0.5" + checksum: 20eb47b3cefd7db027b9bbb993c658abd36d4edd3fe1060e83699a03ee275b0c9b216cc076ff3f2db29073225fb70e7613987af14269ac1fe2a19803ccc97f1a + languageName: node + linkType: hard + "aws-sign2@npm:~0.7.0": version: 0.7.0 resolution: "aws-sign2@npm:0.7.0" @@ -2724,9 +2824,9 @@ __metadata: linkType: hard "aws4@npm:^1.8.0": - version: 1.11.0 - resolution: "aws4@npm:1.11.0" - checksum: 5a00d045fd0385926d20ebebcfba5ec79d4482fe706f63c27b324d489a04c68edb0db99ed991e19eda09cb8c97dc2452059a34d97545cebf591d7a2b5a10999f + version: 1.12.0 + resolution: "aws4@npm:1.12.0" + checksum: 68f79708ac7c335992730bf638286a3ee0a645cf12575d557860100767c500c08b30e24726b9f03265d74116417f628af78509e1333575e9f8d52a80edfe8cbc languageName: node linkType: hard @@ -2758,6 +2858,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.4.0": + version: 1.4.0 + resolution: "axios@npm:1.4.0" + dependencies: + follow-redirects: ^1.15.0 + form-data: ^4.0.0 + proxy-from-env: ^1.1.0 + checksum: 7fb6a4313bae7f45e89d62c70a800913c303df653f19eafec88e56cea2e3821066b8409bc68be1930ecca80e861c52aa787659df0ffec6ad4d451c7816b9386b + languageName: node + linkType: hard + "babel-plugin-istanbul@npm:^6.1.1": version: 6.1.1 resolution: "babel-plugin-istanbul@npm:6.1.1" @@ -2833,22 +2944,13 @@ __metadata: linkType: hard "bigint-crypto-utils@npm:^3.0.23": - version: 3.1.7 - resolution: "bigint-crypto-utils@npm:3.1.7" - dependencies: - bigint-mod-arith: ^3.1.0 - checksum: 10fa35d3e3d37639c8d501f45e0044c9062e7aa60783ae514e4d4ed3235ac24ac180e0dd0c77dad8cb5410ef24de42e1ea12527a997fec4c59f15fa83ea477ba - languageName: node - linkType: hard - -"bigint-mod-arith@npm:^3.1.0": - version: 3.1.2 - resolution: "bigint-mod-arith@npm:3.1.2" - checksum: badddd745f6e6c45674b22335d26a9ea83250e749abde20c5f84b24afbc747e259bc36798530953332349ed898f38ec39125b326cae8b8ee2dddfaea7ddf8448 + version: 3.3.0 + resolution: "bigint-crypto-utils@npm:3.3.0" + checksum: 9598ce57b23f776c8936d44114c9f051e62b5fa654915b664784cbcbacc5aa0485f4479571c51ff58008abb1210c0d6a234853742f07cf84bda890f2a1e01000 languageName: node linkType: hard -"bignumber.js@npm:^9.0.1, bignumber.js@npm:^9.1.1": +"bignumber.js@npm:^9.1.1": version: 9.1.1 resolution: "bignumber.js@npm:9.1.1" checksum: ad243b7e2f9120b112d670bb3d674128f0bd2ca1745b0a6c9df0433bd2c0252c43e6315d944c2ac07b4c639e7496b425e46842773cf89c6a2dcd4f31e5c4b11e @@ -2978,17 +3080,17 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.20.2": - version: 4.21.1 - resolution: "browserslist@npm:4.21.1" +"browserslist@npm:^4.21.9": + version: 4.21.9 + resolution: "browserslist@npm:4.21.9" dependencies: - caniuse-lite: ^1.0.30001359 - electron-to-chromium: ^1.4.172 - node-releases: ^2.0.5 - update-browserslist-db: ^1.0.4 + caniuse-lite: ^1.0.30001503 + electron-to-chromium: ^1.4.431 + node-releases: ^2.0.12 + update-browserslist-db: ^1.0.11 bin: browserslist: cli.js - checksum: 4904a9ded0702381adc495e003e7f77970abb7f8c8b8edd9e54f026354b5a96b1bddc26e6d9a7df9f043e468ecd2fcff2c8f40fc489909a042880117c2aca8ff + checksum: 80d3820584e211484ad1b1a5cfdeca1dd00442f47be87e117e1dda34b628c87e18b81ae7986fa5977b3e6a03154f6d13cd763baa6b8bf5dd9dd19f4926603698 languageName: node linkType: hard @@ -3079,29 +3181,23 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^16.1.0": - version: 16.1.1 - resolution: "cacache@npm:16.1.1" +"cacache@npm:^17.0.0": + version: 17.1.3 + resolution: "cacache@npm:17.1.3" dependencies: - "@npmcli/fs": ^2.1.0 - "@npmcli/move-file": ^2.0.0 - chownr: ^2.0.0 - fs-minipass: ^2.1.0 - glob: ^8.0.1 - infer-owner: ^1.0.4 + "@npmcli/fs": ^3.1.0 + fs-minipass: ^3.0.0 + glob: ^10.2.2 lru-cache: ^7.7.1 - minipass: ^3.1.6 + minipass: ^5.0.0 minipass-collect: ^1.0.2 minipass-flush: ^1.0.5 minipass-pipeline: ^1.2.4 - mkdirp: ^1.0.4 p-map: ^4.0.0 - promise-inflight: ^1.0.1 - rimraf: ^3.0.2 - ssri: ^9.0.0 + ssri: ^10.0.0 tar: ^6.1.11 - unique-filename: ^1.1.1 - checksum: 488524617008b793f0249b0c4ea2c330c710ca997921376e15650cc2415a8054491ae2dee9f01382c2015602c0641f3f977faf2fa7361aa33d2637dcfb03907a + unique-filename: ^3.0.0 + checksum: 385756781e1e21af089160d89d7462b7ed9883c978e848c7075b90b73cb823680e66092d61513050164588387d2ca87dd6d910e28d64bc13a9ac82cd8580c796 languageName: node linkType: hard @@ -3168,10 +3264,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001359": - version: 1.0.30001363 - resolution: "caniuse-lite@npm:1.0.30001363" - checksum: 8dfcb2fa97724349cbbe61d988810bd90bfb40106a289ed6613188fa96dd1f5885c7e9924e46bb30a641bd1579ec34096fdc2b21b47d8500f8a2bfb0db069323 +"caniuse-lite@npm:^1.0.30001503": + version: 1.0.30001517 + resolution: "caniuse-lite@npm:1.0.30001517" + checksum: e4e87436ae1c4408cf4438aac22902b31eb03f3f5bad7f33bc518d12ffb35f3fd9395ccf7efc608ee046f90ce324ec6f7f26f8a8172b8c43c26a06ecee612a29 languageName: node linkType: hard @@ -3196,17 +3292,7 @@ __metadata: languageName: node linkType: hard -"cbor@npm:^5.0.2": - version: 5.2.0 - resolution: "cbor@npm:5.2.0" - dependencies: - bignumber.js: ^9.0.1 - nofilter: ^1.0.4 - checksum: b3c39dae64370f361526dbec88f51d0f1b47027224cdd21dbd64c228f0fe7eaa945932d349ec5324068a6c6dcdbb1e3b46242852524fd53c526d14cb60514bdc - languageName: node - linkType: hard - -"cbor@npm:^8.0.0": +"cbor@npm:^8.0.0, cbor@npm:^8.1.0": version: 8.1.0 resolution: "cbor@npm:8.1.0" dependencies: @@ -3227,17 +3313,17 @@ __metadata: linkType: hard "chai@npm:^4.3.4": - version: 4.3.6 - resolution: "chai@npm:4.3.6" + version: 4.3.7 + resolution: "chai@npm:4.3.7" dependencies: assertion-error: ^1.1.0 check-error: ^1.0.2 - deep-eql: ^3.0.1 + deep-eql: ^4.1.2 get-func-name: ^2.0.0 loupe: ^2.3.1 pathval: ^1.1.1 type-detect: ^4.0.5 - checksum: acff93fd537f96d4a4d62dd83810285dffcfccb5089e1bf2a1205b28ec82d93dff551368722893cf85004282df10ee68802737c33c90c5493957ed449ed7ce71 + checksum: 0bba7d267848015246a66995f044ce3f0ebc35e530da3cbdf171db744e14cbe301ab913a8d07caf7952b430257ccbb1a4a983c570a7c5748dc537897e5131f7c languageName: node linkType: hard @@ -3336,9 +3422,9 @@ __metadata: linkType: hard "ci-info@npm:^3.2.0": - version: 3.3.2 - resolution: "ci-info@npm:3.3.2" - checksum: fd81f1edd2d3b0f6cb077b2e84365136d87b9db8c055928c1ad69da8a76c2c2f19cba8ea51b90238302157ca927f91f92b653e933f2398dde4867500f08d6e62 + version: 3.8.0 + resolution: "ci-info@npm:3.8.0" + checksum: d0a4d3160497cae54294974a7246202244fff031b0a6ea20dd57b10ec510aa17399c41a1b0982142c105f3255aff2173e5c0dd7302ee1b2f28ba3debda375098 languageName: node linkType: hard @@ -3353,16 +3439,16 @@ __metadata: linkType: hard "classic-level@npm:^1.2.0": - version: 1.2.0 - resolution: "classic-level@npm:1.2.0" + version: 1.3.0 + resolution: "classic-level@npm:1.3.0" dependencies: abstract-level: ^1.0.2 catering: ^2.1.0 module-error: ^1.0.1 - napi-macros: ~2.0.0 + napi-macros: ^2.2.2 node-gyp: latest node-gyp-build: ^4.3.0 - checksum: 88ddd12f2192c2775107d5e462998ac01095cb0222ca01dc2be77d8dcbbf9883c4c0a0248529cceee40a2f1232c68027b1aca731da9f767ad8e9483cbd61dd37 + checksum: 773da48aef52a041115d413fee8340b357a4da2eb505764f327183b155edd7cc9d24819eb4f707c83dbdae8588024f5dddeb322125567c59d5d1f6f16334cdb9 languageName: node linkType: hard @@ -3438,10 +3524,21 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.1 + wrap-ansi: ^7.0.0 + checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56 + languageName: node + linkType: hard + "collect-v8-coverage@npm:^1.0.0": - version: 1.0.1 - resolution: "collect-v8-coverage@npm:1.0.1" - checksum: 4efe0a1fccd517b65478a2364b33dadd0a43fc92a56f59aaece9b6186fe5177b2de471253587de7c91516f07c7268c2f6770b6cbcffc0e0ece353b766ec87e55 + version: 1.0.2 + resolution: "collect-v8-coverage@npm:1.0.2" + checksum: c10f41c39ab84629d16f9f6137bc8a63d332244383fc368caf2d2052b5e04c20cd1fd70f66fcf4e2422b84c8226598b776d39d5f2d2a51867cc1ed5d1982b4da languageName: node linkType: hard @@ -3543,10 +3640,10 @@ __metadata: languageName: node linkType: hard -"compare-versions@npm:^5.0.0": - version: 5.0.1 - resolution: "compare-versions@npm:5.0.1" - checksum: 302a4e46224b47b9280cf894c6c87d8df912671fa391dcdbf0e63438d9b0a69fe20dd747fb439e8d54c43af016ff4eaaf0a4c9d8e7ca358bcd12dadf4ad2935e +"compare-versions@npm:^6.0.0": + version: 6.0.0 + resolution: "compare-versions@npm:6.0.0" + checksum: bf2fa355b2139cbdb0576f2f0328c112dd3c2eb808eff8b70b808b3ed05f8a40b8317c323ff4797a6a5a7ab32d508876749584c626ee5840dc0119361afffc4d languageName: node linkType: hard @@ -3593,11 +3690,9 @@ __metadata: linkType: hard "convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.7.0": - version: 1.8.0 - resolution: "convert-source-map@npm:1.8.0" - dependencies: - safe-buffer: ~5.1.1 - checksum: 985d974a2d33e1a2543ada51c93e1ba2f73eaed608dc39f229afc78f71dcc4c8b7d7c684aa647e3c6a3a204027444d69e53e169ce94e8d1fa8d7dee80c9c8fed + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: dc55a1f28ddd0e9485ef13565f8f756b342f9a46c4ae18b843fe3c30c675d058d6a4823eff86d472f187b176f0adf51ea7b69ea38be34be4a63cbbf91b0593c8 languageName: node linkType: hard @@ -3692,11 +3787,11 @@ __metadata: linkType: hard "cross-fetch@npm:^3.1.5": - version: 3.1.5 - resolution: "cross-fetch@npm:3.1.5" + version: 3.1.8 + resolution: "cross-fetch@npm:3.1.8" dependencies: - node-fetch: 2.6.7 - checksum: f6b8c6ee3ef993ace6277fd789c71b6acf1b504fd5f5c7128df4ef2f125a429e29cd62dc8c127523f04a5f2fa4771ed80e3f3d9695617f441425045f505cf3bb + node-fetch: ^2.6.12 + checksum: 78f993fa099eaaa041122ab037fe9503ecbbcb9daef234d1d2e0b9230a983f64d645d088c464e21a247b825a08dc444a6e7064adfa93536d3a9454b4745b3632 languageName: node linkType: hard @@ -3713,7 +3808,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.2": +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" dependencies: @@ -3747,7 +3842,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:2.6.9, debug@npm:^2.6.0, debug@npm:^2.6.9": +"debug@npm:2.6.9, debug@npm:^2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" dependencies: @@ -3765,7 +3860,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -3807,21 +3902,12 @@ __metadata: languageName: node linkType: hard -"deep-eql@npm:^3.0.1": - version: 3.0.1 - resolution: "deep-eql@npm:3.0.1" - dependencies: - type-detect: ^4.0.0 - checksum: 4f4c9fb79eb994fb6e81d4aa8b063adc40c00f831588aa65e20857d5d52f15fb23034a6576ecf886f7ff6222d5ae42e71e9b7d57113e0715b1df7ea1e812b125 - languageName: node - linkType: hard - -"deep-eql@npm:^4.0.1": - version: 4.1.0 - resolution: "deep-eql@npm:4.1.0" +"deep-eql@npm:^4.0.1, deep-eql@npm:^4.1.2": + version: 4.1.3 + resolution: "deep-eql@npm:4.1.3" dependencies: type-detect: ^4.0.0 - checksum: 2fccd527df9a70a92a1dfa8c771d139753625938e137b09fc946af8577d22360ef28d3c74f0e9c5aaa399bab20542d0899da1529c71db76f280a30147cd2a110 + checksum: 7f6d30cb41c713973dc07eaadded848b2ab0b835e518a88b91bea72f34e08c4c71d167a722a6f302d3a6108f05afd8e6d7650689a84d5d29ec7fe6220420397f languageName: node linkType: hard @@ -3832,32 +3918,6 @@ __metadata: languageName: node linkType: hard -"defender-admin-client@npm:^1.39.0": - version: 1.43.0 - resolution: "defender-admin-client@npm:1.43.0" - dependencies: - axios: ^0.21.2 - defender-base-client: 1.43.0 - ethers: ^5.7.2 - lodash: ^4.17.19 - node-fetch: ^2.6.0 - checksum: 489e1f9822d5f36d04b2668eee411f7b46747a88128e4184ac4b6c9fd2fc6caacfac3541429ffa086b3639c3aeb4fc5b87f53bc0e151790c27de5db5804dca61 - languageName: node - linkType: hard - -"defender-base-client@npm:1.43.0, defender-base-client@npm:^1.40.0": - version: 1.43.0 - resolution: "defender-base-client@npm:1.43.0" - dependencies: - amazon-cognito-identity-js: ^6.0.1 - async-retry: ^1.3.3 - axios: ^0.21.2 - lodash: ^4.17.19 - node-fetch: ^2.6.0 - checksum: e0862837112fd8e00252e11dc32b7f7afa1e25b48ca0ad0cd8b2d443024fa3bf17b683bc0922e8464251b7919378925ce36e8227f7355a7978ef303db7b2ba77 - languageName: node - linkType: hard - "define-lazy-prop@npm:^2.0.0": version: 2.0.0 resolution: "define-lazy-prop@npm:2.0.0" @@ -3865,13 +3925,13 @@ __metadata: languageName: node linkType: hard -"define-properties@npm:^1.1.2, define-properties@npm:^1.1.3, define-properties@npm:^1.1.4": - version: 1.1.4 - resolution: "define-properties@npm:1.1.4" +"define-properties@npm:^1.1.2, define-properties@npm:^1.1.3, define-properties@npm:^1.1.4, define-properties@npm:^1.2.0": + version: 1.2.0 + resolution: "define-properties@npm:1.2.0" dependencies: has-property-descriptors: ^1.0.0 object-keys: ^1.1.1 - checksum: ce0aef3f9eb193562b5cfb79b2d2c86b6a109dfc9fdcb5f45d680631a1a908c06824ddcdb72b7573b54e26ace07f0a23420aaba0d5c627b34d2c1de8ef527e2b + checksum: e60aee6a19b102df4e2b1f301816804e81ab48bb91f00d0d935f269bf4b3f79c88b39e4f89eaa132890d23267335fd1140dfcd8d5ccd61031a0a2c41a54e33a6 languageName: node linkType: hard @@ -3889,20 +3949,13 @@ __metadata: languageName: node linkType: hard -"depd@npm:2.0.0": +"depd@npm:2.0.0, depd@npm:^2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: abbe19c768c97ee2eed6282d8ce3031126662252c58d711f646921c9623f9052e3e1906443066beec1095832f534e57c523b7333f8e7e0d93051ab6baef5ab3a languageName: node linkType: hard -"depd@npm:^1.1.2": - version: 1.1.2 - resolution: "depd@npm:1.1.2" - checksum: 6b406620d269619852885ce15965272b829df6f409724415e0002c8632ab6a8c0a08ec1f0bd2add05dc7bd7507606f7e2cc034fa24224ab829580040b835ecd9 - languageName: node - linkType: hard - "destroy@npm:1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" @@ -3911,15 +3964,15 @@ __metadata: linkType: hard "detect-port@npm:^1.3.0": - version: 1.3.0 - resolution: "detect-port@npm:1.3.0" + version: 1.5.1 + resolution: "detect-port@npm:1.5.1" dependencies: address: ^1.0.1 - debug: ^2.6.0 + debug: 4 bin: - detect: ./bin/detect-port - detect-port: ./bin/detect-port - checksum: 93c40febe714f56711d1fedc2b7a9cc4cbaa0fcddec0509876c46b9dd6099ed6bfd6662a4f35e5fa0301660f48ed516829253ab0fc90b9e79b823dd77786b379 + detect: bin/detect-port.js + detect-port: bin/detect-port.js + checksum: b48da9340481742547263d5d985e65d078592557863402ecf538511735e83575867e94f91fe74405ea19b61351feb99efccae7e55de9a151d5654e3417cea05b languageName: node linkType: hard @@ -3988,9 +4041,16 @@ __metadata: linkType: hard "dotenv@npm:^16.0.0": - version: 16.0.3 - resolution: "dotenv@npm:16.0.3" - checksum: afcf03f373d7a6d62c7e9afea6328e62851d627a4e73f2e12d0a8deae1cd375892004f3021883f8aec85932cd2834b091f568ced92b4774625b321db83b827f8 + version: 16.3.1 + resolution: "dotenv@npm:16.3.1" + checksum: 15d75e7279018f4bafd0ee9706593dd14455ddb71b3bcba9c52574460b7ccaf67d5cf8b2c08a5af1a9da6db36c956a04a1192b101ee102a3e0cf8817bbcf3dfd + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 7d00d7cd8e49b9afa762a813faac332dee781932d6f2c848dc348939c4253f1d4564341b7af1d041853bc3f32c2ef141b58e0a4d9862c17a7f08f68df1e0f1ed languageName: node linkType: hard @@ -4011,10 +4071,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.4.172": - version: 1.4.180 - resolution: "electron-to-chromium@npm:1.4.180" - checksum: 98df07cfb0f67c383aa124ffc12fd9e9d3fd50e1c79548fc3d1524f3dbc3ac92c41f145a97e4e434d3d959f31e8ceab1e7426a6a5ef846af19e5b30e60bb7e98 +"electron-to-chromium@npm:^1.4.431": + version: 1.4.474 + resolution: "electron-to-chromium@npm:1.4.474" + checksum: 16e55823064dfa6f64088a3d5124c0c5c2a577c981a35e58199a2baa6a237b4d9505ddf406d4c8761cabdf6f7b347013a57a887883781dfb04d1940f8be379b0 languageName: node linkType: hard @@ -4033,13 +4093,6 @@ __metadata: languageName: node linkType: hard -"emoji-regex@npm:^10.0.0": - version: 10.1.0 - resolution: "emoji-regex@npm:10.1.0" - checksum: 5bc780fc4d75f89369155a87c55f7e83a0bf72bcccda7df7f2c570cde4738d8b17d112d12afdadfec16647d1faef6501307b4304f81d35c823a938fe6547df0f - languageName: node - linkType: hard - "emoji-regex@npm:^7.0.1": version: 7.0.3 resolution: "emoji-regex@npm:7.0.3" @@ -4054,6 +4107,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 8487182da74aabd810ac6d6f1994111dfc0e331b01271ae01ec1eb0ad7b5ecc2bbbbd2f053c05cb55a1ac30449527d819bbfbf0e3de1023db308cbcb47f86601 + languageName: node + linkType: hard + "encode-utf8@npm:^1.0.2": version: 1.0.3 resolution: "encode-utf8@npm:1.0.3" @@ -4078,11 +4138,12 @@ __metadata: linkType: hard "enquirer@npm:^2.3.0, enquirer@npm:^2.3.6": - version: 2.3.6 - resolution: "enquirer@npm:2.3.6" + version: 2.4.0 + resolution: "enquirer@npm:2.4.0" dependencies: ansi-colors: ^4.1.1 - checksum: 1c0911e14a6f8d26721c91e01db06092a5f7675159f0261d69c403396a385afd13dd76825e7678f66daffa930cfaa8d45f506fb35f818a2788463d022af1b884 + strip-ansi: ^6.0.1 + checksum: bbdecde92679ed847c751dc5337ff39ce0c32d85e76fb2e47245e831e9cc7f84c12bd35b70f7b1de9b9e1c730d90cccd04201e69f1ecf7986a7a70d8d39349db languageName: node linkType: hard @@ -4109,34 +4170,50 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.19.0, es-abstract@npm:^1.19.1, es-abstract@npm:^1.19.2, es-abstract@npm:^1.19.5, es-abstract@npm:^1.20.1": - version: 1.20.1 - resolution: "es-abstract@npm:1.20.1" +"es-abstract@npm:^1.19.0, es-abstract@npm:^1.20.4, es-abstract@npm:^1.21.2": + version: 1.22.1 + resolution: "es-abstract@npm:1.22.1" dependencies: + array-buffer-byte-length: ^1.0.0 + arraybuffer.prototype.slice: ^1.0.1 + available-typed-arrays: ^1.0.5 call-bind: ^1.0.2 + es-set-tostringtag: ^2.0.1 es-to-primitive: ^1.2.1 - function-bind: ^1.1.1 function.prototype.name: ^1.1.5 - get-intrinsic: ^1.1.1 + get-intrinsic: ^1.2.1 get-symbol-description: ^1.0.0 + globalthis: ^1.0.3 + gopd: ^1.0.1 has: ^1.0.3 has-property-descriptors: ^1.0.0 + has-proto: ^1.0.1 has-symbols: ^1.0.3 - internal-slot: ^1.0.3 - is-callable: ^1.2.4 + internal-slot: ^1.0.5 + is-array-buffer: ^3.0.2 + is-callable: ^1.2.7 is-negative-zero: ^2.0.2 is-regex: ^1.1.4 is-shared-array-buffer: ^1.0.2 is-string: ^1.0.7 + is-typed-array: ^1.1.10 is-weakref: ^1.0.2 - object-inspect: ^1.12.0 + object-inspect: ^1.12.3 object-keys: ^1.1.1 - object.assign: ^4.1.2 - regexp.prototype.flags: ^1.4.3 - string.prototype.trimend: ^1.0.5 - string.prototype.trimstart: ^1.0.5 + object.assign: ^4.1.4 + regexp.prototype.flags: ^1.5.0 + safe-array-concat: ^1.0.0 + safe-regex-test: ^1.0.0 + string.prototype.trim: ^1.2.7 + string.prototype.trimend: ^1.0.6 + string.prototype.trimstart: ^1.0.6 + typed-array-buffer: ^1.0.0 + typed-array-byte-length: ^1.0.0 + typed-array-byte-offset: ^1.0.0 + typed-array-length: ^1.0.4 unbox-primitive: ^1.0.2 - checksum: 28da27ae0ed9c76df7ee8ef5c278df79dcfdb554415faf7068bb7c58f8ba8e2a16bfb59e586844be6429ab4c302ca7748979d48442224cb1140b051866d74b7f + which-typed-array: ^1.1.10 + checksum: 614e2c1c3717cb8d30b6128ef12ea110e06fd7d75ad77091ca1c5dbfb00da130e62e4bbbbbdda190eada098a22b27fe0f99ae5a1171dac2c8663b1e8be8a3a9b languageName: node linkType: hard @@ -4147,6 +4224,17 @@ __metadata: languageName: node linkType: hard +"es-set-tostringtag@npm:^2.0.1": + version: 2.0.1 + resolution: "es-set-tostringtag@npm:2.0.1" + dependencies: + get-intrinsic: ^1.1.3 + has: ^1.0.3 + has-tostringtag: ^1.0.0 + checksum: ec416a12948cefb4b2a5932e62093a7cf36ddc3efd58d6c58ca7ae7064475ace556434b869b0bbeb0c365f1032a8ccd577211101234b69837ad83ad204fff884 + languageName: node + linkType: hard + "es-shim-unscopables@npm:^1.0.0": version: 1.0.0 resolution: "es-shim-unscopables@npm:1.0.0" @@ -4221,7 +4309,7 @@ __metadata: languageName: node linkType: hard -"eslint-config-prettier@npm:^8.5.0": +"eslint-config-prettier@npm:8.5.0": version: 8.5.0 resolution: "eslint-config-prettier@npm:8.5.0" peerDependencies: @@ -4232,7 +4320,7 @@ __metadata: languageName: node linkType: hard -"eslint-config-standard@npm:^16.0.3": +"eslint-config-standard@npm:16.0.3": version: 16.0.3 resolution: "eslint-config-standard@npm:16.0.3" peerDependencies: @@ -4245,22 +4333,25 @@ __metadata: linkType: hard "eslint-import-resolver-node@npm:^0.3.6": - version: 0.3.6 - resolution: "eslint-import-resolver-node@npm:0.3.6" + version: 0.3.7 + resolution: "eslint-import-resolver-node@npm:0.3.7" dependencies: debug: ^3.2.7 - resolve: ^1.20.0 - checksum: 6266733af1e112970e855a5bcc2d2058fb5ae16ad2a6d400705a86b29552b36131ffc5581b744c23d550de844206fb55e9193691619ee4dbf225c4bde526b1c8 + is-core-module: ^2.11.0 + resolve: ^1.22.1 + checksum: 3379aacf1d2c6952c1b9666c6fa5982c3023df695430b0d391c0029f6403a7775414873d90f397e98ba6245372b6c8960e16e74d9e4a3b0c0a4582f3bdbe3d6e languageName: node linkType: hard -"eslint-module-utils@npm:^2.7.3": - version: 2.7.3 - resolution: "eslint-module-utils@npm:2.7.3" +"eslint-module-utils@npm:^2.7.2": + version: 2.8.0 + resolution: "eslint-module-utils@npm:2.8.0" dependencies: debug: ^3.2.7 - find-up: ^2.1.0 - checksum: 77048263f309167a1e6a1e1b896bfb5ddd1d3859b2e2abbd9c32c432aee13d610d46e6820b1ca81b37fba437cf423a404bc6649be64ace9148a3062d1886a678 + peerDependenciesMeta: + eslint: + optional: true + checksum: 74c6dfea7641ebcfe174be61168541a11a14aa8d72e515f5f09af55cd0d0862686104b0524aa4b8e0ce66418a44aa38a94d2588743db5fd07a6b49ffd16921d2 languageName: node linkType: hard @@ -4276,30 +4367,30 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-import@npm:^2.25.4": - version: 2.26.0 - resolution: "eslint-plugin-import@npm:2.26.0" +"eslint-plugin-import@npm:2.25.4": + version: 2.25.4 + resolution: "eslint-plugin-import@npm:2.25.4" dependencies: array-includes: ^3.1.4 array.prototype.flat: ^1.2.5 debug: ^2.6.9 doctrine: ^2.1.0 eslint-import-resolver-node: ^0.3.6 - eslint-module-utils: ^2.7.3 + eslint-module-utils: ^2.7.2 has: ^1.0.3 - is-core-module: ^2.8.1 + is-core-module: ^2.8.0 is-glob: ^4.0.3 - minimatch: ^3.1.2 + minimatch: ^3.0.4 object.values: ^1.1.5 - resolve: ^1.22.0 - tsconfig-paths: ^3.14.1 + resolve: ^1.20.0 + tsconfig-paths: ^3.12.0 peerDependencies: eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - checksum: 0bf77ad80339554481eafa2b1967449e1f816b94c7a6f9614ce33fb4083c4e6c050f10d241dd50b4975d47922880a34de1e42ea9d8e6fd663ebb768baa67e655 + checksum: 0af24f5c7c6ca692f42e3947127f0ae7dfe44f1e02740f7cbe988b510a9c52bab0065d7df04e2d953dcc88a4595a00cbdcf14018acf8cd75cfd47b72efcbb734 languageName: node linkType: hard -"eslint-plugin-node@npm:^11.1.0": +"eslint-plugin-node@npm:11.1.0": version: 11.1.0 resolution: "eslint-plugin-node@npm:11.1.0" dependencies: @@ -4315,9 +4406,9 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-prettier@npm:^4.0.0": - version: 4.2.1 - resolution: "eslint-plugin-prettier@npm:4.2.1" +"eslint-plugin-prettier@npm:4.0.0": + version: 4.0.0 + resolution: "eslint-plugin-prettier@npm:4.0.0" dependencies: prettier-linter-helpers: ^1.0.0 peerDependencies: @@ -4326,11 +4417,11 @@ __metadata: peerDependenciesMeta: eslint-config-prettier: optional: true - checksum: b9e839d2334ad8ec7a5589c5cb0f219bded260839a857d7a486997f9870e95106aa59b8756ff3f37202085ebab658de382b0267cae44c3a7f0eb0bcc03a4f6d6 + checksum: 03d69177a3c21fa2229c7e427ce604429f0b20ab7f411e2e824912f572a207c7f5a41fd1f0a95b9b8afe121e291c1b1f1dc1d44c7aad4b0837487f9c19f5210d languageName: node linkType: hard -"eslint-plugin-promise@npm:^6.0.0": +"eslint-plugin-promise@npm:6.0.0": version: 6.0.0 resolution: "eslint-plugin-promise@npm:6.0.0" peerDependencies: @@ -4360,12 +4451,12 @@ __metadata: linkType: hard "eslint-scope@npm:^7.1.1": - version: 7.1.1 - resolution: "eslint-scope@npm:7.1.1" + version: 7.2.1 + resolution: "eslint-scope@npm:7.2.1" dependencies: esrecurse: ^4.3.0 estraverse: ^5.2.0 - checksum: 9f6e974ab2db641ca8ab13508c405b7b859e72afe9f254e8131ff154d2f40c99ad4545ce326fd9fde3212ff29707102562a4834f1c48617b35d98c71a97fbf3e + checksum: dccda5c8909216f6261969b72c77b95e385f9086bed4bc09d8a6276df8439d8f986810fd9ac3bd02c94c0572cefc7fdbeae392c69df2e60712ab8263986522c5 languageName: node linkType: hard @@ -4412,10 +4503,55 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0": - version: 3.3.0 - resolution: "eslint-visitor-keys@npm:3.3.0" - checksum: d59e68a7c5a6d0146526b0eec16ce87fbf97fe46b8281e0d41384224375c4e52f5ffb9e16d48f4ea50785cde93f766b0c898e31ab89978d88b0e1720fbfb7808 +"eslint-visitor-keys@npm:^3.0.0, eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1": + version: 3.4.1 + resolution: "eslint-visitor-keys@npm:3.4.1" + checksum: f05121d868202736b97de7d750847a328fcfa8593b031c95ea89425333db59676ac087fa905eba438d0a3c5769632f828187e0c1a0d271832a2153c1d3661c2c + languageName: node + linkType: hard + +"eslint@npm:8.14.0": + version: 8.14.0 + resolution: "eslint@npm:8.14.0" + dependencies: + "@eslint/eslintrc": ^1.2.2 + "@humanwhocodes/config-array": ^0.9.2 + ajv: ^6.10.0 + chalk: ^4.0.0 + cross-spawn: ^7.0.2 + debug: ^4.3.2 + doctrine: ^3.0.0 + escape-string-regexp: ^4.0.0 + eslint-scope: ^7.1.1 + eslint-utils: ^3.0.0 + eslint-visitor-keys: ^3.3.0 + espree: ^9.3.1 + esquery: ^1.4.0 + esutils: ^2.0.2 + fast-deep-equal: ^3.1.3 + file-entry-cache: ^6.0.1 + functional-red-black-tree: ^1.0.1 + glob-parent: ^6.0.1 + globals: ^13.6.0 + ignore: ^5.2.0 + import-fresh: ^3.0.0 + imurmurhash: ^0.1.4 + is-glob: ^4.0.0 + js-yaml: ^4.1.0 + json-stable-stringify-without-jsonify: ^1.0.1 + levn: ^0.4.1 + lodash.merge: ^4.6.2 + minimatch: ^3.0.4 + natural-compare: ^1.4.0 + optionator: ^0.9.1 + regexpp: ^3.2.0 + strip-ansi: ^6.0.1 + strip-json-comments: ^3.1.0 + text-table: ^0.2.0 + v8-compile-cache: ^2.0.3 + bin: + eslint: bin/eslint.js + checksum: 87d2e3e5eb93216d4ab36006e7b8c0bfad02f40b0a0f193f1d42754512cd3a9d8244152f1c69df5db2e135b3c4f1c10d0ed2f0881fe8a8c01af55465968174c1 languageName: node linkType: hard @@ -4465,51 +4601,6 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.14.0": - version: 8.19.0 - resolution: "eslint@npm:8.19.0" - dependencies: - "@eslint/eslintrc": ^1.3.0 - "@humanwhocodes/config-array": ^0.9.2 - ajv: ^6.10.0 - chalk: ^4.0.0 - cross-spawn: ^7.0.2 - debug: ^4.3.2 - doctrine: ^3.0.0 - escape-string-regexp: ^4.0.0 - eslint-scope: ^7.1.1 - eslint-utils: ^3.0.0 - eslint-visitor-keys: ^3.3.0 - espree: ^9.3.2 - esquery: ^1.4.0 - esutils: ^2.0.2 - fast-deep-equal: ^3.1.3 - file-entry-cache: ^6.0.1 - functional-red-black-tree: ^1.0.1 - glob-parent: ^6.0.1 - globals: ^13.15.0 - ignore: ^5.2.0 - import-fresh: ^3.0.0 - imurmurhash: ^0.1.4 - is-glob: ^4.0.0 - js-yaml: ^4.1.0 - json-stable-stringify-without-jsonify: ^1.0.1 - levn: ^0.4.1 - lodash.merge: ^4.6.2 - minimatch: ^3.1.2 - natural-compare: ^1.4.0 - optionator: ^0.9.1 - regexpp: ^3.2.0 - strip-ansi: ^6.0.1 - strip-json-comments: ^3.1.0 - text-table: ^0.2.0 - v8-compile-cache: ^2.0.3 - bin: - eslint: bin/eslint.js - checksum: 0bc9df1a3a09dcd5a781ec728f280aa8af3ab19c2d1f14e2668b5ee5b8b1fb0e72dde5c3acf738e7f4281685fb24ec149b6154255470b06cf41de76350bca7a4 - languageName: node - linkType: hard - "espree@npm:^5.0.1": version: 5.0.1 resolution: "espree@npm:5.0.1" @@ -4521,14 +4612,14 @@ __metadata: languageName: node linkType: hard -"espree@npm:^9.3.2": - version: 9.3.2 - resolution: "espree@npm:9.3.2" +"espree@npm:^9.3.1, espree@npm:^9.4.0": + version: 9.6.1 + resolution: "espree@npm:9.6.1" dependencies: - acorn: ^8.7.1 + acorn: ^8.9.0 acorn-jsx: ^5.3.2 - eslint-visitor-keys: ^3.3.0 - checksum: 9a790d6779847051e87f70d720a0f6981899a722419e80c92ab6dee01e1ab83b8ce52d11b4dc96c2c490182efb5a4c138b8b0d569205bfe1cd4629e658e58c30 + eslint-visitor-keys: ^3.4.1 + checksum: eb8c149c7a2a77b3f33a5af80c10875c3abd65450f60b8af6db1bfcfa8f101e21c1e56a561c6dc13b848e18148d43469e7cd208506238554fb5395a9ea5a1ab9 languageName: node linkType: hard @@ -4553,11 +4644,11 @@ __metadata: linkType: hard "esquery@npm:^1.0.1, esquery@npm:^1.4.0": - version: 1.4.0 - resolution: "esquery@npm:1.4.0" + version: 1.5.0 + resolution: "esquery@npm:1.5.0" dependencies: estraverse: ^5.1.0 - checksum: a0807e17abd7fbe5fbd4fab673038d6d8a50675cdae6b04fbaa520c34581be0c5fa24582990e8acd8854f671dd291c78bb2efb9e0ed5b62f33bac4f9cf820210 + checksum: aefb0d2596c230118656cd4ec7532d447333a410a48834d80ea648b1e7b5c9bc9ed8b5e33a89cb04e487b60d622f44cf5713bf4abed7c97343edefdc84a35900 languageName: node linkType: hard @@ -4605,7 +4696,7 @@ __metadata: languageName: node linkType: hard -"eth-gas-reporter@npm:^0.2.24": +"eth-gas-reporter@npm:^0.2.25": version: 0.2.25 resolution: "eth-gas-reporter@npm:0.2.25" dependencies: @@ -4634,11 +4725,11 @@ __metadata: linkType: hard "eth-permit@npm:^0.2.1": - version: 0.2.1 - resolution: "eth-permit@npm:0.2.1" + version: 0.2.3 + resolution: "eth-permit@npm:0.2.3" dependencies: utf8: ^3.0.0 - checksum: 8ffd51659cbd33ccf50abccd1e0fd2abd4067de328af0bfe32b251f298b9daea40bafed995dfe40d78a28b9af4863e5d2eb69fd3b91dfe405643e8c7e7da3359 + checksum: 9bf3eed8ecb8c914aadfff97d6c3d19fc432928c72fbe205075153e84289ea95f3a101f77e2dae78ad629e09682700241f207b5436b575ca7d4fbae68eacd3f6 languageName: node linkType: hard @@ -4675,14 +4766,14 @@ __metadata: linkType: hard "ethereum-cryptography@npm:^1.0.3": - version: 1.1.0 - resolution: "ethereum-cryptography@npm:1.1.0" + version: 1.2.0 + resolution: "ethereum-cryptography@npm:1.2.0" dependencies: - "@noble/hashes": 1.1.1 - "@noble/secp256k1": 1.6.0 - "@scure/bip32": 1.1.0 - "@scure/bip39": 1.1.0 - checksum: cba0bc58272ccc9eca4cf045bd4b6edb083486069ae0a62fba0fea5385a8c4257ea0faf135868440044fb37047bc0d7f39090c21ea409be106a9f9004a3792b5 + "@noble/hashes": 1.2.0 + "@noble/secp256k1": 1.7.1 + "@scure/bip32": 1.1.5 + "@scure/bip39": 1.1.1 + checksum: 97e8e8253cb9f5a9271bd0201c37609c451c890eb85883b9c564f14743c3d7c673287406c93bf5604307593ee298ad9a03983388b85c11ca61461b9fc1a4f2c7 languageName: node linkType: hard @@ -4817,16 +4908,23 @@ __metadata: languageName: node linkType: hard -"expect@npm:^28.1.1": - version: 28.1.1 - resolution: "expect@npm:28.1.1" +"expect@npm:^28.1.3": + version: 28.1.3 + resolution: "expect@npm:28.1.3" dependencies: - "@jest/expect-utils": ^28.1.1 + "@jest/expect-utils": ^28.1.3 jest-get-type: ^28.0.2 - jest-matcher-utils: ^28.1.1 - jest-message-util: ^28.1.1 - jest-util: ^28.1.1 - checksum: 6e557b681f4cfb0bf61efad50c5787cc6eb4596a3c299be69adc83fcad0265b5f329b997c2bb7ec92290e609681485616e51e16301a7f0ba3c57139b337c9351 + jest-matcher-utils: ^28.1.3 + jest-message-util: ^28.1.3 + jest-util: ^28.1.3 + checksum: 101e0090de300bcafedb7dbfd19223368a2251ce5fe0105bbb6de5720100b89fb6b64290ebfb42febc048324c76d6a4979cdc4b61eb77747857daf7a5de9b03d + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.1 + resolution: "exponential-backoff@npm:3.1.1" + checksum: 3d21519a4f8207c99f7457287291316306255a328770d320b401114ec8481986e4e467e854cb9914dd965e0a1ca810a23ccb559c642c88f4c7f55c55778a9b48 languageName: node linkType: hard @@ -4932,22 +5030,22 @@ __metadata: linkType: hard "fast-diff@npm:^1.1.2": - version: 1.2.0 - resolution: "fast-diff@npm:1.2.0" - checksum: 1b5306eaa9e826564d9e5ffcd6ebd881eb5f770b3f977fcbf38f05c824e42172b53c79920e8429c54eb742ce15a0caf268b0fdd5b38f6de52234c4a8368131ae + version: 1.3.0 + resolution: "fast-diff@npm:1.3.0" + checksum: d22d371b994fdc8cce9ff510d7b8dc4da70ac327bcba20df607dd5b9cae9f908f4d1028f5fe467650f058d1e7270235ae0b8230809a262b4df587a3b3aa216c3 languageName: node linkType: hard "fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.9": - version: 3.2.11 - resolution: "fast-glob@npm:3.2.11" + version: 3.3.1 + resolution: "fast-glob@npm:3.3.1" dependencies: "@nodelib/fs.stat": ^2.0.2 "@nodelib/fs.walk": ^1.2.3 glob-parent: ^5.1.2 merge2: ^1.3.0 micromatch: ^4.0.4 - checksum: f473105324a7780a20c06de842e15ddbb41d3cb7e71d1e4fe6e8373204f22245d54f5ab9e2061e6a1c613047345954d29b022e0e76f5c28b1df9858179a0e6d7 + checksum: b6f3add6403e02cf3a798bfbb1183d0f6da2afd368f27456010c0bc1f9640aea308243d4cb2c0ab142f618276e65ecb8be1661d7c62a7b4e5ba774b9ce5432e5 languageName: node linkType: hard @@ -4966,20 +5064,20 @@ __metadata: linkType: hard "fastq@npm:^1.6.0": - version: 1.13.0 - resolution: "fastq@npm:1.13.0" + version: 1.15.0 + resolution: "fastq@npm:1.15.0" dependencies: reusify: ^1.0.4 - checksum: 32cf15c29afe622af187d12fc9cd93e160a0cb7c31a3bb6ace86b7dea3b28e7b72acde89c882663f307b2184e14782c6c664fa315973c03626c7d4bff070bb0b + checksum: 0170e6bfcd5d57a70412440b8ef600da6de3b2a6c5966aeaf0a852d542daff506a0ee92d6de7679d1de82e644bce69d7a574a6c93f0b03964b5337eed75ada1a languageName: node linkType: hard "fb-watchman@npm:^2.0.0": - version: 2.0.1 - resolution: "fb-watchman@npm:2.0.1" + version: 2.0.2 + resolution: "fb-watchman@npm:2.0.2" dependencies: bser: 2.1.1 - checksum: 8510230778ab3a51c27dffb1b76ef2c24fab672a42742d3c0a45c2e9d1e5f20210b1fbca33486088da4a9a3958bde96b5aec0a63aac9894b4e9df65c88b2cbd6 + checksum: b15a124cef28916fe07b400eb87cbc73ca082c142abf7ca8e8de6af43eca79ca7bd13eb4d4d48240b3bd3136eaac40d16e42d6edf87a8e5d1dd8070626860c78 languageName: node linkType: hard @@ -5138,9 +5236,9 @@ __metadata: linkType: hard "flatted@npm:^3.1.0": - version: 3.2.6 - resolution: "flatted@npm:3.2.6" - checksum: 33b87aa88dfa40ca6ee31d7df61712bbbad3d3c05c132c23e59b9b61d34631b337a18ff2b8dc5553acdc871ec72b741e485f78969cf006124a3f57174de29a0e + version: 3.2.7 + resolution: "flatted@npm:3.2.7" + checksum: 427633049d55bdb80201c68f7eb1cbd533e03eac541f97d3aecab8c5526f12a20ccecaeede08b57503e772c769e7f8680b37e8d482d1e5f8d7e2194687f9ea35 languageName: node linkType: hard @@ -5153,7 +5251,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.12.1, follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.4, follow-redirects@npm:^1.14.9": +"follow-redirects@npm:^1.12.1, follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.4, follow-redirects@npm:^1.14.9, follow-redirects@npm:^1.15.0": version: 1.15.2 resolution: "follow-redirects@npm:1.15.2" peerDependenciesMeta: @@ -5163,6 +5261,25 @@ __metadata: languageName: node linkType: hard +"for-each@npm:^0.3.3": + version: 0.3.3 + resolution: "for-each@npm:0.3.3" + dependencies: + is-callable: ^1.1.3 + checksum: 6c48ff2bc63362319c65e2edca4a8e1e3483a2fabc72fbe7feaf8c73db94fc7861bd53bc02c8a66a0c1dd709da6b04eec42e0abdd6b40ce47305ae92a25e5d28 + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0": + version: 3.1.1 + resolution: "foreground-child@npm:3.1.1" + dependencies: + cross-spawn: ^7.0.0 + signal-exit: ^4.0.1 + checksum: 139d270bc82dc9e6f8bc045fe2aae4001dc2472157044fdfad376d0a3457f77857fa883c1c8b21b491c6caade9a926a4bed3d3d2e8d3c9202b151a4cbbd0bcd5 + languageName: node + linkType: hard + "forever-agent@npm:~0.6.1": version: 0.6.1 resolution: "forever-agent@npm:0.6.1" @@ -5300,7 +5417,7 @@ __metadata: languageName: node linkType: hard -"fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0": +"fs-minipass@npm:^2.0.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" dependencies: @@ -5309,6 +5426,15 @@ __metadata: languageName: node linkType: hard +"fs-minipass@npm:^3.0.0": + version: 3.0.2 + resolution: "fs-minipass@npm:3.0.2" + dependencies: + minipass: ^5.0.0 + checksum: e9cc0e1f2d01c6f6f62f567aee59530aba65c6c7b2ae88c5027bc34c711ebcfcfaefd0caf254afa6adfe7d1fba16bc2537508a6235196bac7276747d078aef0a + languageName: node + linkType: hard + "fs-readdir-recursive@npm:^1.1.0": version: 1.1.0 resolution: "fs-readdir-recursive@npm:1.1.0" @@ -5323,7 +5449,7 @@ __metadata: languageName: node linkType: hard -"fsevents@^2.3.2, fsevents@~2.3.2": +"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": version: 2.3.2 resolution: "fsevents@npm:2.3.2" dependencies: @@ -5333,30 +5459,30 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@^2.3.2#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin": - version: 2.3.2 - resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=df0bf1" +"fsevents@npm:~2.1.1": + version: 2.1.3 + resolution: "fsevents@npm:2.1.3" dependencies: node-gyp: latest + checksum: b5ec0516b44d75b60af5c01ff80a80cd995d175e4640d2a92fbabd02991dd664d76b241b65feef0775c23d531c3c74742c0fbacd6205af812a9c3cef59f04292 conditions: os=darwin languageName: node linkType: hard -"fsevents@patch:fsevents@~2.1.1#~builtin": - version: 2.1.3 - resolution: "fsevents@patch:fsevents@npm%3A2.1.3#~builtin::version=2.1.3&hash=31d12a" +"fsevents@patch:fsevents@^2.3.2#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin": + version: 2.3.2 + resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=df0bf1" dependencies: node-gyp: latest conditions: os=darwin languageName: node linkType: hard -fsevents@~2.1.1: +"fsevents@patch:fsevents@~2.1.1#~builtin": version: 2.1.3 - resolution: "fsevents@npm:2.1.3" + resolution: "fsevents@patch:fsevents@npm%3A2.1.3#~builtin::version=2.1.3&hash=31d12a" dependencies: node-gyp: latest - checksum: b5ec0516b44d75b60af5c01ff80a80cd995d175e4640d2a92fbabd02991dd664d76b241b65feef0775c23d531c3c74742c0fbacd6205af812a9c3cef59f04292 conditions: os=darwin languageName: node linkType: hard @@ -5387,7 +5513,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"functions-have-names@npm:^1.2.2": +"functions-have-names@npm:^1.2.2, functions-have-names@npm:^1.2.3": version: 1.2.3 resolution: "functions-have-names@npm:1.2.3" checksum: c3f1f5ba20f4e962efb71344ce0a40722163e85bee2101ce25f88214e78182d2d2476aa85ef37950c579eb6cf6ee811c17b3101bb84004bb75655f3e33f3fdb5 @@ -5431,14 +5557,15 @@ fsevents@~2.1.1: languageName: node linkType: hard -"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1": - version: 1.1.2 - resolution: "get-intrinsic@npm:1.1.2" +"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.0, get-intrinsic@npm:^1.2.1": + version: 1.2.1 + resolution: "get-intrinsic@npm:1.2.1" dependencies: function-bind: ^1.1.1 has: ^1.0.3 + has-proto: ^1.0.1 has-symbols: ^1.0.3 - checksum: 252f45491f2ba88ebf5b38018020c7cc3279de54b1d67ffb70c0cdf1dfa8ab31cd56467b5d117a8b4275b7a4dde91f86766b163a17a850f036528a7b2faafb2b + checksum: 5b61d88552c24b0cf6fa2d1b3bc5459d7306f699de060d76442cce49a4721f52b8c560a33ab392cf5575b7810277d54ded9d4d39a1ea61855619ebc005aa7e5f languageName: node linkType: hard @@ -5533,6 +5660,21 @@ fsevents@~2.1.1: languageName: node linkType: hard +"glob@npm:^10.2.2": + version: 10.3.3 + resolution: "glob@npm:10.3.3" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^2.0.3 + minimatch: ^9.0.1 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + path-scurry: ^1.10.1 + bin: + glob: dist/cjs/src/bin.js + checksum: 29190d3291f422da0cb40b77a72fc8d2c51a36524e99b8bf412548b7676a6627489528b57250429612b6eec2e6fe7826d328451d3e694a9d15e575389308ec53 + languageName: node + linkType: hard + "glob@npm:^5.0.15": version: 5.0.15 resolution: "glob@npm:5.0.15" @@ -5560,19 +5702,6 @@ fsevents@~2.1.1: languageName: node linkType: hard -"glob@npm:^8.0.1": - version: 8.0.3 - resolution: "glob@npm:8.0.3" - dependencies: - fs.realpath: ^1.0.0 - inflight: ^1.0.4 - inherits: 2 - minimatch: ^5.0.1 - once: ^1.3.0 - checksum: 50bcdea19d8e79d8de5f460b1939ffc2b3299eac28deb502093fdca22a78efebc03e66bf54f0abc3d3d07d8134d19a32850288b7440d77e072aa55f9d33b18c5 - languageName: node - linkType: hard - "global-modules@npm:^2.0.0": version: 2.0.0 resolution: "global-modules@npm:2.0.0" @@ -5600,12 +5729,21 @@ fsevents@~2.1.1: languageName: node linkType: hard -"globals@npm:^13.15.0": - version: 13.16.0 - resolution: "globals@npm:13.16.0" +"globals@npm:^13.19.0, globals@npm:^13.6.0": + version: 13.20.0 + resolution: "globals@npm:13.20.0" dependencies: type-fest: ^0.20.2 - checksum: e571b28462b8922a29ac78c8df89848cfd5dc9bdd5d8077440c022864f512a4aae82e7561a2f366337daa86fd4b366aec16fd3f08686de387e4089b01be6cb14 + checksum: ad1ecf914bd051325faad281d02ea2c0b1df5d01bd94d368dcc5513340eac41d14b3c61af325768e3c7f8d44576e72780ec0b6f2d366121f8eec6e03c3a3b97a + languageName: node + linkType: hard + +"globalthis@npm:^1.0.3": + version: 1.0.3 + resolution: "globalthis@npm:1.0.3" + dependencies: + define-properties: ^1.1.3 + checksum: fbd7d760dc464c886d0196166d92e5ffb4c84d0730846d6621a39fbbc068aeeb9c8d1421ad330e94b7bca4bb4ea092f5f21f3d36077812af5d098b4dc006c998 languageName: node linkType: hard @@ -5625,7 +5763,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"globby@npm:^11.1.0": +"globby@npm:^11.0.4": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -5639,10 +5777,19 @@ fsevents@~2.1.1: languageName: node linkType: hard +"gopd@npm:^1.0.1": + version: 1.0.1 + resolution: "gopd@npm:1.0.1" + dependencies: + get-intrinsic: ^1.1.3 + checksum: a5ccfb8806e0917a94e0b3de2af2ea4979c1da920bc381667c260e00e7cafdbe844e2cb9c5bcfef4e5412e8bf73bab837285bc35c7ba73aaaf0134d4583393a6 + languageName: node + linkType: hard + "graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": - version: 4.2.10 - resolution: "graceful-fs@npm:4.2.10" - checksum: 3f109d70ae123951905d85032ebeae3c2a5a7a997430df00ea30df0e3a6c60cf6689b109654d6fdacd28810a053348c4d14642da1d075049e6be1ba5216218da + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 languageName: node linkType: hard @@ -5661,9 +5808,9 @@ fsevents@~2.1.1: linkType: hard "graphql@npm:^16.6.0": - version: 16.6.0 - resolution: "graphql@npm:16.6.0" - checksum: bf1d9e3c1938ce3c1a81e909bd3ead1ae4707c577f91cff1ca2eca474bfbc7873d5d7b942e1e9777ff5a8304421dba57a4b76d7a29eb19de8711cb70e3c2415e + version: 16.7.1 + resolution: "graphql@npm:16.7.1" + checksum: c924d8428daf0e96a5ea43e9bc3cd1b6802899907d284478ac8f705c8fd233a0a51eef915f7569fb5de8acb2e85b802ccc6c85c2b157ad805c1e9adba5a299bd languageName: node linkType: hard @@ -5710,20 +5857,21 @@ fsevents@~2.1.1: linkType: hard "hardhat-contract-sizer@npm:^2.4.0": - version: 2.6.1 - resolution: "hardhat-contract-sizer@npm:2.6.1" + version: 2.10.0 + resolution: "hardhat-contract-sizer@npm:2.10.0" dependencies: chalk: ^4.0.0 cli-table3: ^0.6.0 + strip-ansi: ^6.0.0 peerDependencies: hardhat: ^2.0.0 - checksum: a82ae2405a8571e8b0cd0a21dea9a10946b342f1ada04c72c9cbe28fca955f9a2b1394c70400003f388182298dc1de00e80bf56dbfa5e36833d3c93ab1f50c0c + checksum: 870e7cad5d96ad7288b64da0faec7962a9a18e1eaaa02ed474e4f9285cd4b1a0fc6f66326e6a7476f7063fdf99aee57f227084519b1fb3723700a2d65fc65cfa languageName: node linkType: hard "hardhat-deploy@npm:^0.11.14": - version: 0.11.30 - resolution: "hardhat-deploy@npm:0.11.30" + version: 0.11.34 + resolution: "hardhat-deploy@npm:0.11.34" dependencies: "@ethersproject/abi": ^5.7.0 "@ethersproject/abstract-signer": ^5.7.0 @@ -5749,26 +5897,26 @@ fsevents@~2.1.1: murmur-128: ^0.2.1 qs: ^6.9.4 zksync-web3: ^0.14.3 - checksum: 7b9ac9d856097be1df88ed86cbec88e5bdeb6258c7167c097d6ad4e80a1131b9288fc7704ff6457253f293f57c9992d83383f15ce8f22190d94966b8bb05d832 + checksum: 3c4bcd657a80e4f22c1f8bcee021e5277060849ce4180cbc721e0c2d625f2f98de8ebbfad23875d32eeaf06d88bdba06cb43ab29359cb9531e8bb7851a98ead1 languageName: node linkType: hard "hardhat-gas-reporter@npm:^1.0.8": - version: 1.0.8 - resolution: "hardhat-gas-reporter@npm:1.0.8" + version: 1.0.9 + resolution: "hardhat-gas-reporter@npm:1.0.9" dependencies: array-uniq: 1.0.3 - eth-gas-reporter: ^0.2.24 + eth-gas-reporter: ^0.2.25 sha1: ^1.1.1 peerDependencies: hardhat: ^2.0.2 - checksum: bf18aacd08e0bdef81b180f3c97f76fcab885de3e92ed2dc014712e671c83ee7f77755c0e6c0f923a95f8372714cfcb7cdaa019afc42984c159603f8a8d724cf + checksum: 77f8f8d085ff3d9d7787f0227e5355e1800f7d6707bc70171e0567bf69706703ae7f6f53dce1be1d409e7e71e3629a434c94b546bdbbc1e4c1af47cd5d0c6776 languageName: node linkType: hard "hardhat@npm:^2.12.3": - version: 2.14.0 - resolution: "hardhat@npm:2.14.0" + version: 2.17.0 + resolution: "hardhat@npm:2.17.0" dependencies: "@ethersproject/abi": ^5.1.2 "@metamask/eth-sig-util": ^4.0.0 @@ -5809,7 +5957,6 @@ fsevents@~2.1.1: mnemonist: ^0.38.0 mocha: ^10.0.0 p-map: ^4.0.0 - qs: ^6.7.0 raw-body: ^2.4.1 resolve: 1.17.0 semver: ^6.3.0 @@ -5830,7 +5977,7 @@ fsevents@~2.1.1: optional: true bin: hardhat: internal/cli/bootstrap.js - checksum: 7a11ad4650759851306d65c30252ccffa2aca9cb461c66f3fcef5f29d38fe66402d6bc295d293670fa0a72bf3572cc95029c5cd0b0fd45f45edd99d6eb5b7586 + checksum: fcbbee245069a9c3fd0b7f015bc7b99529d3b2be6b8c0c9a61d6a68e5eb2bece8d4f7a9a206808af8fa46a3f3f05df51571caa4e88d3ebbc977c0f16fdb0aafe languageName: node linkType: hard @@ -5871,7 +6018,14 @@ fsevents@~2.1.1: languageName: node linkType: hard -"has-symbols@npm:^1.0.0, has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": +"has-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "has-proto@npm:1.0.1" + checksum: febc5b5b531de8022806ad7407935e2135f1cc9e64636c3916c6842bd7995994ca3b29871ecd7954bd35f9e2986c17b3b227880484d22259e2f8e6ce63fd383e + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.0, has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": version: 1.0.3 resolution: "has-symbols@npm:1.0.3" checksum: a054c40c631c0d5741a8285010a0777ea0c068f99ed43e5d6eb12972da223f8af553a455132fdb0801bdcfa0e0f443c0c03a68d8555aa529b3144b446c3f2410 @@ -5973,10 +6127,10 @@ fsevents@~2.1.1: languageName: node linkType: hard -"http-cache-semantics@npm:^4.1.0": - version: 4.1.0 - resolution: "http-cache-semantics@npm:4.1.0" - checksum: 974de94a81c5474be07f269f9fd8383e92ebb5a448208223bfb39e172a9dbc26feff250192ecc23b9593b3f92098e010406b0f24bd4d588d631f80214648ed42 +"http-cache-semantics@npm:^4.1.1": + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 languageName: node linkType: hard @@ -6091,17 +6245,17 @@ fsevents@~2.1.1: languageName: node linkType: hard -"ignore@npm:^5.1.1, ignore@npm:^5.2.0": - version: 5.2.0 - resolution: "ignore@npm:5.2.0" - checksum: 6b1f926792d614f64c6c83da3a1f9c83f6196c2839aa41e1e32dd7b8d174cef2e329d75caabb62cb61ce9dc432f75e67d07d122a037312db7caa73166a1bdb77 +"ignore@npm:^5.1.1, ignore@npm:^5.1.8, ignore@npm:^5.2.0": + version: 5.2.4 + resolution: "ignore@npm:5.2.4" + checksum: 3d4c309c6006e2621659311783eaea7ebcd41fe4ca1d78c91c473157ad6666a57a2df790fe0d07a12300d9aac2888204d7be8d59f9aaf665b1c7fcdb432517ef languageName: node linkType: hard "immutable@npm:^4.0.0-rc.12": - version: 4.1.0 - resolution: "immutable@npm:4.1.0" - checksum: b9bc1f14fb18eb382d48339c064b24a1f97ae4cf43102e0906c0a6e186a27afcd18b55ca4a0b63c98eefb58143e2b5ebc7755a5fb4da4a7ad84b7a6096ac5b13 + version: 4.3.1 + resolution: "immutable@npm:4.3.1" + checksum: a3a5ba29bd43f3f9a2e4d599763d7455d11a0ea57e50bf43f2836672fc80003e90d69f2a4f5b589f1f3d6986faf97f08ce1e253583740dd33c00adebab88b217 languageName: node linkType: hard @@ -6146,13 +6300,6 @@ fsevents@~2.1.1: languageName: node linkType: hard -"infer-owner@npm:^1.0.4": - version: 1.0.4 - resolution: "infer-owner@npm:1.0.4" - checksum: 181e732764e4a0611576466b4b87dac338972b839920b2a8cde43642e4ed6bd54dc1fb0b40874728f2a2df9a1b097b8ff83b56d5f8f8e3927f837fdcb47d8a89 - languageName: node - linkType: hard - "inflight@npm:^1.0.4": version: 1.0.6 resolution: "inflight@npm:1.0.6" @@ -6198,14 +6345,14 @@ fsevents@~2.1.1: languageName: node linkType: hard -"internal-slot@npm:^1.0.3": - version: 1.0.3 - resolution: "internal-slot@npm:1.0.3" +"internal-slot@npm:^1.0.5": + version: 1.0.5 + resolution: "internal-slot@npm:1.0.5" dependencies: - get-intrinsic: ^1.1.0 + get-intrinsic: ^1.2.0 has: ^1.0.3 side-channel: ^1.0.4 - checksum: 1944f92e981e47aebc98a88ff0db579fd90543d937806104d0b96557b10c1f170c51fb777b97740a8b6ddeec585fca8c39ae99fd08a8e058dfc8ab70937238bf + checksum: 97e84046bf9e7574d0956bd98d7162313ce7057883b6db6c5c7b5e5f05688864b0978ba07610c726d15d66544ffe4b1050107d93f8a39ebc59b15d8b429b497a languageName: node linkType: hard @@ -6225,10 +6372,10 @@ fsevents@~2.1.1: languageName: node linkType: hard -"ip@npm:^1.1.5": - version: 1.1.8 - resolution: "ip@npm:1.1.8" - checksum: a2ade53eb339fb0cbe9e69a44caab10d6e3784662285eb5d2677117ee4facc33a64679051c35e0dfdb1a3983a51ce2f5d2cb36446d52e10d01881789b76e28fb +"ip@npm:^2.0.0": + version: 2.0.0 + resolution: "ip@npm:2.0.0" + checksum: cfcfac6b873b701996d71ec82a7dd27ba92450afdb421e356f44044ed688df04567344c36cbacea7d01b1c39a4c732dc012570ebe9bebfb06f27314bca625349 languageName: node linkType: hard @@ -6239,6 +6386,17 @@ fsevents@~2.1.1: languageName: node linkType: hard +"is-array-buffer@npm:^3.0.1, is-array-buffer@npm:^3.0.2": + version: 3.0.2 + resolution: "is-array-buffer@npm:3.0.2" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.2.0 + is-typed-array: ^1.1.10 + checksum: dcac9dda66ff17df9cabdc58214172bf41082f956eab30bb0d86bc0fab1e44b690fc8e1f855cf2481245caf4e8a5a006a982a71ddccec84032ed41f9d8da8c14 + languageName: node + linkType: hard + "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -6281,19 +6439,19 @@ fsevents@~2.1.1: languageName: node linkType: hard -"is-callable@npm:^1.1.4, is-callable@npm:^1.2.4": - version: 1.2.4 - resolution: "is-callable@npm:1.2.4" - checksum: 1a28d57dc435797dae04b173b65d6d1e77d4f16276e9eff973f994eadcfdc30a017e6a597f092752a083c1103cceb56c91e3dadc6692fedb9898dfaba701575f +"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 61fd57d03b0d984e2ed3720fb1c7a897827ea174bd44402878e059542ea8c4aeedee0ea0985998aa5cc2736b2fa6e271c08587addb5b3959ac52cf665173d1ac languageName: node linkType: hard -"is-core-module@npm:^2.8.1, is-core-module@npm:^2.9.0": - version: 2.9.0 - resolution: "is-core-module@npm:2.9.0" +"is-core-module@npm:^2.11.0, is-core-module@npm:^2.12.0, is-core-module@npm:^2.8.0": + version: 2.12.1 + resolution: "is-core-module@npm:2.12.1" dependencies: has: ^1.0.3 - checksum: b27034318b4b462f1c8f1dfb1b32baecd651d891a4e2d1922135daeff4141dfced2b82b07aef83ef54275c4a3526aa38da859223664d0868ca24182badb784ce + checksum: f04ea30533b5e62764e7b2e049d3157dc0abd95ef44275b32489ea2081176ac9746ffb1cdb107445cf1ff0e0dfcad522726ca27c27ece64dadf3795428b8e468 languageName: node linkType: hard @@ -6428,8 +6586,17 @@ fsevents@~2.1.1: version: 1.0.4 resolution: "is-symbol@npm:1.0.4" dependencies: - has-symbols: ^1.0.2 - checksum: 92805812ef590738d9de49d677cd17dfd486794773fb6fa0032d16452af46e9b91bb43ffe82c983570f015b37136f4b53b28b8523bfb10b0ece7a66c31a54510 + has-symbols: ^1.0.2 + checksum: 92805812ef590738d9de49d677cd17dfd486794773fb6fa0032d16452af46e9b91bb43ffe82c983570f015b37136f4b53b28b8523bfb10b0ece7a66c31a54510 + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.9": + version: 1.1.12 + resolution: "is-typed-array@npm:1.1.12" + dependencies: + which-typed-array: ^1.1.11 + checksum: 4c89c4a3be07186caddadf92197b17fda663a9d259ea0d44a85f171558270d36059d1c386d34a12cba22dfade5aba497ce22778e866adc9406098c8fc4771796 languageName: node linkType: hard @@ -6472,6 +6639,13 @@ fsevents@~2.1.1: languageName: node linkType: hard +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: bd5bbe4104438c4196ba58a54650116007fa0262eccef13a4c55b2e09a5b36b59f1e75b9fcc49883dd9d4953892e6fc007eef9e9155648ceea036e184b0f930a + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -6514,27 +6688,40 @@ fsevents@~2.1.1: linkType: hard "istanbul-lib-instrument@npm:^5.0.4": - version: 5.2.0 - resolution: "istanbul-lib-instrument@npm:5.2.0" + version: 5.2.1 + resolution: "istanbul-lib-instrument@npm:5.2.1" dependencies: "@babel/core": ^7.12.3 "@babel/parser": ^7.14.7 "@istanbuljs/schema": ^0.1.2 istanbul-lib-coverage: ^3.2.0 semver: ^6.3.0 - checksum: 7c242ed782b6bf7b655656576afae8b6bd23dcc020e5fdc1472cca3dfb6ddb196a478385206d0df5219b9babf46ac4f21fea5d8ea9a431848b6cca6007012353 + checksum: bf16f1803ba5e51b28bbd49ed955a736488381e09375d830e42ddeb403855b2006f850711d95ad726f2ba3f1ae8e7366de7e51d2b9ac67dc4d80191ef7ddf272 languageName: node linkType: hard -"jest-diff@npm:^28.1.1": - version: 28.1.1 - resolution: "jest-diff@npm:28.1.1" +"jackspeak@npm:^2.0.3": + version: 2.2.2 + resolution: "jackspeak@npm:2.2.2" + dependencies: + "@isaacs/cliui": ^8.0.2 + "@pkgjs/parseargs": ^0.11.0 + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 7b1468dd910afc00642db87448f24b062346570b8b47531409aa9012bcb95fdf7ec2b1c48edbb8b57a938c08391f8cc01b5034fc335aa3a2e74dbcc0ee5c555a + languageName: node + linkType: hard + +"jest-diff@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-diff@npm:28.1.3" dependencies: chalk: ^4.0.0 diff-sequences: ^28.1.1 jest-get-type: ^28.0.2 - pretty-format: ^28.1.1 - checksum: d9e0355880bee8728f7615ac0f03c66dcd4e93113935cca056a5f5a2f20ac2c7812aca6ad68e79bd1b11f2428748bd9123e6b1c7e51c93b4da3dfa5a875339f7 + pretty-format: ^28.1.3 + checksum: fa8583e0ccbe775714ce850b009be1b0f6b17a4b6759f33ff47adef27942ebc610dbbcc8a5f7cfb7f12b3b3b05afc9fb41d5f766674616025032ff1e4f9866e0 languageName: node linkType: hard @@ -6545,11 +6732,11 @@ fsevents@~2.1.1: languageName: node linkType: hard -"jest-haste-map@npm:^28.1.1": - version: 28.1.1 - resolution: "jest-haste-map@npm:28.1.1" +"jest-haste-map@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-haste-map@npm:28.1.3" dependencies: - "@jest/types": ^28.1.1 + "@jest/types": ^28.1.3 "@types/graceful-fs": ^4.1.3 "@types/node": "*" anymatch: ^3.0.3 @@ -6557,43 +6744,43 @@ fsevents@~2.1.1: fsevents: ^2.3.2 graceful-fs: ^4.2.9 jest-regex-util: ^28.0.2 - jest-util: ^28.1.1 - jest-worker: ^28.1.1 + jest-util: ^28.1.3 + jest-worker: ^28.1.3 micromatch: ^4.0.4 walker: ^1.0.8 dependenciesMeta: fsevents: optional: true - checksum: db31a2a83906277d96b79017742c433c1573b322d061632a011fb1e184cf6f151f94134da09da7366e4477e8716f280efa676b4cc04a8544c13ce466a44102e8 + checksum: d05fdc108645fc2b39fcd4001952cc7a8cb550e93494e98c1e9ab1fc542686f6ac67177c132e564cf94fe8f81503f3f8db8b825b9b713dc8c5748aec63ba4688 languageName: node linkType: hard -"jest-matcher-utils@npm:^28.1.1": - version: 28.1.1 - resolution: "jest-matcher-utils@npm:28.1.1" +"jest-matcher-utils@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-matcher-utils@npm:28.1.3" dependencies: chalk: ^4.0.0 - jest-diff: ^28.1.1 + jest-diff: ^28.1.3 jest-get-type: ^28.0.2 - pretty-format: ^28.1.1 - checksum: cb73ccd347638cd761ef7e0b606fbd71c115bd8febe29413f7b105fff6855d4356b8094c6b72393c5457db253b9c163498f188f25f9b6308c39c510e4c2886ee + pretty-format: ^28.1.3 + checksum: 6b34f0cf66f6781e92e3bec97bf27796bd2ba31121e5c5997218d9adba6deea38a30df5203937d6785b68023ed95cbad73663cc9aad6fb0cb59aeb5813a58daf languageName: node linkType: hard -"jest-message-util@npm:^28.1.1": - version: 28.1.1 - resolution: "jest-message-util@npm:28.1.1" +"jest-message-util@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-message-util@npm:28.1.3" dependencies: "@babel/code-frame": ^7.12.13 - "@jest/types": ^28.1.1 + "@jest/types": ^28.1.3 "@types/stack-utils": ^2.0.0 chalk: ^4.0.0 graceful-fs: ^4.2.9 micromatch: ^4.0.4 - pretty-format: ^28.1.1 + pretty-format: ^28.1.3 slash: ^3.0.0 stack-utils: ^2.0.3 - checksum: cca23b9a0103c8fb7006a6d21e67a204fcac4289e1a3961450a4a1ad62eb37087c2a19a26337d3c0ea9f82c030a80dda79ac8ec34a18bf3fd5eca3fd55bef957 + checksum: 1f266854166dcc6900d75a88b54a25225a2f3710d463063ff1c99021569045c35c7d58557b25447a17eb3a65ce763b2f9b25550248b468a9d4657db365f39e96 languageName: node linkType: hard @@ -6605,58 +6792,58 @@ fsevents@~2.1.1: linkType: hard "jest-snapshot@npm:^28.1.1": - version: 28.1.2 - resolution: "jest-snapshot@npm:28.1.2" + version: 28.1.3 + resolution: "jest-snapshot@npm:28.1.3" dependencies: "@babel/core": ^7.11.6 "@babel/generator": ^7.7.2 "@babel/plugin-syntax-typescript": ^7.7.2 "@babel/traverse": ^7.7.2 "@babel/types": ^7.3.3 - "@jest/expect-utils": ^28.1.1 - "@jest/transform": ^28.1.2 - "@jest/types": ^28.1.1 + "@jest/expect-utils": ^28.1.3 + "@jest/transform": ^28.1.3 + "@jest/types": ^28.1.3 "@types/babel__traverse": ^7.0.6 "@types/prettier": ^2.1.5 babel-preset-current-node-syntax: ^1.0.0 chalk: ^4.0.0 - expect: ^28.1.1 + expect: ^28.1.3 graceful-fs: ^4.2.9 - jest-diff: ^28.1.1 + jest-diff: ^28.1.3 jest-get-type: ^28.0.2 - jest-haste-map: ^28.1.1 - jest-matcher-utils: ^28.1.1 - jest-message-util: ^28.1.1 - jest-util: ^28.1.1 + jest-haste-map: ^28.1.3 + jest-matcher-utils: ^28.1.3 + jest-message-util: ^28.1.3 + jest-util: ^28.1.3 natural-compare: ^1.4.0 - pretty-format: ^28.1.1 + pretty-format: ^28.1.3 semver: ^7.3.5 - checksum: 5c33c8b05d387d4fa4516556dc6fdeca4d7c0a1d48bfb31d05d5bf182988713800a35b0f7d4d9e40e3646edbde095aba36bb1b64a8d9bac40e34f76e90ddb482 + checksum: 2a46a5493f1fb50b0a236a21f25045e7f46a244f9f3ae37ef4fbcd40249d0d68bb20c950ce77439e4e2cac985b05c3061c90b34739bf6069913a1199c8c716e1 languageName: node linkType: hard -"jest-util@npm:^28.1.1": - version: 28.1.1 - resolution: "jest-util@npm:28.1.1" +"jest-util@npm:^28.1.1, jest-util@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-util@npm:28.1.3" dependencies: - "@jest/types": ^28.1.1 + "@jest/types": ^28.1.3 "@types/node": "*" chalk: ^4.0.0 ci-info: ^3.2.0 graceful-fs: ^4.2.9 picomatch: ^2.2.3 - checksum: bca1601099d6a4c3c4ba997b8c035a698f23b9b04a0a284a427113f7d0399f7402ba9f4d73812328e6777bf952bf93dfe3d3edda6380a6ca27cdc02768d601e0 + checksum: fd6459742c941f070223f25e38a2ac0719aad92561591e9fb2a50d602a5d19d754750b79b4074327a42b00055662b95da3b006542ceb8b54309da44d4a62e721 languageName: node linkType: hard -"jest-worker@npm:^28.1.1": - version: 28.1.1 - resolution: "jest-worker@npm:28.1.1" +"jest-worker@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-worker@npm:28.1.3" dependencies: "@types/node": "*" merge-stream: ^2.0.0 supports-color: ^8.0.0 - checksum: 28519c43b4007e60a3756d27f1e7884192ee9161b6a9587383a64b6535f820cc4868e351a67775e0feada41465f48ccf323a8db34ae87e15a512ddac5d1424b2 + checksum: e921c9a1b8f0909da9ea07dbf3592f95b653aef3a8bb0cbcd20fc7f9a795a1304adecac31eecb308992c167e8d7e75c522061fec38a5928ace0f9571c90169ca languageName: node linkType: hard @@ -6668,9 +6855,9 @@ fsevents@~2.1.1: linkType: hard "js-sdsl@npm:^4.1.4": - version: 4.4.0 - resolution: "js-sdsl@npm:4.4.0" - checksum: 7bb08a2d746ab7ff742720339aa006c631afe05e77d11eda988c1c35fae8e03e492e4e347e883e786e3ce6170685d4780c125619111f0730c11fdb41b04059c7 + version: 4.4.2 + resolution: "js-sdsl@npm:4.4.2" + checksum: ba705adc1788bf3c6f6c8e5077824f2bb4f0acab5a984420ce5cc492c7fff3daddc26335ad2c9a67d4f5e3241ec790f9e5b72a625adcf20cf321d2fd85e62b8b languageName: node linkType: hard @@ -6788,23 +6975,23 @@ fsevents@~2.1.1: languageName: node linkType: hard -"json5@npm:^1.0.1": - version: 1.0.1 - resolution: "json5@npm:1.0.1" +"json5@npm:^1.0.2": + version: 1.0.2 + resolution: "json5@npm:1.0.2" dependencies: minimist: ^1.2.0 bin: json5: lib/cli.js - checksum: e76ea23dbb8fc1348c143da628134a98adf4c5a4e8ea2adaa74a80c455fc2cdf0e2e13e6398ef819bfe92306b610ebb2002668ed9fc1af386d593691ef346fc3 + checksum: 866458a8c58a95a49bef3adba929c625e82532bcff1fe93f01d29cb02cac7c3fe1f4b79951b7792c2da9de0b32871a8401a6e3c5b36778ad852bf5b8a61165d7 languageName: node linkType: hard -"json5@npm:^2.2.1": - version: 2.2.1 - resolution: "json5@npm:2.2.1" +"json5@npm:^2.2.2": + version: 2.2.3 + resolution: "json5@npm:2.2.3" bin: json5: lib/cli.js - checksum: 74b8a23b102a6f2bf2d224797ae553a75488b5adbaee9c9b6e5ab8b510a2fc6e38f876d4c77dea672d4014a44b2399e15f2051ac2b37b87f74c0c7602003543b + checksum: 2a7436a93393830bce797d4626275152e37e877b265e94ca69c99e3d20c2b9dab021279146a39cdb700e71b2dd32a4cebd1514cd57cee102b1af906ce5040349 languageName: node linkType: hard @@ -6865,14 +7052,14 @@ fsevents@~2.1.1: linkType: hard "keccak@npm:^3.0.0, keccak@npm:^3.0.2": - version: 3.0.2 - resolution: "keccak@npm:3.0.2" + version: 3.0.3 + resolution: "keccak@npm:3.0.3" dependencies: node-addon-api: ^2.0.0 node-gyp: latest node-gyp-build: ^4.2.0 readable-stream: ^3.6.0 - checksum: 39a7d6128b8ee4cb7dcd186fc7e20c6087cc39f573a0f81b147c323f688f1f7c2b34f62c4ae189fe9b81c6730b2d1228d8a399cdc1f3d8a4c8f030cdc4f20272 + checksum: f08f04f5cc87013a3fc9e87262f761daff38945c86dd09c01a7f7930a15ae3e14f93b310ef821dcc83675a7b814eb1c983222399a2f263ad980251201d1b9a99 languageName: node linkType: hard @@ -7035,11 +7222,11 @@ fsevents@~2.1.1: linkType: hard "loupe@npm:^2.3.1": - version: 2.3.4 - resolution: "loupe@npm:2.3.4" + version: 2.3.6 + resolution: "loupe@npm:2.3.6" dependencies: get-func-name: ^2.0.0 - checksum: 5af91db61aa18530f1749a64735ee194ac263e65e9f4d1562bf3036c591f1baa948289c193e0e34c7b5e2c1b75d3c1dc4fce87f5edb3cee10b0c0df46bc9ffb3 + checksum: cc83f1b124a1df7384601d72d8d1f5fe95fd7a8185469fec48bb2e4027e45243949e7a013e8d91051a138451ff0552310c32aa9786e60b6a30d1e801bdc2163f languageName: node linkType: hard @@ -7062,9 +7249,16 @@ fsevents@~2.1.1: linkType: hard "lru-cache@npm:^7.7.1": - version: 7.12.0 - resolution: "lru-cache@npm:7.12.0" - checksum: fdb62262978393df7a4bd46a072bc5c3808c50ca5a347a82bb9459410efd841b7bae50655c3cf9004c70d12c756cf6d018f6bff155a16cdde9eba9a82899b5eb + version: 7.18.3 + resolution: "lru-cache@npm:7.18.3" + checksum: e550d772384709deea3f141af34b6d4fa392e2e418c1498c078de0ee63670f1f46f5eee746e8ef7e69e1c895af0d4224e62ee33e66a543a14763b0f2e74c1356 + languageName: node + linkType: hard + +"lru-cache@npm:^9.1.1 || ^10.0.0": + version: 10.0.0 + resolution: "lru-cache@npm:10.0.0" + checksum: 18f101675fe283bc09cda0ef1e3cc83781aeb8373b439f086f758d1d91b28730950db785999cd060d3c825a8571c03073e8c14512b6655af2188d623031baf50 languageName: node linkType: hard @@ -7082,27 +7276,26 @@ fsevents@~2.1.1: languageName: node linkType: hard -"make-fetch-happen@npm:^10.0.3": - version: 10.1.8 - resolution: "make-fetch-happen@npm:10.1.8" +"make-fetch-happen@npm:^11.0.3": + version: 11.1.1 + resolution: "make-fetch-happen@npm:11.1.1" dependencies: agentkeepalive: ^4.2.1 - cacache: ^16.1.0 - http-cache-semantics: ^4.1.0 + cacache: ^17.0.0 + http-cache-semantics: ^4.1.1 http-proxy-agent: ^5.0.0 https-proxy-agent: ^5.0.0 is-lambda: ^1.0.1 lru-cache: ^7.7.1 - minipass: ^3.1.6 - minipass-collect: ^1.0.2 - minipass-fetch: ^2.0.3 + minipass: ^5.0.0 + minipass-fetch: ^3.0.0 minipass-flush: ^1.0.5 minipass-pipeline: ^1.2.4 negotiator: ^0.6.3 promise-retry: ^2.0.1 socks-proxy-agent: ^7.0.0 - ssri: ^9.0.0 - checksum: 5fe9fd9da5368a8a4fe9a3ea5b9aa15f1e91c9ab703cd9027a6b33840ecc8a57c182fbe1c767c139330a88c46a448b1f00da5e32065cec373aff2450b3da54ee + ssri: ^10.0.0 + checksum: 7268bf274a0f6dcf0343829489a4506603ff34bd0649c12058753900b0eb29191dce5dba12680719a5d0a983d3e57810f594a12f3c18494e93a1fbc6348a4540 languageName: node linkType: hard @@ -7256,7 +7449,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"minimatch@npm:2 || 3, minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:2 || 3, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -7283,19 +7476,19 @@ fsevents@~2.1.1: languageName: node linkType: hard -"minimatch@npm:^5.0.1": - version: 5.1.0 - resolution: "minimatch@npm:5.1.0" +"minimatch@npm:^9.0.1": + version: 9.0.3 + resolution: "minimatch@npm:9.0.3" dependencies: brace-expansion: ^2.0.1 - checksum: 15ce53d31a06361e8b7a629501b5c75491bc2b59712d53e802b1987121d91b433d73fcc5be92974fde66b2b51d8fb28d75a9ae900d249feb792bb1ba2a4f0a90 + checksum: 253487976bf485b612f16bf57463520a14f512662e592e95c571afdab1442a6a6864b6c88f248ce6fc4ff0b6de04ac7aa6c8bb51e868e99d1d65eb0658a708b5 languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6": - version: 1.2.6 - resolution: "minimist@npm:1.2.6" - checksum: d15428cd1e11eb14e1233bcfb88ae07ed7a147de251441d61158619dfb32c4d7e9061d09cab4825fdee18ecd6fce323228c8c47b5ba7cd20af378ca4048fb3fb +"minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.7": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0 languageName: node linkType: hard @@ -7308,18 +7501,18 @@ fsevents@~2.1.1: languageName: node linkType: hard -"minipass-fetch@npm:^2.0.3": - version: 2.1.0 - resolution: "minipass-fetch@npm:2.1.0" +"minipass-fetch@npm:^3.0.0": + version: 3.0.3 + resolution: "minipass-fetch@npm:3.0.3" dependencies: encoding: ^0.1.13 - minipass: ^3.1.6 + minipass: ^5.0.0 minipass-sized: ^1.0.3 minizlib: ^2.1.2 dependenciesMeta: encoding: optional: true - checksum: 1334732859a3f7959ed22589bafd9c40384b885aebb5932328071c33f86b3eb181d54c86919675d1825ab5f1c8e4f328878c863873258d113c29d79a4b0c9c9f + checksum: af5ab2552a16fcf505d35fd7ffb84b57f4a0eeb269e6e1d9a2a75824dda48b36e527083250b7cca4a4def21d9544e2ade441e4730e233c0bc2133f6abda31e18 languageName: node linkType: hard @@ -7350,12 +7543,26 @@ fsevents@~2.1.1: languageName: node linkType: hard -"minipass@npm:^3.0.0, minipass@npm:^3.1.1, minipass@npm:^3.1.6": - version: 3.3.4 - resolution: "minipass@npm:3.3.4" +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" dependencies: yallist: ^4.0.0 - checksum: 5d95a7738c54852ba78d484141e850c792e062666a2d0c681a5ac1021275beb7e1acb077e59f9523ff1defb80901aea4e30fac10ded9a20a25d819a42916ef1b + checksum: a30d083c8054cee83cdcdc97f97e4641a3f58ae743970457b1489ce38ee1167b3aaf7d815cd39ec7a99b9c40397fd4f686e83750e73e652b21cb516f6d845e48 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 425dab288738853fded43da3314a0b5c035844d6f3097a8e3b5b29b328da8f3c1af6fc70618b32c29ff906284cf6406b6841376f21caaadd0793c1d5a6a620ea + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0": + version: 7.0.2 + resolution: "minipass@npm:7.0.2" + checksum: 46776de732eb7cef2c7404a15fb28c41f5c54a22be50d47b03c605bf21f5c18d61a173c0a20b49a97e7a65f78d887245066410642551e45fffe04e9ac9e325bc languageName: node linkType: hard @@ -7462,8 +7669,8 @@ fsevents@~2.1.1: linkType: hard "mocha@npm:^10.0.0": - version: 10.1.0 - resolution: "mocha@npm:10.1.0" + version: 10.2.0 + resolution: "mocha@npm:10.2.0" dependencies: ansi-colors: 4.1.1 browser-stdout: 1.3.1 @@ -7489,7 +7696,7 @@ fsevents@~2.1.1: bin: _mocha: bin/_mocha mocha: bin/mocha.js - checksum: c64c7305769e09ae5559c1cd31eae8b4c7c0e19e328cf54d1374e5555a0f01e3d5dced99882911d927e0a9d0c613d0644a1750b848a2848fb7dcf4684f97f65f + checksum: 406c45eab122ffd6ea2003c2f108b2bc35ba036225eee78e0c784b6fa2c7f34e2b13f1dbacef55a4fdf523255d76e4f22d1b5aacda2394bd11666febec17c719 languageName: node linkType: hard @@ -7590,10 +7797,10 @@ fsevents@~2.1.1: languageName: node linkType: hard -"napi-macros@npm:~2.0.0": - version: 2.0.0 - resolution: "napi-macros@npm:2.0.0" - checksum: 30384819386977c1f82034757014163fa60ab3c5a538094f778d38788bebb52534966279956f796a92ea771c7f8ae072b975df65de910d051ffbdc927f62320c +"napi-macros@npm:^2.2.2": + version: 2.2.2 + resolution: "napi-macros@npm:2.2.2" + checksum: c6f9bd71cdbbc37ddc3535aa5be481238641d89585b8a3f4d301cb89abf459e2d294810432bb7d12056d1f9350b1a0899a5afcf460237a3da6c398cf0fec7629 languageName: node linkType: hard @@ -7653,21 +7860,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"node-fetch@npm:2.6.7": - version: 2.6.7 - resolution: "node-fetch@npm:2.6.7" - dependencies: - whatwg-url: ^5.0.0 - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - checksum: 8d816ffd1ee22cab8301c7756ef04f3437f18dace86a1dae22cf81db8ef29c0bf6655f3215cb0cdb22b420b6fe141e64b26905e7f33f9377a7fa59135ea3e10b - languageName: node - linkType: hard - -"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1": +"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12": version: 2.6.12 resolution: "node-fetch@npm:2.6.12" dependencies: @@ -7682,25 +7875,26 @@ fsevents@~2.1.1: linkType: hard "node-gyp-build@npm:^4.2.0, node-gyp-build@npm:^4.3.0": - version: 4.5.0 - resolution: "node-gyp-build@npm:4.5.0" + version: 4.6.0 + resolution: "node-gyp-build@npm:4.6.0" bin: node-gyp-build: bin.js node-gyp-build-optional: optional.js node-gyp-build-test: build-test.js - checksum: d888bae0fb88335f69af1b57a2294a931c5042f36e413d8d364c992c9ebfa0b96ffe773179a5a2c8f04b73856e8634e09cce108dbb9804396d3cc8c5455ff2db + checksum: 25d78c5ef1f8c24291f4a370c47ba52fcea14f39272041a90a7894cd50d766f7c8cb8fb06c0f42bf6f69b204b49d9be3c8fc344aac09714d5bdb95965499eb15 languageName: node linkType: hard "node-gyp@npm:latest": - version: 9.0.0 - resolution: "node-gyp@npm:9.0.0" + version: 9.4.0 + resolution: "node-gyp@npm:9.4.0" dependencies: env-paths: ^2.2.0 + exponential-backoff: ^3.1.1 glob: ^7.1.4 graceful-fs: ^4.2.6 - make-fetch-happen: ^10.0.3 - nopt: ^5.0.0 + make-fetch-happen: ^11.0.3 + nopt: ^6.0.0 npmlog: ^6.0.0 rimraf: ^3.0.2 semver: ^7.3.5 @@ -7708,7 +7902,7 @@ fsevents@~2.1.1: which: ^2.0.2 bin: node-gyp: bin/node-gyp.js - checksum: 4d8ef8860f7e4f4d86c91db3f519d26ed5cc23b48fe54543e2afd86162b4acbd14f21de42a5db344525efb69a991e021b96a68c70c6e2d5f4a5cb770793da6d3 + checksum: 78b404e2e0639d64e145845f7f5a3cb20c0520cdaf6dda2f6e025e9b644077202ea7de1232396ba5bde3fee84cdc79604feebe6ba3ec84d464c85d407bb5da99 languageName: node linkType: hard @@ -7719,17 +7913,10 @@ fsevents@~2.1.1: languageName: node linkType: hard -"node-releases@npm:^2.0.5": - version: 2.0.5 - resolution: "node-releases@npm:2.0.5" - checksum: e85d949addd19f8827f32569d2be5751e7812ccf6cc47879d49f79b5234ff4982225e39a3929315f96370823b070640fb04d79fc0ddec8b515a969a03493a42f - languageName: node - linkType: hard - -"nofilter@npm:^1.0.4": - version: 1.0.4 - resolution: "nofilter@npm:1.0.4" - checksum: 54d864f745de5c3312994e880cf2d4f55e34830d6adc8275dce3731507ca380d21040336e4a277a4901551c07f04c452fbeffd57fad1dc8f68a2943eaf894a04 +"node-releases@npm:^2.0.12": + version: 2.0.13 + resolution: "node-releases@npm:2.0.13" + checksum: 17ec8f315dba62710cae71a8dad3cd0288ba943d2ece43504b3b1aa8625bf138637798ab470b1d9035b0545996f63000a8a926e0f6d35d0996424f8b6d36dda3 languageName: node linkType: hard @@ -7751,14 +7938,14 @@ fsevents@~2.1.1: languageName: node linkType: hard -"nopt@npm:^5.0.0": - version: 5.0.0 - resolution: "nopt@npm:5.0.0" +"nopt@npm:^6.0.0": + version: 6.0.0 + resolution: "nopt@npm:6.0.0" dependencies: - abbrev: 1 + abbrev: ^1.0.0 bin: nopt: bin/nopt.js - checksum: d35fdec187269503843924e0114c0c6533fb54bbf1620d0f28b4b60ba01712d6687f62565c55cc20a504eff0fbe5c63e22340c3fad549ad40469ffb611b04f2f + checksum: 82149371f8be0c4b9ec2f863cc6509a7fd0fa729929c009f3a58e4eb0c9e4cae9920e8f1f8eb46e7d032fec8fb01bede7f0f41a67eb3553b7b8e14fa53de1dac languageName: node linkType: hard @@ -7805,10 +7992,10 @@ fsevents@~2.1.1: languageName: node linkType: hard -"object-inspect@npm:^1.12.0, object-inspect@npm:^1.9.0": - version: 1.12.2 - resolution: "object-inspect@npm:1.12.2" - checksum: a534fc1b8534284ed71f25ce3a496013b7ea030f3d1b77118f6b7b1713829262be9e6243acbcb3ef8c626e2b64186112cb7f6db74e37b2789b9c789ca23048b2 +"object-inspect@npm:^1.12.3, object-inspect@npm:^1.9.0": + version: 1.12.3 + resolution: "object-inspect@npm:1.12.3" + checksum: dabfd824d97a5f407e6d5d24810d888859f6be394d8b733a77442b277e0808860555176719c5905e765e3743a7cada6b8b0a3b85e5331c530fd418cc8ae991db languageName: node linkType: hard @@ -7831,38 +8018,39 @@ fsevents@~2.1.1: languageName: node linkType: hard -"object.assign@npm:^4.1.2": - version: 4.1.2 - resolution: "object.assign@npm:4.1.2" +"object.assign@npm:^4.1.4": + version: 4.1.4 + resolution: "object.assign@npm:4.1.4" dependencies: - call-bind: ^1.0.0 - define-properties: ^1.1.3 - has-symbols: ^1.0.1 + call-bind: ^1.0.2 + define-properties: ^1.1.4 + has-symbols: ^1.0.3 object-keys: ^1.1.1 - checksum: d621d832ed7b16ac74027adb87196804a500d80d9aca536fccb7ba48d33a7e9306a75f94c1d29cbfa324bc091bfc530bc24789568efdaee6a47fcfa298993814 + checksum: 76cab513a5999acbfe0ff355f15a6a125e71805fcf53de4e9d4e082e1989bdb81d1e329291e1e4e0ae7719f0e4ef80e88fb2d367ae60500d79d25a6224ac8864 languageName: node linkType: hard "object.getownpropertydescriptors@npm:^2.0.3": - version: 2.1.4 - resolution: "object.getownpropertydescriptors@npm:2.1.4" + version: 2.1.6 + resolution: "object.getownpropertydescriptors@npm:2.1.6" dependencies: - array.prototype.reduce: ^1.0.4 + array.prototype.reduce: ^1.0.5 call-bind: ^1.0.2 - define-properties: ^1.1.4 - es-abstract: ^1.20.1 - checksum: 988c466fe49fc4f19a28d2d1d894c95c6abfe33c94674ec0b14d96eed71f453c7ad16873d430dc2acbb1760de6d3d2affac4b81237a306012cc4dc49f7539e7f + define-properties: ^1.2.0 + es-abstract: ^1.21.2 + safe-array-concat: ^1.0.0 + checksum: 7757ce0ef61c8bee7f8043f8980fd3d46fc1ab3faf0795bd1f9f836781143b4afc91f7219a3eed4675fbd0b562f3708f7e736d679ebfd43ea37ab6077d9f5004 languageName: node linkType: hard "object.values@npm:^1.1.5": - version: 1.1.5 - resolution: "object.values@npm:1.1.5" + version: 1.1.6 + resolution: "object.values@npm:1.1.6" dependencies: call-bind: ^1.0.2 - define-properties: ^1.1.3 - es-abstract: ^1.19.1 - checksum: 0f17e99741ebfbd0fa55ce942f6184743d3070c61bd39221afc929c8422c4907618c8da694c6915bc04a83ab3224260c779ba37fc07bb668bdc5f33b66a902a4 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: f6fff9fd817c24cfd8107f50fb33061d81cd11bacc4e3dbb3852e9ff7692fde4dbce823d4333ea27cd9637ef1b6690df5fbb61f1ed314fa2959598dc3ae23d8e languageName: node linkType: hard @@ -7926,16 +8114,16 @@ fsevents@~2.1.1: linkType: hard "optionator@npm:^0.9.1": - version: 0.9.1 - resolution: "optionator@npm:0.9.1" + version: 0.9.3 + resolution: "optionator@npm:0.9.3" dependencies: + "@aashutoshrathi/word-wrap": ^1.2.3 deep-is: ^0.1.3 fast-levenshtein: ^2.0.6 levn: ^0.4.1 prelude-ls: ^1.2.1 type-check: ^0.4.0 - word-wrap: ^1.2.3 - checksum: dbc6fa065604b24ea57d734261914e697bd73b69eff7f18e967e8912aa2a40a19a9f599a507fa805be6c13c24c4eae8c71306c239d517d42d4c041c942f508a0 + checksum: 09281999441f2fe9c33a5eeab76700795365a061563d66b098923eb719251a42bdbe432790d35064d0816ead9296dbeb1ad51a733edf4167c96bd5d0882e428a languageName: node linkType: hard @@ -8121,6 +8309,16 @@ fsevents@~2.1.1: languageName: node linkType: hard +"path-scurry@npm:^1.10.1": + version: 1.10.1 + resolution: "path-scurry@npm:1.10.1" + dependencies: + lru-cache: ^9.1.1 || ^10.0.0 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + checksum: e2557cff3a8fb8bc07afdd6ab163a92587884f9969b05bbbaf6fe7379348bfb09af9ed292af12ed32398b15fb443e81692047b786d1eeb6d898a51eb17ed7d90 + languageName: node + linkType: hard + "path-to-regexp@npm:0.1.7": version: 0.1.7 resolution: "path-to-regexp@npm:0.1.7" @@ -8184,22 +8382,9 @@ fsevents@~2.1.1: linkType: hard "pirates@npm:^4.0.4": - version: 4.0.5 - resolution: "pirates@npm:4.0.5" - checksum: c9994e61b85260bec6c4fc0307016340d9b0c4f4b6550a957afaaff0c9b1ad58fbbea5cfcf083860a25cb27a375442e2b0edf52e2e1e40e69934e08dcc52d227 - languageName: node - linkType: hard - -"platform-deploy-client@npm:^0.3.2": - version: 0.3.3 - resolution: "platform-deploy-client@npm:0.3.3" - dependencies: - "@ethersproject/abi": ^5.6.3 - axios: ^0.21.2 - defender-base-client: ^1.40.0 - lodash: ^4.17.19 - node-fetch: ^2.6.0 - checksum: 2e3385497e009e74633eac9755d492d1372ed4cf54141949959812c6c3c793bab2dbdb494524c520d3886d201107c4227f0aa10300fa5db2914e3ea1425b3121 + version: 4.0.6 + resolution: "pirates@npm:4.0.6" + checksum: 46a65fefaf19c6f57460388a5af9ab81e3d7fd0e7bc44ca59d753cb5c4d0df97c6c6e583674869762101836d68675f027d60f841c105d72734df9dfca97cbcc6 languageName: node linkType: hard @@ -8226,19 +8411,19 @@ fsevents@~2.1.1: languageName: node linkType: hard -"prettier-plugin-solidity@npm:^1.0.0-beta.13": - version: 1.0.0-dev.21 - resolution: "prettier-plugin-solidity@npm:1.0.0-dev.21" +"prettier-plugin-solidity@npm:1.0.0-beta.13": + version: 1.0.0-beta.13 + resolution: "prettier-plugin-solidity@npm:1.0.0-beta.13" dependencies: - "@solidity-parser/parser": ^0.14.1 - emoji-regex: ^10.0.0 + "@solidity-parser/parser": ^0.13.2 + emoji-regex: ^9.2.2 escape-string-regexp: ^4.0.0 semver: ^7.3.5 solidity-comments-extractor: ^0.0.7 - string-width: ^4.2.3 + string-width: ^4.2.2 peerDependencies: prettier: ^2.3.0 - checksum: 3c43bb7404c380091310e59be718ec0161d268e4674e5e658723f7a7bc9b0f541df9816a8e7bd93b9e73a289f1e13ea1eab58027d4ee51d25460ea6e63c0c99e + checksum: 253de4255f3e9f64b88dbc2a2d8de595090ff10eb9dca845b58632d9ce71d23f5e1954864d0e5542a34d103aa0f4d1a1a9267a7a7dea3e5dfbe41f1f7bf9cbcf languageName: node linkType: hard @@ -8261,23 +8446,23 @@ fsevents@~2.1.1: linkType: hard "prettier@npm:^2.1.2": - version: 2.7.1 - resolution: "prettier@npm:2.7.1" + version: 2.8.8 + resolution: "prettier@npm:2.8.8" bin: prettier: bin-prettier.js - checksum: 55a4409182260866ab31284d929b3cb961e5fdb91fe0d2e099dac92eaecec890f36e524b4c19e6ceae839c99c6d7195817579cdffc8e2c80da0cb794463a748b + checksum: b49e409431bf129dd89238d64299ba80717b57ff5a6d1c1a8b1a28b590d998a34e083fa13573bc732bb8d2305becb4c9a4407f8486c81fa7d55100eb08263cf8 languageName: node linkType: hard -"pretty-format@npm:^28.1.1": - version: 28.1.1 - resolution: "pretty-format@npm:28.1.1" +"pretty-format@npm:^28.1.3": + version: 28.1.3 + resolution: "pretty-format@npm:28.1.3" dependencies: - "@jest/schemas": ^28.0.2 + "@jest/schemas": ^28.1.3 ansi-regex: ^5.0.1 ansi-styles: ^5.0.0 react-is: ^18.0.0 - checksum: 7fde4e2d6fd57cef8cf2fa9d5560cc62126de481f09c65dccfe89a3e6158a04355cff278853ace07fdf7f2f48c3d77877c00c47d7d3c1c028dcff5c322300d79 + checksum: e69f857358a3e03d271252d7524bec758c35e44680287f36c1cb905187fbc82da9981a6eb07edfd8a03bc3cbeebfa6f5234c13a3d5b59f2bbdf9b4c4053e0a7f languageName: node linkType: hard @@ -8295,13 +8480,6 @@ fsevents@~2.1.1: languageName: node linkType: hard -"promise-inflight@npm:^1.0.1": - version: 1.0.1 - resolution: "promise-inflight@npm:1.0.1" - checksum: 22749483091d2c594261517f4f80e05226d4d5ecc1fc917e1886929da56e22b5718b7f2a75f3807e7a7d471bc3be2907fe92e6e8f373ddf5c64bae35b5af3981 - languageName: node - linkType: hard - "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -8313,11 +8491,11 @@ fsevents@~2.1.1: linkType: hard "promise@npm:^8.0.0": - version: 8.1.0 - resolution: "promise@npm:8.1.0" + version: 8.3.0 + resolution: "promise@npm:8.3.0" dependencies: asap: ~2.0.6 - checksum: 89b71a56154ed7d66a73236d8e8351a9c59adddba3929ecc845f75421ff37fc08ea0c67ad76cd5c0b0d81812c7d07a32bed27e7df5fcc960c6d68b0c1cd771f7 + checksum: a69f0ddbddf78ffc529cffee7ad950d307347615970564b17988ce43fbe767af5c738a9439660b24a9a8cbea106c0dcbb6c2b20e23b7e96a8e89e5c2679e94d5 languageName: node linkType: hard @@ -8352,6 +8530,13 @@ fsevents@~2.1.1: languageName: node linkType: hard +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4 + languageName: node + linkType: hard + "psl@npm:^1.1.28": version: 1.9.0 resolution: "psl@npm:1.9.0" @@ -8360,16 +8545,16 @@ fsevents@~2.1.1: linkType: hard "punycode@npm:^2.1.0, punycode@npm:^2.1.1": - version: 2.1.1 - resolution: "punycode@npm:2.1.1" - checksum: 823bf443c6dd14f669984dea25757b37993f67e8d94698996064035edd43bed8a5a17a9f12e439c2b35df1078c6bec05a6c86e336209eb1061e8025c481168e8 + version: 2.3.0 + resolution: "punycode@npm:2.3.0" + checksum: 39f760e09a2a3bbfe8f5287cf733ecdad69d6af2fe6f97ca95f24b8921858b91e9ea3c9eeec6e08cede96181b3bb33f95c6ffd8c77e63986508aa2e8159fa200 languageName: node linkType: hard "pure-rand@npm:^5.0.1": - version: 5.0.1 - resolution: "pure-rand@npm:5.0.1" - checksum: 2b05a6d80163308583a013fab8d7f7f2958a6f77895680c99d8c3ea1f3e49ac273716a59cb1777cfc370540df53e6dc017e46c70a869da81fe490b2e6703d77d + version: 5.0.5 + resolution: "pure-rand@npm:5.0.5" + checksum: 824b906f7f66695c15ed9a898ff650e925723515e999de0360b0726ebad924ce41a74cc2ac60409dc6c55f5781008855f32ecd0fe0a1f40fbce293d48bd11dd1 languageName: node linkType: hard @@ -8382,7 +8567,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"qs@npm:^6.4.0, qs@npm:^6.7.0, qs@npm:^6.9.4": +"qs@npm:^6.4.0, qs@npm:^6.9.4": version: 6.11.2 resolution: "qs@npm:6.11.2" dependencies: @@ -8421,7 +8606,7 @@ fsevents@~2.1.1: languageName: node linkType: hard -"raw-body@npm:2.5.1, raw-body@npm:^2.4.1": +"raw-body@npm:2.5.1": version: 2.5.1 resolution: "raw-body@npm:2.5.1" dependencies: @@ -8433,6 +8618,18 @@ fsevents@~2.1.1: languageName: node linkType: hard +"raw-body@npm:^2.4.1": + version: 2.5.2 + resolution: "raw-body@npm:2.5.2" + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + checksum: ba1583c8d8a48e8fbb7a873fdbb2df66ea4ff83775421bfe21ee120140949ab048200668c47d9ae3880012f6e217052690628cf679ddfbd82c9fc9358d574676 + languageName: node + linkType: hard + "react-is@npm:^18.0.0": version: 18.2.0 resolution: "react-is@npm:18.2.0" @@ -8441,8 +8638,8 @@ fsevents@~2.1.1: linkType: hard "readable-stream@npm:^2.2.2": - version: 2.3.7 - resolution: "readable-stream@npm:2.3.7" + version: 2.3.8 + resolution: "readable-stream@npm:2.3.8" dependencies: core-util-is: ~1.0.0 inherits: ~2.0.3 @@ -8451,18 +8648,18 @@ fsevents@~2.1.1: safe-buffer: ~5.1.1 string_decoder: ~1.1.1 util-deprecate: ~1.0.1 - checksum: e4920cf7549a60f8aaf694d483a0e61b2a878b969d224f89b3bc788b8d920075132c4b55a7494ee944c7b6a9a0eada28a7f6220d80b0312ece70bbf08eeca755 + checksum: 65645467038704f0c8aaf026a72fbb588a9e2ef7a75cd57a01702ee9db1c4a1e4b03aaad36861a6a0926546a74d174149c8c207527963e0c2d3eee2f37678a42 languageName: node linkType: hard "readable-stream@npm:^3.6.0": - version: 3.6.0 - resolution: "readable-stream@npm:3.6.0" + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" dependencies: inherits: ^2.0.3 string_decoder: ^1.1.1 util-deprecate: ^1.0.1 - checksum: d4ea81502d3799439bb955a3a5d1d808592cf3133350ed352aeaa499647858b27b1c4013984900238b0873ec8d0d8defce72469fb7a83e61d53f5ad61cb80dc8 + checksum: bdcbe6c22e846b6af075e32cf8f4751c2576238c5043169a1c221c92ee2878458a816a4ea33f4c67623c0b6827c8a400409bfb3cf0bf3381392d0b1dfb52ac8d languageName: node linkType: hard @@ -8494,22 +8691,22 @@ fsevents@~2.1.1: linkType: hard "recursive-readdir@npm:^2.2.2": - version: 2.2.2 - resolution: "recursive-readdir@npm:2.2.2" + version: 2.2.3 + resolution: "recursive-readdir@npm:2.2.3" dependencies: - minimatch: 3.0.4 - checksum: a6b22994d76458443d4a27f5fd7147ac63ad31bba972666a291d511d4d819ee40ff71ba7524c14f6a565b8cfaf7f48b318f971804b913cf538d58f04e25d1fee + minimatch: ^3.0.5 + checksum: 88ec96e276237290607edc0872b4f9842837b95cfde0cdbb1e00ba9623dfdf3514d44cdd14496ab60a0c2dd180a6ef8a3f1c34599e6cf2273afac9b72a6fb2b5 languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.4.3": - version: 1.4.3 - resolution: "regexp.prototype.flags@npm:1.4.3" +"regexp.prototype.flags@npm:^1.5.0": + version: 1.5.0 + resolution: "regexp.prototype.flags@npm:1.5.0" dependencies: call-bind: ^1.0.2 - define-properties: ^1.1.3 - functions-have-names: ^1.2.2 - checksum: 51228bae732592adb3ededd5e15426be25f289e9c4ef15212f4da73f4ec3919b6140806374b8894036a86020d054a8d2657d3fee6bb9b4d35d8939c20030b7a6 + define-properties: ^1.2.0 + functions-have-names: ^1.2.3 + checksum: c541687cdbdfff1b9a07f6e44879f82c66bbf07665f9a7544c5fd16acdb3ec8d1436caab01662d2fbcad403f3499d49ab0b77fbc7ef29ef961d98cc4bc9755b4 languageName: node linkType: hard @@ -8641,21 +8838,21 @@ fsevents@~2.1.1: "@types/lodash": ^4.14.177 "@types/mocha": ^9.0.0 "@types/node": ^12.20.37 - "@typescript-eslint/eslint-plugin": ^5.17.0 - "@typescript-eslint/parser": ^5.17.0 + "@typescript-eslint/eslint-plugin": 5.17.0 + "@typescript-eslint/parser": 5.17.0 axios: ^0.24.0 bignumber.js: ^9.1.1 caip: ^1.1.0 chai: ^4.3.4 decimal.js: ^10.4.3 dotenv: ^16.0.0 - eslint: ^8.14.0 - eslint-config-prettier: ^8.5.0 - eslint-config-standard: ^16.0.3 - eslint-plugin-import: ^2.25.4 - eslint-plugin-node: ^11.1.0 - eslint-plugin-prettier: ^4.0.0 - eslint-plugin-promise: ^6.0.0 + eslint: 8.14.0 + eslint-config-prettier: 8.5.0 + eslint-config-standard: 16.0.3 + eslint-plugin-import: 2.25.4 + eslint-plugin-node: 11.1.0 + eslint-plugin-prettier: 4.0.0 + eslint-plugin-promise: 6.0.0 eth-permit: ^0.2.1 ethers: ^5.7.2 fast-check: ^2.24.0 @@ -8670,9 +8867,9 @@ fsevents@~2.1.1: lodash.get: ^4.4.2 mocha-chai-jest-snapshot: ^1.1.3 prettier: 2.5.1 - prettier-plugin-solidity: ^1.0.0-beta.13 - solhint: ^3.3.6 - solhint-plugin-prettier: ^0.0.5 + prettier-plugin-solidity: 1.0.0-beta.13 + solhint: 3.3.6 + solhint-plugin-prettier: 0.0.5 solidity-coverage: ^0.8.2 ts-node: ^10.4.0 tsconfig-paths: ^4.1.0 @@ -8703,14 +8900,14 @@ fsevents@~2.1.1: languageName: node linkType: hard -resolve@1.1.x: +"resolve@npm:1.1.x": version: 1.1.7 resolution: "resolve@npm:1.1.7" checksum: afd20873fbde7641c9125efe3f940c2a99f6b1f90f1b7b743e744bdaac1cb105b2e4e0317bcc052ed7e31d57afa86b394a4dc9a1b33a297977be134fdf0250ab languageName: node linkType: hard -resolve@1.17.0: +"resolve@npm:1.17.0": version: 1.17.0 resolution: "resolve@npm:1.17.0" dependencies: @@ -8719,16 +8916,16 @@ resolve@1.17.0: languageName: node linkType: hard -"resolve@^1.1.6, resolve@^1.10.1, resolve@^1.20.0, resolve@^1.22.0": - version: 1.22.1 - resolution: "resolve@npm:1.22.1" +"resolve@npm:^1.1.6, resolve@npm:^1.10.1, resolve@npm:^1.20.0, resolve@npm:^1.22.1": + version: 1.22.3 + resolution: "resolve@npm:1.22.3" dependencies: - is-core-module: ^2.9.0 + is-core-module: ^2.12.0 path-parse: ^1.0.7 supports-preserve-symlinks-flag: ^1.0.0 bin: resolve: bin/resolve - checksum: 07af5fc1e81aa1d866cbc9e9460fbb67318a10fa3c4deadc35c3ad8a898ee9a71a86a65e4755ac3195e0ea0cfbe201eb323ebe655ce90526fd61917313a34e4e + checksum: fb834b81348428cb545ff1b828a72ea28feb5a97c026a1cf40aa1008352c72811ff4d4e71f2035273dc536dcfcae20c13604ba6283c612d70fa0b6e44519c374 languageName: node linkType: hard @@ -8748,16 +8945,16 @@ resolve@1.17.0: languageName: node linkType: hard -"resolve@patch:resolve@^1.1.6#~builtin, resolve@patch:resolve@^1.10.1#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.0#~builtin": - version: 1.22.1 - resolution: "resolve@patch:resolve@npm%3A1.22.1#~builtin::version=1.22.1&hash=c3c19d" +"resolve@patch:resolve@^1.1.6#~builtin, resolve@patch:resolve@^1.10.1#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin": + version: 1.22.3 + resolution: "resolve@patch:resolve@npm%3A1.22.3#~builtin::version=1.22.3&hash=c3c19d" dependencies: - is-core-module: ^2.9.0 + is-core-module: ^2.12.0 path-parse: ^1.0.7 supports-preserve-symlinks-flag: ^1.0.0 bin: resolve: bin/resolve - checksum: 5656f4d0bedcf8eb52685c1abdf8fbe73a1603bb1160a24d716e27a57f6cecbe2432ff9c89c2bd57542c3a7b9d14b1882b73bfe2e9d7849c9a4c0b8b39f02b8b + checksum: ad59734723b596d0891321c951592ed9015a77ce84907f89c9d9307dd0c06e11a67906a3e628c4cae143d3e44898603478af0ddeb2bba3f229a9373efe342665 languageName: node linkType: hard @@ -8887,6 +9084,18 @@ resolve@1.17.0: languageName: node linkType: hard +"safe-array-concat@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-array-concat@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.2.0 + has-symbols: ^1.0.3 + isarray: ^2.0.5 + checksum: f43cb98fe3b566327d0c09284de2b15fb85ae964a89495c1b1a5d50c7c8ed484190f4e5e71aacc167e16231940079b326f2c0807aea633d47cc7322f40a6b57f + languageName: node + linkType: hard + "safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" @@ -8901,6 +9110,17 @@ resolve@1.17.0: languageName: node linkType: hard +"safe-regex-test@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-regex-test@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.3 + is-regex: ^1.1.4 + checksum: bc566d8beb8b43c01b94e67de3f070fd2781685e835959bbbaaec91cc53381145ca91f69bd837ce6ec244817afa0a5e974fc4e40a2957f0aca68ac3add1ddd34 + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:^2.1.0, safer-buffer@npm:~2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -8959,31 +9179,31 @@ resolve@1.17.0: linkType: hard "semver@npm:^5.5.0, semver@npm:^5.5.1, semver@npm:^5.7.0": - version: 5.7.1 - resolution: "semver@npm:5.7.1" + version: 5.7.2 + resolution: "semver@npm:5.7.2" bin: - semver: ./bin/semver - checksum: 57fd0acfd0bac382ee87cd52cd0aaa5af086a7dc8d60379dfe65fea491fb2489b6016400813930ecd61fd0952dae75c115287a1b16c234b1550887117744dfaf + semver: bin/semver + checksum: fb4ab5e0dd1c22ce0c937ea390b4a822147a9c53dbd2a9a0132f12fe382902beef4fbf12cf51bb955248d8d15874ce8cd89532569756384f994309825f10b686 languageName: node linkType: hard -"semver@npm:^6.1.0, semver@npm:^6.3.0": - version: 6.3.0 - resolution: "semver@npm:6.3.0" +"semver@npm:^6.1.0, semver@npm:^6.3.0, semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" bin: - semver: ./bin/semver.js - checksum: 1b26ecf6db9e8292dd90df4e781d91875c0dcc1b1909e70f5d12959a23c7eebb8f01ea581c00783bbee72ceeaad9505797c381756326073850dc36ed284b21b9 + semver: bin/semver.js + checksum: ae47d06de28836adb9d3e25f22a92943477371292d9b665fb023fae278d345d508ca1958232af086d85e0155aee22e313e100971898bbb8d5d89b8b1d4054ca2 languageName: node linkType: hard -"semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7": - version: 7.3.7 - resolution: "semver@npm:7.3.7" +"semver@npm:^7.3.4, semver@npm:^7.3.5": + version: 7.5.4 + resolution: "semver@npm:7.5.4" dependencies: lru-cache: ^6.0.0 bin: semver: bin/semver.js - checksum: 2fa3e877568cd6ce769c75c211beaed1f9fce80b28338cadd9d0b6c40f2e2862bafd62c19a6cff42f3d54292b7c623277bcab8816a2b5521cf15210d43e75232 + checksum: 12d8ad952fa353b0995bf180cdac205a4068b759a140e5d3c608317098b3575ac2f1e09182206bf2eb26120e1c0ed8fb92c48c592f6099680de56bb071423ca3 languageName: node linkType: hard @@ -9142,6 +9362,13 @@ resolve@1.17.0: languageName: node linkType: hard +"signal-exit@npm:^4.0.1": + version: 4.0.2 + resolution: "signal-exit@npm:4.0.2" + checksum: 41f5928431cc6e91087bf0343db786a6313dd7c6fd7e551dbc141c95bb5fb26663444fd9df8ea47c5d7fc202f60aa7468c3162a9365cbb0615fc5e1b1328fe31 + languageName: node + linkType: hard + "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -9197,12 +9424,12 @@ resolve@1.17.0: linkType: hard "socks@npm:^2.6.2": - version: 2.6.2 - resolution: "socks@npm:2.6.2" + version: 2.7.1 + resolution: "socks@npm:2.7.1" dependencies: - ip: ^1.1.5 + ip: ^2.0.0 smart-buffer: ^4.2.0 - checksum: dd9194293059d737759d5c69273850ad4149f448426249325c4bea0e340d1cf3d266c3b022694b0dcf5d31f759de23657244c481fc1e8322add80b7985c36b5e + checksum: 259d9e3e8e1c9809a7f5c32238c3d4d2a36b39b83851d0f573bfde5f21c4b1288417ce1af06af1452569cd1eb0841169afd4998f0e04ba04656f6b7f0e46d748 languageName: node linkType: hard @@ -9225,7 +9452,7 @@ resolve@1.17.0: languageName: node linkType: hard -"solhint-plugin-prettier@npm:^0.0.5": +"solhint-plugin-prettier@npm:0.0.5": version: 0.0.5 resolution: "solhint-plugin-prettier@npm:0.0.5" dependencies: @@ -9237,11 +9464,11 @@ resolve@1.17.0: languageName: node linkType: hard -"solhint@npm:^3.3.6": - version: 3.3.7 - resolution: "solhint@npm:3.3.7" +"solhint@npm:3.3.6": + version: 3.3.6 + resolution: "solhint@npm:3.3.6" dependencies: - "@solidity-parser/parser": ^0.14.1 + "@solidity-parser/parser": ^0.13.2 ajv: ^6.6.1 antlr4: 4.7.1 ast-parents: 0.0.1 @@ -9261,14 +9488,14 @@ resolve@1.17.0: optional: true bin: solhint: solhint.js - checksum: 140a4660b691ea78aa7de19aca2123991fb4f9bc7be574e1573ae428b356e12919805df56c2892ddbdd031a4a4db477a81425ad85aac6672f3fb73f4887c2abb + checksum: 0ea5c96540adbc33e3c0305dacf270bcdfdbfb6f64652eb3584de2770b98dc1383bd65faf8506fbee95d773e359fe7f3b0a15c492a0596fcc14de0d609a0a335 languageName: node linkType: hard "solidity-ast@npm:^0.4.15": - version: 0.4.35 - resolution: "solidity-ast@npm:0.4.35" - checksum: 6cde9e656dee814fa3d7ce9ef42f1cd0344162515d0d215dbd7d18bf931ed9cd6ce4093aed0a8abbcfb5a4a6faf6638f615aaad479e4657054c6a4ae2cb5092e + version: 0.4.49 + resolution: "solidity-ast@npm:0.4.49" + checksum: f5b0354ddfa882346cf12d33f79c6123796a07637b248ceb9cfeec9f81540e270407f6fca660cf75666e1ba1866270319ab3fbe54b01491dbd35adffd1405243 languageName: node linkType: hard @@ -9280,11 +9507,11 @@ resolve@1.17.0: linkType: hard "solidity-coverage@npm:^0.8.2": - version: 0.8.2 - resolution: "solidity-coverage@npm:0.8.2" + version: 0.8.4 + resolution: "solidity-coverage@npm:0.8.4" dependencies: "@ethersproject/abi": ^5.0.9 - "@solidity-parser/parser": ^0.14.1 + "@solidity-parser/parser": ^0.16.0 chalk: ^2.4.2 death: ^1.1.0 detect-port: ^1.3.0 @@ -9307,7 +9534,7 @@ resolve@1.17.0: hardhat: ^2.11.0 bin: solidity-coverage: plugins/bin.js - checksum: 489f73d56a1279f2394b7a14db315532884895baa00a4016e68a4e5be0eddca90a95cb3322e6a0b15e67f2d9003b9413ee24c1c61d78f558f5a2e1e233840825 + checksum: 263089376d05f572350a2e47b61b2c604b3b5deedf4547cb0334342ecf6b732f823c069790e21063a56502a0d1fb9051a6f7bae1b990e2917af56fc94ac96759 languageName: node linkType: hard @@ -9365,21 +9592,21 @@ resolve@1.17.0: languageName: node linkType: hard -"ssri@npm:^9.0.0": - version: 9.0.1 - resolution: "ssri@npm:9.0.1" +"ssri@npm:^10.0.0": + version: 10.0.4 + resolution: "ssri@npm:10.0.4" dependencies: - minipass: ^3.1.1 - checksum: fb58f5e46b6923ae67b87ad5ef1c5ab6d427a17db0bead84570c2df3cd50b4ceb880ebdba2d60726588272890bae842a744e1ecce5bd2a2a582fccd5068309eb + minipass: ^5.0.0 + checksum: fb14da9f8a72b04eab163eb13a9dda11d5962cd2317f85457c4e0b575e9a6e0e3a6a87b5bf122c75cb36565830cd5f263fb457571bf6f1587eb5f95d095d6165 languageName: node linkType: hard "stack-utils@npm:^2.0.3": - version: 2.0.5 - resolution: "stack-utils@npm:2.0.5" + version: 2.0.6 + resolution: "stack-utils@npm:2.0.6" dependencies: escape-string-regexp: ^2.0.0 - checksum: 76b69da0f5b48a34a0f93c98ee2a96544d2c4ca2557f7eef5ddb961d3bdc33870b46f498a84a7c4f4ffb781df639840e7ebf6639164ed4da5e1aeb659615b9c7 + checksum: 052bf4d25bbf5f78e06c1d5e67de2e088b06871fa04107ca8d3f0e9d9263326e2942c8bedee3545795fc77d787d443a538345eef74db2f8e35db3558c6f91ff7 languageName: node linkType: hard @@ -9413,7 +9640,7 @@ resolve@1.17.0: languageName: node linkType: hard -"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.2, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -9445,25 +9672,47 @@ resolve@1.17.0: languageName: node linkType: hard -"string.prototype.trimend@npm:^1.0.5": - version: 1.0.5 - resolution: "string.prototype.trimend@npm:1.0.5" +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: ^0.2.0 + emoji-regex: ^9.2.2 + strip-ansi: ^7.0.1 + checksum: 7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193 + languageName: node + linkType: hard + +"string.prototype.trim@npm:^1.2.7": + version: 1.2.7 + resolution: "string.prototype.trim@npm:1.2.7" dependencies: call-bind: ^1.0.2 define-properties: ^1.1.4 - es-abstract: ^1.19.5 - checksum: d44f543833112f57224e79182debadc9f4f3bf9d48a0414d6f0cbd2a86f2b3e8c0ca1f95c3f8e5b32ae83e91554d79d932fc746b411895f03f93d89ed3dfb6bc + es-abstract: ^1.20.4 + checksum: 05b7b2d6af63648e70e44c4a8d10d8cc457536df78b55b9d6230918bde75c5987f6b8604438c4c8652eb55e4fc9725d2912789eb4ec457d6995f3495af190c09 languageName: node linkType: hard -"string.prototype.trimstart@npm:^1.0.5": - version: 1.0.5 - resolution: "string.prototype.trimstart@npm:1.0.5" +"string.prototype.trimend@npm:^1.0.6": + version: 1.0.6 + resolution: "string.prototype.trimend@npm:1.0.6" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: 0fdc34645a639bd35179b5a08227a353b88dc089adf438f46be8a7c197fc3f22f8514c1c9be4629b3cd29c281582730a8cbbad6466c60f76b5f99cf2addb132e + languageName: node + linkType: hard + +"string.prototype.trimstart@npm:^1.0.6": + version: 1.0.6 + resolution: "string.prototype.trimstart@npm:1.0.6" dependencies: call-bind: ^1.0.2 define-properties: ^1.1.4 - es-abstract: ^1.19.5 - checksum: a4857c5399ad709d159a77371eeaa8f9cc284469a0b5e1bfe405de16f1fd4166a8ea6f4180e55032f348d1b679b1599fd4301fbc7a8b72bdb3e795e43f7b1048 + es-abstract: ^1.20.4 + checksum: 89080feef416621e6ef1279588994305477a7a91648d9436490d56010a1f7adc39167cddac7ce0b9884b8cdbef086987c4dcb2960209f2af8bac0d23ceff4f41 languageName: node linkType: hard @@ -9485,6 +9734,15 @@ resolve@1.17.0: languageName: node linkType: hard +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: ^5.0.1 + checksum: f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c + languageName: node + linkType: hard + "strip-ansi@npm:^4.0.0": version: 4.0.0 resolution: "strip-ansi@npm:4.0.0" @@ -9503,12 +9761,12 @@ resolve@1.17.0: languageName: node linkType: hard -"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" +"strip-ansi@npm:^7.0.1": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" dependencies: - ansi-regex: ^5.0.1 - checksum: f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c + ansi-regex: ^6.0.1 + checksum: 859c73fcf27869c22a4e4d8c6acfe690064659e84bef9458aa6d13719d09ca88dcfd40cbf31fd0be63518ea1a643fe070b4827d353e09533a5b0b9fd4553d64d languageName: node linkType: hard @@ -9627,29 +9885,29 @@ resolve@1.17.0: linkType: hard "table@npm:^6.8.0": - version: 6.8.0 - resolution: "table@npm:6.8.0" + version: 6.8.1 + resolution: "table@npm:6.8.1" dependencies: ajv: ^8.0.1 lodash.truncate: ^4.4.2 slice-ansi: ^4.0.0 string-width: ^4.2.3 strip-ansi: ^6.0.1 - checksum: 5b07fe462ee03d2e1fac02cbb578efd2e0b55ac07e3d3db2e950aa9570ade5a4a2b8d3c15e9f25c89e4e50b646bc4269934601ee1eef4ca7968ad31960977690 + checksum: 08249c7046125d9d0a944a6e96cfe9ec66908d6b8a9db125531be6eb05fa0de047fd5542e9d43b4f987057f00a093b276b8d3e19af162a9c40db2681058fd306 languageName: node linkType: hard "tar@npm:^6.1.11, tar@npm:^6.1.2": - version: 6.1.11 - resolution: "tar@npm:6.1.11" + version: 6.1.15 + resolution: "tar@npm:6.1.15" dependencies: chownr: ^2.0.0 fs-minipass: ^2.0.0 - minipass: ^3.0.0 + minipass: ^5.0.0 minizlib: ^2.1.1 mkdirp: ^1.0.3 yallist: ^4.0.0 - checksum: a04c07bb9e2d8f46776517d4618f2406fb977a74d914ad98b264fc3db0fe8224da5bec11e5f8902c5b9bcb8ace22d95fbe3c7b36b8593b7dfc8391a25898f32f + checksum: f23832fceeba7578bf31907aac744ae21e74a66f4a17a9e94507acf460e48f6db598c7023882db33bab75b80e027c21f276d405e4a0322d58f51c7088d428268 languageName: node linkType: hard @@ -9816,8 +10074,8 @@ resolve@1.17.0: linkType: hard "ts-node@npm:^10.4.0": - version: 10.8.2 - resolution: "ts-node@npm:10.8.2" + version: 10.9.1 + resolution: "ts-node@npm:10.9.1" dependencies: "@cspotcode/source-map-support": ^0.8.0 "@tsconfig/node10": ^1.0.7 @@ -9849,30 +10107,30 @@ resolve@1.17.0: ts-node-script: dist/bin-script.js ts-node-transpile-only: dist/bin-transpile.js ts-script: dist/bin-script-deprecated.js - checksum: 1eede939beed9f4db35bcc88d78ef803815b99dcdbed1ecac728d861d74dc694918a7f0f437aa08d026193743a31e7e00e2ee34f875f909b5879981c1808e2a7 + checksum: 090adff1302ab20bd3486e6b4799e90f97726ed39e02b39e566f8ab674fd5bd5f727f43615debbfc580d33c6d9d1c6b1b3ce7d8e3cca3e20530a145ffa232c35 languageName: node linkType: hard -"tsconfig-paths@npm:^3.14.1": - version: 3.14.1 - resolution: "tsconfig-paths@npm:3.14.1" +"tsconfig-paths@npm:^3.12.0": + version: 3.14.2 + resolution: "tsconfig-paths@npm:3.14.2" dependencies: "@types/json5": ^0.0.29 - json5: ^1.0.1 + json5: ^1.0.2 minimist: ^1.2.6 strip-bom: ^3.0.0 - checksum: 8afa01c673ebb4782ba53d3a12df97fa837ce524f8ad38ee4e2b2fd57f5ac79abc21c574e9e9eb014d93efe7fe8214001b96233b5c6ea75bd1ea82afe17a4c6d + checksum: a6162eaa1aed680537f93621b82399c7856afd10ec299867b13a0675e981acac4e0ec00896860480efc59fc10fd0b16fdc928c0b885865b52be62cadac692447 languageName: node linkType: hard "tsconfig-paths@npm:^4.1.0": - version: 4.1.0 - resolution: "tsconfig-paths@npm:4.1.0" + version: 4.2.0 + resolution: "tsconfig-paths@npm:4.2.0" dependencies: - json5: ^2.2.1 + json5: ^2.2.2 minimist: ^1.2.6 strip-bom: ^3.0.0 - checksum: e4b101f81b2abd95499d8145e0aa73144e857c2c359191058486cef101b7accae22a69114e5d5814a13d5ab3b0bae70dd0c85bcdb7e829bbe1bfda5c9067c9b1 + checksum: 28c5f7bbbcabc9dabd4117e8fdc61483f6872a1c6b02a4b1c4d68c5b79d06896c3cc9547610c4c3ba64658531caa2de13ead1ea1bf321c7b53e969c4752b98c7 languageName: node linkType: hard @@ -9884,9 +10142,9 @@ resolve@1.17.0: linkType: hard "tslib@npm:^2.3.1, tslib@npm:^2.5.0": - version: 2.5.0 - resolution: "tslib@npm:2.5.0" - checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1 + version: 2.6.1 + resolution: "tslib@npm:2.6.1" + checksum: b0d176d176487905b66ae4d5856647df50e37beea7571c53b8d10ba9222c074b81f1410fb91da13debaf2cbc970663609068bdebafa844ea9d69b146527c38fe languageName: node linkType: hard @@ -10023,6 +10281,53 @@ resolve@1.17.0: languageName: node linkType: hard +"typed-array-buffer@npm:^1.0.0": + version: 1.0.0 + resolution: "typed-array-buffer@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.2.1 + is-typed-array: ^1.1.10 + checksum: 3e0281c79b2a40cd97fe715db803884301993f4e8c18e8d79d75fd18f796e8cd203310fec8c7fdb5e6c09bedf0af4f6ab8b75eb3d3a85da69328f28a80456bd3 + languageName: node + linkType: hard + +"typed-array-byte-length@npm:^1.0.0": + version: 1.0.0 + resolution: "typed-array-byte-length@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + for-each: ^0.3.3 + has-proto: ^1.0.1 + is-typed-array: ^1.1.10 + checksum: b03db16458322b263d87a702ff25388293f1356326c8a678d7515767ef563ef80e1e67ce648b821ec13178dd628eb2afdc19f97001ceae7a31acf674c849af94 + languageName: node + linkType: hard + +"typed-array-byte-offset@npm:^1.0.0": + version: 1.0.0 + resolution: "typed-array-byte-offset@npm:1.0.0" + dependencies: + available-typed-arrays: ^1.0.5 + call-bind: ^1.0.2 + for-each: ^0.3.3 + has-proto: ^1.0.1 + is-typed-array: ^1.1.10 + checksum: 04f6f02d0e9a948a95fbfe0d5a70b002191fae0b8fe0fe3130a9b2336f043daf7a3dda56a31333c35a067a97e13f539949ab261ca0f3692c41603a46a94e960b + languageName: node + linkType: hard + +"typed-array-length@npm:^1.0.4": + version: 1.0.4 + resolution: "typed-array-length@npm:1.0.4" + dependencies: + call-bind: ^1.0.2 + for-each: ^0.3.3 + is-typed-array: ^1.1.9 + checksum: 2228febc93c7feff142b8c96a58d4a0d7623ecde6c7a24b2b98eb3170e99f7c7eff8c114f9b283085cd59dcd2bd43aadf20e25bba4b034a53c5bb292f71f8956 + languageName: node + linkType: hard + "typedarray@npm:^0.0.6": version: 0.0.6 resolution: "typedarray@npm:0.0.6" @@ -10030,23 +10335,23 @@ resolve@1.17.0: languageName: node linkType: hard -typescript@^4.4.2: - version: 4.7.4 - resolution: "typescript@npm:4.7.4" +"typescript@npm:^4.4.2": + version: 4.9.5 + resolution: "typescript@npm:4.9.5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 5750181b1cd7e6482c4195825547e70f944114fb47e58e4aa7553e62f11b3f3173766aef9c281783edfd881f7b8299cf35e3ca8caebe73d8464528c907a164df + checksum: ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db languageName: node linkType: hard "typescript@patch:typescript@^4.4.2#~builtin": - version: 4.7.4 - resolution: "typescript@patch:typescript@npm%3A4.7.4#~builtin::version=4.7.4&hash=65a307" + version: 4.9.5 + resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=ad5954" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 9096d8f6c16cb80ef3bf96fcbbd055bf1c4a43bd14f3b7be45a9fbe7ada46ec977f604d5feed3263b4f2aa7d4c7477ce5f9cd87de0d6feedec69a983f3a4f93e + checksum: 8f6260acc86b56bfdda6004bc53f32ea548f543e8baef7071c8e34d29d292f3e375c8416556c8de10b24deef6933cd1c16a8233dc84a3dd43a13a13265d0faab languageName: node linkType: hard @@ -10058,11 +10363,11 @@ typescript@^4.4.2: linkType: hard "uglify-js@npm:^3.1.4": - version: 3.16.2 - resolution: "uglify-js@npm:3.16.2" + version: 3.17.4 + resolution: "uglify-js@npm:3.17.4" bin: uglifyjs: bin/uglifyjs - checksum: 5b62e748b7fa1d982f0949ed1876b9367dcde4782f74159f4ea0b3d130835336eb0245e090456ec057468d937eb016114677bb38a7a4fdc7f68c3d002ca760ee + checksum: 7b3897df38b6fc7d7d9f4dcd658599d81aa2b1fb0d074829dd4e5290f7318dbca1f4af2f45acb833b95b1fe0ed4698662ab61b87e94328eb4c0a0d3435baf924 languageName: node linkType: hard @@ -10078,12 +10383,12 @@ typescript@^4.4.2: languageName: node linkType: hard -"undici@npm:^5.14.0, undici@npm:^5.4.0": - version: 5.22.0 - resolution: "undici@npm:5.22.0" +"undici@npm:^5.14.0": + version: 5.22.1 + resolution: "undici@npm:5.22.1" dependencies: busboy: ^1.6.0 - checksum: 8dc55240a60ae7680798df344e8f46ad0f872ed0fa434fb94cc4fd2b5b2f8053bdf11994d15902999d3880f9bf7cd875a2e90883d2702bf0f366dacd9cbf3fc6 + checksum: 048a3365f622be44fb319316cedfaa241c59cf7f3368ae7667a12323447e1822e8cc3d00f6956c852d1478a6fde1cbbe753f49e05f2fdaed229693e716ebaf35 languageName: node linkType: hard @@ -10094,21 +10399,21 @@ typescript@^4.4.2: languageName: node linkType: hard -"unique-filename@npm:^1.1.1": - version: 1.1.1 - resolution: "unique-filename@npm:1.1.1" +"unique-filename@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-filename@npm:3.0.0" dependencies: - unique-slug: ^2.0.0 - checksum: cf4998c9228cc7647ba7814e255dec51be43673903897b1786eff2ac2d670f54d4d733357eb08dea969aa5e6875d0e1bd391d668fbdb5a179744e7c7551a6f80 + unique-slug: ^4.0.0 + checksum: 8e2f59b356cb2e54aab14ff98a51ac6c45781d15ceaab6d4f1c2228b780193dc70fae4463ce9e1df4479cb9d3304d7c2043a3fb905bdeca71cc7e8ce27e063df languageName: node linkType: hard -"unique-slug@npm:^2.0.0": - version: 2.0.2 - resolution: "unique-slug@npm:2.0.2" +"unique-slug@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-slug@npm:4.0.0" dependencies: imurmurhash: ^0.1.4 - checksum: 5b6876a645da08d505dedb970d1571f6cebdf87044cb6b740c8dbb24f0d6e1dc8bdbf46825fd09f994d7cf50760e6f6e063cfa197d51c5902c00a861702eb75a + checksum: 0884b58365af59f89739e6f71e3feacb5b1b41f2df2d842d0757933620e6de08eff347d27e9d499b43c40476cbaf7988638d3acb2ffbcb9d35fd035591adfd15 languageName: node linkType: hard @@ -10133,17 +10438,17 @@ typescript@^4.4.2: languageName: node linkType: hard -"update-browserslist-db@npm:^1.0.4": - version: 1.0.4 - resolution: "update-browserslist-db@npm:1.0.4" +"update-browserslist-db@npm:^1.0.11": + version: 1.0.11 + resolution: "update-browserslist-db@npm:1.0.11" dependencies: escalade: ^3.1.1 picocolors: ^1.0.0 peerDependencies: browserslist: ">= 4.21.0" bin: - browserslist-lint: cli.js - checksum: 7c7da28d0fc733b17e01c8fa9385ab909eadce64b8ea644e9603867dc368c2e2a6611af8247e72612b23f9e7cb87ac7c7585a05ff94e1759e9d646cbe9bf49a7 + update-browserslist-db: cli.js + checksum: b98327518f9a345c7cad5437afae4d2ae7d865f9779554baf2a200fdf4bac4969076b679b1115434bd6557376bdd37ca7583d0f9b8f8e302d7d4cc1e91b5f231 languageName: node linkType: hard @@ -10244,8 +10549,8 @@ typescript@^4.4.2: linkType: hard "web3-utils@npm:^1.3.6": - version: 1.8.1 - resolution: "web3-utils@npm:1.8.1" + version: 1.10.0 + resolution: "web3-utils@npm:1.10.0" dependencies: bn.js: ^5.2.1 ethereum-bloom-filters: ^1.0.6 @@ -10254,7 +10559,7 @@ typescript@^4.4.2: number-to-bn: 1.7.0 randombytes: ^2.1.0 utf8: 3.0.0 - checksum: 08bb2df9cd19672f034bb82a27b857e0571b836a620f83de2214377457c6e52446e8dedcf916f8f10a13c86b5a02674dd4f45c60c45698b388368601cce9cf5e + checksum: c6b7662359c0513b5cbfe02cdcb312ce9152778bb19d94d413d44f74cfaa93b7de97190ab6ba11af25a40855c949d2427dcb751929c6d0f257da268c55a3ba2a languageName: node linkType: hard @@ -10266,9 +10571,9 @@ typescript@^4.4.2: linkType: hard "whatwg-fetch@npm:^3.4.1": - version: 3.6.2 - resolution: "whatwg-fetch@npm:3.6.2" - checksum: ee976b7249e7791edb0d0a62cd806b29006ad7ec3a3d89145921ad8c00a3a67e4be8f3fb3ec6bc7b58498724fd568d11aeeeea1f7827e7e1e5eae6c8a275afed + version: 3.6.17 + resolution: "whatwg-fetch@npm:3.6.17" + checksum: 0a8785dc2d1515c17ee9365d3f6438cf8fd281567426652fc6c55fc99e58cc6287ae5d1add5b8b1dd665f149e38d3de4ebe3812fd7170438ba0681d03b88b4dd languageName: node linkType: hard @@ -10296,9 +10601,22 @@ typescript@^4.4.2: linkType: hard "which-module@npm:^2.0.0": - version: 2.0.0 - resolution: "which-module@npm:2.0.0" - checksum: 809f7fd3dfcb2cdbe0180b60d68100c88785084f8f9492b0998c051d7a8efe56784492609d3f09ac161635b78ea29219eb1418a98c15ce87d085bce905705c9c + version: 2.0.1 + resolution: "which-module@npm:2.0.1" + checksum: 1967b7ce17a2485544a4fdd9063599f0f773959cca24176dbe8f405e55472d748b7c549cd7920ff6abb8f1ab7db0b0f1b36de1a21c57a8ff741f4f1e792c52be + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.10, which-typed-array@npm:^1.1.11": + version: 1.1.11 + resolution: "which-typed-array@npm:1.1.11" + dependencies: + available-typed-arrays: ^1.0.5 + call-bind: ^1.0.2 + for-each: ^0.3.3 + gopd: ^1.0.1 + has-tostringtag: ^1.0.0 + checksum: 711ffc8ef891ca6597b19539075ec3e08bb9b4c2ca1f78887e3c07a977ab91ac1421940505a197758fb5939aa9524976d0a5bbcac34d07ed6faa75cedbb17206 languageName: node linkType: hard @@ -10342,10 +10660,10 @@ typescript@^4.4.2: languageName: node linkType: hard -"word-wrap@npm:^1.2.3, word-wrap@npm:~1.2.3": - version: 1.2.3 - resolution: "word-wrap@npm:1.2.3" - checksum: 30b48f91fcf12106ed3186ae4fa86a6a1842416df425be7b60485de14bec665a54a68e4b5156647dec3a70f25e84d270ca8bc8cd23182ed095f5c7206a938c1f +"word-wrap@npm:~1.2.3": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: f93ba3586fc181f94afdaff3a6fef27920b4b6d9eaefed0f428f8e07adea2a7f54a5f2830ce59406c8416f033f86902b91eb824072354645eea687dff3691ccb languageName: node linkType: hard @@ -10363,6 +10681,17 @@ typescript@^4.4.2: languageName: node linkType: hard +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + languageName: node + linkType: hard + "wrap-ansi@npm:^5.1.0": version: 5.1.0 resolution: "wrap-ansi@npm:5.1.0" @@ -10374,14 +10703,14 @@ typescript@^4.4.2: languageName: node linkType: hard -"wrap-ansi@npm:^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" dependencies: - ansi-styles: ^4.0.0 - string-width: ^4.1.0 - strip-ansi: ^6.0.0 - checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + ansi-styles: ^6.1.0 + string-width: ^5.0.1 + strip-ansi: ^7.0.1 + checksum: 371733296dc2d616900ce15a0049dca0ef67597d6394c57347ba334393599e800bab03c41d4d45221b6bc967b8c453ec3ae4749eff3894202d16800fdfe0e238 languageName: node linkType: hard @@ -10393,19 +10722,19 @@ typescript@^4.4.2: linkType: hard "wretch@npm:^2.0.4": - version: 2.0.4 - resolution: "wretch@npm:2.0.4" - checksum: 27278ca4f239dc26af2fb8da0221ba1a6373005343ed0e66d4f8c3227710d39e5e4f4323ed8f81996f5fe62a7343722bf0076f5f6e4930c18815e27a6680917e + version: 2.6.0 + resolution: "wretch@npm:2.6.0" + checksum: 90f583c6673628f248f5517c25b40e0a2d459ea3bb5e7b952fc76a6d61f5c6c1785c9e943ed3e954ef99a476b610c02cca18189db3d4328a00fa17ed376c0da5 languageName: node linkType: hard "write-file-atomic@npm:^4.0.1": - version: 4.0.1 - resolution: "write-file-atomic@npm:4.0.1" + version: 4.0.2 + resolution: "write-file-atomic@npm:4.0.2" dependencies: imurmurhash: ^0.1.4 signal-exit: ^3.0.7 - checksum: 8f780232533ca6223c63c9b9c01c4386ca8c625ebe5017a9ed17d037aec19462ae17109e0aa155bff5966ee4ae7a27b67a99f55caf3f32ffd84155e9da3929fc + checksum: 5da60bd4eeeb935eec97ead3df6e28e5917a6bd317478e4a85a5285e8480b8ed96032bbcc6ecd07b236142a24f3ca871c924ec4a6575e623ec1b11bf8c1c253c languageName: node linkType: hard @@ -10434,8 +10763,8 @@ typescript@^4.4.2: linkType: hard "ws@npm:^7.4.6": - version: 7.5.8 - resolution: "ws@npm:7.5.8" + version: 7.5.9 + resolution: "ws@npm:7.5.9" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ^5.0.2 @@ -10444,7 +10773,7 @@ typescript@^4.4.2: optional: true utf-8-validate: optional: true - checksum: 49479ccf3ddab6500c5906fbcc316e9c8cd44b0ffb3903a6c1caf9b38cb9e06691685722a4c642cfa7d4c6eb390424fc3142cd4f8b940cfc7a9ce9761b1cd65b + checksum: c3c100a181b731f40b7f2fddf004aa023f79d64f489706a28bc23ff88e87f6a64b3c6651fbec3a84a53960b75159574d7a7385709847a62ddb7ad6af76f49138 languageName: node linkType: hard @@ -10507,10 +10836,10 @@ typescript@^4.4.2: languageName: node linkType: hard -"yargs-parser@npm:^21.0.0": - version: 21.0.1 - resolution: "yargs-parser@npm:21.0.1" - checksum: c3ea2ed12cad0377ce3096b3f138df8267edf7b1aa7d710cd502fe16af417bafe4443dd71b28158c22fcd1be5dfd0e86319597e47badf42ff83815485887323a +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c languageName: node linkType: hard @@ -10571,17 +10900,17 @@ typescript@^4.4.2: linkType: hard "yargs@npm:^17.5.1": - version: 17.5.1 - resolution: "yargs@npm:17.5.1" + version: 17.7.2 + resolution: "yargs@npm:17.7.2" dependencies: - cliui: ^7.0.2 + cliui: ^8.0.1 escalade: ^3.1.1 get-caller-file: ^2.0.5 require-directory: ^2.1.1 string-width: ^4.2.3 y18n: ^5.0.5 - yargs-parser: ^21.0.0 - checksum: 00d58a2c052937fa044834313f07910fd0a115dec5ee35919e857eeee3736b21a4eafa8264535800ba8bac312991ce785ecb8a51f4d2cc8c4676d865af1cfbde + yargs-parser: ^21.1.1 + checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a languageName: node linkType: hard From c10d1cf28c37eba518a514aa0a3e31000c148c28 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 23 Aug 2023 16:51:21 -0400 Subject: [PATCH 389/462] README --- README.md | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 2ff52fa10b..0fb2726242 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The Reserve Protocol enables a class of token called RToken: self-issued tokens RTokens can be minted by depositing a basket of _collateral tokens_, and redeemed for the basket as well. Thus, an RToken will tend to trade at the market value of the entire basket that backs it, as any lower or higher price could be arbitraged. -The definition of the collateral basket is set dynamically on a block-by-block basis with respect to a _reference basket_. While the RToken often does its internal calculus in terms of a single unit of account (USD), what constitutes appreciation is entirely a function of the reference basket, which will often be associated with a variety of units. +The definition of the issuance/redemption basket is set dynamically on a block-by-block basis with respect to a _reference basket_. While the RToken often does its internal calculus in terms of a single unit of account (USD), what constitutes appreciation is entirely a function of the reference basket, which is a linear combination of reference units. RTokens can be over-collateralized, which means that if any of their collateral tokens default, there's a pool of value available to make up for the loss. RToken over-collateralization is provided by Reserve Rights (RSR) holders, who may choose to stake their RSR on an RToken instance. Staked RSR can be seized in the case of a default, in a process that is entirely mechanistic based on on-chain price-feeds, and does not depend on governance votes or human judgment. @@ -22,6 +22,7 @@ For a much more detailed explanation of the economic design, including an hour-l - [Testing with Echidna](docs/using-echidna.md): Notes so far on setup and usage of Echidna (which is decidedly an integration-in-progress!) - [Deployment](docs/deployment.md): How to do test deployments in our environment. - [System Design](docs/system-design.md): The overall architecture of our system, and some detailed descriptions about what our protocol is _intended_ to do. +- [Deployment Variables](docs/deployment-variables.md) A detailed description of the governance variables of the protocol. - [Our Solidity Style](docs/solidity-style.md): Common practices, details, and conventions relevant to reading and writing our Solidity source code, estpecially where those go beyond standard practice. - [Writing Collateral Plugins](docs/collateral.md): An overview of how to develop collateral plugins and the concepts / questions involved. - [Building on Top](docs/build-on-top.md): How to build on top of Reserve, including information about long-lived fork environments. @@ -103,13 +104,10 @@ The less-central folders in the repository are dedicated to project management, ## Types of Tests -We conceive of several different types of tests: - -Finally, inside particular testing, it's quite useful to distinguish unit tests from full end-to-end tests. As such, we expect to write tests of the following 5 types: - ### Unit/System Tests - Driven by `hardhat test` +- Addressed by `yarn test:unit` - Checks for expected behavior of the system. - Can run the same tests against both p0 and p1 - Uses contract mocks, where helpful to predict component behavior @@ -119,6 +117,7 @@ Target: Full branch coverage, and testing of any semantically-relevant situation ### End-to-End Tests - Driven by `hardhat test` +- Addressed by `yarn test:integration` - Uses mainnet forking - Can run the same tests against both p0 and p1 - Tests all needed plugin contracts, contract deployment, any migrations, etc. @@ -137,17 +136,7 @@ Located in `fuzz` branch only. Target: The handful of our most depended-upon system properties and invariants are articulated and thoroughly fuzz-tested. Examples of such properties include: - Unless the basket is switched (due to token default or governance) the protocol always remains fully-collateralized. -- Unless the protocol is paused, RToken holders can always redeem -- If the protocol is paused, and governance does not act further, the protocol will later become unpaused. - -### Differential Testing - -Located in `fuzz` branch only. - -- Driven by Echidna -- Asserts that the behavior of each p1 contract matches that of p0 - -Target: Intensive equivalence testing, run continuously for days or weeks, sensitive to any difference between observable behaviors of p0 and p1. +- Unless the protocol is frozen, RToken holders can always redeem ## Contributing @@ -159,7 +148,7 @@ To get setup with tenderly, install the [tenderly cli](https://github.com/Tender ## Responsible Disclosure -[Immunifi](https://immunefi.com/bounty/reserve/) +See: [Immunifi](https://immunefi.com/bounty/reserve/) ## External Documentation From c4c0a2aa3bc4ad67559f4719fef905b1d659bd45 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 24 Aug 2023 18:13:52 -0400 Subject: [PATCH 390/462] nit: re-order usings to alphabetic ordering --- contracts/p1/BasketHandler.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 387055fc63..2cb493d1a3 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -21,9 +21,9 @@ import "./mixins/Component.sol"; /// @custom:oz-upgrades-unsafe-allow external-library-linking contract BasketHandlerP1 is ComponentP1, IBasketHandler { using BasketLibP1 for Basket; + using CollateralStatusComparator for CollateralStatus; using EnumerableMap for EnumerableMap.Bytes32ToUintMap; using EnumerableSet for EnumerableSet.Bytes32Set; - using CollateralStatusComparator for CollateralStatus; using FixLib for uint192; uint192 public constant MAX_TARGET_AMT = 1e3 * FIX_ONE; // {target/BU} max basket weight From 182874420c8b8b4c528a4a790f3fb4dfb0460e6c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 24 Aug 2023 18:23:19 -0400 Subject: [PATCH 391/462] C4 40: Restrict dutch trades to being disabled only by BackingManager-started trades (#913) --- CHANGELOG.md | 1 + contracts/p0/Broker.sol | 17 ++++++++++------- contracts/p1/Broker.sol | 17 ++++++++++------- test/Revenues.test.ts | 18 +++++++++--------- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 163413e8af..2e16d9a2f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ Bump solidity version to 0.8.19 - Rename event `AuctionLengthSet()` -> `BatchAuctionLengthSet()` - Add `dutchAuctionLength` and `setDutchAuctionLength()` setter and `DutchAuctionLengthSet()` event - Add `dutchTradeImplementation` and `setDutchTradeImplementation()` setter and `DutchTradeImplementationSet()` event + - Only permit BackingManager-started dutch auctions to report violations and disable trading - Modify `openTrade(TradeRequest memory reg)` -> `openTrade(TradeKind kind, TradeRequest memory req)` - Allow when paused / frozen, since caller must be in-system diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 41584907d2..13ddbbff1a 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -101,13 +101,16 @@ contract BrokerP0 is ComponentP0, IBroker { emit BatchTradeDisabledSet(batchTradeDisabled, true); batchTradeDisabled = true; } else if (kind == TradeKind.DUTCH_AUCTION) { - IERC20Metadata sell = trade.sell(); - emit DutchTradeDisabledSet(sell, dutchTradeDisabled[sell], true); - dutchTradeDisabled[sell] = true; - - IERC20Metadata buy = trade.buy(); - emit DutchTradeDisabledSet(buy, dutchTradeDisabled[buy], true); - dutchTradeDisabled[buy] = true; + // Only allow BackingManager-started trades to disable Dutch Auctions + if (DutchTrade(address(trade)).origin() == main.backingManager()) { + IERC20Metadata sell = trade.sell(); + emit DutchTradeDisabledSet(sell, dutchTradeDisabled[sell], true); + dutchTradeDisabled[sell] = true; + + IERC20Metadata buy = trade.buy(); + emit DutchTradeDisabledSet(buy, dutchTradeDisabled[buy], true); + dutchTradeDisabled[buy] = true; + } } else { revert("unrecognized trade kind"); } diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 98e52120e4..a87ae21d3d 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -137,13 +137,16 @@ contract BrokerP1 is ComponentP1, IBroker { emit BatchTradeDisabledSet(batchTradeDisabled, true); batchTradeDisabled = true; } else if (kind == TradeKind.DUTCH_AUCTION) { - IERC20Metadata sell = trade.sell(); - emit DutchTradeDisabledSet(sell, dutchTradeDisabled[sell], true); - dutchTradeDisabled[sell] = true; - - IERC20Metadata buy = trade.buy(); - emit DutchTradeDisabledSet(buy, dutchTradeDisabled[buy], true); - dutchTradeDisabled[buy] = true; + // Only allow BackingManager-started trades to disable Dutch Auctions + if (DutchTrade(address(trade)).origin() == backingManager) { + IERC20Metadata sell = trade.sell(); + emit DutchTradeDisabledSet(sell, dutchTradeDisabled[sell], true); + dutchTradeDisabled[sell] = true; + + IERC20Metadata buy = trade.buy(); + emit DutchTradeDisabledSet(buy, dutchTradeDisabled[buy], true); + dutchTradeDisabled[buy] = true; + } } else { revert("unrecognized trade kind"); } diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 6b4dad02c8..9581b78271 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2228,7 +2228,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(furnace.address)).to.be.closeTo(minBuyAmtRToken.sub(10), 50) }) - it('Should report violation when Dutch Auction clears in geometric phase', async () => { + it('Should not report violation when Dutch Auction clears in geometric phase', async () => { // This test needs to be in this file and not Broker.test.ts because settleTrade() // requires the BackingManager _actually_ started the trade @@ -2322,24 +2322,24 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Advance time near end of geometric phase await advanceBlocks(config.dutchAuctionLength.div(12).div(5).sub(5)) - // Should settle RSR auction + // Should settle RSR auction without disabling dutch auctions await rsr.connect(addr1).approve(rsrTrade.address, sellAmt.mul(10)) await expect(rsrTrade.connect(addr1).bid()) .to.emit(rsrTrader, 'TradeSettled') .withArgs(anyValue, aaveToken.address, rsr.address, sellAmt, anyValue) - expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(true) - expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(true) + expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(false) - // Should still be able to settle RToken auction, even though aaveToken is now disabled + // Should still be able to settle RToken auction await rToken.connect(addr1).approve(rTokenTrade.address, sellAmtRToken.mul(10)) await expect(rTokenTrade.connect(addr1).bid()) .to.emit(rTokenTrader, 'TradeSettled') .withArgs(anyValue, aaveToken.address, rToken.address, sellAmtRToken, anyValue) - // Check all 3 tokens are disabled for dutch auctions - expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(true) - expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(true) - expect(await broker.dutchTradeDisabled(rToken.address)).to.equal(true) + // Check all no tokens are disabled for dutch auctions + expect(await broker.dutchTradeDisabled(aaveToken.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(rToken.address)).to.equal(false) }) it('Should not report violation when Dutch Auction clears in first linear phase', async () => { From 97f6d4b6bf6e3addad5a75e5f7f2cb15d00390d5 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 24 Aug 2023 18:58:34 -0400 Subject: [PATCH 392/462] CHANGELOG.md --- CHANGELOG.md | 128 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 163413e8af..aecf655ad9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,43 +14,49 @@ Call the following functions: - `RevenueTrader.cacheComponents()` (for both rsrTrader and rTokenTrader) - `Distributor.cacheComponents()` -Collateral / Asset plugins from 2.1.0 do not need to be upgraded with the exception of Compound V2 cToken collateral ([CTokenFiatCollateral.sol](contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol)), which needs to be swapped in via `AssetRegistry.swapRegistered()`. Skipping this step will result in COMP rewards becoming unclaimable. Note that this will change the ERC20 for the collateral plugin, causing the protocol to trade out of the old ERC20. Since COMP rewards are claimed on every transfer, COMP does not need to be claimed beforehand. +_All_ asset plugins (and their corresponding ERC20s) must be upgraded. + +- Make sure to use `Deployer.deployRTokenAsset()` to create new `RTokenAsset` instances. This asset must be swapped too. #### Optional Steps Call the following functions, once it is desired to turn on the new features: -- `BaasketHandler.setWarmupPeriod()` +- `BasketHandler.setWarmupPeriod()` - `StRSR.setWithdrawalLeak()` - `Broker.setDutchAuctionLength()` -### Core Protocol Contracts +It is acceptable to leave these function calls out of the initial upgrade tx and follow up with them later. The protocol will continue to function, just without dutch auctions, RSR unstaking gas-savings, and the warmup period. -Bump solidity version to 0.8.19 +### Core Protocol Contracts Bump solidity version to 0.8.19 - `AssetRegistry` [+1 slot] - Summary: Other component contracts need to know when refresh() was last called + Summary: StRSR contract need to know when refresh() was last called - Add last refresh timestamp tracking and expose via `lastRefresh()` getter - Add `size()` getter for number of registered assets + - Require asset is SOUND on registration + - Bugfix: Fix gas attack that could result in someone disabling the basket - `BackingManager` [+2 slots] Summary: manageTokens was broken out into rebalancing and surplus-forwarding functions to allow users to more precisely call the protocol - Replace `manageTokens(IERC20[] memory erc20s)` with: - - `rebalance(TradeKind)` + `RecollateralizationLibP1` - - Modify trading algorithm to not trade RToken, and instead dissolve it when it has a balance above ~1e6. "dissolve" = melt() with a basketsNeeded change, like redemption. + - `rebalance(TradeKind)` + - Modify trading algorithm to not trade RToken, and instead dissolve it when it has a balance above ~1e6 RToken quanta. "dissolve" = melt() with a basketsNeeded change, similar to redemption but without transfer of RToken collateral. + - Use `lotPrice()` to set trade prices instead of `price()` - Add significant caching to save gas - `forwardRevenue(IERC20[] memory erc20s)` - - Modify backingBuffer logic to keep the backing buffer in collateral tokens only. Fix subtle and inconsequential bug that resulted in not maximizing RToken minting locally, though overall RToken production would not have been lower. + - Modify backingBuffer logic to keep the backing buffer in collateral tokens only. Fix subtle and inconsequential bug that resulted in not maximizing RToken minting locally, though overall RToken production does not change. - Use `nonReentrant` over CEI pattern for gas improvement. related to discussion of [this](https://github.com/code-423n4/2023-01-reserve-findings/issues/347) cross-contract reentrancy risk - move `nonReentrant` up outside `tryTrade` internal helper - Remove `manageTokensSortedOrder(IERC20[] memory erc20s)` - Modify `settleTrade(IERC20 sell)` to call `rebalance()` when caller is a trade it deployed. - - Remove all `delegatecall` during reward claiming + - Remove all `delegatecall` during reward claiming; call `claimRewards()` directly on ERC20 - Functions now revert on unproductive executions, instead of no-op - Do not trade until a warmupPeriod (last time SOUND was newly attained) has passed - Add `cacheComponents()` refresher to be called on upgrade + - Add concept of `tradeNonce` - Bugfix: consider `maxTradeVolume()` from both assets on a trade, not just 1 - `BasketHandler` [+5 slots] @@ -62,18 +68,19 @@ Bump solidity version to 0.8.19 - Enforce `setPrimeBasket()` does not change the net value of a basket in terms of its target units - Add `quoteCustomRedemption(uint48[] basketNonces, uint192[] memory portions, ..)` to quote a linear combination of current-or-previous baskets for redemption - Add `getHistoricalBasket(uint48 basketNonce)` view + - Bugfix: Protect against high BU price overflow -- `Broker` [+1 slot] - Summary: Add a new trading plugin that performs single-lot dutch auctions. Batch auctions via Gnosis EasyAuction are expected to be the backup auction (can be faster if more gas costly) going forward. +- `Broker` [+2 slot] + Summary: Add a second trading method for single-lot dutch auctions. Batch auctions via Gnosis EasyAuction are expected to be the backup auction going forward. - - Add `TradeKind` enum to track multiple trading types - Add new dutch auction `DutchTrade` - - Add minimum auction length of 24s; applies to all auction types + - Add minimum auction length of 20 blocks based on network block time - Rename variable `auctionLength` -> `batchAuctionLength` - Rename setter `setAuctionLength()` -> `setBatchAuctionLength()` - Rename event `AuctionLengthSet()` -> `BatchAuctionLengthSet()` - Add `dutchAuctionLength` and `setDutchAuctionLength()` setter and `DutchAuctionLengthSet()` event - Add `dutchTradeImplementation` and `setDutchTradeImplementation()` setter and `DutchTradeImplementationSet()` event + - Unlike batch auctions, dutch auctions can be disabled _per-ERC20_, and can only be disabled by BackingManager-started trades - Modify `openTrade(TradeRequest memory reg)` -> `openTrade(TradeKind kind, TradeRequest memory req)` - Allow when paused / frozen, since caller must be in-system @@ -82,13 +89,18 @@ Bump solidity version to 0.8.19 - Modify to handle new gov params: `warmupPeriod`, `dutchAuctionLength`, and `withdrawalLeak` - Do not grant OWNER any of the roles other than ownership + - Add `deployRTokenAsset()` to allow easy creation of new `RTokenAsset` instances -- `Distributor` [+0 slots] - Summary: Waste of gas to double-check this, since caller is another component +- `Distributor` [+2 slots] + Summary: Restrict callers to system components and remove paused/frozen checks - Remove `notPausedOrFrozen` modifier from `distribute()` - `Furnace` [+0 slots] - Summary: Should be able to melting while redeeming when frozen - - Modify `melt()` modifier: `notPausedOrFrozen` -> `notFrozen` + Summary: Allow melting while paused + + - Allow melting while paused + - Melt during updates to the melting ratio + - Lower `MAX_RATIO` from 1e18 to 1e14. + - `Main` [+0 slots] Summary: Breakup pausing into two types of pausing: issuance and trading @@ -98,88 +110,106 @@ Bump solidity version to 0.8.19 - `pausedOrFrozen()` -> `tradingPausedOrFrozen()` and `issuancePausedOrFrozen()` - `PausedSet()` event -> `TradingPausedSet()` and `IssuancePausedSet()` -- `RevenueTrader` [+3 slots] +- `RevenueTrader` [+4 slots] Summary: QoL improvements. Make compatible with new dutch auction trading method - - Remove `delegatecall` during reward claiming + - Remove `delegatecall` during reward claiming; call `claimRewards()` directly on ERC20 - Add `cacheComponents()` refresher to be called on upgrade - - `manageToken(IERC20 sell)` -> `manageToken(IERC20 sell, TradeKind kind)` - - Allow `manageToken(..)` to open dust auctions - - Revert on 0 balance or collision auction, instead of no-op + - `manageToken(IERC20 sell)` -> `manageTokens(IERC20[] calldata erc20s, TradeKind[] memory kinds)` + - Allow multiple auctions to be launched at once + - Allow opening dust auctions (i.e ignore `minTradeVolume`) + - Revert on 0 balance or collision auction instead of no-op - Refresh buy and sell asset before trade - - `settleTrade(IERC20)` now distributes `tokenToBuy`, instead of requiring separate `manageToken(IERC20)` call + - `settleTrade(IERC20)` now distributes `tokenToBuy` automatically, instead of requiring separate `manageToken(IERC20)` call + - Add `returnTokens(IERC20[] memory erc20s)` to return tokens to the BackingManager when the distribution is set to 0 + - Add concept of `tradeNonce` - `RToken` [+0 slots] - Summary: Provide multiple redemption methods for when fullyCollateralized vs not. Should support a higher RToken price during basket changes. + Summary: Provide multiple redemption methods for fullyCollateralized vs uncollateralized. - - Remove `exchangeRateIsValidAfter` modifier from all functions except `setBasketsNeeded()` - - Modify `issueTo()` to revert before `warmupPeriod` - - Modify `redeem(uint256 amount, uint48 basketNonce)` -> `redeem(uint256 amount)`. Redemptions are on the current basket nonce and revert under partial redemption + - Gas: Remove `exchangeRateIsValidAfter` modifier from all functions except `setBasketsNeeded()` + - Modify issuance`to revert before`warmupPeriod` + - Modify `redeem(uint256 amount, uint48 basketNonce)` -> `redeem(uint256 amount)`. Redemptions are always on the current basket nonce and revert under partial redemption - Modify `redeemTo(address recipient, uint256 amount, uint48 basketNonce)` -> `redeemTo(address recipient, uint256 amount)`. Redemptions are on the current basket nonce and revert under partial redemption - - Add new `redeemCustom(.., uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, ..)` function to allow redemption from a linear combination of current and previous baskets. During rebalancing this method of redemption will provide a higher overall redemption value than prorata redemption on the current basket nonce would. - - `mint(address recipient, uint256 amtRToken)` -> `mint(uint256 amtRToken)`, since recipient is _always_ BackingManager. Expand scope to include adjustments to `basketsNeeded` + - Add new `redeemCustom(.., uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, ..)` function to allow redemption from a linear combination of current and previous baskets. During rebalancing this method of redemption may provide a higher overall redemption value than prorata redemption on the current basket nonce would. + - Modify `mint(address recipient, uint256 amtRToken)` -> `mint(uint256 amtRToken)`, since recipient is _always_ BackingManager. Expand scope to include adjustments to `basketsNeeded` - Add `dissolve(uint256 amount)`: burns RToken and reduces `basketsNeeded`, similar to redemption. Only callable by BackingManager - Modify `setBasketsNeeded(..)` to revert when supply is 0 + - Bugfix: Accumulate throttles upon change - `StRSR` [+2 slots] - Summary: Add the ability to cancel unstakings and a withdrawal() gas-saver to allow small RSR amounts to be exempt from refreshes + Summary: Add the ability to cancel unstakings and a withdrawal() gas-saver to allow small RSR amounts to be exempt from asset refreshes + - Lower `MAX_REWARD_RATIO` from 1e18 to 1e14. - Remove duplicate `stakeRate()` getter (same as `1 / exchangeRate()`) - Add `withdrawalLeak` gov param, with `setWithdrawalLeak(..)` setter and `WithdrawalLeakSet()` event - - Modify `withdraw()` to allow a small % of RSR too exit without paying to refresh all assets + - Modify `withdraw()` to allow a small % of RSR to exit without paying to refresh all assets - Modify `withdraw()` to check for `warmupPeriod` - Add ability to re-stake during a withdrawal via `cancelUnstake(uint256 endId)` - Add `UnstakingCancelled()` event + - Allow payout of (already acquired) RSR rewards while frozen + - Add ability for governance to `resetStakes()` when stake rate falls outside (1e12, 1e24) - `StRSRVotes` [+0 slots] - - Add `stakeAndDelegate(uint256 rsrAmount, address delegate)` function, to encourage people to receive voting weight upon staking + - Add `stakeAndDelegate(uint256 rsrAmount, address delegate)` function to encourage people to receive voting weight upon staking ### Facades -- `FacadeWrite` - Summary: More expressive and fine-grained control over the set of pausers and freezers - - - Do not automatically grant Guardian PAUSER/SHORT_FREEZER/LONG_FREEZER - - Do not automatically grant Owner PAUSER/SHORT_FREEZER/LONG_FREEZER - - Add ability to initialize with multiple pausers, short freezers, and long freezers - - Modify `setupGovernance(.., address owner, address guardian, address pauser)` -> `setupGovernance(.., GovernanceRoles calldata govRoles)` - - Update `DeploymentParams` and `Implementations` struct to contain new gov params and dutch trade plugin - - `FacadeAct` Summary: Remove unused getActCalldata and add way to run revenue auctions - Remove `getActCalldata(..)` + - Remove `canRunRecollateralizationAuctions(..)` - Modify `runRevenueAuctions(..)` to work with both 3.0.0 and 2.1.0 interfaces + - Add `revenueOverview(..)` callstatic function to get an overview of the current revenue state + - Add `nextRecollateralizationAuction(..)` callstatic function to get an overview of the rebalancing state + +- Remove `FacadeMonitor` - `FacadeRead` Summary: Add new data summary views frontends may be interested in - Remove `basketNonce` from `redeem(.., uint48 basketNonce)` + - Add `redeemCustom(.., uint48[] memory basketNonces, uint192[] memory portions)` callstatic to simulate multi-basket redemptions - Remove `traderBalances(..)` - `balancesAcrossAllTraders(IBackingManager) returns (IERC20[] memory erc20s, uint256[] memory balances, uint256[] memory balancesNeededByBackingManager)` - - Add `nextRecollateralizationAuction(..) returns (bool canStart, IERC20 sell, IERC20 buy, uint256 sellAmount)` - - Add `revenueOverview(IRevenueTrader) returns ( IERC20[] memory erc20s, bool[] memory canStart, uint256[] memory surpluses, uint256[] memory minTradeAmounts)` -- Remove `FacadeMonitor` - redundant with `nextRecollateralizationAuction()` and `revenueOverview()` +- `FacadeWrite` + Summary: More expressive and fine-grained control over the set of pausers and freezers + + - Do not automatically grant Guardian PAUSER/SHORT_FREEZER/LONG_FREEZER + - Do not automatically grant Owner PAUSER/SHORT_FREEZER/LONG_FREEZER + - Add ability to initialize with multiple pausers, short freezers, and long freezers + - Modify `setupGovernance(.., address owner, address guardian, address pauser)` -> `setupGovernance(.., GovernanceRoles calldata govRoles)` + - Update `DeploymentParams` and `Implementations` struct to contain new gov params and dutch trade plugin ## Plugins ### DutchTrade -A cheaper, simpler, trading method. Intended to be the new dominant trading method, with GnosisTrade (batch auctions) available as a faster-but-more-gas-expensive backup option. +A cheaper, simpler, trading method. Intended to be the new dominant trading method, with GnosisTrade (batch auctions) available as a backup option. Generally speaking the batch auction length can be kept shorter than the dutch auction length. -DutchTrade implements a two-stage, single-lot, falling price dutch auction. In the first 40% of the auction, the price falls from 1000x to the best-case price in a geometric/exponential decay as a price manipulation defense mechanism. Bids are not expected to occur (but note: unlike the GnosisTrade batch auction, this mechanism is not resistant to _arbitrary_ price manipulation). +DutchTrade implements a four-stage, single-lot, falling price dutch auction: -Over the last 60% of the auction, the price falls linearly from the best-case price to the worst-case price. Only a single bidder can bid fill the auction, and settlement is atomic. If no bids are received, the capital cycles back to the BackingManager and no loss is taken. +1. In the first 20% of the auction, the price falls from 1000x the best price to the best price in a geometric/exponential decay as a price manipulation defense mechanism. Bids are not expected to occur (but note: unlike the GnosisTrade batch auction, this mechanism is not resistant to _arbitrary_ price manipulation). If a bid occurs, then trading for the pair of tokens is disabled as long as the trade was started by the BackingManager. +2. Between 20% and 45%, the price falls linearly from 1.5x the best price to the best price. +3. Between 45% and 95%, the price falls linearly from the best price to the worst price. +4. Over the last 5% of the auction, the price remains constant at the worst price. Duration: 30 min (default) ### Assets and Collateral -- Bugfix: `lotPrice()` now begins at 100% the lastSavedPrice, instead of below 100%. It can be at 100% for up to the oracleTimeout in the worst-case. - Add `version() return (string)` getter to pave way for separation of asset versioning and core protocol versioning -- Update `claimRewards()` on all assets to 3.0.0-style, without `delegatecall` +- Deprecate `claimRewards()` - Add `lastSave()` to `RTokenAsset` +- Remove `CurveVolatileCollateral` +- Switch `CToken*Collateral` (Compound V2) to using a CTokenVault ERC20 rather than the raw cToken +- Bugfix: `lotPrice()` now begins at 100% the lastSavedPrice, instead of below 100%. It can be at 100% for up to the oracleTimeout in the worst-case. +- Bugfix: Handle oracle deprecation as indicated by the `aggregator()` being set to the zero address +- Bugfix: `AnkrStakedETHCollateral`/`CBETHCollateral`/`RethCollateral` now correctly detects soft default (note that Ankr still requires a new oracle before it can be deployed) +- Bugfix: Adjust `Curve*Collateral` and `RTokenAsset` to treat FIX_MAX correctly as +inf +- Bugfix: Continue updating cached price after collateral default (impacts all appreciating collateral) # 2.1.0 From 51bbdf1f46b079e87c637357848f9ac3fbc60eb4 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 24 Aug 2023 19:03:35 -0400 Subject: [PATCH 393/462] collateral.md --- docs/collateral.md | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/docs/collateral.md b/docs/collateral.md index d9589954e1..a1108021c1 100644 --- a/docs/collateral.md +++ b/docs/collateral.md @@ -15,7 +15,6 @@ In our inheritance tree, Collateral is a subtype of Asset (i.e. `ICollateral is - How to get its price - A maximum volume per trade -- How to claim token rewards, if the token offers them A Collateral contract is a subtype of Asset (i.e. `ICollateral is IAsset`), so it does everything as Asset does. Beyond that, a Collateral plugin provides the Reserve Protocol with the information it needs to use its token as collateral -- as backing, held in the RToken's basket. @@ -27,20 +26,6 @@ A Collateral contract is a subtype of Asset (i.e. `ICollateral is IAsset`), so i The IAsset and ICollateral interfaces, from `IAsset.sol`, are as follows: ```solidity -/** - * @title IRewardable - * @notice A simple interface mixin to support claiming of rewards. - */ -interface IRewardable { - /// Emitted whenever a reward token balance is claimed - event RewardsClaimed(IERC20 indexed erc20, uint256 indexed amount); - - /// Claim rewards earned by holding a balance of the ERC20 token - /// Must emit `RewardsClaimed` for each token rewards are claimed for - /// @custom:interaction - function claimRewards() external; -} - /** * @title IAsset * @notice Supertype. Any token that interacts with our system must be wrapped in an asset, @@ -77,8 +62,11 @@ interface IAsset is IRewardable { /// @return If the asset is an instance of ICollateral or not function isCollateral() external view returns (bool); - /// @param {UoA} The max trade volume, in UoA + /// @return {UoA} The max trade volume, in UoA function maxTradeVolume() external view returns (uint192); + + /// @return {s} The timestamp of the last refresh() that saved prices + function lastSave() external view returns (uint48); } /// CollateralStatus must obey a linear ordering. That is: @@ -199,9 +187,9 @@ Note, this doesn't disqualify collateral with USD as its target unit! It's fine ### Representing Fractional Values -Wherever contract variables have these units, it's understood that even though they're handled as `uint`s, they represent fractional values with 18 decimals. In particular, a `{tok}` value is a number of "whole tokens" with 18 decimals. So even though DAI has 18 decimals and USDC has 6 decimals, $1 in either token would be 1e18 when working in units of `{tok}`. +Wherever contract variables have these units, it's understood that even though they're handled as `uint192`s, they represent fractional values with 18 decimals. In particular, a `{tok}` value is a number of "whole tokens" with 18 decimals. So even though DAI has 18 decimals and USDC has 6 decimals, $1 in either token would be 1e18 when working in units of `{tok}`. -For more about our approach for handling decimal-fixed-point, see our [docs on the Fix Library](solidity-style.md#The-Fix-Library). +For more about our approach for handling decimal-fixed-point, see our [docs on the Fix Library](solidity-style.md#The-Fix-Library). Ideally a user-defined type would be used but we found static analyses tools had trouble with that. ## Synthetic Units @@ -349,9 +337,9 @@ If `status()` ever returns `CollateralStatus.DISABLED`, then it must always retu ### Token rewards should be claimable. -Protocol contracts that hold an asset for any significant amount of time are all able to call `claimRewards()` on the ERC20 itself (previously on the asset/collateral plugin via delegatecall). The erc20 or its wrapper contract should include whatever logic is necessary to claim rewards from all relevant defi protocols. These rewards are often emissions from other protocols, but may also be something like trading fees in the case of UNIV3 collateral. To take advantage of this: +Protocol contracts that hold an asset for any significant amount of time must be able to call `claimRewards()` on the ERC20 itself (previously on the asset/collateral plugin via delegatecall). The erc20 should include whatever logic is necessary to claim rewards from all relevant defi protocols. These rewards are often emissions from other protocols, but may also be something like trading fees in the case of UNIV3 collateral. To take advantage of this: -- `claimRewards()` must claim all rewards that may be earned by holding the asset ERC20 and send them to the holder. +- `claimRewards()` must claim all rewards that may be earned by holding the asset ERC20 and send them to the holder, in the correct proportions based on amount of time held. - The `RewardsClaimed` event should be emitted for each token type claimed. ### Smaller Constraints @@ -371,7 +359,6 @@ Collateral implementors who extend from [Fiat Collateral](../contracts/plugins/a - `tryPrice()` (not on the ICollateral interface; used by `price()`/`lotPrice()`/`refresh()`) - `refPerTok()` - `targetPerRef()` -- `claimRewards()` ### refresh() @@ -490,6 +477,6 @@ If implementing a demurrage-based collateral plugin, make sure your targetName f ## Practical Advice from Previous Work -In most cases [Fiat Collateral](../contracts/plugins/asset/FiatCollateral.sol) can be extended, pretty easily, to support a new collateral type. This allows the collateral developer to limit their attention to the overriding of four functions: `tryPrice()`, `refPerTok()`, `targetPerRef()`, `claimRewards()`. +In most cases [Fiat Collateral](../contracts/plugins/asset/FiatCollateral.sol) can be extended, pretty easily, to support a new collateral type. This allows the collateral developer to limit their attention to the overriding of three functions: `tryPrice()`, `refPerTok()`, `targetPerRef()`. If you're quite stuck, you might also find it useful to read through our other Collateral plugins as models, found in our repository in `/contracts/plugins/assets`. From 4ed50dc2c2b3821e1d551e9cdb78615765296a5b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 24 Aug 2023 19:05:39 -0400 Subject: [PATCH 394/462] mev.md --- docs/mev.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/mev.md b/docs/mev.md index 8d2cfdf714..7d32e64687 100644 --- a/docs/mev.md +++ b/docs/mev.md @@ -27,7 +27,7 @@ Bidding instructions from the `DutchTrade` contract: `DutchTrade` (relevant) interface: ```solidity -function bid() external; // execute a bid at the current block timestamp +function bid() external; // execute a bid at the current block number function sell() external view returns (IERC20); @@ -37,7 +37,7 @@ function status() external view returns (uint8); // 0: not_started, 1: active, 2 function lot() external view returns (uint256); // {qSellTok} the number of tokens being sold -function bidAmount(uint48 timestamp) external view returns (uint256); // {qBuyTok} the number of tokens required to buy the lot, at a particular timestamp +function bidAmount(uint256 blockNumber) external view returns (uint256); // {qBuyTok} the number of tokens required to buy the lot, at a particular block number ``` @@ -45,7 +45,7 @@ To participate: 1. Call `status()` view; the auction is ongoing if return value is 1 2. Call `lot()` to see the number of tokens being sold -3. Call `bidAmount()` to see the number of tokens required to buy the lot, at various timestamps +3. Call `bidAmount()` to see the number of tokens required to buy the lot, at various block numbers 4. After finding an attractive bidAmount, provide an approval for the `buy()` token. The spender should be the `DutchTrade` contract. **Note**: it is very important to set tight approvals! Do not set more than the `bidAmount()` for the desired bidding block else reorgs present risk. 5. Wait until the desired block is reached (hopefully not in the first 40% of the auction) From 9dc44c6040e06bc9a552dc173768b0e322a267a1 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 25 Aug 2023 11:13:11 -0400 Subject: [PATCH 395/462] save gas by only checking aggregator if there is a revert (#914) --- contracts/plugins/assets/OracleLib.sol | 47 ++++++++++++++++++-------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/contracts/plugins/assets/OracleLib.sol b/contracts/plugins/assets/OracleLib.sol index 495186e36c..79b268ed46 100644 --- a/contracts/plugins/assets/OracleLib.sol +++ b/contracts/plugins/assets/OracleLib.sol @@ -12,7 +12,7 @@ interface EACAggregatorProxy { /// Used by asset plugins to price their collateral library OracleLib { - /// @dev Use for on-the-fly calculations that should revert + /// @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(AggregatorV3Interface chainlinkFeed, uint48 timeout) @@ -20,22 +20,39 @@ library OracleLib { view returns (uint192) { - // If the aggregator is not set, the chainlink feed has been deprecated - if (EACAggregatorProxy(address(chainlinkFeed)).aggregator() == address(0)) { - revert StalePrice(); - } + try chainlinkFeed.latestRoundData() returns ( + uint80 roundId, + int256 p, + uint256, + uint256 updateTime, + uint80 answeredInRound + ) { + if (updateTime == 0 || answeredInRound < roundId) { + revert StalePrice(); + } - (uint80 roundId, int256 p, , uint256 updateTime, uint80 answeredInRound) = chainlinkFeed - .latestRoundData(); + // Downcast is safe: uint256(-) reverts on underflow; block.timestamp assumed < 2^48 + uint48 secondsSince = uint48(block.timestamp - updateTime); + if (secondsSince > timeout) revert StalePrice(); - 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(); + // {UoA/tok} + return shiftl_toFix(uint256(p), -int8(chainlinkFeed.decimals())); + } catch (bytes memory errData) { + // Check if the aggregator 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 (EACAggregatorProxy(address(chainlinkFeed)).aggregator() == address(0)) { + revert StalePrice(); + } + // solhint-disable-next-line reason-string + revert(); + } - // {UoA/tok} - return shiftl_toFix(uint256(p), -int8(chainlinkFeed.decimals())); + // Otherwise, preserve the error bytes + // solhint-disable-next-line no-inline-assembly + assembly { + revert(add(32, errData), mload(errData)) + } + } } } From 9657c31cef4e4c0f08cd2bead1c8b150b21eb1db Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Sun, 27 Aug 2023 11:13:34 -0300 Subject: [PATCH 396/462] facade support batch auctions (#918) --- contracts/facade/FacadeAct.sol | 25 +++++++++++---- contracts/interfaces/IFacadeAct.sol | 2 +- test/Facade.test.ts | 50 +++++++++++++++++++++++------ 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 549ccf16ae..ab3e77152e 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -5,6 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/utils/Multicall.sol"; import "../plugins/trading/DutchTrade.sol"; +import "../plugins/trading/GnosisTrade.sol"; import "../interfaces/IBackingManager.sol"; import "../interfaces/IFacadeAct.sol"; import "../interfaces/IFacadeRead.sol"; @@ -159,7 +160,7 @@ contract FacadeAct is IFacadeAct, Multicall { /// @return buy The buy token in the auction /// @return sellAmount {qSellTok} How much would be sold /// @custom:static-call - function nextRecollateralizationAuction(IBackingManager bm) + function nextRecollateralizationAuction(IBackingManager bm, TradeKind kind) external returns ( bool canStart, @@ -183,22 +184,34 @@ contract FacadeAct is IFacadeAct, Multicall { // If no auctions ongoing, to find a new auction to start if (bm.tradesOpen() == 0) { - _rebalance(bm); + _rebalance(bm, kind); // Find the started auction for (uint256 i = 0; i < erc20s.length; ++i) { - DutchTrade trade = DutchTrade(address(bm.trades(erc20s[i]))); + ITrade trade = ITrade(address(bm.trades(erc20s[i]))); if (address(trade) != address(0)) { canStart = true; sell = trade.sell(); buy = trade.buy(); - sellAmount = trade.sellAmount(); + sellAmount = _getSellAmount(trade); } } } } // === Private === + function _getSellAmount(ITrade trade) private view returns (uint256) { + if (trade.KIND() == TradeKind.DUTCH_AUCTION) { + return + DutchTrade(address(trade)).sellAmount().shiftl_toUint( + int8(trade.sell().decimals()) + ); + } else if (trade.KIND() == TradeKind.BATCH_AUCTION) { + return GnosisTrade(address(trade)).initBal(); + } else { + revert("invalid trade type"); + } + } function _settleTrade(ITrading trader, IERC20 toSettle) private { bytes1 majorVersion = bytes(trader.version())[0]; @@ -249,12 +262,12 @@ contract FacadeAct is IFacadeAct, Multicall { } } - function _rebalance(IBackingManager bm) private { + function _rebalance(IBackingManager bm, TradeKind kind) private { bytes1 majorVersion = bytes(bm.version())[0]; if (majorVersion == MAJOR_VERSION_3) { // solhint-disable-next-line no-empty-blocks - try bm.rebalance(TradeKind.DUTCH_AUCTION) {} catch {} + try bm.rebalance(kind) {} catch {} } else if (majorVersion == MAJOR_VERSION_2 || majorVersion == MAJOR_VERSION_1) { IERC20[] memory emptyERC20s = new IERC20[](0); // solhint-disable-next-line avoid-low-level-calls diff --git a/contracts/interfaces/IFacadeAct.sol b/contracts/interfaces/IFacadeAct.sol index 61085f0a72..eef569af4f 100644 --- a/contracts/interfaces/IFacadeAct.sol +++ b/contracts/interfaces/IFacadeAct.sol @@ -66,7 +66,7 @@ interface IFacadeAct { /// @return buy The buy token in the auction /// @return sellAmount {qSellTok} How much would be sold /// @custom:static-call - function nextRecollateralizationAuction(IBackingManager bm) + function nextRecollateralizationAuction(IBackingManager bm, TradeKind kind) external returns ( bool canStart, diff --git a/test/Facade.test.ts b/test/Facade.test.ts index f500a6a977..fc1a7121ff 100644 --- a/test/Facade.test.ts +++ b/test/Facade.test.ts @@ -666,7 +666,10 @@ describe('FacadeRead + FacadeAct contracts', () => { it('Should return nextRecollateralizationAuction', async () => { // Confirm no auction to run yet - should not revert let [canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + await facadeAct.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.DUTCH_AUCTION + ) expect(canStart).to.equal(false) // Setup prime basket @@ -682,7 +685,10 @@ describe('FacadeRead + FacadeAct contracts', () => { // Confirm nextRecollateralizationAuction is true ;[canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + await facadeAct.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.DUTCH_AUCTION + ) expect(canStart).to.equal(true) expect(sell).to.equal(token.address) expect(buy).to.equal(usdc.address) @@ -704,7 +710,10 @@ describe('FacadeRead + FacadeAct contracts', () => { // nextRecollateralizationAuction should return false (trade open) ;[canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + await facadeAct.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.DUTCH_AUCTION + ) expect(canStart).to.equal(false) expect(sell).to.equal(ZERO_ADDRESS) expect(buy).to.equal(ZERO_ADDRESS) @@ -716,7 +725,10 @@ describe('FacadeRead + FacadeAct contracts', () => { // nextRecollateralizationAuction should return the next trade // In this case it will retry the same auction ;[canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + await facadeAct.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.DUTCH_AUCTION + ) expect(canStart).to.equal(true) expect(sell).to.equal(token.address) expect(buy).to.equal(usdc.address) @@ -746,7 +758,10 @@ describe('FacadeRead + FacadeAct contracts', () => { // Confirm no auction to run yet - should not revert let [canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + await facadeAct.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.BATCH_AUCTION + ) expect(canStart).to.equal(false) // Setup prime basket @@ -762,7 +777,10 @@ describe('FacadeRead + FacadeAct contracts', () => { // Confirm nextRecollateralizationAuction is true ;[canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + await facadeAct.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.BATCH_AUCTION + ) expect(canStart).to.equal(true) expect(sell).to.equal(token.address) expect(buy).to.equal(usdc.address) @@ -787,7 +805,10 @@ describe('FacadeRead + FacadeAct contracts', () => { // nextRecollateralizationAuction should return false (trade open) ;[canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + await facadeAct.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.BATCH_AUCTION + ) expect(canStart).to.equal(false) expect(sell).to.equal(ZERO_ADDRESS) expect(buy).to.equal(ZERO_ADDRESS) @@ -799,7 +820,10 @@ describe('FacadeRead + FacadeAct contracts', () => { // nextRecollateralizationAuction should return the next trade // In this case it will retry the same auction ;[canStart, sell, buy, sellAmount] = - await facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + await facadeAct.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.BATCH_AUCTION + ) expect(canStart).to.equal(true) expect(sell).to.equal(token.address) expect(buy).to.equal(usdc.address) @@ -809,7 +833,10 @@ describe('FacadeRead + FacadeAct contracts', () => { await backingManager.connect(owner).upgradeTo(backingManagerInvalidVer.address) await expect( - facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + facadeAct.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.BATCH_AUCTION + ) ).to.be.revertedWith('unrecognized version') }) @@ -836,7 +863,10 @@ describe('FacadeRead + FacadeAct contracts', () => { // Attempt to trigger recollateralization await expect( - facadeAct.callStatic.nextRecollateralizationAuction(backingManager.address) + facadeAct.callStatic.nextRecollateralizationAuction( + backingManager.address, + TradeKind.BATCH_AUCTION + ) ).to.be.revertedWith('unrecognized version') }) From 1db057bf5184ae2625e3690175a8165f703f997d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 28 Aug 2023 12:17:48 -0400 Subject: [PATCH 397/462] document RTokenAsset.price() oracleError double-counting (#916) --- contracts/plugins/assets/RTokenAsset.sol | 9 ++++++++- docs/collateral.md | 2 ++ docs/writing-collateral-plugins.md | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index fd8c78fa24..6aee26fd40 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -12,7 +12,7 @@ uint256 constant ORACLE_TIMEOUT = 15 minutes; /// Once an RToken gets large enough to get a price feed, replacing this asset with /// a simpler one will do wonders for gas usage -// @dev This RTokenAsset is ONLY compatible with Protocol ^3.0.0 +/// @dev This RTokenAsset is ONLY compatible with Protocol ^3.0.0 contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { using FixLib for uint192; using OracleLib for AggregatorV3Interface; @@ -48,6 +48,11 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { } /// Can revert, used by other contract functions in order to catch errors + /// @dev This method for calculating the price can provide a 2x larger range than the average + /// oracleError of the RToken's backing collateral. This only occurs when there is + /// less RSR overcollateralization in % terms than the average (weighted) oracleError. + /// This arises from the use of oracleErrors inside of `basketRange()` and inside + /// `basketHandler.price()`. When `range.bottom == range.top` then there is no compounding. /// @return low {UoA/tok} The low price estimate /// @return high {UoA/tok} The high price estimate function tryPrice() external view virtual returns (uint192 low, uint192 high) { @@ -81,6 +86,7 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { // solhint-enable no-empty-blocks /// Should not revert + /// @dev See `tryPrice` caveat about possible compounding error in calculating price /// @return {UoA/tok} The lower end of the price estimate /// @return {UoA/tok} The upper end of the price estimate function price() public view virtual returns (uint192, uint192) { @@ -95,6 +101,7 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { /// Should not revert /// lotLow should be nonzero when the asset might be worth selling + /// @dev See `tryPrice` caveat about possible compounding error in calculating price /// @return lotLow {UoA/tok} The lower end of the lot price estimate /// @return lotHigh {UoA/tok} The upper end of the lot price estimate function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh) { diff --git a/docs/collateral.md b/docs/collateral.md index a1108021c1..88727f0fb1 100644 --- a/docs/collateral.md +++ b/docs/collateral.md @@ -428,6 +428,8 @@ Should never revert. Should return a lower and upper estimate for the price of the token on secondary markets. +The difference between the upper and lower estimate should not exceed 5%, though this is not a hard-and-fast rule. When the difference (usually arising from an oracleError) is large, it can lead to [the price estimation of the RToken](../contracts/plugins/assets/RTokenAsset.sol) somewhat degrading. While this is not usually an issue it can come into play when one RToken is using another RToken as collateral either directly or indirectly through an LP token. If there is RSR overcollateralization then this issue is mitigated. + Lower estimate must be <= upper estimate. Should return `(0, FIX_MAX)` if pricing data is unavailable or stale. diff --git a/docs/writing-collateral-plugins.md b/docs/writing-collateral-plugins.md index 01fe3d75d4..be05b3ec64 100644 --- a/docs/writing-collateral-plugins.md +++ b/docs/writing-collateral-plugins.md @@ -27,6 +27,7 @@ Here are some basic questions to answer before beginning to write a new collater 7. **What amount of revenue should this plugin hide? (a minimum of `1e-6`% is recommended, but some collateral may require higher thresholds, and, in rare cases, `0` can be used)** 8. **Are there rewards that can be claimed by holding this collateral? If so, how are they claimed?** Include a github link to the callable function or an example of how to claim. 9. **Does the collateral need to be "refreshed" in order to update its internal state before refreshing the plugin?** Include a github link to the callable function. +10. **Can the `price()` range be kept <5%? What is the largest possible % difference (while priced) between `price().high` and `price().low`?** See [RTokenAsset.tryPrice()](../contracts/plugins/assets/RTokenAsset.sol) and [docs/collateral.md](./collateral.md#price) for additional context. ## Implementation From 57c88d118b9c186b245fe5b968bc3580a74a619d Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 30 Aug 2023 11:59:11 -0300 Subject: [PATCH 398/462] try catch on ctoken exchangeRateCurrent (#921) Co-authored-by: Taylor Brent --- .../compoundv2/CTokenFiatCollateral.sol | 14 ++- .../CTokenSelfReferentialCollateral.sol | 14 ++- contracts/plugins/mocks/CTokenMock.sol | 9 ++ ...WrapperMock2.sol => CTokenWrapperMock.sol} | 0 test/plugins/Collateral.test.ts | 97 +++++++++++++++++++ .../compoundv2/CTokenFiatCollateral.test.ts | 66 +++++++++++++ 6 files changed, 198 insertions(+), 2 deletions(-) rename contracts/plugins/mocks/{CTokenWrapperMock2.sol => CTokenWrapperMock.sol} (100%) diff --git a/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol index 9688f437d2..78d37b5bcb 100644 --- a/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol @@ -39,7 +39,19 @@ contract CTokenFiatCollateral is AppreciatingFiatCollateral { function refresh() public virtual override { // == Refresh == // Update the Compound Protocol - cToken.exchangeRateCurrent(); + // solhint-disable no-empty-blocks + try cToken.exchangeRateCurrent() {} catch (bytes memory errData) { + CollateralStatus oldStatus = status(); + + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + markStatus(CollateralStatus.DISABLED); + + CollateralStatus newStatus = status(); + if (oldStatus != newStatus) { + emit CollateralStatusChanged(oldStatus, newStatus); + } + } // Intentional and correct for the super call to be last! super.refresh(); // already handles all necessary default checks diff --git a/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol index f4b8adf30b..00218ec37e 100644 --- a/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenSelfReferentialCollateral.sol @@ -64,7 +64,19 @@ contract CTokenSelfReferentialCollateral is AppreciatingFiatCollateral { function refresh() public virtual override { // == Refresh == // Update the Compound Protocol -- access cToken directly - cToken.exchangeRateCurrent(); + // solhint-disable no-empty-blocks + try cToken.exchangeRateCurrent() {} catch (bytes memory errData) { + CollateralStatus oldStatus = status(); + + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + markStatus(CollateralStatus.DISABLED); + + CollateralStatus newStatus = status(); + if (oldStatus != newStatus) { + emit CollateralStatusChanged(oldStatus, newStatus); + } + } // Violation of calling super first! Composition broken! Intentional! super.refresh(); // already handles all necessary default checks diff --git a/contracts/plugins/mocks/CTokenMock.sol b/contracts/plugins/mocks/CTokenMock.sol index d02401dee0..a0d4f0b562 100644 --- a/contracts/plugins/mocks/CTokenMock.sol +++ b/contracts/plugins/mocks/CTokenMock.sol @@ -11,6 +11,8 @@ contract CTokenMock is ERC20Mock { uint256 internal _exchangeRate; + bool public revertExchangeRate; + constructor( string memory name, string memory symbol, @@ -25,6 +27,9 @@ contract CTokenMock is ERC20Mock { } function exchangeRateCurrent() external returns (uint256) { + if (revertExchangeRate) { + revert("reverting exchange rate current"); + } _exchangeRate = _exchangeRate; // just to avoid sol warning return _exchangeRate; } @@ -48,4 +53,8 @@ contract CTokenMock is ERC20Mock { int8 leftShift = 18 - int8(decimals()) + int8(IERC20Metadata(_underlyingToken).decimals()); return fiatcoinRedemptionRate.shiftl(leftShift).mul_toUint(start); } + + function setRevertExchangeRate(bool newVal) external { + revertExchangeRate = newVal; + } } diff --git a/contracts/plugins/mocks/CTokenWrapperMock2.sol b/contracts/plugins/mocks/CTokenWrapperMock.sol similarity index 100% rename from contracts/plugins/mocks/CTokenWrapperMock2.sol rename to contracts/plugins/mocks/CTokenWrapperMock.sol diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index ea5db3b17e..7f4404952d 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -11,6 +11,7 @@ import { ComptrollerMock, CTokenFiatCollateral, CTokenNonFiatCollateral, + CTokenMock, CTokenWrapperMock, CTokenSelfReferentialCollateral, ERC20Mock, @@ -919,6 +920,38 @@ describe('Collateral contracts', () => { expect(await aTokenCollateral.status()).to.equal(CollateralStatus.IFFY) }) + it('CTokens - Enters DISABLED state when exchangeRateCurrent() reverts', async () => { + const currRate = await cToken.exchangeRateStored() + const [currLow, currHigh] = await cTokenCollateral.price() + + expect(await cTokenCollateral.status()).to.equal(CollateralStatus.SOUND) + await expectPrice(cTokenCollateral.address, fp('0.02'), ORACLE_ERROR, true) + + // Make cToken revert on exchangeRateCurrent() + const cTokenErc20Mock = ( + await ethers.getContractAt('CTokenMock', await cToken.underlying()) + ) + await cTokenErc20Mock.setRevertExchangeRate(true) + + // Refresh - should not revert - Sets DISABLED + await expect(cTokenCollateral.refresh()) + .to.emit(cTokenCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) + + expect(await cTokenCollateral.status()).to.equal(CollateralStatus.DISABLED) + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) + expect(await cTokenCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + + // Exchange rate stored is still accessible + expect(await cToken.exchangeRateStored()).to.equal(currRate) + + // Price remains the same + await expectPrice(cTokenCollateral.address, fp('0.02'), ORACLE_ERROR, true) + const [newLow, newHigh] = await cTokenCollateral.price() + expect(newLow).to.equal(currLow) + expect(newHigh).to.equal(currHigh) + }) + it('Reverts if Chainlink feed reverts or runs out of gas, maintains status - Fiat', async () => { const invalidChainlinkFeed: InvalidMockV3Aggregator = ( await InvalidMockV3AggregatorFactory.deploy(8, bn('1e8')) @@ -1521,6 +1554,38 @@ describe('Collateral contracts', () => { expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) }) + it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { + const currRate = await cNonFiatTokenVault.exchangeRateStored() + const [currLow, currHigh] = await cTokenNonFiatCollateral.price() + + expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) + await expectPrice(cTokenNonFiatCollateral.address, fp('400'), ORACLE_ERROR, true) + + // Make cToken revert on exchangeRateCurrent() + const cTokenErc20Mock = ( + await ethers.getContractAt('CTokenMock', await cNonFiatTokenVault.underlying()) + ) + await cTokenErc20Mock.setRevertExchangeRate(true) + + // Refresh - should not revert - Sets DISABLED + await expect(cTokenNonFiatCollateral.refresh()) + .to.emit(cTokenNonFiatCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) + + expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.DISABLED) + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) + expect(await cTokenNonFiatCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + + // Exchange rate stored is still accessible + expect(await cNonFiatTokenVault.exchangeRateStored()).to.equal(currRate) + + // Price remains the same + await expectPrice(cTokenNonFiatCollateral.address, fp('400'), ORACLE_ERROR, true) + const [newLow, newHigh] = await cTokenNonFiatCollateral.price() + expect(newLow).to.equal(currLow) + expect(newHigh).to.equal(currHigh) + }) + it('Reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { const invalidChainlinkFeed: InvalidMockV3Aggregator = ( await InvalidMockV3AggregatorFactory.deploy(8, bn('1e8')) @@ -1883,6 +1948,38 @@ describe('Collateral contracts', () => { expect(await cTokenSelfReferentialCollateral.status()).to.equal(CollateralStatus.IFFY) }) + it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { + const currRate = await cSelfRefToken.exchangeRateStored() + const [currLow, currHigh] = await cTokenSelfReferentialCollateral.price() + + expect(await cTokenSelfReferentialCollateral.status()).to.equal(CollateralStatus.SOUND) + await expectPrice(cTokenSelfReferentialCollateral.address, fp('0.02'), ORACLE_ERROR, true) + + // Make cToken revert on exchangeRateCurrent() + const cTokenErc20Mock = ( + await ethers.getContractAt('CTokenMock', await cSelfRefToken.underlying()) + ) + await cTokenErc20Mock.setRevertExchangeRate(true) + + // Refresh - should not revert - Sets DISABLED + await expect(cTokenSelfReferentialCollateral.refresh()) + .to.emit(cTokenSelfReferentialCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) + + expect(await cTokenSelfReferentialCollateral.status()).to.equal(CollateralStatus.DISABLED) + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) + expect(await cTokenSelfReferentialCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + + // Exchange rate stored is still accessible + expect(await cSelfRefToken.exchangeRateStored()).to.equal(currRate) + + // Price remains the same + await expectPrice(cTokenSelfReferentialCollateral.address, fp('0.02'), ORACLE_ERROR, true) + const [newLow, newHigh] = await cTokenSelfReferentialCollateral.price() + expect(newLow).to.equal(currLow) + expect(newHigh).to.equal(currHigh) + }) + it('Reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { const invalidChainlinkFeed: InvalidMockV3Aggregator = ( await InvalidMockV3AggregatorFactory.deploy(8, bn('1e8')) diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 75aae7ce72..7efc9ab318 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -844,6 +844,72 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi expect(await newCDaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) }) + it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { + // Note: In this case requires to use a CToken mock to be able to change the rate + const CTokenMockFactory: ContractFactory = await ethers.getContractFactory('CTokenMock') + const symbol = await cDai.symbol() + const cDaiMock: CTokenMock = ( + await CTokenMockFactory.deploy(symbol + ' Token', symbol, dai.address) + ) + + const cDaiVaultFactory: ContractFactory = await ethers.getContractFactory('CTokenWrapper') + const cDaiMockVault = ( + await cDaiVaultFactory.deploy( + cDaiMock.address, + 'cDAI Mock RToken Vault', + 'rv_mock_cDAI', + comptroller.address + ) + ) + + // Redeploy plugin using the new cDai mock + const newCDaiCollateral: CTokenFiatCollateral = await ( + await ethers.getContractFactory('CTokenFiatCollateral') + ).deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await cDaiCollateral.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: cDaiMockVault.address, + maxTradeVolume: await cDaiCollateral.maxTradeVolume(), + oracleTimeout: await cDaiCollateral.oracleTimeout(), + targetName: await cDaiCollateral.targetName(), + defaultThreshold, + delayUntilDefault: await cDaiCollateral.delayUntilDefault(), + }, + REVENUE_HIDING + ) + await newCDaiCollateral.refresh() + + // Check initial state + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.SOUND) + expect(await newCDaiCollateral.whenDefault()).to.equal(MAX_UINT48) + await expectPrice(newCDaiCollateral.address, fp('0.02'), ORACLE_ERROR, true) + const [currLow, currHigh] = await newCDaiCollateral.price() + const currRate = await cDaiMockVault.exchangeRateStored() + + // Make exchangeRateCurrent() revert + await cDaiMock.setRevertExchangeRate(true) + + // Force updates - Should set to DISABLED + await expect(newCDaiCollateral.refresh()) + .to.emit(newCDaiCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) + + expect(await newCDaiCollateral.status()).to.equal(CollateralStatus.DISABLED) + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) + expect(await newCDaiCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + + // Exchange rate stored is still accessible + expect(await cDaiMockVault.exchangeRateStored()).to.equal(currRate) + + // Price remains the same + await expectPrice(newCDaiCollateral.address, fp('0.02'), ORACLE_ERROR, true) + const [newLow, newHigh] = await newCDaiCollateral.price() + expect(newLow).to.equal(currLow) + expect(newHigh).to.equal(currHigh) + }) + it('Reverts if oracle reverts or runs out of gas, maintains status', async () => { const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( 'InvalidMockV3Aggregator' From ea1e6c14cf36cd41f633538d8d52022ba0c2b634 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 30 Aug 2023 11:59:24 -0300 Subject: [PATCH 399/462] Clarify rewards handling on transfer (#920) --- .../plugins/assets/aave/StaticATokenLM.sol | 15 ++++- .../aave/StaticATokenLM.test.ts | 63 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/contracts/plugins/assets/aave/StaticATokenLM.sol b/contracts/plugins/assets/aave/StaticATokenLM.sol index bbecab06dd..56a537a1b6 100644 --- a/contracts/plugins/assets/aave/StaticATokenLM.sol +++ b/contracts/plugins/assets/aave/StaticATokenLM.sol @@ -24,7 +24,17 @@ import { SafeMath } from "@aave/protocol-v2/contracts/dependencies/openzeppelin/ * @title StaticATokenLM * @notice Wrapper token that allows to deposit tokens on the Aave protocol and receive * a token which balance doesn't increase automatically, but uses an ever-increasing exchange rate. - * The token support claiming liquidity mining rewards from the Aave system. + * + * The token supports claiming liquidity mining rewards from the Aave system. However, there might be + * be permanent loss of rewards for the sender of the token when a `transfer` is performed. This is due + * to the fact that only rewards previously collected from the Incentives Controller are processed (and + * assigned to the `sender`) when tokens are transferred. Any rewards pending to be collected are ignored + * on `transfer`, and might be later claimed by the `receiver`. It was designed this way to reduce gas + * costs on every transfer which would probably outweigh any missing/unprocessed/unclaimed rewards. + * It is important to remark that several operations such as `deposit`, `withdraw`, `collectAndUpdateRewards`, + * among others, will update rewards balances correctly, so while it is true that under certain circumstances + * rewards may not be fully accurate, we expect them only to be slightly off. + * * @author Aave * From: https://github.com/aave/protocol-v2/blob/238e5af2a95c3fbb83b0c8f44501ed2541215122/contracts/protocol/tokenization/StaticATokenLM.sol#L255 **/ @@ -366,6 +376,9 @@ contract StaticATokenLM is /** * @notice Updates rewards for senders and receiver in a transfer (not updating rewards for address(0)) + * Only rewards which were previously collected from the Incentives Controller will be updated on + * every transfer. It is designed this way to reduce gas costs on `transfer`, which will likely + * outweigh the pending (uncollected) rewards for the sender under certain circumstances. * @param from The address of the sender of tokens * @param to The address of the receiver of tokens */ diff --git a/test/plugins/individual-collateral/aave/StaticATokenLM.test.ts b/test/plugins/individual-collateral/aave/StaticATokenLM.test.ts index bb78081cd7..c065fe9f72 100644 --- a/test/plugins/individual-collateral/aave/StaticATokenLM.test.ts +++ b/test/plugins/individual-collateral/aave/StaticATokenLM.test.ts @@ -1868,6 +1868,69 @@ describeFork('StaticATokenLM: aToken wrapper with static balances and liquidity expect(totClaimable4).to.be.gt(userBalance4) expect(unclaimedRewards4).to.be.eq(0) }) + + it('Potential loss of rewards on transfer', async () => { + const amountToDeposit = utils.parseEther('5') + + // Just preparation + await waitForTx(await weth.deposit({ value: amountToDeposit.mul(2) })) + await waitForTx( + await weth.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams) + ) + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ) + + const pendingRewards1_u1 = await staticAToken.getClaimableRewards(userSigner._address) + const pendingRewards1_u2 = await staticAToken.getClaimableRewards(user2Signer._address) + + // No rewards assigned yet + expect(pendingRewards1_u1).to.be.eq(0) + expect(pendingRewards1_u2).to.be.eq(0) + + await advanceTime(60 * 60) + + // User1 now has some pending rewards. User2 should have no rewards. + const pendingRewards2_u1 = await staticAToken.getClaimableRewards(userSigner._address) + const pendingRewards2_u2 = await staticAToken.getClaimableRewards(user2Signer._address) + expect(pendingRewards2_u1).to.be.gt(pendingRewards1_u1) + expect(pendingRewards2_u2).to.be.eq(0) + + // Transfer staticATokens to user2 + await waitForTx( + await staticAToken.transfer( + user2Signer._address, + await staticAToken.balanceOf(userSigner._address) + ) + ) + + // User1 now has zero pending rewards, all transferred to User2 + const pendingRewards3_u1 = await staticAToken.getClaimableRewards(userSigner._address) + const pendingRewards3_u2 = await staticAToken.getClaimableRewards(user2Signer._address) + + expect(pendingRewards3_u1).to.be.eq(0) + expect(pendingRewards3_u2).to.be.gt(pendingRewards2_u1) + + // User2 can keep the rewards if for example `collectAndUpdateRewards` is called + await staticAToken.collectAndUpdateRewards() + + // If transfer is performed to User1, rewards stay with User2 + await waitForTx( + await staticAToken + .connect(user2Signer) + .transfer(userSigner._address, await staticAToken.balanceOf(user2Signer._address)) + ) + + // User1 gets only some small rewards, but User2 keeps the rewards + const pendingRewards4_u1 = await staticAToken.getClaimableRewards(userSigner._address) + const pendingRewards4_u2 = await staticAToken.getClaimableRewards(user2Signer._address) + + expect(pendingRewards4_u1).to.be.gt(0) + expect(pendingRewards4_u1).to.be.lt(pendingRewards4_u2) + expect(pendingRewards4_u2).to.be.gt(pendingRewards3_u2) + }) }) it('Multiple users deposit WETH on stataWETH, wait 1 hour, update rewards, one user transfer, then claim and update rewards.', async () => { From b4200598b43445e3a7961f8e22e625038046158c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 31 Aug 2023 10:06:20 -0400 Subject: [PATCH 400/462] 3.1.0 c4 20 (#922) --- CHANGELOG.md | 2 + contracts/interfaces/IBroker.sol | 4 +- contracts/p0/Broker.sol | 24 +++---- contracts/p1/Broker.sol | 12 ++-- contracts/plugins/mocks/InvalidBrokerMock.sol | 4 +- test/Broker.test.ts | 72 ++++++++++--------- test/Revenues.test.ts | 19 ++++- .../aave/ATokenFiatCollateral.test.ts | 7 +- .../compoundv2/CTokenFiatCollateral.test.ts | 7 +- 9 files changed, 82 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4c5452c71..648ca86e2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Then call `Broker.cacheComponents()`. - `Broker` [+1 slot] - Disallow starting dutch trades with non-RTokenAsset assets when `lastSave() != block.timestamp` - Only permit BackingManager-started dutch auctions to report violations and disable trading + - Remove `setBatchTradeDisabled(bool)` and replace with `enableBatchTrade()` + - Remove `setDutchTradeDisabled(IERC20 erc20, bool)` and replace with `enableDutchTrade(IERC20 erc20)` ## Plugins diff --git a/contracts/interfaces/IBroker.sol b/contracts/interfaces/IBroker.sol index cecaf8d7b0..fcaeac2c10 100644 --- a/contracts/interfaces/IBroker.sol +++ b/contracts/interfaces/IBroker.sol @@ -89,9 +89,9 @@ interface TestIBroker is IBroker { function setDutchAuctionLength(uint48 newAuctionLength) external; - function setBatchTradeDisabled(bool disabled) external; + function enableBatchTrade() external; - function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external; + function enableDutchTrade(IERC20Metadata erc20) external; // only present on pre-3.0.0 Brokers; used by EasyAuction regression test function disabled() external view returns (bool); diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 9527a213dc..51fcf6ac10 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -190,6 +190,18 @@ contract BrokerP0 is ComponentP0, IBroker { dutchAuctionLength = newAuctionLength; } + /// @custom:governance + function enableBatchTrade() external governance { + emit BatchTradeDisabledSet(batchTradeDisabled, false); + batchTradeDisabled = false; + } + + /// @custom:governance + function enableDutchTrade(IERC20Metadata erc20) external governance { + emit DutchTradeDisabledSet(erc20, dutchTradeDisabled[erc20], false); + dutchTradeDisabled[erc20] = false; + } + // === Private === function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { @@ -245,18 +257,6 @@ contract BrokerP0 is ComponentP0, IBroker { return trade; } - /// @custom:governance - function setBatchTradeDisabled(bool disabled) external governance { - emit BatchTradeDisabledSet(batchTradeDisabled, disabled); - batchTradeDisabled = disabled; - } - - /// @custom:governance - function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external governance { - emit DutchTradeDisabledSet(erc20, dutchTradeDisabled[erc20], disabled); - dutchTradeDisabled[erc20] = disabled; - } - /// @return true if the price is current, or it's the RTokenAsset function priceIsCurrent(IAsset asset) private view returns (bool) { return diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index a8fd023aee..c5cdb649e8 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -216,15 +216,15 @@ contract BrokerP1 is ComponentP1, IBroker { } /// @custom:governance - function setBatchTradeDisabled(bool disabled) external governance { - emit BatchTradeDisabledSet(batchTradeDisabled, disabled); - batchTradeDisabled = disabled; + function enableBatchTrade() external governance { + emit BatchTradeDisabledSet(batchTradeDisabled, false); + batchTradeDisabled = false; } /// @custom:governance - function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external governance { - emit DutchTradeDisabledSet(erc20, dutchTradeDisabled[erc20], disabled); - dutchTradeDisabled[erc20] = disabled; + function enableDutchTrade(IERC20Metadata erc20) external governance { + emit DutchTradeDisabledSet(erc20, dutchTradeDisabled[erc20], false); + dutchTradeDisabled[erc20] = false; } // === Private === diff --git a/contracts/plugins/mocks/InvalidBrokerMock.sol b/contracts/plugins/mocks/InvalidBrokerMock.sol index 7b19993c94..191df5136f 100644 --- a/contracts/plugins/mocks/InvalidBrokerMock.sol +++ b/contracts/plugins/mocks/InvalidBrokerMock.sol @@ -64,9 +64,9 @@ contract InvalidBrokerMock is ComponentP0, IBroker { /// Dummy implementation /* solhint-disable no-empty-blocks */ - function setBatchTradeDisabled(bool disabled) external governance {} + function enableBatchTrade() external governance {} /// Dummy implementation /* solhint-disable no-empty-blocks */ - function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external governance {} + function enableDutchTrade(IERC20Metadata erc20) external governance {} } diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 5db42e6e2b..46c5d69280 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -132,6 +132,30 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { prices = { sellLow: fp('1'), sellHigh: fp('1'), buyLow: fp('1'), buyHigh: fp('1') } }) + const disableBatchTrade = async () => { + if (IMPLEMENTATION == Implementation.P1) { + const slot = await getStorageAt(broker.address, 205) + await setStorageAt( + broker.address, + 205, + slot.replace(slot.slice(2, 14), '1'.padStart(12, '0')) + ) + } else { + const slot = await getStorageAt(broker.address, 56) + await setStorageAt(broker.address, 56, slot.replace(slot.slice(2, 42), '1'.padStart(40, '0'))) + } + expect(await broker.batchTradeDisabled()).to.equal(true) + } + + const disableDutchTrade = async (erc20: string) => { + const mappingSlot = IMPLEMENTATION == Implementation.P1 ? bn('208') : bn('57') + const p = mappingSlot.toHexString().slice(2).padStart(64, '0') + const key = erc20.slice(2).padStart(64, '0') + const slot = ethers.utils.keccak256('0x' + key + p) + await setStorageAt(broker.address, slot, '0x' + '1'.padStart(64, '0')) + expect(await broker.dutchTradeDisabled(erc20)).to.equal(true) + } + describe('Deployment', () => { it('Should setup Broker correctly', async () => { expect(await broker.gnosis()).to.equal(gnosis.address) @@ -378,28 +402,21 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) // If not owner cannot update - await expect(broker.connect(other).setBatchTradeDisabled(true)).to.be.revertedWith( + await expect(broker.connect(other).enableBatchTrade()).to.be.revertedWith('governance only') + await expect(broker.connect(other).enableDutchTrade(token0.address)).to.be.revertedWith( 'governance only' ) - await expect( - broker.connect(other).setDutchTradeDisabled(token0.address, true) - ).to.be.revertedWith('governance only') // Check value did not change expect(await broker.batchTradeDisabled()).to.equal(false) expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) - // Update batchTradeDisabled with owner - await expect(broker.connect(owner).setBatchTradeDisabled(true)) - .to.emit(broker, 'BatchTradeDisabledSet') - .withArgs(false, true) - - // Check value was updated + // Disable batch trade manually + await disableBatchTrade() expect(await broker.batchTradeDisabled()).to.equal(true) - expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) - // Update back to false - await expect(broker.connect(owner).setBatchTradeDisabled(false)) + // Enable batch trade with owner + await expect(broker.connect(owner).enableBatchTrade()) .to.emit(broker, 'BatchTradeDisabledSet') .withArgs(true, false) @@ -407,24 +424,19 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await broker.batchTradeDisabled()).to.equal(false) expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) - // Update dutchTradeDisabled with owner - await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, true)) - .to.emit(broker, 'DutchTradeDisabledSet') - .withArgs(token0.address, false, true) - - // Check value was updated - expect(await broker.batchTradeDisabled()).to.equal(false) + // Disable dutch trade manually + await disableDutchTrade(token0.address) expect(await broker.dutchTradeDisabled(token0.address)).to.equal(true) - expect(await broker.dutchTradeDisabled(token1.address)).to.equal(false) - // Update back to false - await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, false)) + // Enable dutch trade with owner + await expect(broker.connect(owner).enableDutchTrade(token0.address)) .to.emit(broker, 'DutchTradeDisabledSet') .withArgs(token0.address, true, false) // Check value was updated expect(await broker.batchTradeDisabled()).to.equal(false) expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) expect(await broker.dutchTradeDisabled(token1.address)).to.equal(false) }) }) @@ -432,9 +444,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { describe('Trade Management', () => { it('Should not allow to open Batch trade if Disabled', async () => { // Disable Broker Batch Auctions - await expect(broker.connect(owner).setBatchTradeDisabled(true)) - .to.emit(broker, 'BatchTradeDisabledSet') - .withArgs(false, true) + await disableBatchTrade() const tradeRequest: ITradeRequest = { sell: collateral0.address, @@ -469,9 +479,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { .callStatic.openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) // Disable Broker Dutch Auctions for token0 - await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, true)) - .to.emit(broker, 'DutchTradeDisabledSet') - .withArgs(token0.address, false, true) + await disableDutchTrade(token0.address) // Dutch Auction openTrade should fail now await expect( @@ -479,7 +487,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ).to.be.revertedWith('dutch auctions disabled for token pair') // Re-enable Dutch Auctions for token0 - await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, false)) + await expect(broker.connect(owner).enableDutchTrade(token0.address)) .to.emit(broker, 'DutchTradeDisabledSet') .withArgs(token0.address, true, false) @@ -490,9 +498,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { .callStatic.openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) // Disable Broker Dutch Auctions for token1 - await expect(broker.connect(owner).setDutchTradeDisabled(token1.address, true)) - .to.emit(broker, 'DutchTradeDisabledSet') - .withArgs(token1.address, false, true) + await disableDutchTrade(token1.address) // Dutch Auction openTrade should fail now await expect( diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 86ba41c71c..3573d8310a 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -1,4 +1,4 @@ -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { loadFixture, getStorageAt, setStorageAt } from '@nomicfoundation/hardhat-network-helpers' import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' @@ -142,6 +142,21 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { return divCeil(divCeil(product, highBuyPrice), fp('1')) // (c) } + const disableBatchTrade = async () => { + if (IMPLEMENTATION == Implementation.P1) { + const slot = await getStorageAt(broker.address, 205) + await setStorageAt( + broker.address, + 205, + slot.replace(slot.slice(2, 14), '1'.padStart(12, '0')) + ) + } else { + const slot = await getStorageAt(broker.address, 56) + await setStorageAt(broker.address, 56, slot.replace(slot.slice(2, 42), '1'.padStart(40, '0'))) + } + expect(await broker.batchTradeDisabled()).to.equal(true) + } + beforeEach(async () => { ;[owner, addr1, addr2, other] = await ethers.getSigners() @@ -2484,7 +2499,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) // Disable broker - await broker.connect(owner).setBatchTradeDisabled(true) + await disableBatchTrade() // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) const sellAmt: BigNumber = rewardAmountAAVE.mul(60).div(100) // due to f = 60% diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index 11497aa3fc..8eb81b79df 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -31,12 +31,7 @@ import { import { expectEvents, expectInIndirectReceipt } from '../../../../common/events' import { bn, fp } from '../../../../common/numbers' import { whileImpersonating } from '../../../utils/impersonation' -import { - expectPrice, - expectRTokenPrice, - expectUnpriced, - setOraclePrice, -} from '../../../utils/oracles' +import { expectPrice, expectRTokenPrice, setOraclePrice } from '../../../utils/oracles' import { advanceBlocks, advanceTime, diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 53025c4ef8..6bc97b5a2d 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -31,12 +31,7 @@ import { import { expectEvents, expectInIndirectReceipt } from '../../../../common/events' import { bn, fp, toBNDecimals } from '../../../../common/numbers' import { whileImpersonating } from '../../../utils/impersonation' -import { - expectPrice, - expectRTokenPrice, - expectUnpriced, - setOraclePrice, -} from '../../../utils/oracles' +import { expectPrice, expectRTokenPrice, setOraclePrice } from '../../../utils/oracles' import { advanceBlocks, advanceTime, From b0849fd470eedd1fa406e51ac5004e3f06132880 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 31 Aug 2023 12:10:32 -0400 Subject: [PATCH 401/462] C4 +inf (#917) Co-authored-by: Akshat Mittal --- contracts/plugins/assets/OracleLib.sol | 3 + contracts/plugins/assets/RTokenAsset.sol | 31 ++++---- .../curve/CurveStableMetapoolCollateral.sol | 22 ++---- contracts/plugins/assets/curve/PoolTokens.sol | 2 + contracts/plugins/mocks/AssetMock.sol | 56 +++++++++++++ .../plugins/mocks/BadCollateralPlugin.sol | 1 + .../plugins/mocks/InvalidFiatCollateral.sol | 8 ++ .../mocks/InvalidRefPerTokCollateral.sol | 5 +- contracts/plugins/trading/DutchTrade.sol | 4 +- test/Facade.test.ts | 12 +-- test/Main.test.ts | 6 +- test/Recollateralization.test.ts | 23 +++--- test/Revenues.test.ts | 20 +++-- test/integration/AssetPlugins.test.ts | 32 ++++---- test/integration/EasyAuction.test.ts | 22 ++---- test/plugins/Asset.test.ts | 78 +++++++++++++------ test/plugins/Collateral.test.ts | 57 ++++++++------ .../aave/ATokenFiatCollateral.test.ts | 4 +- .../individual-collateral/collateralTests.ts | 25 +----- .../compoundv2/CTokenFiatCollateral.test.ts | 4 +- .../curve/collateralTests.ts | 59 +++++++------- .../CrvStableRTokenMetapoolTestSuite.test.ts | 49 +++++++++++- .../CvxStableRTokenMetapoolTestSuite.test.ts | 49 +++++++++++- 23 files changed, 365 insertions(+), 207 deletions(-) create mode 100644 contracts/plugins/mocks/AssetMock.sol diff --git a/contracts/plugins/assets/OracleLib.sol b/contracts/plugins/assets/OracleLib.sol index 79b268ed46..ff9bd0ef59 100644 --- a/contracts/plugins/assets/OracleLib.sol +++ b/contracts/plugins/assets/OracleLib.sol @@ -5,6 +5,7 @@ import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "../../libraries/Fixed.sol"; error StalePrice(); +error ZeroPrice(); interface EACAggregatorProxy { function aggregator() external view returns (address); @@ -35,6 +36,8 @@ library OracleLib { 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) { diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index fd8c78fa24..eae0c8b2b0 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -50,8 +50,11 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { /// 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 - function tryPrice() external view virtual returns (uint192 low, uint192 high) { - (uint192 lowBUPrice, uint192 highBUPrice) = basketHandler.price(); // {UoA/BU} + function tryPrice(bool useLotPrice) external view virtual returns (uint192 low, uint192 high) { + (uint192 lowBUPrice, uint192 highBUPrice) = useLotPrice + ? basketHandler.lotPrice() + : basketHandler.price(); // {UoA/BU} + require(lowBUPrice != 0 && highBUPrice != FIX_MAX, "invalid price"); assert(lowBUPrice <= highBUPrice); // not obviously true just by inspection // Here we take advantage of the fact that we know RToken has 18 decimals @@ -84,7 +87,7 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { /// @return {UoA/tok} The lower end of the price estimate /// @return {UoA/tok} The upper end of the price estimate function price() public view virtual returns (uint192, uint192) { - try this.tryPrice() returns (uint192 low, uint192 high) { + try this.tryPrice(false) returns (uint192 low, uint192 high) { return (low, high); } catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data @@ -98,20 +101,14 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { /// @return lotLow {UoA/tok} The lower end of the lot price estimate /// @return lotHigh {UoA/tok} The upper end of the lot price estimate function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh) { - (uint192 buLow, uint192 buHigh) = basketHandler.lotPrice(); // {UoA/BU} - - // Here we take advantage of the fact that we know RToken has 18 decimals - // to convert between uint256 an uint192. Fits due to assumed max totalSupply. - uint192 supply = _safeWrap(IRToken(address(erc20)).totalSupply()); - - if (supply == 0) return (buLow, buHigh); - - BasketRange memory range = basketRange(); // {BU} - - // {UoA/tok} = {BU} * {UoA/BU} / {tok} - lotLow = range.bottom.mulDiv(buLow, supply, FLOOR); - lotHigh = range.top.mulDiv(buHigh, supply, CEIL); - assert(lotLow <= lotHigh); // not obviously true + try this.tryPrice(true) returns (uint192 low, uint192 high) { + lotLow = low; + lotHigh = high; + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + return (0, 0); + } } /// @return {tok} The balance of the ERC20 in whole tokens diff --git a/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol b/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol index 874b48b94e..7fd4fe005b 100644 --- a/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol @@ -81,12 +81,8 @@ contract CurveStableMetapoolCollateral is CurveStableCollateral { ) { // {UoA/pairedTok} - uint192 lowPaired; - uint192 highPaired = FIX_MAX; - try this.tryPairedPrice() returns (uint192 lowPaired_, uint192 highPaired_) { - lowPaired = lowPaired_; - highPaired = highPaired_; - } catch {} + (uint192 lowPaired, uint192 highPaired) = tryPairedPrice(); + require(lowPaired != 0 && highPaired != FIX_MAX, "invalid price"); // {UoA} (uint192 aumLow, uint192 aumHigh) = _metapoolBalancesValue(lowPaired, highPaired); @@ -124,8 +120,8 @@ contract CurveStableMetapoolCollateral is CurveStableCollateral { // Check for defaults outside the pool function _anyDepeggedOutsidePool() internal view virtual override returns (bool) { try this.tryPairedPrice() returns (uint192 low, uint192 high) { - // {UoA/tok} = {UoA/tok} + {UoA/tok} - uint192 mid = (low + high) / 2; + // D18{UoA/tok} = D18{UoA/tok} + D18{UoA/tok} + uint256 mid = (low + uint256(high)) / 2; // If the price is below the default-threshold price, default eventually // uint192(+/-) is the same as Fix.plus/minus @@ -140,6 +136,7 @@ contract CurveStableMetapoolCollateral is CurveStableCollateral { return false; } + /// @dev Warning: Can revert /// @param lowPaired {UoA/pairedTok} /// @param highPaired {UoA/pairedTok} /// @return aumLow {UoA} @@ -172,13 +169,6 @@ contract CurveStableMetapoolCollateral is CurveStableCollateral { // Add-in contribution from pairedTok // {UoA} = {UoA} + {UoA/pairedTok} * {pairedTok} aumLow += lowPaired.mul(pairedBal, FLOOR); - - // Add-in high part carefully - uint192 toAdd = highPaired.safeMul(pairedBal, CEIL); - if (aumHigh + uint256(toAdd) >= FIX_MAX) { - aumHigh = FIX_MAX; - } else { - aumHigh += toAdd; - } + aumHigh += highPaired.mul(pairedBal, CEIL); } } diff --git a/contracts/plugins/assets/curve/PoolTokens.sol b/contracts/plugins/assets/curve/PoolTokens.sol index bc5367dcdf..1af669572b 100644 --- a/contracts/plugins/assets/curve/PoolTokens.sol +++ b/contracts/plugins/assets/curve/PoolTokens.sol @@ -229,6 +229,7 @@ contract PoolTokens { } } + /// @dev Warning: Can revert /// @param index The index of the token: 0, 1, 2, or 3 /// @return low {UoA/ref_index} /// @return high {UoA/ref_index} @@ -278,6 +279,7 @@ contract PoolTokens { // === Internal === + /// @dev Warning: Can revert /// @return low {UoA} /// @return high {UoA} function totalBalancesValue() internal view returns (uint192 low, uint192 high) { diff --git a/contracts/plugins/mocks/AssetMock.sol b/contracts/plugins/mocks/AssetMock.sol new file mode 100644 index 0000000000..b6abe6fffb --- /dev/null +++ b/contracts/plugins/mocks/AssetMock.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../assets/Asset.sol"; + +contract AssetMock is Asset { + uint192 private lowPrice; + uint192 private highPrice; + + /// @param priceTimeout_ {s} The number of seconds over which savedHighPrice decays to 0 + /// @param chainlinkFeed_ Feed units: {UoA/tok} + /// @param oracleError_ {1} The % the oracle feed can be off by + /// @param maxTradeVolume_ {UoA} The max trade volume, in UoA + /// @param oracleTimeout_ {s} The number of seconds until a oracle value becomes invalid + /// @dev oracleTimeout_ is also used as the timeout value in lotPrice(), should be highest of + /// all assets' oracleTimeout in a collateral if there are multiple oracles + constructor( + uint48 priceTimeout_, + AggregatorV3Interface chainlinkFeed_, + uint192 oracleError_, + IERC20Metadata erc20_, + uint192 maxTradeVolume_, + uint48 oracleTimeout_ + ) Asset(priceTimeout_, chainlinkFeed_, oracleError_, erc20_, maxTradeVolume_, oracleTimeout_) {} + + /// Can revert, used by other contract functions in order to catch errors + /// Should not return FIX_MAX for low + /// Should only return FIX_MAX for high if low is 0 + /// @dev The third (unused) variable is only here for compatibility with Collateral + /// @return low {UoA/tok} The low price estimate + /// @return high {UoA/tok} The high price estimate + function tryPrice() + external + view + virtual + override + returns ( + uint192 low, + uint192 high, + uint192 + ) + { + return (lowPrice, highPrice, 0); + } + + /// Should not revert + /// Refresh saved prices + function refresh() public virtual override { + // pass + } + + function setPrice(uint192 low, uint192 high) external { + lowPrice = low; + highPrice = high; + } +} diff --git a/contracts/plugins/mocks/BadCollateralPlugin.sol b/contracts/plugins/mocks/BadCollateralPlugin.sol index 5001542aaf..765d95892a 100644 --- a/contracts/plugins/mocks/BadCollateralPlugin.sol +++ b/contracts/plugins/mocks/BadCollateralPlugin.sol @@ -57,6 +57,7 @@ contract BadCollateralPlugin is ATokenFiatCollateral { savedHighPrice = high; lastSave = uint48(block.timestamp); } + // If the price is below the default-threshold price, default eventually // uint192(+/-) is the same as Fix.plus/minus if (pegPrice < pegBottom || pegPrice > pegTop || low == 0) { diff --git a/contracts/plugins/mocks/InvalidFiatCollateral.sol b/contracts/plugins/mocks/InvalidFiatCollateral.sol index 343737bd38..89787b0290 100644 --- a/contracts/plugins/mocks/InvalidFiatCollateral.sol +++ b/contracts/plugins/mocks/InvalidFiatCollateral.sol @@ -8,6 +8,8 @@ contract InvalidFiatCollateral is FiatCollateral { bool public simplyRevert; + bool public unpriced; + /// @param config.chainlinkFeed Feed units: {UoA/ref} constructor(CollateralConfig memory config) FiatCollateral(config) {} @@ -15,6 +17,8 @@ contract InvalidFiatCollateral is FiatCollateral { function price() public view virtual override(Asset, IAsset) returns (uint192, uint192) { if (simplyRevert) { revert("errormsg"); // Revert with no reason + } else if (unpriced) { + return (0, FIX_MAX); } else { // Run out of gas this.infiniteLoop{ gas: 10 }(); @@ -28,6 +32,10 @@ contract InvalidFiatCollateral is FiatCollateral { simplyRevert = on; } + function setUnpriced(bool on) external { + unpriced = on; + } + function infiniteLoop() external pure { uint256 i = 0; uint256[1] memory array; diff --git a/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol b/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol index aca54d543c..a8a644df53 100644 --- a/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol +++ b/contracts/plugins/mocks/InvalidRefPerTokCollateral.sol @@ -37,14 +37,11 @@ contract InvalidRefPerTokCollateralMock is AppreciatingFiatCollateral { // {UoA/tok}, {UoA/tok}, {target/ref} // (0, 0) is a valid price; (0, FIX_MAX) is unpriced - // Save prices if priced + // Save prices if high price is finite if (high < FIX_MAX) { savedLowPrice = low; savedHighPrice = high; lastSave = uint48(block.timestamp); - } else { - // must be unpriced - assert(low == 0); } } catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index 002af6b057..ba647e95a8 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -155,8 +155,8 @@ contract DutchTrade is ITrade { ); // misuse by caller // Only start dutch auctions under well-defined prices - require(prices.sellLow > 0 && prices.sellHigh < FIX_MAX / 1000, "bad sell pricing"); - require(prices.buyLow > 0 && prices.buyHigh < FIX_MAX / 1000, "bad buy pricing"); + require(prices.sellLow != 0 && prices.sellHigh < FIX_MAX / 1000, "bad sell pricing"); + require(prices.buyLow != 0 && prices.buyHigh < FIX_MAX / 1000, "bad buy pricing"); broker = IBroker(msg.sender); origin = origin_; diff --git a/test/Facade.test.ts b/test/Facade.test.ts index fc1a7121ff..ecbf420a25 100644 --- a/test/Facade.test.ts +++ b/test/Facade.test.ts @@ -552,6 +552,11 @@ describe('FacadeRead + FacadeAct contracts', () => { it('Should return revenue + chain into FacadeAct.runRevenueAuctions', async () => { const traders = [rTokenTrader, rsrTrader] + const initialPrice = await usdcAsset.price() + + // Set lotLow to 0 == revenueOverview() should not revert + await setOraclePrice(usdcAsset.address, bn('0')) + await usdcAsset.refresh() for (let traderIndex = 0; traderIndex < traders.length; traderIndex++) { const trader = traders[traderIndex] @@ -560,11 +565,8 @@ describe('FacadeRead + FacadeAct contracts', () => { const tokenSurplus = bn('0.5e18') await token.connect(addr1).transfer(trader.address, tokenSurplus) - // Set lotLow to 0 == revenueOverview() should not revert - await setOraclePrice(usdcAsset.address, bn('0')) - await usdcAsset.refresh() const [lotLow] = await usdcAsset.lotPrice() - expect(lotLow).to.equal(0) + expect(lotLow).to.equal(initialPrice[0]) // revenue let [erc20s, canStart, surpluses, minTradeAmounts] = @@ -582,7 +584,7 @@ describe('FacadeRead + FacadeAct contracts', () => { expect(surpluses[i]).to.equal(0) } const asset = await ethers.getContractAt('IAsset', await assetRegistry.toAsset(erc20s[i])) - const [low] = await asset.price() + const [low] = await asset.lotPrice() expect(minTradeAmounts[i]).to.equal( low.gt(0) ? minTradeVolume.mul(bn('10').pow(await asset.erc20Decimals())).div(low) : 0 ) // 1% oracleError diff --git a/test/Main.test.ts b/test/Main.test.ts index b8613d40a0..788d4eba4b 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -2814,8 +2814,10 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await setOraclePrice(newColl2.address, bn('0')) // Check status and price again - expect(await basketHandler.status()).to.equal(CollateralStatus.DISABLED) - await expectPrice(basketHandler.address, fp('0.25'), ORACLE_ERROR, true) + const p = await basketHandler.price() + expect(p[0]).to.be.closeTo(fp('1').div(4), fp('1').div(4).div(100)) // within 1% + expect(p[0]).to.be.lt(fp('1').div(4)) + expect(p[1]).to.equal(MAX_UINT192) }) it('Should handle a collateral (price * quantity) overflow', async () => { diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 5b7718a225..e806d4b316 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -3048,7 +3048,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) }) - it('Should not sell worthless asset when doing recollateralization- Use RSR directly for remainder', async () => { + it('Should sell worthless asset when doing recollateralization - Use RSR directly for remainder', async () => { // Set prime basket await basketHandler.connect(owner).setPrimeBasket([token1.address], [fp('1')]) @@ -3129,6 +3129,8 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Send the excess revenue tokens to backing manager - should try to use it instead of RSR // But we set price = $0, so it wont be sold -Will use RSR for remainder await aaveToken.connect(owner).mint(backingManager.address, buyAmtBidRemToken.mul(2)) + + // Make aaveToken worthless await setOraclePrice(aaveAsset.address, bn('0')) await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ { @@ -3148,7 +3150,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { name: 'TradeStarted', args: [ anyValue, - rsr.address, + aaveToken.address, token1.address, anyValue, toBNDecimals(buyAmtBidRemToken, 6).add(1), @@ -3161,13 +3163,13 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // RSR Token -> Token1 Auction await expectTrade(backingManager, { - sell: rsr.address, + sell: aaveToken.address, buy: token1.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), externalId: bn('1'), }) - const t = await getTrade(backingManager, rsr.address) + const t = await getTrade(backingManager, aaveToken.address) const sellAmtRemToken = await t.initBal() expect(toBNDecimals(buyAmtBidRemToken, 6).add(1)).to.equal( toBNDecimals(await toMinBuyAmt(sellAmtRemToken, fp('1'), fp('1')), 6).add(1) @@ -3180,15 +3182,10 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await token1.balanceOf(backingManager.address)).to.equal( toBNDecimals(minBuyAmt, 6).add(1) ) - // Aave token balance remains unchanged - expect(await aaveToken.balanceOf(backingManager.address)).to.equal( - buyAmtBidRemToken.mul(2) - ) - expect(await rToken.totalSupply()).to.equal(issueAmount) - // Check Gnosis- using RSR - expect(await rsr.balanceOf(gnosis.address)).to.equal(sellAmtRemToken) + // Check Gnosis - using AAVE + expect(await aaveToken.balanceOf(gnosis.address)).to.equal(sellAmtRemToken) // Perform Mock Bids for the new Token (addr1 has balance) // Cover buyAmtBidRevToken which is all the amount required @@ -3209,7 +3206,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { name: 'TradeSettled', args: [ anyValue, - rsr.address, + aaveToken.address, token1.address, sellAmtRemToken, toBNDecimals(buyAmtBidRemToken, 6).add(1), @@ -3237,7 +3234,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) // Stakes used in this case - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount.sub(sellAmtRemToken)) + expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) }) }) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 9581b78271..e0f16dadb8 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -1029,6 +1029,12 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { const p1RevenueTrader = await ethers.getContractAt('RevenueTraderP1', rTokenTrader.address) await setOraclePrice(collateral0.address, bn(0)) await collateral0.refresh() + await advanceTime(PRICE_TIMEOUT.add(ORACLE_TIMEOUT).toString()) + await setOraclePrice(collateral1.address, bn('1e8')) + + const p = await collateral0.lotPrice() + expect(p[0]).to.equal(0) + expect(p[1]).to.equal(0) await expect( p1RevenueTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) ).to.revertedWith('bad sell pricing') @@ -2035,7 +2041,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(furnace.address)).to.equal(0) }) - it('Should not trade if price for buy token = 0', async () => { + it('Should trade even if price for buy token = 0', async () => { // Set AAVE tokens as reward rewardAmountAAVE = bn('1e18') @@ -2078,13 +2084,13 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Set RSR price to 0 await setOraclePrice(rsrAsset.address, bn('0')) - // Should revert - await expect( - rsrTrader.manageTokens([aaveToken.address], [TradeKind.BATCH_AUCTION]) - ).to.be.revertedWith('buy asset price unknown') + // Should not revert + await expect(rsrTrader.manageTokens([aaveToken.address], [TradeKind.BATCH_AUCTION])).to.not + .be.reverted - // Funds still in Trader - expect(await aaveToken.balanceOf(rsrTrader.address)).to.equal(expectedToTrader) + // Trade open for aaveToken + const t = await getTrade(rsrTrader, aaveToken.address) + expect(t.address).to.not.equal(ZERO_ADDRESS) }) it('Should report violation when Batch Auction behaves incorrectly', async () => { diff --git a/test/integration/AssetPlugins.test.ts b/test/integration/AssetPlugins.test.ts index 672f5566de..7e067fa875 100644 --- a/test/integration/AssetPlugins.test.ts +++ b/test/integration/AssetPlugins.test.ts @@ -1120,8 +1120,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await setOraclePrice(zeroPriceAsset.address, bn(0)) - // Zero price - await expectPrice(zeroPriceAsset.address, bn('0'), bn('0'), false) + // Unpriced + await expectUnpriced(zeroPriceAsset.address) }) it('Should handle invalid/stale Price - Collateral - Fiat', async () => { @@ -1192,8 +1192,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await setOraclePrice(zeroFiatCollateral.address, bn(0)) - // With zero price - await expectPrice(zeroFiatCollateral.address, bn('0'), bn('0'), false) + // Unpriced + await expectUnpriced(zeroFiatCollateral.address) // Refresh should mark status IFFY await zeroFiatCollateral.refresh() @@ -1269,8 +1269,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await setOraclePrice(zeropriceCtokenCollateral.address, bn(0)) - // With zero price - await expectPrice(zeropriceCtokenCollateral.address, bn('0'), bn('0'), false) + // Unpriced + await expectUnpriced(zeropriceCtokenCollateral.address) // Refresh should mark status IFFY await zeropriceCtokenCollateral.refresh() @@ -1346,8 +1346,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, await setOraclePrice(zeroPriceAtokenCollateral.address, bn(0)) - // With zero price - await expectPrice(zeroPriceAtokenCollateral.address, bn('0'), bn('0'), false) + // Unpriced + await expectUnpriced(zeroPriceAtokenCollateral.address) // Refresh should mark status IFFY await zeroPriceAtokenCollateral.refresh() @@ -1418,8 +1418,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) await v3Aggregator.updateAnswer(bn(0)) - // Does not revert with zero price - await expectPrice(zeroPriceNonFiatCollateral.address, bn('0'), bn('0'), false) + // Unpriced + await expectUnpriced(zeroPriceNonFiatCollateral.address) // Refresh should mark status IFFY await zeroPriceNonFiatCollateral.refresh() @@ -1497,8 +1497,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) await v3Aggregator.updateAnswer(bn(0)) - // With zero price - await expectPrice(zeropriceCtokenNonFiatCollateral.address, bn('0'), bn('0'), false) + // Unpriced + await expectUnpriced(zeropriceCtokenNonFiatCollateral.address) // Refresh should mark status IFFY await zeropriceCtokenNonFiatCollateral.refresh() @@ -1558,8 +1558,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Set price = 0 await setOraclePrice(zeroPriceSelfReferentialCollateral.address, bn(0)) - // Does not revert with zero price - await expectPrice(zeroPriceSelfReferentialCollateral.address, bn('0'), bn('0'), false) + // Unpriced + await expectUnpriced(zeroPriceSelfReferentialCollateral.address) // Refresh should mark status IFFY await zeroPriceSelfReferentialCollateral.refresh() @@ -1634,8 +1634,8 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, // Set price = 0 await setOraclePrice(zeroPriceCtokenSelfReferentialCollateral.address, bn(0)) - // With zero price - await expectPrice(zeroPriceCtokenSelfReferentialCollateral.address, bn('0'), bn('0'), false) + // Unpriced + await expectUnpriced(zeroPriceCtokenSelfReferentialCollateral.address) // Refresh should mark status IFFY await zeroPriceCtokenSelfReferentialCollateral.refresh() diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 16798523be..3d63f9e5b8 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -10,6 +10,8 @@ import { Implementation, SLOW, ORACLE_ERROR, + ORACLE_TIMEOUT, + PRICE_TIMEOUT, defaultFixture, // intentional } from '../fixtures' import { bn, fp, shortString, divCeil } from '../../common/numbers' @@ -549,7 +551,11 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function }) it('should be able to scoop entire auction cheaply when minBuyAmount = 0', async () => { - await setOraclePrice(collateral0.address, bn('0')) // make collateral0 worthless + // Make collateral0 lotPrice (0, 0) + await setOraclePrice(collateral0.address, bn('0')) + await collateral0.refresh() + await advanceTime(PRICE_TIMEOUT.add(ORACLE_TIMEOUT).toString()) + await setOraclePrice(await assetRegistry.toAsset(rsr.address), bn('1e8')) // force a revenue dust auction await expect(rsrTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])).to.emit( @@ -576,20 +582,6 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function await rsr.mint(addr1.address, issueAmount) await rsr.connect(addr1).approve(easyAuction.address, issueAmount) - // Bid with a too-small order and fail. - const lowBidAmt = 2 - await expect( - easyAuction - .connect(addr1) - .placeSellOrders( - auctionId, - [issueAmount], - [lowBidAmt], - [QUEUE_START], - ethers.constants.HashZero - ) - ).to.be.revertedWith('order too small') - // Bid with a nontheless pretty small order, and succeed. const bidAmt = (await trade.DEFAULT_MIN_BID()).add(1) await easyAuction diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index 3d9661263f..0a534ae9cc 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -250,45 +250,44 @@ describe('Assets contracts #fast', () => { ) }) - it('Should return (0, 0) if price is zero', async () => { + it('Should become unpriced if price is zero', async () => { + const compInitPrice = await compAsset.price() + const aaveInitPrice = await aaveAsset.price() + const rsrInitPrice = await rsrAsset.price() + const rTokenInitPrice = await rTokenAsset.price() + // Update values in Oracles to 0 await setOraclePrice(compAsset.address, bn('0')) await setOraclePrice(aaveAsset.address, bn('0')) await setOraclePrice(rsrAsset.address, bn('0')) - // New prices should be (0, 0) - await expectPrice(rsrAsset.address, bn('0'), bn('0'), false) - await expectPrice(compAsset.address, bn('0'), bn('0'), false) - await expectPrice(aaveAsset.address, bn('0'), bn('0'), false) + // Should be unpriced + await expectUnpriced(rsrAsset.address) + await expectUnpriced(compAsset.address) + await expectUnpriced(aaveAsset.address) - // Fallback prices should be zero - let [lotLow, lotHigh] = await rsrAsset.lotPrice() - expect(lotLow).to.eq(0) - expect(lotHigh).to.eq(0) + // Fallback prices should be initial prices + let [lotLow, lotHigh] = await compAsset.lotPrice() + expect(lotLow).to.eq(compInitPrice[0]) + expect(lotHigh).to.eq(compInitPrice[1]) ;[lotLow, lotHigh] = await rsrAsset.lotPrice() - expect(lotLow).to.eq(0) - expect(lotHigh).to.eq(0) + expect(lotLow).to.eq(rsrInitPrice[0]) + expect(lotHigh).to.eq(rsrInitPrice[1]) ;[lotLow, lotHigh] = await aaveAsset.lotPrice() - expect(lotLow).to.eq(0) - expect(lotHigh).to.eq(0) + expect(lotLow).to.eq(aaveInitPrice[0]) + expect(lotHigh).to.eq(aaveInitPrice[1]) // Update values of underlying tokens of RToken to 0 await setOraclePrice(collateral0.address, bn(0)) await setOraclePrice(collateral1.address, bn(0)) // RTokenAsset should be unpriced now - await expectRTokenPrice( - rTokenAsset.address, - bn(0), - ORACLE_ERROR, - await backingManager.maxTradeSlippage(), - config.minTradeVolume.mul((await assetRegistry.erc20s()).length) - ) + await expectUnpriced(rTokenAsset.address) - // Should have lot price + // Should have initial lot price ;[lotLow, lotHigh] = await rTokenAsset.lotPrice() - expect(lotLow).to.eq(0) - expect(lotHigh).to.eq(0) + expect(lotLow).to.eq(rTokenInitPrice[0]) + expect(lotHigh).to.eq(rTokenInitPrice[1]) }) it('Should return 0 price for RTokenAsset in full haircut scenario', async () => { @@ -381,7 +380,7 @@ describe('Assets contracts #fast', () => { await expectUnpriced(aaveAsset.address) }) - it('Should handle unpriced edge cases for RToken', async () => { + it('Should handle reverting edge cases for RToken', async () => { // Swap one of the collaterals for an invalid one const InvalidFiatCollateralFactory = await ethers.getContractFactory('InvalidFiatCollateral') const invalidFiatCollateral: InvalidFiatCollateral = ( @@ -408,7 +407,7 @@ describe('Assets contracts #fast', () => { // Check RToken unpriced await expectUnpriced(rTokenAsset.address) - // Runnning out of gas + // Runnning out of gas await invalidFiatCollateral.setSimplyRevert(false) await expect(invalidFiatCollateral.price()).to.be.reverted @@ -416,6 +415,35 @@ describe('Assets contracts #fast', () => { await expect(rTokenAsset.price()).to.be.reverted }) + it('Regression test -- Should handle unpriced collateral for RToken', async () => { + // https://github.com/code-423n4/2023-07-reserve-findings/issues/20 + + // Swap one of the collaterals for an invalid one + const InvalidFiatCollateralFactory = await ethers.getContractFactory('InvalidFiatCollateral') + const invalidFiatCollateral: InvalidFiatCollateral = ( + await InvalidFiatCollateralFactory.deploy({ + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await collateral0.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: await collateral0.erc20(), + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }) + ) + + // Swap asset + await assetRegistry.swapRegistered(invalidFiatCollateral.address) + + // Set unpriced collateral + await invalidFiatCollateral.setUnpriced(true) + + // Check RToken is unpriced + await expectUnpriced(rTokenAsset.address) + }) + it('Should be able to refresh saved prices', async () => { // Check initial prices - use RSR as example let currBlockTimestamp: number = await getLatestBlockTimestamp() diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index 7f4404952d..ff0441b43c 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -582,19 +582,29 @@ describe('Collateral contracts', () => { ) }) - it('Should be (0, 0) if price is zero', async () => { - // Set price of token to 0 in Aave + it('Should become unpriced if price is zero', async () => { + const compInitPrice = await tokenCollateral.price() + const aaveInitPrice = await aTokenCollateral.price() + const rsrInitPrice = await cTokenCollateral.price() + + // Update values in Oracles to 0 await setOraclePrice(tokenCollateral.address, bn('0')) - // Check price of tokens - await expectPrice(tokenCollateral.address, bn('0'), bn('0'), false) - await expectPrice(aTokenCollateral.address, bn('0'), bn('0'), false) - await expectPrice(cTokenCollateral.address, bn('0'), bn('0'), false) + // Should be unpriced + await expectUnpriced(cTokenCollateral.address) + await expectUnpriced(tokenCollateral.address) + await expectUnpriced(aTokenCollateral.address) - // Lot prices should be zero - const [lotLow, lotHigh] = await tokenCollateral.lotPrice() - expect(lotLow).to.eq(0) - expect(lotHigh).to.eq(0) + // Fallback prices should be initial prices + let [lotLow, lotHigh] = await tokenCollateral.lotPrice() + expect(lotLow).to.eq(compInitPrice[0]) + expect(lotHigh).to.eq(compInitPrice[1]) + ;[lotLow, lotHigh] = await cTokenCollateral.lotPrice() + expect(lotLow).to.eq(rsrInitPrice[0]) + expect(lotHigh).to.eq(rsrInitPrice[1]) + ;[lotLow, lotHigh] = await aTokenCollateral.lotPrice() + expect(lotLow).to.eq(aaveInitPrice[0]) + expect(lotHigh).to.eq(aaveInitPrice[1]) // When refreshed, sets status to Unpriced await tokenCollateral.refresh() @@ -1232,7 +1242,7 @@ describe('Collateral contracts', () => { // Unpriced if price is zero - Update Oracles and check prices await targetUnitOracle.updateAnswer(bn('0')) - await expectPrice(nonFiatCollateral.address, bn('0'), bn('0'), false) + await expectUnpriced(nonFiatCollateral.address) // When refreshed, sets status to IFFY await nonFiatCollateral.refresh() @@ -1245,9 +1255,9 @@ describe('Collateral contracts', () => { // Check the other oracle await referenceUnitOracle.updateAnswer(bn('0')) - await expectPrice(nonFiatCollateral.address, bn('0'), bn('0'), false) + await expectUnpriced(nonFiatCollateral.address) - // When refreshed, sets status to Unpriced + // When refreshed, sets status to IFFY await nonFiatCollateral.refresh() expect(await nonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) }) @@ -1535,11 +1545,12 @@ describe('Collateral contracts', () => { // Unpriced if price is zero - Update Oracles and check prices await targetUnitOracle.updateAnswer(bn('0')) - await expectPrice(cTokenNonFiatCollateral.address, bn('0'), bn('0'), false) + await expectUnpriced(cTokenNonFiatCollateral.address) - // When refreshed, sets status to Unpriced + // When refreshed, sets status to IFFY await cTokenNonFiatCollateral.refresh() expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) + // Restore await targetUnitOracle.updateAnswer(bn('22000e8')) await cTokenNonFiatCollateral.refresh() @@ -1547,9 +1558,9 @@ describe('Collateral contracts', () => { // Revert if price is zero - Update the other Oracle await referenceUnitOracle.updateAnswer(bn('0')) - await expectPrice(cTokenNonFiatCollateral.address, bn('0'), bn('0'), false) + await expectUnpriced(cTokenNonFiatCollateral.address) - // When refreshed, sets status to Unpriced + // When refreshed, sets status to IFFY await cTokenNonFiatCollateral.refresh() expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) }) @@ -1730,9 +1741,9 @@ describe('Collateral contracts', () => { // Unpriced if price is zero - Update Oracles and check prices await setOraclePrice(selfReferentialCollateral.address, bn(0)) - await expectPrice(selfReferentialCollateral.address, bn('0'), bn('0'), false) + await expectUnpriced(selfReferentialCollateral.address) - // When refreshed, sets status to Unpriced + // When refreshed, sets status to IFFY await selfReferentialCollateral.refresh() expect(await selfReferentialCollateral.status()).to.equal(CollateralStatus.IFFY) @@ -1941,9 +1952,9 @@ describe('Collateral contracts', () => { // Unpriced if price is zero - Update Oracles and check prices await setOraclePrice(cTokenSelfReferentialCollateral.address, bn(0)) - await expectPrice(cTokenSelfReferentialCollateral.address, bn('0'), bn('0'), false) + await expectUnpriced(cTokenSelfReferentialCollateral.address) - // When refreshed, sets status to Unpriced + // When refreshed, sets status to IFFY await cTokenSelfReferentialCollateral.refresh() expect(await cTokenSelfReferentialCollateral.status()).to.equal(CollateralStatus.IFFY) }) @@ -2185,9 +2196,9 @@ describe('Collateral contracts', () => { // Unpriced if price is zero - Update Oracles and check prices await referenceUnitOracle.updateAnswer(bn('0')) - await expectPrice(eurFiatCollateral.address, bn('0'), bn('0'), false) + await expectUnpriced(eurFiatCollateral.address) - // When refreshed, sets status to Unpriced + // When refreshed, sets status to IFFY await eurFiatCollateral.refresh() expect(await eurFiatCollateral.status()).to.equal(CollateralStatus.IFFY) diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index 3d23c0d407..9dc2c4f7fa 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -707,8 +707,8 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await setOraclePrice(zeropriceCtokenCollateral.address, bn(0)) - // Does not revert with zero price - await expectPrice(zeropriceCtokenCollateral.address, bn('0'), bn('0'), false) + // Unpriced + await expectUnpriced(zeropriceCtokenCollateral.address) // Refresh should mark status IFFY await zeropriceCtokenCollateral.refresh() diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index d6dd85740d..09443b0846 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -25,7 +25,7 @@ import { CollateralTestSuiteFixtures, CollateralStatus, } from './pluginTestTypes' -import { expectPrice } from '../../utils/oracles' +import { expectPrice, expectUnpriced } from '../../utils/oracles' import snapshotGasCost from '../../utils/snapshotGasCost' import { IMPLEMENTATION, Implementation } from '../../fixtures' @@ -259,15 +259,13 @@ export default function fn( expect(newHigh).to.be.gt(initHigh) }) - it('returns a 0 price', async () => { + it('returns unpriced for 0-valued oracle', async () => { // Set price of underlying to 0 const updateAnswerTx = await chainlinkFeed.updateAnswer(0) await updateAnswerTx.wait() // (0, FIX_MAX) is returned - const [low, high] = await collateral.price() - expect(low).to.equal(0) - expect(high).to.equal(0) + await expectUnpriced(collateral.address) // When refreshed, sets status to Unpriced await collateral.refresh() @@ -287,23 +285,6 @@ export default function fn( expect(await collateral.status()).to.equal(CollateralStatus.IFFY) }) - it('does not update the saved prices if collateral is unpriced', async () => { - /* - want to cover this block from the refresh function - is it even possible to cover this w/ the tryPrice from AppreciatingFiatCollateral? - - if (high < FIX_MAX) { - savedLowPrice = low; - savedHighPrice = high; - lastSave = uint48(block.timestamp); - } else { - // must be unpriced - assert(low == 0); - } - */ - expect(true) - }) - itHasRevenueHiding('does revenue hiding correctly', async () => { ctx.collateral = await deployCollateral({ erc20: ctx.tok.address, diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 7efc9ab318..df5264769f 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -720,8 +720,8 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await setOraclePrice(zeropriceCtokenCollateral.address, bn(0)) - // Does not revert with zero price - await expectPrice(zeropriceCtokenCollateral.address, bn('0'), bn('0'), false) + // Unpriced + await expectUnpriced(zeropriceCtokenCollateral.address) // Refresh should mark status IFFY await zeropriceCtokenCollateral.refresh() diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index 9f48ae2077..bb31869a26 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -393,15 +393,13 @@ export default function fn( } }) - it('returns a 0 price', async () => { + it('returns unpriced for 0-valued oracle', async () => { for (const feed of ctx.feeds) { await feed.updateAnswer(0).then((e) => e.wait()) } - // (0, 0) is returned - const [low, high] = await ctx.collateral.price() - expect(low).to.equal(0) - expect(high).to.equal(0) + // (0, FIX_MAX) is returned + await expectUnpriced(ctx.collateral.address) // When refreshed, sets status to Unpriced await ctx.collateral.refresh() @@ -453,6 +451,32 @@ export default function fn( }) describe('status', () => { + before(resetFork) + + it('reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { + const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( + 'InvalidMockV3Aggregator' + ) + const invalidChainlinkFeed = ( + await InvalidMockV3AggregatorFactory.deploy(6, bn('1e6')) + ) + + const [invalidCollateral] = await deployCollateral({ + erc20: ctx.wrapper.address, + feeds: defaultOpts.feeds!.map((f) => f.map(() => invalidChainlinkFeed.address)), + }) + + // Reverting with no reason + await invalidChainlinkFeed.setSimplyRevert(true) + await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() + expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) + + // Runnning out of gas (same error) + await invalidChainlinkFeed.setSimplyRevert(false) + await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() + expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) + }) + it('maintains status in normal situations', async () => { // Check initial state expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) @@ -614,31 +638,6 @@ export default function fn( expect(await ctx.collateral.whenDefault()).to.equal(await getLatestBlockTimestamp()) }) - it('reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { - const InvalidMockV3AggregatorFactory = await ethers.getContractFactory( - 'InvalidMockV3Aggregator' - ) - const invalidChainlinkFeed = ( - await InvalidMockV3AggregatorFactory.deploy(6, bn('1e6')) - ) - - ctx = await loadFixture(makeCollateralFixtureContext(ctx.alice, {})) - const [invalidCollateral] = await deployCollateral({ - erc20: ctx.wrapper.address, - feeds: defaultOpts.feeds!.map((f) => f.map(() => invalidChainlinkFeed.address)), - }) - - // Reverting with no reason - await invalidChainlinkFeed.setSimplyRevert(true) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - - // Runnning out of gas (same error) - await invalidChainlinkFeed.setSimplyRevert(false) - await expect(invalidCollateral.refresh()).to.be.revertedWithoutReason() - expect(await invalidCollateral.status()).to.equal(CollateralStatus.SOUND) - }) - describe('collateral-specific tests', collateralSpecificStatusTests) }) diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts index ec1a4c1582..091863c246 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts @@ -7,6 +7,7 @@ import { import { makeWeUSDFraxBP, mintWeUSDFraxBP, resetFork } from './helpers' import { ethers } from 'hardhat' import { ContractFactory, BigNumberish } from 'ethers' +import { expectUnpriced } from '../../../../utils/oracles' import { ERC20Mock, MockV3Aggregator, @@ -14,7 +15,7 @@ import { TestICollateral, } from '../../../../../typechain' import { bn } from '../../../../../common/numbers' -import { ZERO_ADDRESS, ONE_ADDRESS } from '../../../../../common/constants' +import { ZERO_ADDRESS, ONE_ADDRESS, MAX_UINT192 } from '../../../../../common/constants' import { expect } from 'chai' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { @@ -34,7 +35,9 @@ import { CurvePoolType, CRV, eUSD_FRAX_HOLDER, + eUSD, } from '../constants' +import { whileImpersonating } from '../../../../utils/impersonation' type Fixture = () => Promise @@ -48,7 +51,7 @@ export const defaultCrvStableCollateralOpts: CurveMetapoolCollateralOpts = { maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, - revenueHiding: bn('0'), // TODO + revenueHiding: bn('0'), nTokens: 2, curvePool: FRAX_BP, lpToken: FRAX_BP_TOKEN, @@ -196,7 +199,47 @@ const collateralSpecificConstructorTests = () => { } // eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificStatusTests = () => {} +const collateralSpecificStatusTests = () => { + it('Regression test -- becomes unpriced if inner RTokenAsset becomes unpriced', async () => { + const [collateral] = await deployCollateral({}) + const initialPrice = await collateral.price() + expect(initialPrice[0]).to.be.gt(0) + expect(initialPrice[1]).to.be.lt(MAX_UINT192) + + // Swap out eUSD's RTokenAsset with a mock one + const AssetMockFactory = await ethers.getContractFactory('AssetMock') + const mockRTokenAsset = await AssetMockFactory.deploy( + bn('1'), // unused + ONE_ADDRESS, // unused + bn('1'), // unused + eUSD, + bn('1'), // unused + bn('1') // unused + ) + const eUSDAssetRegistry = await ethers.getContractAt( + 'IAssetRegistry', + '0x9B85aC04A09c8C813c37de9B3d563C2D3F936162' + ) + await whileImpersonating('0xc8Ee187A5e5c9dC9b42414Ddf861FFc615446a2c', async (signer) => { + await eUSDAssetRegistry.connect(signer).swapRegistered(mockRTokenAsset.address) + }) + + // Set RTokenAsset to unpriced + // Would be the price under a stale oracle timeout for a poorly-coded RTokenAsset + await mockRTokenAsset.setPrice(0, MAX_UINT192) + + // refresh() should not revert + await collateral.refresh() + + // Should be unpriced + await expectUnpriced(collateral.address) + + // Lot price should be initial price + const lotP = await collateral.lotPrice() + expect(lotP[0]).to.eq(initialPrice[0]) + expect(lotP[1]).to.eq(initialPrice[1]) + }) +} /* Run the test suite diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts index fca510042b..e1646193a8 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts @@ -7,6 +7,7 @@ import { import { makeWeUSDFraxBP, mintWeUSDFraxBP, resetFork } from './helpers' import { ethers } from 'hardhat' import { ContractFactory, BigNumberish } from 'ethers' +import { expectUnpriced } from '../../../../utils/oracles' import { ERC20Mock, MockV3Aggregator, @@ -14,7 +15,7 @@ import { TestICollateral, } from '../../../../../typechain' import { bn } from '../../../../../common/numbers' -import { ZERO_ADDRESS, ONE_ADDRESS } from '../../../../../common/constants' +import { ZERO_ADDRESS, ONE_ADDRESS, MAX_UINT192 } from '../../../../../common/constants' import { expect } from 'chai' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { @@ -35,7 +36,9 @@ import { CurvePoolType, CRV, eUSD_FRAX_HOLDER, + eUSD, } from '../constants' +import { whileImpersonating } from '../../../../utils/impersonation' type Fixture = () => Promise @@ -49,7 +52,7 @@ export const defaultCvxStableCollateralOpts: CurveMetapoolCollateralOpts = { maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, - revenueHiding: bn('0'), // TODO + revenueHiding: bn('0'), nTokens: 2, curvePool: FRAX_BP, lpToken: FRAX_BP_TOKEN, @@ -198,7 +201,47 @@ const collateralSpecificConstructorTests = () => { } // eslint-disable-next-line @typescript-eslint/no-empty-function -const collateralSpecificStatusTests = () => {} +const collateralSpecificStatusTests = () => { + it('Regression test -- becomes unpriced if inner RTokenAsset becomes unpriced', async () => { + const [collateral] = await deployCollateral({}) + const initialPrice = await collateral.price() + expect(initialPrice[0]).to.be.gt(0) + expect(initialPrice[1]).to.be.lt(MAX_UINT192) + + // Swap out eUSD's RTokenAsset with a mock one + const AssetMockFactory = await ethers.getContractFactory('AssetMock') + const mockRTokenAsset = await AssetMockFactory.deploy( + bn('1'), // unused + ONE_ADDRESS, // unused + bn('1'), // unused + eUSD, + bn('1'), // unused + bn('1') // unused + ) + const eUSDAssetRegistry = await ethers.getContractAt( + 'IAssetRegistry', + '0x9B85aC04A09c8C813c37de9B3d563C2D3F936162' + ) + await whileImpersonating('0xc8Ee187A5e5c9dC9b42414Ddf861FFc615446a2c', async (signer) => { + await eUSDAssetRegistry.connect(signer).swapRegistered(mockRTokenAsset.address) + }) + + // Set RTokenAsset to unpriced + // Would be the price under a stale oracle timeout for a poorly-coded RTokenAsset + await mockRTokenAsset.setPrice(0, MAX_UINT192) + + // refresh() should not revert + await collateral.refresh() + + // Should be unpriced + await expectUnpriced(collateral.address) + + // Lot price should be initial price + const lotP = await collateral.lotPrice() + expect(lotP[0]).to.eq(initialPrice[0]) + expect(lotP[1]).to.eq(initialPrice[1]) + }) +} /* Run the test suite From 4d989e53f19195afd10fd349b7453c4931f78031 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 31 Aug 2023 14:11:59 -0300 Subject: [PATCH 402/462] distribute token to buy in setDistribution (#925) --- contracts/p0/Distributor.sol | 5 +++ contracts/p1/Distributor.sol | 5 +++ test/Revenues.test.ts | 80 +++++++++++++++++++++++++++++++++--- 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/contracts/p0/Distributor.sol b/contracts/p0/Distributor.sol index d305e9b521..8c5f429b69 100644 --- a/contracts/p0/Distributor.sol +++ b/contracts/p0/Distributor.sol @@ -33,6 +33,11 @@ contract DistributorP0 is ComponentP0, IDistributor { /// Set the RevenueShare for destination `dest`. Destinations `FURNACE` and `ST_RSR` refer to /// main.furnace() and main.stRSR(). function setDistribution(address dest, RevenueShare memory share) external governance { + // solhint-disable-next-line no-empty-blocks + try main.rsrTrader().distributeTokenToBuy() {} catch {} + // solhint-disable-next-line no-empty-blocks + try main.rTokenTrader().distributeTokenToBuy() {} catch {} + _setDistribution(dest, share); RevenueTotals memory revTotals = totals(); _ensureNonZeroDistribution(revTotals.rTokenTotal, revTotals.rsrTotal); diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index ca818f5a14..bce53c5eff 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -57,6 +57,11 @@ contract DistributorP1 is ComponentP1, IDistributor { // destinations' = destinations.add(dest) // distribution' = distribution.set(dest, share) function setDistribution(address dest, RevenueShare memory share) external governance { + // solhint-disable-next-line no-empty-blocks + try main.rsrTrader().distributeTokenToBuy() {} catch {} + // solhint-disable-next-line no-empty-blocks + try main.rTokenTrader().distributeTokenToBuy() {} catch {} + _setDistribution(dest, share); RevenueTotals memory revTotals = totals(); _ensureNonZeroDistribution(revTotals.rTokenTotal, revTotals.rsrTotal); diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 3573d8310a..dcdd1ff538 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -651,9 +651,75 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rsr.balanceOf(stRSR.address)).to.be.closeTo(expectedAmount, 100) }) + it('Should distribute tokenToBuy before updating distribution', async () => { + // Check initial status + const [rTokenTotal, rsrTotal] = await distributor.totals() + expect(rsrTotal).equal(bn(60)) + expect(rTokenTotal).equal(bn(40)) + + // Set some balance of token-to-buy in traders + const issueAmount = bn('100e18') + + // RSR Trader + const stRSRBal = await rsr.balanceOf(stRSR.address) + await rsr.connect(owner).mint(rsrTrader.address, issueAmount) + + // RToken Trader + const rTokenBal = await rToken.balanceOf(furnace.address) + await rToken.connect(addr1).issueTo(rTokenTrader.address, issueAmount) + + // Update distributions with owner - Set f = 1 + await distributor + .connect(owner) + .setDistribution(FURNACE_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) + + // Check tokens were transferred from Traders + const expectedAmountRSR = stRSRBal.add(issueAmount) + expect(await rsr.balanceOf(stRSR.address)).to.be.closeTo(expectedAmountRSR, 100) + expect(await rsr.balanceOf(rsrTrader.address)).to.be.closeTo(bn(0), 100) + + const expectedAmountRToken = rTokenBal.add(issueAmount) + expect(await rToken.balanceOf(furnace.address)).to.be.closeTo(expectedAmountRToken, 100) + expect(await rsr.balanceOf(rTokenTrader.address)).to.be.closeTo(bn(0), 100) + + // Check updated distributions + const [newRTokenTotal, newRsrTotal] = await distributor.totals() + expect(newRsrTotal).equal(bn(60)) + expect(newRTokenTotal).equal(bn(0)) + }) + + it('Should update distribution even if distributeTokenToBuy() reverts', async () => { + // Check initial status + const [rTokenTotal, rsrTotal] = await distributor.totals() + expect(rsrTotal).equal(bn(60)) + expect(rTokenTotal).equal(bn(40)) + + // Set some balance of token-to-buy in RSR trader + const issueAmount = bn('100e18') + const stRSRBal = await rsr.balanceOf(stRSR.address) + await rsr.connect(owner).mint(rsrTrader.address, issueAmount) + + // Pause the system, makes distributeTokenToBuy() revert + await main.connect(owner).pauseTrading() + await expect(rsrTrader.distributeTokenToBuy()).to.be.reverted + + // Update distributions with owner - Set f = 1 + await distributor + .connect(owner) + .setDistribution(FURNACE_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) + + // Check no tokens were transferred + expect(await rsr.balanceOf(stRSR.address)).to.equal(stRSRBal) + expect(await rsr.balanceOf(rsrTrader.address)).to.equal(issueAmount) + + // Check updated distributions + const [newRTokenTotal, newRsrTotal] = await distributor.totals() + expect(newRsrTotal).equal(bn(60)) + expect(newRTokenTotal).equal(bn(0)) + }) + it('Should return tokens to BackingManager correctly - rsrTrader.returnTokens()', async () => { // Mint tokens - await rsr.connect(owner).mint(rsrTrader.address, issueAmount) await token0.connect(owner).mint(rsrTrader.address, issueAmount.add(1)) await token1.connect(owner).mint(rsrTrader.address, issueAmount.add(2)) @@ -676,6 +742,9 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ).to.be.revertedWith('rsrTotal > 0') await distributor.setDistribution(STRSR_DEST, { rTokenDist: bn('0'), rsrDist: bn('0') }) + // Mint RSR + await rsr.connect(owner).mint(rsrTrader.address, issueAmount) + // Should fail for unregistered token await assetRegistry.connect(owner).unregister(collateral1.address) await expect( @@ -981,6 +1050,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { minBuyAmtRToken.div(bn('1e15')) ) }) + it('Should be able to start a dust auction BATCH_AUCTION, if enabled', async () => { const minTrade = bn('1e18') @@ -1963,10 +2033,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should only allow RevenueTraders to call distribute()', async () => { const distAmount: BigNumber = bn('100e18') - // Transfer some RSR to RevenueTraders - await rsr.connect(addr1).transfer(rTokenTrader.address, distAmount) - await rsr.connect(addr1).transfer(rsrTrader.address, distAmount) - // Set f = 1 await expect( distributor @@ -1984,6 +2050,10 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { .to.emit(distributor, 'DistributionSet') .withArgs(STRSR_DEST, bn(0), bn(1)) + // Transfer some RSR to RevenueTraders + await rsr.connect(addr1).transfer(rTokenTrader.address, distAmount) + await rsr.connect(addr1).transfer(rsrTrader.address, distAmount) + // Check funds in RevenueTraders and destinations expect(await rsr.balanceOf(rTokenTrader.address)).to.equal(distAmount) expect(await rsr.balanceOf(rsrTrader.address)).to.equal(distAmount) From 875bfc3b146e2f3d6726ea4e87313a797aed7a85 Mon Sep 17 00:00:00 2001 From: pmckelvy1 Date: Thu, 31 Aug 2023 14:14:44 -0400 Subject: [PATCH 403/462] C4 mit 31 - standoff scenario (#923) --- contracts/p1/StRSR.sol | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contracts/p1/StRSR.sol b/contracts/p1/StRSR.sol index ffe8d0669f..527d63f50c 100644 --- a/contracts/p1/StRSR.sol +++ b/contracts/p1/StRSR.sol @@ -488,6 +488,14 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab /// @custom:governance /// Reset all stakes and advance era + /// @notice This function is only callable when the stake rate is unsafe. + /// The stake rate is unsafe when it is either too high or too low. + /// There is the possibility of the rate reaching the borderline of being unsafe, + /// where users won't stake in fear that a reset might be executed. + /// A user may also grief this situation by staking enough RSR to vote against any reset. + /// This standoff will continue until enough RSR is staked and a reset is executed. + /// There is currently no good and easy way to mitigate the possibility of this situation, + /// and the risk of it occurring is low enough that it is not worth the effort to mitigate. function resetStakes() external { requireGovernanceOnly(); require( From 8d9edaa087273d05748903230269443a37548618 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 31 Aug 2023 17:26:17 -0400 Subject: [PATCH 404/462] cherry-pick enable-only auction setters (#924) Co-authored-by: Patrick McKelvy --- CHANGELOG.md | 2 + contracts/interfaces/IBroker.sol | 4 +- contracts/p0/Broker.sol | 24 +++--- contracts/p1/Broker.sol | 12 +-- contracts/plugins/mocks/InvalidBrokerMock.sol | 4 +- test/Broker.test.ts | 74 ++++++++++--------- test/Revenues.test.ts | 19 ++++- 7 files changed, 81 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e16d9a2f1..114233c2ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,8 @@ Bump solidity version to 0.8.19 - Add `dutchAuctionLength` and `setDutchAuctionLength()` setter and `DutchAuctionLengthSet()` event - Add `dutchTradeImplementation` and `setDutchTradeImplementation()` setter and `DutchTradeImplementationSet()` event - Only permit BackingManager-started dutch auctions to report violations and disable trading + - Remove `setBatchTradeDisabled(bool)` and replace with `enableBatchTrade()` + - Remove `setDutchTradeDisabled(IERC20 erc20, bool)` and replace with `enableDutchTrade(IERC20 erc20)` - Modify `openTrade(TradeRequest memory reg)` -> `openTrade(TradeKind kind, TradeRequest memory req)` - Allow when paused / frozen, since caller must be in-system diff --git a/contracts/interfaces/IBroker.sol b/contracts/interfaces/IBroker.sol index 0c83eb9216..20e2ed0cb0 100644 --- a/contracts/interfaces/IBroker.sol +++ b/contracts/interfaces/IBroker.sol @@ -89,9 +89,9 @@ interface TestIBroker is IBroker { function setDutchAuctionLength(uint48 newAuctionLength) external; - function setBatchTradeDisabled(bool disabled) external; + function enableBatchTrade() external; - function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external; + function enableDutchTrade(IERC20Metadata erc20) external; // only present on pre-3.0.0 Brokers; used by EasyAuction regression test function disabled() external view returns (bool); diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 13ddbbff1a..02b88de2f3 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -190,6 +190,18 @@ contract BrokerP0 is ComponentP0, IBroker { dutchAuctionLength = newAuctionLength; } + /// @custom:governance + function enableBatchTrade() external governance { + emit BatchTradeDisabledSet(batchTradeDisabled, false); + batchTradeDisabled = false; + } + + /// @custom:governance + function enableDutchTrade(IERC20Metadata erc20) external governance { + emit DutchTradeDisabledSet(erc20, dutchTradeDisabled[erc20], false); + dutchTradeDisabled[erc20] = false; + } + // === Private === function newBatchAuction(TradeRequest memory req, address caller) private returns (ITrade) { @@ -239,16 +251,4 @@ contract BrokerP0 is ComponentP0, IBroker { trade.init(caller, req.sell, req.buy, req.sellAmount, dutchAuctionLength, prices); return trade; } - - /// @custom:governance - function setBatchTradeDisabled(bool disabled) external governance { - emit BatchTradeDisabledSet(batchTradeDisabled, disabled); - batchTradeDisabled = disabled; - } - - /// @custom:governance - function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external governance { - emit DutchTradeDisabledSet(erc20, dutchTradeDisabled[erc20], disabled); - dutchTradeDisabled[erc20] = disabled; - } } diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index a87ae21d3d..4e23432832 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -207,15 +207,15 @@ contract BrokerP1 is ComponentP1, IBroker { } /// @custom:governance - function setBatchTradeDisabled(bool disabled) external governance { - emit BatchTradeDisabledSet(batchTradeDisabled, disabled); - batchTradeDisabled = disabled; + function enableBatchTrade() external governance { + emit BatchTradeDisabledSet(batchTradeDisabled, false); + batchTradeDisabled = false; } /// @custom:governance - function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external governance { - emit DutchTradeDisabledSet(erc20, dutchTradeDisabled[erc20], disabled); - dutchTradeDisabled[erc20] = disabled; + function enableDutchTrade(IERC20Metadata erc20) external governance { + emit DutchTradeDisabledSet(erc20, dutchTradeDisabled[erc20], false); + dutchTradeDisabled[erc20] = false; } // === Private === diff --git a/contracts/plugins/mocks/InvalidBrokerMock.sol b/contracts/plugins/mocks/InvalidBrokerMock.sol index 7b19993c94..191df5136f 100644 --- a/contracts/plugins/mocks/InvalidBrokerMock.sol +++ b/contracts/plugins/mocks/InvalidBrokerMock.sol @@ -64,9 +64,9 @@ contract InvalidBrokerMock is ComponentP0, IBroker { /// Dummy implementation /* solhint-disable no-empty-blocks */ - function setBatchTradeDisabled(bool disabled) external governance {} + function enableBatchTrade() external governance {} /// Dummy implementation /* solhint-disable no-empty-blocks */ - function setDutchTradeDisabled(IERC20Metadata erc20, bool disabled) external governance {} + function enableDutchTrade(IERC20Metadata erc20) external governance {} } diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 61398dba6f..6d4f1ab032 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -1,4 +1,4 @@ -import { loadFixture, setStorageAt } from '@nomicfoundation/hardhat-network-helpers' +import { loadFixture, getStorageAt, setStorageAt } from '@nomicfoundation/hardhat-network-helpers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' import { expect } from 'chai' @@ -132,6 +132,30 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { prices = { sellLow: fp('1'), sellHigh: fp('1'), buyLow: fp('1'), buyHigh: fp('1') } }) + const disableBatchTrade = async () => { + if (IMPLEMENTATION == Implementation.P1) { + const slot = await getStorageAt(broker.address, 205) + await setStorageAt( + broker.address, + 205, + slot.replace(slot.slice(2, 14), '1'.padStart(12, '0')) + ) + } else { + const slot = await getStorageAt(broker.address, 56) + await setStorageAt(broker.address, 56, slot.replace(slot.slice(2, 42), '1'.padStart(40, '0'))) + } + expect(await broker.batchTradeDisabled()).to.equal(true) + } + + const disableDutchTrade = async (erc20: string) => { + const mappingSlot = IMPLEMENTATION == Implementation.P1 ? bn('208') : bn('57') + const p = mappingSlot.toHexString().slice(2).padStart(64, '0') + const key = erc20.slice(2).padStart(64, '0') + const slot = ethers.utils.keccak256('0x' + key + p) + await setStorageAt(broker.address, slot, '0x' + '1'.padStart(64, '0')) + expect(await broker.dutchTradeDisabled(erc20)).to.equal(true) + } + describe('Deployment', () => { it('Should setup Broker correctly', async () => { expect(await broker.gnosis()).to.equal(gnosis.address) @@ -378,28 +402,21 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) // If not owner cannot update - await expect(broker.connect(other).setBatchTradeDisabled(true)).to.be.revertedWith( + await expect(broker.connect(other).enableBatchTrade()).to.be.revertedWith('governance only') + await expect(broker.connect(other).enableDutchTrade(token0.address)).to.be.revertedWith( 'governance only' ) - await expect( - broker.connect(other).setDutchTradeDisabled(token0.address, true) - ).to.be.revertedWith('governance only') // Check value did not change expect(await broker.batchTradeDisabled()).to.equal(false) expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) - // Update batchTradeDisabled with owner - await expect(broker.connect(owner).setBatchTradeDisabled(true)) - .to.emit(broker, 'BatchTradeDisabledSet') - .withArgs(false, true) - - // Check value was updated + // Disable batch trade manually + await disableBatchTrade() expect(await broker.batchTradeDisabled()).to.equal(true) - expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) - // Update back to false - await expect(broker.connect(owner).setBatchTradeDisabled(false)) + // Enable batch trade with owner + await expect(broker.connect(owner).enableBatchTrade()) .to.emit(broker, 'BatchTradeDisabledSet') .withArgs(true, false) @@ -407,24 +424,19 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await broker.batchTradeDisabled()).to.equal(false) expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) - // Update dutchTradeDisabled with owner - await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, true)) - .to.emit(broker, 'DutchTradeDisabledSet') - .withArgs(token0.address, false, true) - - // Check value was updated - expect(await broker.batchTradeDisabled()).to.equal(false) + // Disable dutch trade manually + await disableDutchTrade(token0.address) expect(await broker.dutchTradeDisabled(token0.address)).to.equal(true) - expect(await broker.dutchTradeDisabled(token1.address)).to.equal(false) - // Update back to false - await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, false)) + // Enable dutch trade with owner + await expect(broker.connect(owner).enableDutchTrade(token0.address)) .to.emit(broker, 'DutchTradeDisabledSet') .withArgs(token0.address, true, false) // Check value was updated expect(await broker.batchTradeDisabled()).to.equal(false) expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) + expect(await broker.dutchTradeDisabled(token0.address)).to.equal(false) expect(await broker.dutchTradeDisabled(token1.address)).to.equal(false) }) }) @@ -432,9 +444,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { describe('Trade Management', () => { it('Should not allow to open Batch trade if Disabled', async () => { // Disable Broker Batch Auctions - await expect(broker.connect(owner).setBatchTradeDisabled(true)) - .to.emit(broker, 'BatchTradeDisabledSet') - .withArgs(false, true) + await disableBatchTrade() const tradeRequest: ITradeRequest = { sell: collateral0.address, @@ -468,9 +478,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { .callStatic.openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) // Disable Broker Dutch Auctions for token0 - await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, true)) - .to.emit(broker, 'DutchTradeDisabledSet') - .withArgs(token0.address, false, true) + await disableDutchTrade(token0.address) // Dutch Auction openTrade should fail now await expect( @@ -478,7 +486,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { ).to.be.revertedWith('dutch auctions disabled for token pair') // Re-enable Dutch Auctions for token0 - await expect(broker.connect(owner).setDutchTradeDisabled(token0.address, false)) + await expect(broker.connect(owner).enableDutchTrade(token0.address)) .to.emit(broker, 'DutchTradeDisabledSet') .withArgs(token0.address, true, false) @@ -488,9 +496,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { .callStatic.openTrade(TradeKind.DUTCH_AUCTION, tradeRequest, prices) // Disable Broker Dutch Auctions for token1 - await expect(broker.connect(owner).setDutchTradeDisabled(token1.address, true)) - .to.emit(broker, 'DutchTradeDisabledSet') - .withArgs(token1.address, false, true) + await disableDutchTrade(token1.address) // Dutch Auction openTrade should fail now await expect( diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index e0f16dadb8..4277386931 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -1,4 +1,4 @@ -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { loadFixture, getStorageAt, setStorageAt } from '@nomicfoundation/hardhat-network-helpers' import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' @@ -142,6 +142,21 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { return divCeil(divCeil(product, highBuyPrice), fp('1')) // (c) } + const disableBatchTrade = async () => { + if (IMPLEMENTATION == Implementation.P1) { + const slot = await getStorageAt(broker.address, 205) + await setStorageAt( + broker.address, + 205, + slot.replace(slot.slice(2, 14), '1'.padStart(12, '0')) + ) + } else { + const slot = await getStorageAt(broker.address, 56) + await setStorageAt(broker.address, 56, slot.replace(slot.slice(2, 42), '1'.padStart(40, '0'))) + } + expect(await broker.batchTradeDisabled()).to.equal(true) + } + beforeEach(async () => { ;[owner, addr1, addr2, other] = await ethers.getSigners() @@ -2490,7 +2505,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await aaveToken.balanceOf(rTokenTrader.address)).to.equal(0) // Disable broker - await broker.connect(owner).setBatchTradeDisabled(true) + await disableBatchTrade() // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) const sellAmt: BigNumber = rewardAmountAAVE.mul(60).div(100) // due to f = 60% From 907299c969d97a26803a139e7cd478462895d111 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 31 Aug 2023 18:27:11 -0300 Subject: [PATCH 405/462] loss of pending rewards when claiming in StaticAToken (#926) --- .../plugins/assets/aave/StaticATokenLM.sol | 6 +++ .../aave/StaticATokenLM.test.ts | 53 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/contracts/plugins/assets/aave/StaticATokenLM.sol b/contracts/plugins/assets/aave/StaticATokenLM.sol index 56a537a1b6..ef776a19bc 100644 --- a/contracts/plugins/assets/aave/StaticATokenLM.sol +++ b/contracts/plugins/assets/aave/StaticATokenLM.sol @@ -35,6 +35,10 @@ import { SafeMath } from "@aave/protocol-v2/contracts/dependencies/openzeppelin/ * among others, will update rewards balances correctly, so while it is true that under certain circumstances * rewards may not be fully accurate, we expect them only to be slightly off. * + * Users should also be careful when claiming rewards using `forceUpdate=false` as this will result on permanent + * loss of pending/uncollected rewards. It is recommended to always claim rewards using `forceUpdate=true` + * unless the user is sure that gas costs would exceed the lost rewards. + * * @author Aave * From: https://github.com/aave/protocol-v2/blob/238e5af2a95c3fbb83b0c8f44501ed2541215122/contracts/protocol/tokenization/StaticATokenLM.sol#L255 **/ @@ -467,6 +471,8 @@ contract StaticATokenLM is /** * @notice Claim rewards on behalf of a user and send them to a receiver + * Users should be careful when claiming rewards using `forceUpdate=false` as this will result on permanent + * loss of pending/uncollected rewards. Always claim rewards using `forceUpdate=true` when possible. * @param onBehalfOf The address to claim on behalf of * @param receiver The address to receive the rewards * @param forceUpdate Flag to retrieve latest rewards from `INCENTIVES_CONTROLLER` diff --git a/test/plugins/individual-collateral/aave/StaticATokenLM.test.ts b/test/plugins/individual-collateral/aave/StaticATokenLM.test.ts index c065fe9f72..943738b43e 100644 --- a/test/plugins/individual-collateral/aave/StaticATokenLM.test.ts +++ b/test/plugins/individual-collateral/aave/StaticATokenLM.test.ts @@ -1931,6 +1931,59 @@ describeFork('StaticATokenLM: aToken wrapper with static balances and liquidity expect(pendingRewards4_u1).to.be.lt(pendingRewards4_u2) expect(pendingRewards4_u2).to.be.gt(pendingRewards3_u2) }) + + it('Loss of rewards when claiming with forceUpdate=false', async () => { + const amountToDeposit = utils.parseEther('5') + + // Just preparation + await waitForTx(await weth.deposit({ value: amountToDeposit.mul(2) })) + await waitForTx( + await weth.approve(staticAToken.address, amountToDeposit.mul(2), defaultTxParams) + ) + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ) + await advanceTime(1) + + //***** need small reward balace + await staticAToken.collectAndUpdateRewards() + const staticATokenBalanceFirst = await stkAave.balanceOf(staticAToken.address) + expect(staticATokenBalanceFirst).to.be.gt(0) + + await advanceTime(60 * 60 * 24) + + // Depositing + await waitForTx( + await staticAToken.deposit(userSigner._address, amountToDeposit, 0, true, defaultTxParams) + ) + + const beforeRewardBalance = await stkAave.balanceOf(userSigner._address) + const pendingRewardsBefore = await staticAToken.getClaimableRewards(userSigner._address) + + // User has no balance yet + expect(beforeRewardBalance).to.equal(0) + // Additional rewards exist to be collected + expect(pendingRewardsBefore).to.be.gt(staticATokenBalanceFirst) + + // user claim forceUpdate = false + await waitForTx(await staticAToken.connect(userSigner).claimRewardsToSelf(false)) + + const afterRewardBalance = await stkAave.balanceOf(userSigner._address) + const pendingRewardsAfter = await staticAToken.getClaimableRewards(userSigner._address) + + const pendingRewardsDecline = pendingRewardsBefore.toNumber() - pendingRewardsAfter.toNumber() + const getRewards = afterRewardBalance.toNumber() - beforeRewardBalance.toNumber() + const staticATokenBalanceAfter = await stkAave.balanceOf(staticAToken.address) + + // User has the funds, nothing remains in contract + expect(afterRewardBalance).to.equal(staticATokenBalanceFirst) + expect(staticATokenBalanceAfter).to.equal(0) + + // Check there is a loss + expect(pendingRewardsDecline - getRewards).to.be.gt(0) + }) }) it('Multiple users deposit WETH on stataWETH, wait 1 hour, update rewards, one user transfer, then claim and update rewards.', async () => { From e1d26ca2cc5d762c797168ea26e78b1c5c81010a Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 31 Aug 2023 18:03:07 -0400 Subject: [PATCH 406/462] update CHANGELOG and a few stale comments --- CHANGELOG.md | 39 ++++++++++++++++++--------------- contracts/facade/FacadeAct.sol | 2 +- contracts/facade/FacadeRead.sol | 1 + 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83dfae64ad..80654bcf8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,16 +30,17 @@ It is acceptable to leave these function calls out of the initial upgrade tx and ### Core Protocol Contracts -Bump solidity version to 0.8.19 - - `AssetRegistry` [+1 slot] Summary: StRSR contract need to know when refresh() was last called - - Add last refresh timestamp tracking and expose via `lastRefresh()` getter + - # Add last refresh timestamp tracking and expose via `lastRefresh()` getter + Summary: Other component contracts need to know when refresh() was last called + - Add `lastRefresh()` timestamp getter + > > > > > > > Stashed changes - Add `size()` getter for number of registered assets - Require asset is SOUND on registration - Bugfix: Fix gas attack that could result in someone disabling the basket - `BackingManager` [+2 slots] - Summary: manageTokens was broken out into rebalancing and surplus-forwarding functions to allow users to more precisely call the protocol + Summary: manageTokens was broken out into separate rebalancing and surplus-forwarding functions to allow users to more precisely call the protocol - Replace `manageTokens(IERC20[] memory erc20s)` with: - `rebalance(TradeKind)` @@ -63,6 +64,7 @@ Bump solidity version to 0.8.19 Summary: Introduces a notion of basket warmup to defend against short-term oracle manipulation attacks. Prevent RTokens from changing in value due to governance - Add new gov param: `warmupPeriod` with setter `setWarmupPeriod(..)` and event `WarmupPeriodSet()` + - Add `trackStatus()` refresher - Add `isReady()` view - Extract basket switching logic out into external library `BasketLibP1` - Enforce `setPrimeBasket()` does not change the net value of a basket in terms of its target units @@ -80,9 +82,10 @@ Bump solidity version to 0.8.19 - Rename event `AuctionLengthSet()` -> `BatchAuctionLengthSet()` - Add `dutchAuctionLength` and `setDutchAuctionLength()` setter and `DutchAuctionLengthSet()` event - Add `dutchTradeImplementation` and `setDutchTradeImplementation()` setter and `DutchTradeImplementationSet()` event - - Unlike batch auctions, dutch auctions can be disabled _per-ERC20_, and can only be disabled by BackingManager-started trades - - Only permit BackingManager-started dutch auctions to report violations and disable trading - - Modify `openTrade(TradeRequest memory reg)` -> `openTrade(TradeKind kind, TradeRequest memory req)` + - Modify `setBatchTradeDisabled(bool)` -> `enableBatchTrade()` + - Modify `setDutchTradeDisabled(IERC20 erc20, bool)` -> `enableDutchTrade(IERC20 erc20)` + - Unlike batch auctions, dutch auctions can be disabled _per-ERC20_, and can only be disabled by BackingManager-started trades + - Modify `openTrade(TradeRequest memory reg)` -> `openTrade(TradeKind kind, TradeRequest memory req, TradePrices memory prices)` - Allow when paused / frozen, since caller must be in-system - `Deployer` [+0 slots] @@ -103,9 +106,9 @@ Bump solidity version to 0.8.19 - Lower `MAX_RATIO` from 1e18 to 1e14. - `Main` [+0 slots] - Summary: Breakup pausing into two types of pausing: issuance and trading + Summary: Split pausing into two types of pausing: issuance and trading - - Break `paused` into `issuancePaused` and `tradingPaused` + - Split `paused` into `issuancePaused` and `tradingPaused` - `pause()` -> `pauseTrading()` and `pauseIssuance()` - `unpause()` -> `unpauseTrading()` and `unpauseIssuance()` - `pausedOrFrozen()` -> `tradingPausedOrFrozen()` and `issuancePausedOrFrozen()` @@ -146,7 +149,7 @@ Bump solidity version to 0.8.19 - Add `withdrawalLeak` gov param, with `setWithdrawalLeak(..)` setter and `WithdrawalLeakSet()` event - Modify `withdraw()` to allow a small % of RSR to exit without paying to refresh all assets - Modify `withdraw()` to check for `warmupPeriod` - - Add ability to re-stake during a withdrawal via `cancelUnstake(uint256 endId)` + - Add `cancelUnstake(uint256 endId)` to allow re-staking during unstaking - Add `UnstakingCancelled()` event - Allow payout of (already acquired) RSR rewards while frozen - Add ability for governance to `resetStakes()` when stake rate falls outside (1e12, 1e24) @@ -156,16 +159,17 @@ Bump solidity version to 0.8.19 ### Facades +Remove `FacadeMonitor` - now redundant with `nextRecollateralizationAuction()` and `revenueOverview()` + - `FacadeAct` - Summary: Remove unused getActCalldata and add way to run revenue auctions + Summary: Remove unused `getActCalldata()` and add way to run revenue auctions - Remove `getActCalldata(..)` - Remove `canRunRecollateralizationAuctions(..)` - - Modify `runRevenueAuctions(..)` to work with both 3.0.0 and 2.1.0 interfaces - - Add `revenueOverview(..)` callstatic function to get an overview of the current revenue state - - Add `nextRecollateralizationAuction(..)` callstatic function to get an overview of the rebalancing state - -- Remove `FacadeMonitor` + - Remove `runRevenueAuctions(..)` + - Add `revenueOverview(IRevenueTrader) returns ( IERC20[] memory erc20s, bool[] memory canStart, uint256[] memory surpluses, uint256[] memory minTradeAmounts)` + - Add `nextRecollateralizationAuction(..) returns (bool canStart, IERC20 sell, IERC20 buy, uint256 sellAmount)` + - Modify all functions to work on both 3.0.0 and 2.1.0 RTokens - `FacadeRead` Summary: Add new data summary views frontends may be interested in @@ -173,7 +177,7 @@ Bump solidity version to 0.8.19 - Remove `basketNonce` from `redeem(.., uint48 basketNonce)` - Add `redeemCustom(.., uint48[] memory basketNonces, uint192[] memory portions)` callstatic to simulate multi-basket redemptions - Remove `traderBalances(..)` - - `balancesAcrossAllTraders(IBackingManager) returns (IERC20[] memory erc20s, uint256[] memory balances, uint256[] memory balancesNeededByBackingManager)` + - Add `balancesAcrossAllTraders(IBackingManager) returns (IERC20[] memory erc20s, uint256[] memory balances, uint256[] memory balancesNeededByBackingManager)` - `FacadeWrite` Summary: More expressive and fine-grained control over the set of pausers and freezers @@ -182,7 +186,6 @@ Bump solidity version to 0.8.19 - Do not automatically grant Owner PAUSER/SHORT_FREEZER/LONG_FREEZER - Add ability to initialize with multiple pausers, short freezers, and long freezers - Modify `setupGovernance(.., address owner, address guardian, address pauser)` -> `setupGovernance(.., GovernanceRoles calldata govRoles)` - - Update `DeploymentParams` and `Implementations` struct to contain new gov params and dutch trade plugin ## Plugins diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index 549ccf16ae..3ded3549d9 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -12,7 +12,7 @@ import "../interfaces/IFacadeRead.sol"; /** * @title Facade * @notice A Facade to help batch compound actions that cannot be done from an EOA, solely. - * For use with ^3.0.0 RTokens. + * Compatible with both 2.1.0 and ^3.0.0 RTokens. */ contract FacadeAct is IFacadeAct, Multicall { using Address for address; diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 694ae839c6..10f60d9182 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -16,6 +16,7 @@ import "../p1/StRSRVotes.sol"; /** * @title Facade * @notice A UX-friendly layer for reading out the state of a ^3.0.0 RToken in summary views. + * Backwards-compatible with 2.1.0 RTokens with the exception of `redeemCustom()`. * @custom:static-call - Use ethers callStatic() to get result after update; do not execute */ contract FacadeRead is IFacadeRead { From 1bcc26837e47ab043f479a2c1603799584be9ffa Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 31 Aug 2023 18:07:43 -0400 Subject: [PATCH 407/462] clarify aave V2 erc20s don't need to upgrade --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01a444e106..9a03c0e1ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,9 @@ Call the following functions: - `RevenueTrader.cacheComponents()` (for both rsrTrader and rTokenTrader) - `Distributor.cacheComponents()` -_All_ asset plugins (and their corresponding ERC20s) must be upgraded. +_All_ asset plugins (and their corresponding ERC20s) must be upgraded. The only exception is the `StaticATokenLM` ERC20s from Aave V2. These can be left the same, however their assets should upgraded. -- Make sure to use `Deployer.deployRTokenAsset()` to create new `RTokenAsset` instances. This asset must be swapped too. +- Note: Make sure to use `Deployer.deployRTokenAsset()` to create new `RTokenAsset` instances. This asset should be swapped too. #### Optional Steps From 817c36d20e5f90b54378ad95adbea91d9889b6fc Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 31 Aug 2023 18:14:37 -0400 Subject: [PATCH 408/462] CHANGELOG.md --- CHANGELOG.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82f201dbd5..45c59ff34f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,19 +4,18 @@ ### Upgrade Steps -- Required -Upgrade `BackingManager`, `Broker`, and _all_ assets +Upgrade `BackingManager`, `Broker`, and _all_ assets. ERC20s do not need to be upgraded. -Then call `Broker.cacheComponents()`. +Then, call `Broker.cacheComponents()`. ### Core Protocol Contracts - `BackingManager` - Replace use of `lotPrice()` with `price()` +- `BasketHandler` + - Remove `lotPrice()` - `Broker` [+1 slot] - Disallow starting dutch trades with non-RTokenAsset assets when `lastSave() != block.timestamp` - - Only permit BackingManager-started dutch auctions to report violations and disable trading - - Remove `setBatchTradeDisabled(bool)` and replace with `enableBatchTrade()` - - Remove `setDutchTradeDisabled(IERC20 erc20, bool)` and replace with `enableDutchTrade(IERC20 erc20)` ## Plugins @@ -25,7 +24,9 @@ Then call `Broker.cacheComponents()`. - Remove `lotPrice()` - Alter `price().high` to decay upwards to 3x over the price timeout -# 3.0.0 - Unreleased +# 3.0.0 + +Bump solidity version to 0.8.19 ### Upgrade Steps From 9587d282c83c8d2820c1a363770c6088e4c142e3 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 31 Aug 2023 18:27:36 -0400 Subject: [PATCH 409/462] compile --- contracts/plugins/assets/RTokenAsset.sol | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index 314134cbc6..2dd0010f07 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -55,10 +55,8 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { /// `basketHandler.price()`. When `range.bottom == range.top` then there is no compounding. /// @return low {UoA/tok} The low price estimate /// @return high {UoA/tok} The high price estimate - function tryPrice(bool useLotPrice) external view virtual returns (uint192 low, uint192 high) { - (uint192 lowBUPrice, uint192 highBUPrice) = useLotPrice - ? basketHandler.lotPrice() - : basketHandler.price(); // {UoA/BU} + function tryPrice() external view virtual returns (uint192 low, uint192 high) { + (uint192 lowBUPrice, uint192 highBUPrice) = basketHandler.price(); // {UoA/BU} require(lowBUPrice != 0 && highBUPrice != FIX_MAX, "invalid price"); assert(lowBUPrice <= highBUPrice); // not obviously true just by inspection @@ -93,7 +91,7 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { /// @return {UoA/tok} The lower end of the price estimate /// @return {UoA/tok} The upper end of the price estimate function price() public view virtual returns (uint192, uint192) { - try this.tryPrice(false) returns (uint192 low, uint192 high) { + try this.tryPrice() returns (uint192 low, uint192 high) { return (low, high); } catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data From ecc62f3c65f420381e21cd2c6519f9b55bc2d6bc Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 31 Aug 2023 20:59:35 -0400 Subject: [PATCH 410/462] Update docs for 3.0.0 (#915) --- CHANGELOG.md | 154 ++++++++++++++--------- README.md | 23 +--- contracts/facade/FacadeAct.sol | 2 +- contracts/facade/FacadeRead.sol | 1 + contracts/p1/BasketHandler.sol | 2 +- contracts/plugins/assets/RTokenAsset.sol | 9 +- docs/collateral.md | 33 ++--- docs/mev.md | 6 +- docs/writing-collateral-plugins.md | 1 + 9 files changed, 124 insertions(+), 107 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 114233c2ca..9a03c0e1ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,70 +14,77 @@ Call the following functions: - `RevenueTrader.cacheComponents()` (for both rsrTrader and rTokenTrader) - `Distributor.cacheComponents()` -Collateral / Asset plugins from 2.1.0 do not need to be upgraded with the exception of Compound V2 cToken collateral ([CTokenFiatCollateral.sol](contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol)), which needs to be swapped in via `AssetRegistry.swapRegistered()`. Skipping this step will result in COMP rewards becoming unclaimable. Note that this will change the ERC20 for the collateral plugin, causing the protocol to trade out of the old ERC20. Since COMP rewards are claimed on every transfer, COMP does not need to be claimed beforehand. +_All_ asset plugins (and their corresponding ERC20s) must be upgraded. The only exception is the `StaticATokenLM` ERC20s from Aave V2. These can be left the same, however their assets should upgraded. + +- Note: Make sure to use `Deployer.deployRTokenAsset()` to create new `RTokenAsset` instances. This asset should be swapped too. #### Optional Steps Call the following functions, once it is desired to turn on the new features: -- `BaasketHandler.setWarmupPeriod()` +- `BasketHandler.setWarmupPeriod()` - `StRSR.setWithdrawalLeak()` - `Broker.setDutchAuctionLength()` -### Core Protocol Contracts - -Bump solidity version to 0.8.19 +It is acceptable to leave these function calls out of the initial upgrade tx and follow up with them later. The protocol will continue to function, just without dutch auctions, RSR unstaking gas-savings, and the warmup period. -Bump solidity version to 0.8.19 +### Core Protocol Contracts - `AssetRegistry` [+1 slot] - Summary: Other component contracts need to know when refresh() was last called - - Add last refresh timestamp tracking and expose via `lastRefresh()` getter + Summary: StRSR contract need to know when refresh() was last called + - # Add last refresh timestamp tracking and expose via `lastRefresh()` getter + Summary: Other component contracts need to know when refresh() was last called + - Add `lastRefresh()` timestamp getter - Add `size()` getter for number of registered assets + - Require asset is SOUND on registration + - Bugfix: Fix gas attack that could result in someone disabling the basket - `BackingManager` [+2 slots] - Summary: manageTokens was broken out into rebalancing and surplus-forwarding functions to allow users to more precisely call the protocol + Summary: manageTokens was broken out into separate rebalancing and surplus-forwarding functions to allow users to more precisely call the protocol - Replace `manageTokens(IERC20[] memory erc20s)` with: - - `rebalance(TradeKind)` + `RecollateralizationLibP1` - - Modify trading algorithm to not trade RToken, and instead dissolve it when it has a balance above ~1e6. "dissolve" = melt() with a basketsNeeded change, like redemption. + - `rebalance(TradeKind)` + - Modify trading algorithm to not trade RToken, and instead dissolve it when it has a balance above ~1e6 RToken quanta. "dissolve" = melt() with a basketsNeeded change, similar to redemption but without transfer of RToken collateral. + - Use `lotPrice()` to set trade prices instead of `price()` - Add significant caching to save gas - `forwardRevenue(IERC20[] memory erc20s)` - - Modify backingBuffer logic to keep the backing buffer in collateral tokens only. Fix subtle and inconsequential bug that resulted in not maximizing RToken minting locally, though overall RToken production would not have been lower. + - Modify backingBuffer logic to keep the backing buffer in collateral tokens only. Fix subtle and inconsequential bug that resulted in not maximizing RToken minting locally, though overall RToken production does not change. - Use `nonReentrant` over CEI pattern for gas improvement. related to discussion of [this](https://github.com/code-423n4/2023-01-reserve-findings/issues/347) cross-contract reentrancy risk - move `nonReentrant` up outside `tryTrade` internal helper - Remove `manageTokensSortedOrder(IERC20[] memory erc20s)` - Modify `settleTrade(IERC20 sell)` to call `rebalance()` when caller is a trade it deployed. - - Remove all `delegatecall` during reward claiming + - Remove all `delegatecall` during reward claiming; call `claimRewards()` directly on ERC20 - Functions now revert on unproductive executions, instead of no-op - Do not trade until a warmupPeriod (last time SOUND was newly attained) has passed - Add `cacheComponents()` refresher to be called on upgrade + - Add concept of `tradeNonce` - Bugfix: consider `maxTradeVolume()` from both assets on a trade, not just 1 - `BasketHandler` [+5 slots] Summary: Introduces a notion of basket warmup to defend against short-term oracle manipulation attacks. Prevent RTokens from changing in value due to governance - Add new gov param: `warmupPeriod` with setter `setWarmupPeriod(..)` and event `WarmupPeriodSet()` + - Add `trackStatus()` refresher - Add `isReady()` view - Extract basket switching logic out into external library `BasketLibP1` - Enforce `setPrimeBasket()` does not change the net value of a basket in terms of its target units - Add `quoteCustomRedemption(uint48[] basketNonces, uint192[] memory portions, ..)` to quote a linear combination of current-or-previous baskets for redemption - Add `getHistoricalBasket(uint48 basketNonce)` view + - Bugfix: Protect against high BU price overflow -- `Broker` [+1 slot] - Summary: Add a new trading plugin that performs single-lot dutch auctions. Batch auctions via Gnosis EasyAuction are expected to be the backup auction (can be faster if more gas costly) going forward. +- `Broker` [+2 slot] + Summary: Add a second trading method for single-lot dutch auctions. Batch auctions via Gnosis EasyAuction are expected to be the backup auction going forward. - - Add `TradeKind` enum to track multiple trading types - Add new dutch auction `DutchTrade` - - Add minimum auction length of 24s; applies to all auction types + - Add minimum auction length of 20 blocks based on network block time - Rename variable `auctionLength` -> `batchAuctionLength` - Rename setter `setAuctionLength()` -> `setBatchAuctionLength()` - Rename event `AuctionLengthSet()` -> `BatchAuctionLengthSet()` - Add `dutchAuctionLength` and `setDutchAuctionLength()` setter and `DutchAuctionLengthSet()` event - Add `dutchTradeImplementation` and `setDutchTradeImplementation()` setter and `DutchTradeImplementationSet()` event - - Only permit BackingManager-started dutch auctions to report violations and disable trading - - Remove `setBatchTradeDisabled(bool)` and replace with `enableBatchTrade()` - - Remove `setDutchTradeDisabled(IERC20 erc20, bool)` and replace with `enableDutchTrade(IERC20 erc20)` - - Modify `openTrade(TradeRequest memory reg)` -> `openTrade(TradeKind kind, TradeRequest memory req)` + - Modify `setBatchTradeDisabled(bool)` -> `enableBatchTrade()` + - Modify `setDutchTradeDisabled(IERC20 erc20, bool)` -> `enableDutchTrade(IERC20 erc20)` + - Unlike batch auctions, dutch auctions can be disabled _per-ERC20_, and can only be disabled by BackingManager-started trades + - Modify `openTrade(TradeRequest memory reg)` -> `openTrade(TradeKind kind, TradeRequest memory req, TradePrices memory prices)` - Allow when paused / frozen, since caller must be in-system - `Deployer` [+0 slots] @@ -85,104 +92,127 @@ Bump solidity version to 0.8.19 - Modify to handle new gov params: `warmupPeriod`, `dutchAuctionLength`, and `withdrawalLeak` - Do not grant OWNER any of the roles other than ownership + - Add `deployRTokenAsset()` to allow easy creation of new `RTokenAsset` instances -- `Distributor` [+0 slots] - Summary: Waste of gas to double-check this, since caller is another component +- `Distributor` [+2 slots] + Summary: Restrict callers to system components and remove paused/frozen checks - Remove `notPausedOrFrozen` modifier from `distribute()` - `Furnace` [+0 slots] - Summary: Should be able to melting while redeeming when frozen - - Modify `melt()` modifier: `notPausedOrFrozen` -> `notFrozen` + Summary: Allow melting while paused + + - Allow melting while paused + - Melt during updates to the melting ratio + - Lower `MAX_RATIO` from 1e18 to 1e14. + - `Main` [+0 slots] - Summary: Breakup pausing into two types of pausing: issuance and trading + Summary: Split pausing into two types of pausing: issuance and trading - - Break `paused` into `issuancePaused` and `tradingPaused` + - Split `paused` into `issuancePaused` and `tradingPaused` - `pause()` -> `pauseTrading()` and `pauseIssuance()` - `unpause()` -> `unpauseTrading()` and `unpauseIssuance()` - `pausedOrFrozen()` -> `tradingPausedOrFrozen()` and `issuancePausedOrFrozen()` - `PausedSet()` event -> `TradingPausedSet()` and `IssuancePausedSet()` -- `RevenueTrader` [+3 slots] +- `RevenueTrader` [+4 slots] Summary: QoL improvements. Make compatible with new dutch auction trading method - - Remove `delegatecall` during reward claiming + - Remove `delegatecall` during reward claiming; call `claimRewards()` directly on ERC20 - Add `cacheComponents()` refresher to be called on upgrade - - `manageToken(IERC20 sell)` -> `manageToken(IERC20 sell, TradeKind kind)` - - Allow `manageToken(..)` to open dust auctions - - Revert on 0 balance or collision auction, instead of no-op + - `manageToken(IERC20 sell)` -> `manageTokens(IERC20[] calldata erc20s, TradeKind[] memory kinds)` + - Allow multiple auctions to be launched at once + - Allow opening dust auctions (i.e ignore `minTradeVolume`) + - Revert on 0 balance or collision auction instead of no-op - Refresh buy and sell asset before trade - - `settleTrade(IERC20)` now distributes `tokenToBuy`, instead of requiring separate `manageToken(IERC20)` call + - `settleTrade(IERC20)` now distributes `tokenToBuy` automatically, instead of requiring separate `manageToken(IERC20)` call + - Add `returnTokens(IERC20[] memory erc20s)` to return tokens to the BackingManager when the distribution is set to 0 + - Add concept of `tradeNonce` - `RToken` [+0 slots] - Summary: Provide multiple redemption methods for when fullyCollateralized vs not. Should support a higher RToken price during basket changes. + Summary: Provide multiple redemption methods for fullyCollateralized vs uncollateralized. - - Remove `exchangeRateIsValidAfter` modifier from all functions except `setBasketsNeeded()` - - Modify `issueTo()` to revert before `warmupPeriod` - - Modify `redeem(uint256 amount, uint48 basketNonce)` -> `redeem(uint256 amount)`. Redemptions are on the current basket nonce and revert under partial redemption + - Gas: Remove `exchangeRateIsValidAfter` modifier from all functions except `setBasketsNeeded()` + - Modify issuance`to revert before`warmupPeriod` + - Modify `redeem(uint256 amount, uint48 basketNonce)` -> `redeem(uint256 amount)`. Redemptions are always on the current basket nonce and revert under partial redemption - Modify `redeemTo(address recipient, uint256 amount, uint48 basketNonce)` -> `redeemTo(address recipient, uint256 amount)`. Redemptions are on the current basket nonce and revert under partial redemption - - Add new `redeemCustom(.., uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, ..)` function to allow redemption from a linear combination of current and previous baskets. During rebalancing this method of redemption will provide a higher overall redemption value than prorata redemption on the current basket nonce would. - - `mint(address recipient, uint256 amtRToken)` -> `mint(uint256 amtRToken)`, since recipient is _always_ BackingManager. Expand scope to include adjustments to `basketsNeeded` + - Add new `redeemCustom(.., uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, ..)` function to allow redemption from a linear combination of current and previous baskets. During rebalancing this method of redemption may provide a higher overall redemption value than prorata redemption on the current basket nonce would. + - Modify `mint(address recipient, uint256 amtRToken)` -> `mint(uint256 amtRToken)`, since recipient is _always_ BackingManager. Expand scope to include adjustments to `basketsNeeded` - Add `dissolve(uint256 amount)`: burns RToken and reduces `basketsNeeded`, similar to redemption. Only callable by BackingManager - Modify `setBasketsNeeded(..)` to revert when supply is 0 + - Bugfix: Accumulate throttles upon change - `StRSR` [+2 slots] - Summary: Add the ability to cancel unstakings and a withdrawal() gas-saver to allow small RSR amounts to be exempt from refreshes + Summary: Add the ability to cancel unstakings and a withdrawal() gas-saver to allow small RSR amounts to be exempt from asset refreshes + - Lower `MAX_REWARD_RATIO` from 1e18 to 1e14. - Remove duplicate `stakeRate()` getter (same as `1 / exchangeRate()`) - Add `withdrawalLeak` gov param, with `setWithdrawalLeak(..)` setter and `WithdrawalLeakSet()` event - - Modify `withdraw()` to allow a small % of RSR too exit without paying to refresh all assets + - Modify `withdraw()` to allow a small % of RSR to exit without paying to refresh all assets - Modify `withdraw()` to check for `warmupPeriod` - - Add ability to re-stake during a withdrawal via `cancelUnstake(uint256 endId)` + - Add `cancelUnstake(uint256 endId)` to allow re-staking during unstaking - Add `UnstakingCancelled()` event + - Allow payout of (already acquired) RSR rewards while frozen + - Add ability for governance to `resetStakes()` when stake rate falls outside (1e12, 1e24) - `StRSRVotes` [+0 slots] - - Add `stakeAndDelegate(uint256 rsrAmount, address delegate)` function, to encourage people to receive voting weight upon staking + - Add `stakeAndDelegate(uint256 rsrAmount, address delegate)` function to encourage people to receive voting weight upon staking ### Facades -- `FacadeWrite` - Summary: More expressive and fine-grained control over the set of pausers and freezers - - - Do not automatically grant Guardian PAUSER/SHORT_FREEZER/LONG_FREEZER - - Do not automatically grant Owner PAUSER/SHORT_FREEZER/LONG_FREEZER - - Add ability to initialize with multiple pausers, short freezers, and long freezers - - Modify `setupGovernance(.., address owner, address guardian, address pauser)` -> `setupGovernance(.., GovernanceRoles calldata govRoles)` - - Update `DeploymentParams` and `Implementations` struct to contain new gov params and dutch trade plugin +Remove `FacadeMonitor` - now redundant with `nextRecollateralizationAuction()` and `revenueOverview()` - `FacadeAct` - Summary: Remove unused getActCalldata and add way to run revenue auctions + Summary: Remove unused `getActCalldata()` and add way to run revenue auctions - Remove `getActCalldata(..)` - - Modify `runRevenueAuctions(..)` to work with both 3.0.0 and 2.1.0 interfaces + - Remove `canRunRecollateralizationAuctions(..)` + - Remove `runRevenueAuctions(..)` + - Add `revenueOverview(IRevenueTrader) returns ( IERC20[] memory erc20s, bool[] memory canStart, uint256[] memory surpluses, uint256[] memory minTradeAmounts)` + - Add `nextRecollateralizationAuction(..) returns (bool canStart, IERC20 sell, IERC20 buy, uint256 sellAmount)` + - Modify all functions to work on both 3.0.0 and 2.1.0 RTokens - `FacadeRead` Summary: Add new data summary views frontends may be interested in - Remove `basketNonce` from `redeem(.., uint48 basketNonce)` + - Add `redeemCustom(.., uint48[] memory basketNonces, uint192[] memory portions)` callstatic to simulate multi-basket redemptions - Remove `traderBalances(..)` - - `balancesAcrossAllTraders(IBackingManager) returns (IERC20[] memory erc20s, uint256[] memory balances, uint256[] memory balancesNeededByBackingManager)` - - Add `nextRecollateralizationAuction(..) returns (bool canStart, IERC20 sell, IERC20 buy, uint256 sellAmount)` - - Add `revenueOverview(IRevenueTrader) returns ( IERC20[] memory erc20s, bool[] memory canStart, uint256[] memory surpluses, uint256[] memory minTradeAmounts)` + - Add `balancesAcrossAllTraders(IBackingManager) returns (IERC20[] memory erc20s, uint256[] memory balances, uint256[] memory balancesNeededByBackingManager)` -- Remove `FacadeMonitor` - redundant with `nextRecollateralizationAuction()` and `revenueOverview()` +- `FacadeWrite` + Summary: More expressive and fine-grained control over the set of pausers and freezers + + - Do not automatically grant Guardian PAUSER/SHORT_FREEZER/LONG_FREEZER + - Do not automatically grant Owner PAUSER/SHORT_FREEZER/LONG_FREEZER + - Add ability to initialize with multiple pausers, short freezers, and long freezers + - Modify `setupGovernance(.., address owner, address guardian, address pauser)` -> `setupGovernance(.., GovernanceRoles calldata govRoles)` ## Plugins ### DutchTrade -A cheaper, simpler, trading method. Intended to be the new dominant trading method, with GnosisTrade (batch auctions) available as a faster-but-more-gas-expensive backup option. +A cheaper, simpler, trading method. Intended to be the new dominant trading method, with GnosisTrade (batch auctions) available as a backup option. Generally speaking the batch auction length can be kept shorter than the dutch auction length. -DutchTrade implements a two-stage, single-lot, falling price dutch auction. In the first 40% of the auction, the price falls from 1000x to the best-case price in a geometric/exponential decay as a price manipulation defense mechanism. Bids are not expected to occur (but note: unlike the GnosisTrade batch auction, this mechanism is not resistant to _arbitrary_ price manipulation). +DutchTrade implements a four-stage, single-lot, falling price dutch auction: -Over the last 60% of the auction, the price falls linearly from the best-case price to the worst-case price. Only a single bidder can bid fill the auction, and settlement is atomic. If no bids are received, the capital cycles back to the BackingManager and no loss is taken. +1. In the first 20% of the auction, the price falls from 1000x the best price to the best price in a geometric/exponential decay as a price manipulation defense mechanism. Bids are not expected to occur (but note: unlike the GnosisTrade batch auction, this mechanism is not resistant to _arbitrary_ price manipulation). If a bid occurs, then trading for the pair of tokens is disabled as long as the trade was started by the BackingManager. +2. Between 20% and 45%, the price falls linearly from 1.5x the best price to the best price. +3. Between 45% and 95%, the price falls linearly from the best price to the worst price. +4. Over the last 5% of the auction, the price remains constant at the worst price. Duration: 30 min (default) ### Assets and Collateral -- Bugfix: `lotPrice()` now begins at 100% the lastSavedPrice, instead of below 100%. It can be at 100% for up to the oracleTimeout in the worst-case. - Add `version() return (string)` getter to pave way for separation of asset versioning and core protocol versioning -- Update `claimRewards()` on all assets to 3.0.0-style, without `delegatecall` +- Deprecate `claimRewards()` - Add `lastSave()` to `RTokenAsset` +- Remove `CurveVolatileCollateral` +- Switch `CToken*Collateral` (Compound V2) to using a CTokenVault ERC20 rather than the raw cToken +- Bugfix: `lotPrice()` now begins at 100% the lastSavedPrice, instead of below 100%. It can be at 100% for up to the oracleTimeout in the worst-case. +- Bugfix: Handle oracle deprecation as indicated by the `aggregator()` being set to the zero address +- Bugfix: `AnkrStakedETHCollateral`/`CBETHCollateral`/`RethCollateral` now correctly detects soft default (note that Ankr still requires a new oracle before it can be deployed) +- Bugfix: Adjust `Curve*Collateral` and `RTokenAsset` to treat FIX_MAX correctly as +inf +- Bugfix: Continue updating cached price after collateral default (impacts all appreciating collateral) # 2.1.0 diff --git a/README.md b/README.md index 2ff52fa10b..0fb2726242 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The Reserve Protocol enables a class of token called RToken: self-issued tokens RTokens can be minted by depositing a basket of _collateral tokens_, and redeemed for the basket as well. Thus, an RToken will tend to trade at the market value of the entire basket that backs it, as any lower or higher price could be arbitraged. -The definition of the collateral basket is set dynamically on a block-by-block basis with respect to a _reference basket_. While the RToken often does its internal calculus in terms of a single unit of account (USD), what constitutes appreciation is entirely a function of the reference basket, which will often be associated with a variety of units. +The definition of the issuance/redemption basket is set dynamically on a block-by-block basis with respect to a _reference basket_. While the RToken often does its internal calculus in terms of a single unit of account (USD), what constitutes appreciation is entirely a function of the reference basket, which is a linear combination of reference units. RTokens can be over-collateralized, which means that if any of their collateral tokens default, there's a pool of value available to make up for the loss. RToken over-collateralization is provided by Reserve Rights (RSR) holders, who may choose to stake their RSR on an RToken instance. Staked RSR can be seized in the case of a default, in a process that is entirely mechanistic based on on-chain price-feeds, and does not depend on governance votes or human judgment. @@ -22,6 +22,7 @@ For a much more detailed explanation of the economic design, including an hour-l - [Testing with Echidna](docs/using-echidna.md): Notes so far on setup and usage of Echidna (which is decidedly an integration-in-progress!) - [Deployment](docs/deployment.md): How to do test deployments in our environment. - [System Design](docs/system-design.md): The overall architecture of our system, and some detailed descriptions about what our protocol is _intended_ to do. +- [Deployment Variables](docs/deployment-variables.md) A detailed description of the governance variables of the protocol. - [Our Solidity Style](docs/solidity-style.md): Common practices, details, and conventions relevant to reading and writing our Solidity source code, estpecially where those go beyond standard practice. - [Writing Collateral Plugins](docs/collateral.md): An overview of how to develop collateral plugins and the concepts / questions involved. - [Building on Top](docs/build-on-top.md): How to build on top of Reserve, including information about long-lived fork environments. @@ -103,13 +104,10 @@ The less-central folders in the repository are dedicated to project management, ## Types of Tests -We conceive of several different types of tests: - -Finally, inside particular testing, it's quite useful to distinguish unit tests from full end-to-end tests. As such, we expect to write tests of the following 5 types: - ### Unit/System Tests - Driven by `hardhat test` +- Addressed by `yarn test:unit` - Checks for expected behavior of the system. - Can run the same tests against both p0 and p1 - Uses contract mocks, where helpful to predict component behavior @@ -119,6 +117,7 @@ Target: Full branch coverage, and testing of any semantically-relevant situation ### End-to-End Tests - Driven by `hardhat test` +- Addressed by `yarn test:integration` - Uses mainnet forking - Can run the same tests against both p0 and p1 - Tests all needed plugin contracts, contract deployment, any migrations, etc. @@ -137,17 +136,7 @@ Located in `fuzz` branch only. Target: The handful of our most depended-upon system properties and invariants are articulated and thoroughly fuzz-tested. Examples of such properties include: - Unless the basket is switched (due to token default or governance) the protocol always remains fully-collateralized. -- Unless the protocol is paused, RToken holders can always redeem -- If the protocol is paused, and governance does not act further, the protocol will later become unpaused. - -### Differential Testing - -Located in `fuzz` branch only. - -- Driven by Echidna -- Asserts that the behavior of each p1 contract matches that of p0 - -Target: Intensive equivalence testing, run continuously for days or weeks, sensitive to any difference between observable behaviors of p0 and p1. +- Unless the protocol is frozen, RToken holders can always redeem ## Contributing @@ -159,7 +148,7 @@ To get setup with tenderly, install the [tenderly cli](https://github.com/Tender ## Responsible Disclosure -[Immunifi](https://immunefi.com/bounty/reserve/) +See: [Immunifi](https://immunefi.com/bounty/reserve/) ## External Documentation diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index ab3e77152e..c8be6f29ad 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -13,7 +13,7 @@ import "../interfaces/IFacadeRead.sol"; /** * @title Facade * @notice A Facade to help batch compound actions that cannot be done from an EOA, solely. - * For use with ^3.0.0 RTokens. + * Compatible with both 2.1.0 and ^3.0.0 RTokens. */ contract FacadeAct is IFacadeAct, Multicall { using Address for address; diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 694ae839c6..10f60d9182 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -16,6 +16,7 @@ import "../p1/StRSRVotes.sol"; /** * @title Facade * @notice A UX-friendly layer for reading out the state of a ^3.0.0 RToken in summary views. + * Backwards-compatible with 2.1.0 RTokens with the exception of `redeemCustom()`. * @custom:static-call - Use ethers callStatic() to get result after update; do not execute */ contract FacadeRead is IFacadeRead { diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 387055fc63..2cb493d1a3 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -21,9 +21,9 @@ import "./mixins/Component.sol"; /// @custom:oz-upgrades-unsafe-allow external-library-linking contract BasketHandlerP1 is ComponentP1, IBasketHandler { using BasketLibP1 for Basket; + using CollateralStatusComparator for CollateralStatus; using EnumerableMap for EnumerableMap.Bytes32ToUintMap; using EnumerableSet for EnumerableSet.Bytes32Set; - using CollateralStatusComparator for CollateralStatus; using FixLib for uint192; uint192 public constant MAX_TARGET_AMT = 1e3 * FIX_ONE; // {target/BU} max basket weight diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index eae0c8b2b0..68a9da9863 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -12,7 +12,7 @@ uint256 constant ORACLE_TIMEOUT = 15 minutes; /// Once an RToken gets large enough to get a price feed, replacing this asset with /// a simpler one will do wonders for gas usage -// @dev This RTokenAsset is ONLY compatible with Protocol ^3.0.0 +/// @dev This RTokenAsset is ONLY compatible with Protocol ^3.0.0 contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { using FixLib for uint192; using OracleLib for AggregatorV3Interface; @@ -48,6 +48,11 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { } /// Can revert, used by other contract functions in order to catch errors + /// @dev This method for calculating the price can provide a 2x larger range than the average + /// oracleError of the RToken's backing collateral. This only occurs when there is + /// less RSR overcollateralization in % terms than the average (weighted) oracleError. + /// This arises from the use of oracleErrors inside of `basketRange()` and inside + /// `basketHandler.price()`. When `range.bottom == range.top` then there is no compounding. /// @return low {UoA/tok} The low price estimate /// @return high {UoA/tok} The high price estimate function tryPrice(bool useLotPrice) external view virtual returns (uint192 low, uint192 high) { @@ -84,6 +89,7 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { // solhint-enable no-empty-blocks /// Should not revert + /// @dev See `tryPrice` caveat about possible compounding error in calculating price /// @return {UoA/tok} The lower end of the price estimate /// @return {UoA/tok} The upper end of the price estimate function price() public view virtual returns (uint192, uint192) { @@ -98,6 +104,7 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { /// Should not revert /// lotLow should be nonzero when the asset might be worth selling + /// @dev See `tryPrice` caveat about possible compounding error in calculating price /// @return lotLow {UoA/tok} The lower end of the lot price estimate /// @return lotHigh {UoA/tok} The upper end of the lot price estimate function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh) { diff --git a/docs/collateral.md b/docs/collateral.md index d9589954e1..88727f0fb1 100644 --- a/docs/collateral.md +++ b/docs/collateral.md @@ -15,7 +15,6 @@ In our inheritance tree, Collateral is a subtype of Asset (i.e. `ICollateral is - How to get its price - A maximum volume per trade -- How to claim token rewards, if the token offers them A Collateral contract is a subtype of Asset (i.e. `ICollateral is IAsset`), so it does everything as Asset does. Beyond that, a Collateral plugin provides the Reserve Protocol with the information it needs to use its token as collateral -- as backing, held in the RToken's basket. @@ -27,20 +26,6 @@ A Collateral contract is a subtype of Asset (i.e. `ICollateral is IAsset`), so i The IAsset and ICollateral interfaces, from `IAsset.sol`, are as follows: ```solidity -/** - * @title IRewardable - * @notice A simple interface mixin to support claiming of rewards. - */ -interface IRewardable { - /// Emitted whenever a reward token balance is claimed - event RewardsClaimed(IERC20 indexed erc20, uint256 indexed amount); - - /// Claim rewards earned by holding a balance of the ERC20 token - /// Must emit `RewardsClaimed` for each token rewards are claimed for - /// @custom:interaction - function claimRewards() external; -} - /** * @title IAsset * @notice Supertype. Any token that interacts with our system must be wrapped in an asset, @@ -77,8 +62,11 @@ interface IAsset is IRewardable { /// @return If the asset is an instance of ICollateral or not function isCollateral() external view returns (bool); - /// @param {UoA} The max trade volume, in UoA + /// @return {UoA} The max trade volume, in UoA function maxTradeVolume() external view returns (uint192); + + /// @return {s} The timestamp of the last refresh() that saved prices + function lastSave() external view returns (uint48); } /// CollateralStatus must obey a linear ordering. That is: @@ -199,9 +187,9 @@ Note, this doesn't disqualify collateral with USD as its target unit! It's fine ### Representing Fractional Values -Wherever contract variables have these units, it's understood that even though they're handled as `uint`s, they represent fractional values with 18 decimals. In particular, a `{tok}` value is a number of "whole tokens" with 18 decimals. So even though DAI has 18 decimals and USDC has 6 decimals, $1 in either token would be 1e18 when working in units of `{tok}`. +Wherever contract variables have these units, it's understood that even though they're handled as `uint192`s, they represent fractional values with 18 decimals. In particular, a `{tok}` value is a number of "whole tokens" with 18 decimals. So even though DAI has 18 decimals and USDC has 6 decimals, $1 in either token would be 1e18 when working in units of `{tok}`. -For more about our approach for handling decimal-fixed-point, see our [docs on the Fix Library](solidity-style.md#The-Fix-Library). +For more about our approach for handling decimal-fixed-point, see our [docs on the Fix Library](solidity-style.md#The-Fix-Library). Ideally a user-defined type would be used but we found static analyses tools had trouble with that. ## Synthetic Units @@ -349,9 +337,9 @@ If `status()` ever returns `CollateralStatus.DISABLED`, then it must always retu ### Token rewards should be claimable. -Protocol contracts that hold an asset for any significant amount of time are all able to call `claimRewards()` on the ERC20 itself (previously on the asset/collateral plugin via delegatecall). The erc20 or its wrapper contract should include whatever logic is necessary to claim rewards from all relevant defi protocols. These rewards are often emissions from other protocols, but may also be something like trading fees in the case of UNIV3 collateral. To take advantage of this: +Protocol contracts that hold an asset for any significant amount of time must be able to call `claimRewards()` on the ERC20 itself (previously on the asset/collateral plugin via delegatecall). The erc20 should include whatever logic is necessary to claim rewards from all relevant defi protocols. These rewards are often emissions from other protocols, but may also be something like trading fees in the case of UNIV3 collateral. To take advantage of this: -- `claimRewards()` must claim all rewards that may be earned by holding the asset ERC20 and send them to the holder. +- `claimRewards()` must claim all rewards that may be earned by holding the asset ERC20 and send them to the holder, in the correct proportions based on amount of time held. - The `RewardsClaimed` event should be emitted for each token type claimed. ### Smaller Constraints @@ -371,7 +359,6 @@ Collateral implementors who extend from [Fiat Collateral](../contracts/plugins/a - `tryPrice()` (not on the ICollateral interface; used by `price()`/`lotPrice()`/`refresh()`) - `refPerTok()` - `targetPerRef()` -- `claimRewards()` ### refresh() @@ -441,6 +428,8 @@ Should never revert. Should return a lower and upper estimate for the price of the token on secondary markets. +The difference between the upper and lower estimate should not exceed 5%, though this is not a hard-and-fast rule. When the difference (usually arising from an oracleError) is large, it can lead to [the price estimation of the RToken](../contracts/plugins/assets/RTokenAsset.sol) somewhat degrading. While this is not usually an issue it can come into play when one RToken is using another RToken as collateral either directly or indirectly through an LP token. If there is RSR overcollateralization then this issue is mitigated. + Lower estimate must be <= upper estimate. Should return `(0, FIX_MAX)` if pricing data is unavailable or stale. @@ -490,6 +479,6 @@ If implementing a demurrage-based collateral plugin, make sure your targetName f ## Practical Advice from Previous Work -In most cases [Fiat Collateral](../contracts/plugins/asset/FiatCollateral.sol) can be extended, pretty easily, to support a new collateral type. This allows the collateral developer to limit their attention to the overriding of four functions: `tryPrice()`, `refPerTok()`, `targetPerRef()`, `claimRewards()`. +In most cases [Fiat Collateral](../contracts/plugins/asset/FiatCollateral.sol) can be extended, pretty easily, to support a new collateral type. This allows the collateral developer to limit their attention to the overriding of three functions: `tryPrice()`, `refPerTok()`, `targetPerRef()`. If you're quite stuck, you might also find it useful to read through our other Collateral plugins as models, found in our repository in `/contracts/plugins/assets`. diff --git a/docs/mev.md b/docs/mev.md index 8d2cfdf714..7d32e64687 100644 --- a/docs/mev.md +++ b/docs/mev.md @@ -27,7 +27,7 @@ Bidding instructions from the `DutchTrade` contract: `DutchTrade` (relevant) interface: ```solidity -function bid() external; // execute a bid at the current block timestamp +function bid() external; // execute a bid at the current block number function sell() external view returns (IERC20); @@ -37,7 +37,7 @@ function status() external view returns (uint8); // 0: not_started, 1: active, 2 function lot() external view returns (uint256); // {qSellTok} the number of tokens being sold -function bidAmount(uint48 timestamp) external view returns (uint256); // {qBuyTok} the number of tokens required to buy the lot, at a particular timestamp +function bidAmount(uint256 blockNumber) external view returns (uint256); // {qBuyTok} the number of tokens required to buy the lot, at a particular block number ``` @@ -45,7 +45,7 @@ To participate: 1. Call `status()` view; the auction is ongoing if return value is 1 2. Call `lot()` to see the number of tokens being sold -3. Call `bidAmount()` to see the number of tokens required to buy the lot, at various timestamps +3. Call `bidAmount()` to see the number of tokens required to buy the lot, at various block numbers 4. After finding an attractive bidAmount, provide an approval for the `buy()` token. The spender should be the `DutchTrade` contract. **Note**: it is very important to set tight approvals! Do not set more than the `bidAmount()` for the desired bidding block else reorgs present risk. 5. Wait until the desired block is reached (hopefully not in the first 40% of the auction) diff --git a/docs/writing-collateral-plugins.md b/docs/writing-collateral-plugins.md index 01fe3d75d4..be05b3ec64 100644 --- a/docs/writing-collateral-plugins.md +++ b/docs/writing-collateral-plugins.md @@ -27,6 +27,7 @@ Here are some basic questions to answer before beginning to write a new collater 7. **What amount of revenue should this plugin hide? (a minimum of `1e-6`% is recommended, but some collateral may require higher thresholds, and, in rare cases, `0` can be used)** 8. **Are there rewards that can be claimed by holding this collateral? If so, how are they claimed?** Include a github link to the callable function or an example of how to claim. 9. **Does the collateral need to be "refreshed" in order to update its internal state before refreshing the plugin?** Include a github link to the callable function. +10. **Can the `price()` range be kept <5%? What is the largest possible % difference (while priced) between `price().high` and `price().low`?** See [RTokenAsset.tryPrice()](../contracts/plugins/assets/RTokenAsset.sol) and [docs/collateral.md](./collateral.md#price) for additional context. ## Implementation From b31005bf95f79fa6c1014fe063790905b89fb3b2 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Fri, 1 Sep 2023 00:28:07 -0300 Subject: [PATCH 411/462] Upg checker 3.0 (#909) Co-authored-by: Taylor Brent Co-authored-by: Patrick McKelvy --- .../upgrade-checker-utils/constants.ts | 16 +- .../upgrade-checker-utils/governance.ts | 6 +- tasks/testing/upgrade-checker-utils/logs.ts | 2 + .../testing/upgrade-checker-utils/oracles.ts | 27 +- .../testing/upgrade-checker-utils/rewards.ts | 4 +- .../testing/upgrade-checker-utils/rtokens.ts | 193 ++++++- tasks/testing/upgrade-checker-utils/trades.ts | 213 +++++++- .../upgrade-checker-utils/upgrades/3_0_0.ts | 481 ++++++++++++++++++ tasks/testing/upgrade-checker.ts | 171 ++++--- 9 files changed, 981 insertions(+), 132 deletions(-) create mode 100644 tasks/testing/upgrade-checker-utils/upgrades/3_0_0.ts diff --git a/tasks/testing/upgrade-checker-utils/constants.ts b/tasks/testing/upgrade-checker-utils/constants.ts index a58d60a7fe..518a48adc3 100644 --- a/tasks/testing/upgrade-checker-utils/constants.ts +++ b/tasks/testing/upgrade-checker-utils/constants.ts @@ -4,14 +4,22 @@ export const whales: { [key: string]: string } = { [networkConfig['1'].tokens.USDT!.toLowerCase()]: '0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503', [networkConfig['1'].tokens.USDC!.toLowerCase()]: '0x756D64Dc5eDb56740fC617628dC832DDBCfd373c', [networkConfig['1'].tokens.RSR!.toLowerCase()]: '0x6bab6EB87Aa5a1e4A8310C73bDAAA8A5dAAd81C1', - [networkConfig['1'].tokens.cUSDT!.toLowerCase()]: '0x5754284f345afc66a98fbB0a0Afe71e0F007B949', - [networkConfig['1'].tokens.aUSDT!.toLowerCase()]: '0x5754284f345afc66a98fbB0a0Afe71e0F007B949', + [networkConfig['1'].tokens.cUSDT!.toLowerCase()]: '0xb99CC7e10Fe0Acc68C50C7829F473d81e23249cc', + [networkConfig['1'].tokens.aUSDT!.toLowerCase()]: '0x0B6B712B0f3998961Cd3109341b00c905b16124A', ['0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9'.toLowerCase()]: '0x5754284f345afc66a98fbB0a0Afe71e0F007B949', // saUSDT - [networkConfig['1'].tokens.aUSDC!.toLowerCase()]: '0x51eDF02152EBfb338e03E30d65C15fBf06cc9ECC', - [networkConfig['1'].tokens.cUSDC!.toLowerCase()]: '0x51eDF02152EBfb338e03E30d65C15fBf06cc9ECC', + // TODO: Replace with real address + ['0x840748F7Fd3EA956E5f4c88001da5CC1ABCBc038'.toLowerCase()]: + '0x5754284f345afc66a98fbB0a0Afe71e0F007B949', // cUSDTVault + + [networkConfig['1'].tokens.aUSDC!.toLowerCase()]: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', + [networkConfig['1'].tokens.cUSDC!.toLowerCase()]: '0x97D868b5C2937355Bf89C5E5463d52016240fE86', ['0x60C384e226b120d93f3e0F4C502957b2B9C32B15'.toLowerCase()]: '0x51eDF02152EBfb338e03E30d65C15fBf06cc9ECC', // saUSDC + // TODO: Replace with real address + ['0xf201fFeA8447AB3d43c98Da3349e0749813C9009'.toLowerCase()]: + '0x51eDF02152EBfb338e03E30d65C15fBf06cc9ECC', // cUSDCVault + [networkConfig['1'].tokens.RSR!.toLowerCase()]: '0x6bab6EB87Aa5a1e4A8310C73bDAAA8A5dAAd81C1', [networkConfig['1'].tokens.WBTC!.toLowerCase()]: '0x8eb8a3b98659cce290402893d0123abb75e3ab28', [networkConfig['1'].tokens.stETH!.toLowerCase()]: '0x176F3DAb24a159341c0509bB36B833E7fdd0a132', diff --git a/tasks/testing/upgrade-checker-utils/governance.ts b/tasks/testing/upgrade-checker-utils/governance.ts index 7b312efb34..1003f1e0ee 100644 --- a/tasks/testing/upgrade-checker-utils/governance.ts +++ b/tasks/testing/upgrade-checker-utils/governance.ts @@ -5,6 +5,7 @@ import { Delegate, Proposal, getDelegates, getProposalDetails } from '#/utils/su import { advanceBlocks, advanceTime } from '#/utils/time' import { BigNumber, PopulatedTransaction } from 'ethers' import { HardhatRuntimeEnvironment } from 'hardhat/types' +import { pushOraclesForward } from './oracles' export const passAndExecuteProposal = async ( hre: HardhatRuntimeEnvironment, @@ -105,6 +106,7 @@ export const passAndExecuteProposal = async ( // Advance time required by timelock await advanceTime(hre, minDelay.add(1).toString()) await advanceBlocks(hre, 1) + await pushOraclesForward(hre, rtokenAddress) // Execute await governor.execute(proposal.targets, proposal.values, proposal.calldatas, descriptionHash) @@ -122,10 +124,8 @@ export const passAndExecuteProposal = async ( export const stakeAndDelegateRsr = async ( hre: HardhatRuntimeEnvironment, rtokenAddress: string, - governorAddress: string, user: string ) => { - const governor = await hre.ethers.getContractAt('Governance', governorAddress) const rToken = await hre.ethers.getContractAt('RTokenP1', rtokenAddress) const main = await hre.ethers.getContractAt('IMain', await rToken.main()) const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) @@ -167,7 +167,7 @@ export const proposeUpgrade = async ( const [tester] = await hre.ethers.getSigners() await hre.run('give-rsr', { address: tester.address }) - await stakeAndDelegateRsr(hre, rTokenAddress, governorAddress, tester.address) + await stakeAndDelegateRsr(hre, rTokenAddress, tester.address) const proposal = await proposalBuilder(hre, rTokenAddress, governorAddress) diff --git a/tasks/testing/upgrade-checker-utils/logs.ts b/tasks/testing/upgrade-checker-utils/logs.ts index ec25ff1afb..756c7594d5 100644 --- a/tasks/testing/upgrade-checker-utils/logs.ts +++ b/tasks/testing/upgrade-checker-utils/logs.ts @@ -44,6 +44,8 @@ const tokens: { [key: string]: string } = { [networkConfig['1'].tokens.DAI!.toLowerCase()]: 'DAI', ['0x60C384e226b120d93f3e0F4C502957b2B9C32B15'.toLowerCase()]: 'saUSDC', ['0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9'.toLowerCase()]: 'saUSDT', + ['0xf201fFeA8447AB3d43c98Da3349e0749813C9009'.toLowerCase()]: 'cUSDCVault', // TODO: Replace with real address + ['0x840748F7Fd3EA956E5f4c88001da5CC1ABCBc038'.toLowerCase()]: 'cUSDTVault', // TODO: Replace with real address } export const logToken = (tokenAddress: string) => { diff --git a/tasks/testing/upgrade-checker-utils/oracles.ts b/tasks/testing/upgrade-checker-utils/oracles.ts index 198592464b..aa5d536188 100644 --- a/tasks/testing/upgrade-checker-utils/oracles.ts +++ b/tasks/testing/upgrade-checker-utils/oracles.ts @@ -1,12 +1,16 @@ import { setCode } from '@nomicfoundation/hardhat-network-helpers' import { EACAggregatorProxyMock } from '@typechain/EACAggregatorProxyMock' import { HardhatRuntimeEnvironment } from 'hardhat/types' +import { BigNumber } from 'ethers' export const overrideOracle = async ( hre: HardhatRuntimeEnvironment, oracleAddress: string ): Promise => { - const oracle = await hre.ethers.getContractAt('EACAggregatorProxy', oracleAddress) + const oracle = await hre.ethers.getContractAt( + 'contracts/plugins/mocks/EACAggregatorProxyMock.sol:EACAggregatorProxy', + oracleAddress + ) const aggregator = await oracle.aggregator() const accessController = await oracle.accessController() const initPrice = await oracle.latestRoundData() @@ -48,3 +52,24 @@ export const pushOracleForward = async (hre: HardhatRuntimeEnvironment, asset: s const oracle = await overrideOracle(hre, realChainlinkFeed.address) await oracle.updateAnswer(initPrice.answer) } + +export const setOraclePrice = async ( + hre: HardhatRuntimeEnvironment, + asset: string, + value: BigNumber +) => { + const assetContract = await hre.ethers.getContractAt('TestIAsset', asset) + let chainlinkFeed = '' + try { + chainlinkFeed = await assetContract.chainlinkFeed() + } catch { + console.log(`no chainlink oracle found. skipping RTokenAsset ${asset}...`) + return + } + const realChainlinkFeed = await hre.ethers.getContractAt( + 'AggregatorV3Interface', + await assetContract.chainlinkFeed() + ) + const oracle = await overrideOracle(hre, realChainlinkFeed.address) + await oracle.updateAnswer(value) +} diff --git a/tasks/testing/upgrade-checker-utils/rewards.ts b/tasks/testing/upgrade-checker-utils/rewards.ts index 4281738bc4..22a291619a 100644 --- a/tasks/testing/upgrade-checker-utils/rewards.ts +++ b/tasks/testing/upgrade-checker-utils/rewards.ts @@ -5,7 +5,7 @@ import { advanceBlocks, advanceTime } from '#/utils/time' import { IRewardable } from '@typechain/IRewardable' import { formatEther } from 'ethers/lib/utils' import { HardhatRuntimeEnvironment } from 'hardhat/types' -import { runTrade } from '../upgrade-checker-utils/trades' +import { runBatchTrade } from '../upgrade-checker-utils/trades' const claimRewards = async (claimer: IRewardable) => { const resp = await claimer.claimRewards() @@ -43,7 +43,7 @@ export const claimRsrRewards = async (hre: HardhatRuntimeEnvironment, rtokenAddr }) await rsrTrader.manageTokens([comp], [TradeKind.BATCH_AUCTION]) - await runTrade(hre, rsrTrader, comp, false) + await runBatchTrade(hre, rsrTrader, comp, false) await rsrTrader.manageTokens([rsr.address], [TradeKind.BATCH_AUCTION]) await strsr.payoutRewards() await advanceBlocks(hre, 100) diff --git a/tasks/testing/upgrade-checker-utils/rtokens.ts b/tasks/testing/upgrade-checker-utils/rtokens.ts index 6573909e88..b092d51a8f 100644 --- a/tasks/testing/upgrade-checker-utils/rtokens.ts +++ b/tasks/testing/upgrade-checker-utils/rtokens.ts @@ -1,12 +1,15 @@ import { bn } from '#/common/numbers' -import { TradeKind } from '#/common/constants' +import { ONE_PERIOD, TradeKind } from '#/common/constants' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { BigNumber } from 'ethers' -import { Interface, LogDescription, formatEther } from 'ethers/lib/utils' +import { BigNumber, ContractFactory } from 'ethers' +import { formatEther } from 'ethers/lib/utils' +import { advanceTime } from '#/utils/time' +import { fp } from '#/common/numbers' import { HardhatRuntimeEnvironment } from 'hardhat/types' -import { runTrade } from './trades' -import { logToken } from './logs' +import { callAndGetNextTrade, runBatchTrade, runDutchTrade } from './trades' import { CollateralStatus } from '#/common/constants' +import { FacadeAct } from '@typechain/FacadeAct' +import { FacadeRead } from '@typechain/FacadeRead' type Balances = { [key: string]: BigNumber } @@ -78,8 +81,92 @@ export const redeemRTokens = async ( console.log(`successfully redeemed ${formatEther(redeemAmount)} RTokens`) } -export const recollateralize = async (hre: HardhatRuntimeEnvironment, rtokenAddress: string) => { - console.log(`\n\n* * * * * Recollateralizing RToken ${rtokenAddress}...`) +export const customRedeemRTokens = async ( + hre: HardhatRuntimeEnvironment, + user: SignerWithAddress, + rTokenAddress: string, + basketNonce: number, + redeemAmount: BigNumber +) => { + console.log(`\nCustom Redeeming ${formatEther(redeemAmount)}...`) + const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) + const main = await hre.ethers.getContractAt('IMain', await rToken.main()) + + const FacadeReadFactory: ContractFactory = await hre.ethers.getContractFactory('FacadeRead') + const facadeRead = await FacadeReadFactory.deploy() + const redeemQuote = await facadeRead.callStatic.redeemCustom( + rToken.address, + redeemAmount, + [basketNonce], + [fp('1')] + ) + const expectedTokens = redeemQuote[0] + const expectedQuantities = redeemQuote[1] + const expectedBalances: Balances = {} + let log = '' + for (const erc20 in expectedTokens) { + console.log('Token: ', expectedTokens[erc20]) + console.log( + 'Balance: ', + await ( + await hre.ethers.getContractAt('ERC20Mock', expectedTokens[erc20]) + ).balanceOf(await main.backingManager()) + ) + expectedBalances[expectedTokens[erc20]] = expectedQuantities[erc20] + log += `\n\t${expectedTokens[erc20]}: ${expectedQuantities[erc20]}` + } + console.log(`Expecting to receive: ${log}`) + + const preRedeemRTokenBal = await rToken.balanceOf(user.address) + const preRedeemErc20Bals = await getAccountBalances(hre, user.address, expectedTokens) + + await rToken.connect(user).redeemCustom( + user.address, + redeemAmount, + [basketNonce], + [fp('1')], + expectedTokens, + expectedQuantities.map((q) => q.mul(99).div(100)) + ) + const postRedeemRTokenBal = await rToken.balanceOf(user.address) + const postRedeemErc20Bals = await getAccountBalances(hre, user.address, expectedTokens) + + for (const erc20 of expectedTokens) { + const receivedBalance = postRedeemErc20Bals[erc20].sub(preRedeemErc20Bals[erc20]) + if (!closeTo(receivedBalance, expectedBalances[erc20], bn(1))) { + throw new Error( + `Did not receive the correct amount of token from custom redemption \n token: ${erc20} \n received: ${receivedBalance} \n expected: ${expectedBalances[erc20]}` + ) + } + } + + if (!preRedeemRTokenBal.sub(postRedeemRTokenBal).eq(redeemAmount)) { + throw new Error( + `Did not custom redeem the correct amount of RTokens \n expected: ${redeemAmount} \n redeemed: ${postRedeemRTokenBal.sub( + preRedeemRTokenBal + )}` + ) + } + + console.log(`successfully custom redeemed ${formatEther(redeemAmount)} RTokens`) +} + +export const recollateralize = async ( + hre: HardhatRuntimeEnvironment, + rtokenAddress: string, + kind: TradeKind +) => { + if (kind == TradeKind.BATCH_AUCTION) { + await recollateralizeBatch(hre, rtokenAddress) + } else if (kind == TradeKind.DUTCH_AUCTION) { + await recollateralizeDutch(hre, rtokenAddress) + } else { + throw new Error(`Invalid Trade Type`) + } +} + +const recollateralizeBatch = async (hre: HardhatRuntimeEnvironment, rtokenAddress: string) => { + console.log(`\n\n* * * * * Recollateralizing (Batch) RToken ${rtokenAddress}...`) const rToken = await hre.ethers.getContractAt('RTokenP1', rtokenAddress) const main = await hre.ethers.getContractAt('IMain', await rToken.main()) const backingManager = await hre.ethers.getContractAt( @@ -91,31 +178,30 @@ export const recollateralize = async (hre: HardhatRuntimeEnvironment, rtokenAddr await main.basketHandler() ) - let r = await backingManager.rebalance(TradeKind.BATCH_AUCTION) + // Deploy FacadeAct + const FacadeActFactory: ContractFactory = await hre.ethers.getContractFactory('FacadeAct') + const facadeAct = await FacadeActFactory.deploy() + + // Move post trading delay + await advanceTime(hre, (await backingManager.tradingDelay()) + 1) - const iface: Interface = backingManager.interface + //const iface: Interface = backingManager.interface let tradesRemain = true while (tradesRemain) { - tradesRemain = false - const resp = await r.wait() - for (const event of resp.events!) { - let parsedLog: LogDescription | undefined - try { - parsedLog = iface.parseLog(event) - } catch {} - if (parsedLog && parsedLog.name == 'TradeStarted') { - tradesRemain = true - console.log( - `\n====== Trade Started: sell ${logToken(parsedLog.args.sell)} / buy ${logToken( - parsedLog.args.buy - )} ======\n\tmbuyAmount: ${parsedLog.args.minBuyAmount}\n\tsellAmount: ${ - parsedLog.args.sellAmount - }` - ) - await runTrade(hre, backingManager, parsedLog.args.sell, false) - } + const [newTradeCreated, newSellToken] = await callAndGetNextTrade( + backingManager.rebalance(TradeKind.BATCH_AUCTION), + backingManager + ) + + if (newTradeCreated) { + await runBatchTrade(hre, backingManager, newSellToken, false) } - r = await backingManager.rebalance(TradeKind.BATCH_AUCTION) + + // Set tradesRemain + ;[tradesRemain, , ,] = await facadeAct.callStatic.nextRecollateralizationAuction( + backingManager.address + ) + await advanceTime(hre, ONE_PERIOD.toString()) } const basketStatus = await basketHandler.status() @@ -123,5 +209,56 @@ export const recollateralize = async (hre: HardhatRuntimeEnvironment, rtokenAddr throw new Error(`Basket is not SOUND after recollateralizing new basket`) } + if (!(await basketHandler.fullyCollateralized())) { + throw new Error(`Basket is not fully collateralized!`) + } + + console.log('Recollateralization complete!') +} + +const recollateralizeDutch = async (hre: HardhatRuntimeEnvironment, rtokenAddress: string) => { + console.log(`\n\n* * * * * Recollateralizing (Dutch) RToken ${rtokenAddress}...`) + const rToken = await hre.ethers.getContractAt('RTokenP1', rtokenAddress) + + const main = await hre.ethers.getContractAt('IMain', await rToken.main()) + const backingManager = await hre.ethers.getContractAt( + 'BackingManagerP1', + await main.backingManager() + ) + const basketHandler = await hre.ethers.getContractAt( + 'BasketHandlerP1', + await main.basketHandler() + ) + + // Move post trading delay + await advanceTime(hre, (await backingManager.tradingDelay()) + 1) + + let tradesRemain = false + let sellToken: string = '' + + const [newTradeCreated, initialSellToken] = await callAndGetNextTrade( + backingManager.rebalance(TradeKind.DUTCH_AUCTION), + backingManager + ) + + if (newTradeCreated) { + tradesRemain = true + sellToken = initialSellToken + + while (tradesRemain) { + ;[tradesRemain, sellToken] = await runDutchTrade(hre, backingManager, sellToken) + await advanceTime(hre, ONE_PERIOD.toString()) + } + } + + const basketStatus = await basketHandler.status() + if (basketStatus != CollateralStatus.SOUND) { + throw new Error(`Basket is not SOUND after recollateralizing new basket`) + } + + if (!(await basketHandler.fullyCollateralized())) { + throw new Error(`Basket is not fully collateralized!`) + } + console.log('Recollateralization complete!') } diff --git a/tasks/testing/upgrade-checker-utils/trades.ts b/tasks/testing/upgrade-checker-utils/trades.ts index 17082bcabc..11062484bb 100644 --- a/tasks/testing/upgrade-checker-utils/trades.ts +++ b/tasks/testing/upgrade-checker-utils/trades.ts @@ -1,16 +1,22 @@ import { whileImpersonating } from '#/utils/impersonation' -import { advanceTime, getLatestBlockTimestamp } from '#/utils/time' -import { getTrade } from '#/utils/trades' +import { + advanceBlocks, + advanceTime, + getLatestBlockTimestamp, + getLatestBlockNumber, +} from '#/utils/time' import { TestITrading } from '@typechain/TestITrading' -import { BigNumber } from 'ethers' +import { BigNumber, ContractTransaction } from 'ethers' import { HardhatRuntimeEnvironment } from 'hardhat/types' -import { QUEUE_START } from '#/common/constants' +import { QUEUE_START, TradeKind, TradeStatus } from '#/common/constants' +import { Interface, LogDescription } from 'ethers/lib/utils' import { collateralToUnderlying, whales } from './constants' import { bn, fp } from '#/common/numbers' import { logToken } from './logs' -import { networkConfig } from '#/common/configuration' +import { GnosisTrade } from '@typechain/GnosisTrade' +import { DutchTrade } from '@typechain/DutchTrade' -export const runTrade = async ( +export const runBatchTrade = async ( hre: HardhatRuntimeEnvironment, trader: TestITrading, tradeToken: string, @@ -20,7 +26,14 @@ export const runTrade = async ( // buy & sell are from the perspective of the auction-starter // placeSellOrders() flips it to be from the perspective of the trader - const trade = await getTrade(hre, trader, tradeToken) + const tradeAddr = await trader.trades(tradeToken) + const trade = await hre.ethers.getContractAt('GnosisTrade', tradeAddr) + + // Only works for Batch trades + if ((await trade.KIND()) != TradeKind.BATCH_AUCTION) { + throw new Error(`Invalid Trade Type`) + } + const buyTokenAddress = await trade.buy() console.log(`Running trade: sell ${logToken(tradeToken)} for ${logToken(buyTokenAddress)}...`) const endTime = await trade.endTime() @@ -41,9 +54,15 @@ export const runTrade = async ( buyAmount = buyAmount.add(fp('1').div(bn(10 ** (18 - buyDecimals)))) const gnosis = await hre.ethers.getContractAt('EasyAuction', await trade.gnosis()) - await whileImpersonating(hre, whales[buyTokenAddress.toLowerCase()], async (whale) => { + const whaleAddr = whales[buyTokenAddress.toLowerCase()] + + // For newly wrapped tokens we need to feed the whale + await getTokens(hre, buyTokenAddress, buyAmount, whaleAddr) + + await whileImpersonating(hre, whaleAddr, async (whale) => { const sellToken = await hre.ethers.getContractAt('ERC20Mock', buyTokenAddress) await sellToken.connect(whale).approve(gnosis.address, buyAmount) + await gnosis .connect(whale) .placeSellOrders( @@ -60,25 +79,116 @@ export const runTrade = async ( await trader.settleTrade(tradeToken) console.log(`Settled trade for ${logToken(buyTokenAddress)}.`) } -// impersonate the whale to get the token -const mintTokensIfNeeded = async ( + +export const runDutchTrade = async ( + hre: HardhatRuntimeEnvironment, + trader: TestITrading, + tradeToken: string +): Promise<[boolean, string]> => { + // NOTE: + // buy & sell are from the perspective of the auction-starter + // bid() flips it to be from the perspective of the trader + + let tradesRemain: boolean = false + let newSellToken: string = '' + + const tradeAddr = await trader.trades(tradeToken) + const trade = await hre.ethers.getContractAt('DutchTrade', tradeAddr) + + // Only works for Dutch trades + if ((await trade.KIND()) != TradeKind.DUTCH_AUCTION) { + throw new Error(`Invalid Trade Type`) + } + + const buyTokenAddress = await trade.buy() + console.log(`Running trade: sell ${logToken(tradeToken)} for ${logToken(buyTokenAddress)}...`) + + const endBlock = await trade.endBlock() + const whaleAddr = whales[buyTokenAddress.toLowerCase()] + + // Bid close to end block + await advanceBlocks(hre, endBlock.sub(await getLatestBlockNumber(hre)).sub(5)) + const buyAmount = await trade.bidAmount(await getLatestBlockNumber(hre)) + + // Ensure funds available + await getTokens(hre, buyTokenAddress, buyAmount, whaleAddr) + + await whileImpersonating(hre, whaleAddr, async (whale) => { + const sellToken = await hre.ethers.getContractAt('ERC20Mock', buyTokenAddress) + await sellToken.connect(whale).approve(trade.address, buyAmount) + // Bid + ;[tradesRemain, newSellToken] = await callAndGetNextTrade(trade.connect(whale).bid(), trader) + }) + + if ( + (await trade.canSettle()) || + (await trade.status()) != TradeStatus.CLOSED || + (await trade.bidder()) != whaleAddr + ) { + throw new Error(`Error settling Dutch Trade`) + } + + console.log(`Settled trade for ${logToken(buyTokenAddress)}.`) + + // Return new trade (if exists) + return [tradesRemain, newSellToken] +} + +export const callAndGetNextTrade = async ( + tx: Promise, + trader: TestITrading +): Promise<[boolean, string]> => { + let tradesRemain: boolean = false + let newSellToken: string = '' + + // Process transaction and get next trade + const r = await tx + const resp = await r.wait() + const iface: Interface = trader.interface + for (const event of resp.events!) { + let parsedLog: LogDescription | undefined + try { + parsedLog = iface.parseLog(event) + } catch {} + if (parsedLog && parsedLog.name == 'TradeStarted') { + console.log( + `\n====== Trade Started: sell ${logToken(parsedLog.args.sell)} / buy ${logToken( + parsedLog.args.buy + )} ======\n\tmbuyAmount: ${parsedLog.args.minBuyAmount}\n\tsellAmount: ${ + parsedLog.args.sellAmount + }` + ) + tradesRemain = true + newSellToken = parsedLog.args.sell + } + } + + return [tradesRemain, newSellToken] +} +// impersonate the whale to provide the required tokens to recipient +export const getTokens = async ( hre: HardhatRuntimeEnvironment, tokenAddress: string, amount: BigNumber, recipient: string ) => { switch (tokenAddress) { - case networkConfig['1'].tokens.aUSDC: - case networkConfig['1'].tokens.aUSDT: - await mintAToken(hre, tokenAddress, amount, recipient) - case networkConfig['1'].tokens.cUSDC: - case networkConfig['1'].tokens.cUSDT: - await mintCToken(hre, tokenAddress, amount, recipient) + case '0x60C384e226b120d93f3e0F4C502957b2B9C32B15': // saUSDC + case '0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9': // saUSDT + await getStaticAToken(hre, tokenAddress, amount, recipient) + break + // TODO: Replace with real addresses + case '0xf201fFeA8447AB3d43c98Da3349e0749813C9009': // cUSDCVault + case '0x840748F7Fd3EA956E5f4c88001da5CC1ABCBc038': // cUSDTVault + await getCTokenVault(hre, tokenAddress, amount, recipient) + break default: + await getERC20Tokens(hre, tokenAddress, amount, recipient) return } } +// mint regular cTokens for an amount of `underlying` const mintCToken = async ( hre: HardhatRuntimeEnvironment, tokenAddress: string, @@ -91,18 +201,15 @@ const mintCToken = async ( collateralToUnderlying[tokenAddress.toLowerCase()] ) await whileImpersonating(hre, whales[tokenAddress.toLowerCase()], async (whaleSigner) => { - console.log('0', amount, recipient, collateral.address, underlying.address, whaleSigner.address) await underlying.connect(whaleSigner).approve(collateral.address, amount) - console.log('1', amount, recipient) await collateral.connect(whaleSigner).mint(amount) - console.log('2', amount, recipient) const bal = await collateral.balanceOf(whaleSigner.address) - console.log('3', amount, recipient, bal) await collateral.connect(whaleSigner).transfer(recipient, bal) }) } -const mintAToken = async ( +// mints staticAToken for an amount of `underlying` +const mintStaticAToken = async ( hre: HardhatRuntimeEnvironment, tokenAddress: string, amount: BigNumber, @@ -113,8 +220,66 @@ const mintAToken = async ( 'ERC20Mock', collateralToUnderlying[tokenAddress.toLowerCase()] ) - await whileImpersonating(hre, whales[tokenAddress.toLowerCase()], async (usdtSigner) => { - await underlying.connect(usdtSigner).approve(collateral.address, amount) - await collateral.connect(usdtSigner).deposit(recipient, amount, 0, true) + await whileImpersonating(hre, whales[tokenAddress.toLowerCase()], async (whaleSigner) => { + await underlying.connect(whaleSigner).approve(collateral.address, amount) + await collateral.connect(whaleSigner).deposit(recipient, amount, 0, true) + }) +} + +// get a specific amount of wrapped cTokens +const getCTokenVault = async ( + hre: HardhatRuntimeEnvironment, + tokenAddress: string, + amount: BigNumber, + recipient: string +) => { + const collateral = await hre.ethers.getContractAt('CTokenWrapper', tokenAddress) + const cToken = await hre.ethers.getContractAt('ICToken', await collateral.underlying()) + + await whileImpersonating(hre, whales[cToken.address.toLowerCase()], async (whaleSigner) => { + await cToken.connect(whaleSigner).transfer(recipient, amount) + }) + + await whileImpersonating(hre, recipient, async (recipientSigner) => { + await cToken.connect(recipientSigner).approve(collateral.address, amount) + await collateral.connect(recipientSigner).deposit(amount, recipient) + }) +} + +// get a specific amount of static aTokens +const getStaticAToken = async ( + hre: HardhatRuntimeEnvironment, + tokenAddress: string, + amount: BigNumber, + recipient: string +) => { + const collateral = await hre.ethers.getContractAt('StaticATokenLM', tokenAddress) + const aTokensNeeded = await collateral.staticToDynamicAmount(amount) + const aToken = await hre.ethers.getContractAt( + '@aave/protocol-v2/contracts/interfaces/IAToken.sol:IAToken', + await collateral.ATOKEN() + ) + + await whileImpersonating(hre, whales[aToken.address.toLowerCase()], async (whaleSigner) => { + await aToken.connect(whaleSigner).transfer(recipient, aTokensNeeded.mul(101).div(100)) // buffer to ensure enough balance + }) + + await whileImpersonating(hre, recipient, async (recipientSigner) => { + const bal = await aToken.balanceOf(recipientSigner.address) + await aToken.connect(recipientSigner).approve(collateral.address, bal) + await collateral.connect(recipientSigner).deposit(recipient, bal, 0, false) + }) +} + +// get a specific amount of erc20 plain token +const getERC20Tokens = async ( + hre: HardhatRuntimeEnvironment, + tokenAddress: string, + amount: BigNumber, + recipient: string +) => { + const token = await hre.ethers.getContractAt('ERC20Mock', tokenAddress) + await whileImpersonating(hre, whales[token.address.toLowerCase()], async (whaleSigner) => { + await token.connect(whaleSigner).transfer(recipient, amount) }) } diff --git a/tasks/testing/upgrade-checker-utils/upgrades/3_0_0.ts b/tasks/testing/upgrade-checker-utils/upgrades/3_0_0.ts new file mode 100644 index 0000000000..e7b772ca6a --- /dev/null +++ b/tasks/testing/upgrade-checker-utils/upgrades/3_0_0.ts @@ -0,0 +1,481 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types' +import { expect } from 'chai' +import { ProposalBuilder, buildProposal } from '../governance' +import { Proposal } from '#/utils/subgraph' +import { IImplementations, networkConfig } from '#/common/configuration' +import { bn, fp, toBNDecimals } from '#/common/numbers' +import { CollateralStatus, TradeKind, ZERO_ADDRESS } from '#/common/constants' +import { setOraclePrice } from '../oracles' +import { whileImpersonating } from '#/utils/impersonation' +import { whales } from '../constants' +import { getTokens, runDutchTrade } from '../trades' +import { + AssetRegistryP1, + BackingManagerP1, + BasketHandlerP1, + BasketLibP1, + BrokerP1, + CTokenFiatCollateral, + DistributorP1, + EURFiatCollateral, + FurnaceP1, + MockV3Aggregator, + GnosisTrade, + IERC20Metadata, + DutchTrade, + RevenueTraderP1, + RTokenP1, + StRSRP1Votes, + MainP1, + RecollateralizationLibP1, +} from '../../../../typechain' +import { advanceTime, getLatestBlockTimestamp, setNextBlockTimestamp } from '#/utils/time' + +export default async ( + hre: HardhatRuntimeEnvironment, + rTokenAddress: string, + governorAddress: string +) => { + console.log('\n* * * * * Run checks for release 3.0.0...') + const [tester] = await hre.ethers.getSigners() + const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) + const main = await hre.ethers.getContractAt('IMain', await rToken.main()) + const governor = await hre.ethers.getContractAt('Governance', governorAddress) + const timelockAddress = await governor.timelock() + const assetRegistry = await hre.ethers.getContractAt( + 'AssetRegistryP1', + await main.assetRegistry() + ) + const basketHandler = await hre.ethers.getContractAt( + 'BasketHandlerP1', + await main.basketHandler() + ) + const backingManager = await hre.ethers.getContractAt( + 'BackingManagerP1', + await main.backingManager() + ) + const furnace = await hre.ethers.getContractAt('FurnaceP1', await main.furnace()) + const rsrTrader = await hre.ethers.getContractAt('RevenueTraderP1', await main.rsrTrader()) + const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) + const rsr = await hre.ethers.getContractAt('StRSRP1Votes', await main.rsr()) + + /* + Asset Registry - new getters + */ + const nextTimestamp = (await getLatestBlockTimestamp(hre)) + 10 + await setNextBlockTimestamp(hre, nextTimestamp) + await assetRegistry.refresh() + expect(await assetRegistry.lastRefresh()).to.equal(nextTimestamp) + expect(await assetRegistry.size()).to.equal(16) + + /* + New Basket validations - units and weights + */ + const usdcCollat = await assetRegistry.toColl(networkConfig['1'].tokens.USDC!) + const usdcFiatColl = await hre.ethers.getContractAt('FiatCollateral', usdcCollat) + const usdc = await hre.ethers.getContractAt('USDCMock', await usdcFiatColl.erc20()) + + // Attempt to change target weights in basket + await whileImpersonating(hre, timelockAddress, async (tl) => { + await expect( + basketHandler.connect(tl).setPrimeBasket([usdc.address], [fp('20')]) + ).to.be.revertedWith('new target weights') + }) + + // Attempt to change target unit in basket + const eurt = await hre.ethers.getContractAt('ERC20Mock', networkConfig['1'].tokens.EURT!) + const EURFiatCollateralFactory = await hre.ethers.getContractFactory('EURFiatCollateral') + const feedMock = ( + await (await hre.ethers.getContractFactory('MockV3Aggregator')).deploy(8, bn('1e8')) + ) + const eurFiatCollateral = await EURFiatCollateralFactory.deploy( + { + priceTimeout: bn('604800'), + chainlinkFeed: feedMock.address, + oracleError: fp('0.01'), + erc20: eurt.address, + maxTradeVolume: fp('1000'), + oracleTimeout: await usdcFiatColl.oracleTimeout(), + targetName: hre.ethers.utils.formatBytes32String('EUR'), + defaultThreshold: fp('0.01'), + delayUntilDefault: bn('86400'), + }, + feedMock.address, + await usdcFiatColl.oracleTimeout() + ) + await eurFiatCollateral.refresh() + + // Attempt to set basket with an EUR token + await whileImpersonating(hre, timelockAddress, async (tl) => { + await assetRegistry.connect(tl).register(eurFiatCollateral.address) + await expect( + basketHandler.connect(tl).setPrimeBasket([eurt.address], [fp('1')]) + ).to.be.revertedWith('new target weights') + await assetRegistry.connect(tl).unregister(eurFiatCollateral.address) + }) + + /* + Main - Pausing issuance and trading + */ + // Can pause/unpause issuance and trading separately + await whileImpersonating(hre, timelockAddress, async (tl) => { + await main.connect(tl).pauseIssuance() + + await expect(rToken.connect(tester).issue(fp('100'))).to.be.revertedWith( + 'frozen or issuance paused' + ) + + await main.connect(tl).unpauseIssuance() + + await expect(rToken.connect(tester).issue(fp('100'))).to.emit(rToken, 'Issuance') + + await main.connect(tl).pauseTrading() + + await expect(backingManager.connect(tester).forwardRevenue([])).to.be.revertedWith( + 'frozen or trading paused' + ) + + await main.connect(tl).unpauseTrading() + + await expect(backingManager.connect(tester).forwardRevenue([])).to.not.be.reverted + }) + + /* + Dust Auctions + */ + const minTrade = bn('1e18') + const minTradePrev = await rsrTrader.minTradeVolume() + await whileImpersonating(hre, timelockAddress, async (tl) => { + await rsrTrader.connect(tl).setMinTradeVolume(minTrade) + }) + await usdcFiatColl.refresh() + + const dustAmount = bn('1e17') + await getTokens(hre, usdc.address, toBNDecimals(dustAmount, 6), tester.address) + await usdc.connect(tester).transfer(rsrTrader.address, toBNDecimals(dustAmount, 6)) + + await expect(rsrTrader.manageTokens([usdc.address], [TradeKind.DUTCH_AUCTION])).to.emit( + rsrTrader, + 'TradeStarted' + ) + + await runDutchTrade(hre, rsrTrader, usdc.address) + + // Restore values + await whileImpersonating(hre, timelockAddress, async (tl) => { + await rsrTrader.connect(tl).setMinTradeVolume(minTradePrev) + }) + + /* + Warmup period + */ + const usdcChainlinkFeed = await hre.ethers.getContractAt( + 'AggregatorV3Interface', + await usdcFiatColl.chainlinkFeed() + ) + + const roundData = await usdcChainlinkFeed.latestRoundData() + await setOraclePrice(hre, usdcFiatColl.address, bn('0.8e8')) + await assetRegistry.refresh() + expect(await usdcFiatColl.status()).to.equal(CollateralStatus.IFFY) + expect(await basketHandler.status()).to.equal(CollateralStatus.IFFY) + expect(await basketHandler.isReady()).to.equal(false) + + // Restore SOUND + await setOraclePrice(hre, usdcFiatColl.address, roundData.answer) + await assetRegistry.refresh() + + // Still cannot issue + expect(await usdcFiatColl.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.isReady()).to.equal(false) + await expect(rToken.connect(tester).issue(fp('1'))).to.be.revertedWith('basket not ready') + + // Move post warmup period + await advanceTime(hre, Number(await basketHandler.warmupPeriod()) + 1) + + // Can issue now + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await basketHandler.isReady()).to.equal(true) + await expect(rToken.connect(tester).issue(fp('1'))).to.emit(rToken, 'Issuance') + + /* + Melting occurs when paused + */ + await whileImpersonating(hre, timelockAddress, async (tl) => { + await main.connect(tl).pauseIssuance() + await main.connect(tl).pauseTrading() + + await furnace.melt() + + await main.connect(tl).unpauseIssuance() + await main.connect(tl).unpauseTrading() + }) + + /* + Stake and delegate + */ + const stakeAmount = fp('4e6') + + await whileImpersonating(hre, whales[networkConfig['1'].tokens.RSR!], async (rsrSigner) => { + expect(await stRSR.delegates(rsrSigner.address)).to.equal(ZERO_ADDRESS) + expect(await stRSR.balanceOf(rsrSigner.address)).to.equal(0) + + await rsr.connect(rsrSigner).approve(stRSR.address, stakeAmount) + await stRSR.connect(rsrSigner).stakeAndDelegate(stakeAmount, rsrSigner.address) + + expect(await stRSR.delegates(rsrSigner.address)).to.equal(rsrSigner.address) + expect(await stRSR.balanceOf(rsrSigner.address)).to.be.gt(0) + }) + + console.log('\n3.0.0 check succeeded!') +} + +export const proposal_3_0_0: ProposalBuilder = async ( + hre: HardhatRuntimeEnvironment, + rTokenAddress: string +): Promise => { + const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) + const main = await hre.ethers.getContractAt('MainP1', await rToken.main()) + const assetRegistry = await hre.ethers.getContractAt( + 'AssetRegistryP1', + await main.assetRegistry() + ) + const backingManager = await hre.ethers.getContractAt( + 'BackingManagerP1', + await main.backingManager() + ) + const basketHandler = await hre.ethers.getContractAt( + 'BasketHandlerP1', + await main.basketHandler() + ) + const broker = await hre.ethers.getContractAt('BrokerP1', await main.broker()) + const distributor = await hre.ethers.getContractAt('DistributorP1', await main.distributor()) + const furnace = await hre.ethers.getContractAt('FurnaceP1', await main.furnace()) + const rsrTrader = await hre.ethers.getContractAt('RevenueTraderP1', await main.rsrTrader()) + const rTokenTrader = await hre.ethers.getContractAt('RevenueTraderP1', await main.rTokenTrader()) + const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) + + // TODO: Uncomment and replace with deployed addresses once they are available + /* + const mainImplAddr = '0x...' + const batchTradeImplAddr = '0x...' + const dutchTradeImplAddr = '0x...' + const assetRegImplAddr = '0x...' + const bckMgrImplAddr = '0x...' + const bsktHdlImplAddr = '0x...' + const brokerImplAddr = '0x...' + const distImplAddr = '0x...' + const furnaceImplAddr = '0x...' + const rsrTraderImplAddr = '0x...' + const rTokenTraderImplAddr = '0x...' + const rTokenImplAddr = '0x...' + const stRSRImplAddr = '0x...' + */ + + // TODO: Remove code once addresses are available + const implementations: IImplementations = await deployNewImplementations(hre) + const mainImplAddr = implementations.main + const batchTradeImplAddr = implementations.trading.gnosisTrade + const dutchTradeImplAddr = implementations.trading.dutchTrade + const assetRegImplAddr = implementations.components.assetRegistry + const bckMgrImplAddr = implementations.components.backingManager + const bsktHdlImplAddr = implementations.components.basketHandler + const brokerImplAddr = implementations.components.broker + const distImplAddr = implementations.components.distributor + const furnaceImplAddr = implementations.components.furnace + const rsrTraderImplAddr = implementations.components.rsrTrader + const rTokenTraderImplAddr = implementations.components.rTokenTrader + const rTokenImplAddr = implementations.components.rToken + const stRSRImplAddr = implementations.components.stRSR + + // TODO: Uncomment and replace with deployed addresses once they are available + /* + const cUSDCVaultAddr = '0x...' + const cUSDCNewCollateralAddr = '0x...' + const cUSDTVaultAddr = '0x...' + const cUSDTNewCollateralAddr = '0x...' + */ + const saUSDCCollateralAddr = '0x60C384e226b120d93f3e0F4C502957b2B9C32B15' + const saUSDTCollateralAddr = '0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9' + + // TODO: Remove code once addresses are available + // CUSDC Vault and collateral + const [cUSDCVaultAddr, cUSDCVaultCollateralAddr] = await makeCTokenVaultCollateral( + hre, + networkConfig['1'].tokens.cUSDC!, + await assetRegistry.toColl(networkConfig['1'].tokens.cUSDC!), + networkConfig['1'].COMPTROLLER! + ) + const [cUSDTVaultAddr, cUSDTVaultCollateralAddr] = await makeCTokenVaultCollateral( + hre, + networkConfig['1'].tokens.cUSDT!, + await assetRegistry.toColl(networkConfig['1'].tokens.cUSDT!), + networkConfig['1'].COMPTROLLER! + ) + + // Step 1 - Update implementations and config + const txs = [ + await main.populateTransaction.upgradeTo(mainImplAddr), + await assetRegistry.populateTransaction.upgradeTo(assetRegImplAddr), + await backingManager.populateTransaction.upgradeTo(bckMgrImplAddr), + await basketHandler.populateTransaction.upgradeTo(bsktHdlImplAddr), + await broker.populateTransaction.upgradeTo(brokerImplAddr), + await distributor.populateTransaction.upgradeTo(distImplAddr), + await furnace.populateTransaction.upgradeTo(furnaceImplAddr), + await rsrTrader.populateTransaction.upgradeTo(rsrTraderImplAddr), + await rTokenTrader.populateTransaction.upgradeTo(rTokenTraderImplAddr), + await rToken.populateTransaction.upgradeTo(rTokenImplAddr), + await stRSR.populateTransaction.upgradeTo(stRSRImplAddr), + await broker.populateTransaction.setBatchTradeImplementation(batchTradeImplAddr), + await broker.populateTransaction.setDutchTradeImplementation(dutchTradeImplAddr), + await backingManager.populateTransaction.cacheComponents(), + await rsrTrader.populateTransaction.cacheComponents(), + await rTokenTrader.populateTransaction.cacheComponents(), + await distributor.populateTransaction.cacheComponents(), + await basketHandler.populateTransaction.setWarmupPeriod(900), + await stRSR.populateTransaction.setWithdrawalLeak(bn('5e16')), + await broker.populateTransaction.setDutchAuctionLength(1800), + ] + + // Step 2 - Basket change + txs.push( + await assetRegistry.populateTransaction.register(cUSDCVaultCollateralAddr), + await assetRegistry.populateTransaction.register(cUSDTVaultCollateralAddr), + await basketHandler.populateTransaction.setPrimeBasket( + [saUSDCCollateralAddr, cUSDCVaultAddr, saUSDTCollateralAddr, cUSDTVaultAddr], + [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.25')] + ), + await basketHandler.populateTransaction.refreshBasket() + ) + + const description = + 'Upgrade implementations, set trade plugins, components, config values, and update basket' + + return buildProposal(txs, description) +} + +// TODO: Remove once final addresses exist on Mainnet +const deployNewImplementations = async ( + hre: HardhatRuntimeEnvironment +): Promise => { + // Deploy new implementations + const MainImplFactory = await hre.ethers.getContractFactory('MainP1') + const mainImpl: MainP1 = await MainImplFactory.deploy() + + // Deploy TradingLib external library + const TradingLibFactory = await hre.ethers.getContractFactory('RecollateralizationLibP1') + const tradingLib: RecollateralizationLibP1 = ( + await TradingLibFactory.deploy() + ) + + // Deploy BasketLib external library + const BasketLibFactory = await hre.ethers.getContractFactory('BasketLibP1') + const basketLib: BasketLibP1 = await BasketLibFactory.deploy() + + const AssetRegImplFactory = await hre.ethers.getContractFactory('AssetRegistryP1') + const assetRegImpl: AssetRegistryP1 = await AssetRegImplFactory.deploy() + + const BackingMgrImplFactory = await hre.ethers.getContractFactory('BackingManagerP1', { + libraries: { + RecollateralizationLibP1: tradingLib.address, + }, + }) + const backingMgrImpl: BackingManagerP1 = await BackingMgrImplFactory.deploy() + + const BskHandlerImplFactory = await hre.ethers.getContractFactory('BasketHandlerP1', { + libraries: { BasketLibP1: basketLib.address }, + }) + const bskHndlrImpl: BasketHandlerP1 = await BskHandlerImplFactory.deploy() + + const DistribImplFactory = await hre.ethers.getContractFactory('DistributorP1') + const distribImpl: DistributorP1 = await DistribImplFactory.deploy() + + const RevTraderImplFactory = await hre.ethers.getContractFactory('RevenueTraderP1') + const revTraderImpl: RevenueTraderP1 = await RevTraderImplFactory.deploy() + + const FurnaceImplFactory = await hre.ethers.getContractFactory('FurnaceP1') + const furnaceImpl: FurnaceP1 = await FurnaceImplFactory.deploy() + + const GnosisTradeImplFactory = await hre.ethers.getContractFactory('GnosisTrade') + const gnosisTrade: GnosisTrade = await GnosisTradeImplFactory.deploy() + + const DutchTradeImplFactory = await hre.ethers.getContractFactory('DutchTrade') + const dutchTrade: DutchTrade = await DutchTradeImplFactory.deploy() + + const BrokerImplFactory = await hre.ethers.getContractFactory('BrokerP1') + const brokerImpl: BrokerP1 = await BrokerImplFactory.deploy() + + const RTokenImplFactory = await hre.ethers.getContractFactory('RTokenP1') + const rTokenImpl: RTokenP1 = await RTokenImplFactory.deploy() + + const StRSRImplFactory = await hre.ethers.getContractFactory('StRSRP1Votes') + const stRSRImpl: StRSRP1Votes = await StRSRImplFactory.deploy() + + return { + main: mainImpl.address, + trading: { gnosisTrade: gnosisTrade.address, dutchTrade: dutchTrade.address }, + components: { + assetRegistry: assetRegImpl.address, + backingManager: backingMgrImpl.address, + basketHandler: bskHndlrImpl.address, + broker: brokerImpl.address, + distributor: distribImpl.address, + furnace: furnaceImpl.address, + rsrTrader: revTraderImpl.address, + rTokenTrader: revTraderImpl.address, + rToken: rTokenImpl.address, + stRSR: stRSRImpl.address, + }, + } +} + +// TODO: Remove once final addresses exist on Mainnet +const makeCTokenVaultCollateral = async ( + hre: HardhatRuntimeEnvironment, + tokenAddress: string, + collAddress: string, + comptrollerAddr: string +): Promise<[string, string]> => { + const CTokenWrapperFactory = await hre.ethers.getContractFactory('CTokenWrapper') + const CTokenCollateralFactory = await hre.ethers.getContractFactory('CTokenFiatCollateral') + + const erc20: IERC20Metadata = ( + await hre.ethers.getContractAt('CTokenMock', tokenAddress) + ) + + const currentColl: CTokenFiatCollateral = ( + await hre.ethers.getContractAt('CTokenFiatCollateral', collAddress) + ) + + const vault = await CTokenWrapperFactory.deploy( + erc20.address, + `${await erc20.name()} Vault`, + `${await erc20.symbol()}-VAULT`, + comptrollerAddr + ) + + await vault.deployed() + + const coll = await CTokenCollateralFactory.deploy( + { + priceTimeout: await currentColl.priceTimeout(), + chainlinkFeed: await currentColl.chainlinkFeed(), + oracleError: await currentColl.oracleError(), + erc20: vault.address, + maxTradeVolume: await currentColl.maxTradeVolume(), + oracleTimeout: await currentColl.oracleTimeout(), + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.0125').toString(), + delayUntilDefault: await currentColl.delayUntilDefault(), + }, + fp('1e-6') + ) + + await coll.deployed() + + await (await coll.refresh()).wait() + + return [vault.address, coll.address] +} diff --git a/tasks/testing/upgrade-checker.ts b/tasks/testing/upgrade-checker.ts index 19d82d4fb1..aeb3dedf8e 100644 --- a/tasks/testing/upgrade-checker.ts +++ b/tasks/testing/upgrade-checker.ts @@ -3,18 +3,28 @@ import { networkConfig } from '../../common/configuration' import { getChainId } from '../../common/blockchain-utils' import { whileImpersonating } from '#/utils/impersonation' import { useEnv } from '#/utils/env' +import { expect } from 'chai' import { resetFork } from '#/utils/chain' import { bn, fp } from '#/common/numbers' +import { TradeKind } from '#/common/constants' import { formatEther, formatUnits } from 'ethers/lib/utils' import { pushOraclesForward } from './upgrade-checker-utils/oracles' -import { recollateralize, redeemRTokens } from './upgrade-checker-utils/rtokens' +import { + recollateralize, + redeemRTokens, + customRedeemRTokens, +} from './upgrade-checker-utils/rtokens' import { claimRsrRewards } from './upgrade-checker-utils/rewards' import { whales } from './upgrade-checker-utils/constants' -import runChecks2_1_0, { proposal_2_1_0 } from './upgrade-checker-utils/upgrades/2_1_0' -import { passAndExecuteProposal, proposeUpgrade } from './upgrade-checker-utils/governance' +import runChecks3_0_0, { proposal_3_0_0 } from './upgrade-checker-utils/upgrades/3_0_0' +import { + passAndExecuteProposal, + proposeUpgrade, + stakeAndDelegateRsr, +} from './upgrade-checker-utils/governance' import { advanceBlocks, advanceTime, getLatestBlockNumber } from '#/utils/time' -// run script for eUSD +// run script for eUSD (version 3.0.0) // npx hardhat upgrade-checker --rtoken 0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F --governor 0x7e880d8bD9c9612D6A9759F96aCD23df4A4650E6 /* @@ -59,9 +69,10 @@ task('upgrade-checker', 'Mints all the tokens to an address') console.log(`starting at block ${await getLatestBlockNumber(hre)}`) - // 1. Approve and execute the govnerance proposal + // 1. Approve and execute the governance proposal if (!params.proposalid) { - const proposal = await proposeUpgrade(hre, params.rtoken, params.governor, proposal_2_1_0) + const proposal = await proposeUpgrade(hre, params.rtoken, params.governor, proposal_3_0_0) + await passAndExecuteProposal( hre, params.rtoken, @@ -76,15 +87,9 @@ task('upgrade-checker', 'Mints all the tokens to an address') // we pushed the chain forward, so we need to keep the rToken SOUND await pushOraclesForward(hre, params.rtoken) - // 2. Run various checks - const saUsdtAddress = '0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9'.toLowerCase() - const saUsdcAddress = '0x60C384e226b120d93f3e0F4C502957b2B9C32B15'.toLowerCase() - const usdtAddress = networkConfig['1'].tokens.USDT! - const usdcAddress = networkConfig['1'].tokens.USDC! - const cUsdtAddress = networkConfig['1'].tokens.cUSDT! - const cUsdcAddress = networkConfig['1'].tokens.cUSDC! - const rToken = await hre.ethers.getContractAt('RTokenP1', params.rtoken) + + // 2. Bring back to fully collateralized const main = await hre.ethers.getContractAt('IMain', await rToken.main()) const basketHandler = await hre.ethers.getContractAt( 'BasketHandlerP1', @@ -94,9 +99,25 @@ task('upgrade-checker', 'Mints all the tokens to an address') 'BackingManagerP1', await main.backingManager() ) + const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) + + // Move past trading delay + await advanceTime(hre, (await backingManager.tradingDelay()) + 1) + + await recollateralize(hre, rToken.address, TradeKind.DUTCH_AUCTION) // DUTCH_AUCTION + + // 3. Run various checks + const saUsdtAddress = '0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9'.toLowerCase() + const saUsdcAddress = '0x60C384e226b120d93f3e0F4C502957b2B9C32B15'.toLowerCase() + const usdtAddress = networkConfig['1'].tokens.USDT! + const usdcAddress = networkConfig['1'].tokens.USDC! + const cUsdtAddress = networkConfig['1'].tokens.cUSDT! + const cUsdcAddress = networkConfig['1'].tokens.cUSDC! + const cUsdtVaultAddress = '0x840748F7Fd3EA956E5f4c88001da5CC1ABCBc038'.toLowerCase() + const cUsdcVaultAddress = '0xf201fFeA8447AB3d43c98Da3349e0749813C9009'.toLowerCase() /* - + mint this is another area that needs to be made general @@ -110,8 +131,10 @@ task('upgrade-checker', 'Mints all the tokens to an address') const usdc = await hre.ethers.getContractAt('ERC20Mock', usdcAddress) const saUsdt = await hre.ethers.getContractAt('StaticATokenLM', saUsdtAddress) const cUsdt = await hre.ethers.getContractAt('ICToken', cUsdtAddress) + const cUsdtVault = await hre.ethers.getContractAt('CTokenWrapper', cUsdtVaultAddress) const saUsdc = await hre.ethers.getContractAt('StaticATokenLM', saUsdcAddress) const cUsdc = await hre.ethers.getContractAt('ICToken', cUsdcAddress) + const cUsdcVault = await hre.ethers.getContractAt('CTokenWrapper', cUsdcVaultAddress) // get saUsdt await whileImpersonating( @@ -125,20 +148,21 @@ task('upgrade-checker', 'Mints all the tokens to an address') const saUsdtBal = await saUsdt.balanceOf(tester.address) await saUsdt.connect(tester).approve(rToken.address, saUsdtBal) - // get cUsdt + // get cUsdtVault await whileImpersonating( hre, whales[networkConfig['1'].tokens.USDT!.toLowerCase()], async (usdtSigner) => { - console.log(cUsdt.address, usdt.address, usdtSigner.address) await usdt.connect(usdtSigner).approve(cUsdt.address, initialBal) await cUsdt.connect(usdtSigner).mint(initialBal) const bal = await cUsdt.balanceOf(usdtSigner.address) - await cUsdt.connect(usdtSigner).transfer(tester.address, bal) + await cUsdt.connect(usdtSigner).approve(cUsdtVault.address, bal) + await cUsdtVault.connect(usdtSigner).deposit(bal, tester.address) } ) - const cUsdtBal = await cUsdt.balanceOf(tester.address) - await cUsdt.connect(tester).approve(rToken.address, cUsdtBal) + + const cUsdtVaultBal = await cUsdtVault.balanceOf(tester.address) + await cUsdtVault.connect(tester).approve(rToken.address, cUsdtVaultBal) // get saUsdc await whileImpersonating( @@ -152,7 +176,7 @@ task('upgrade-checker', 'Mints all the tokens to an address') const saUsdcBal = await saUsdc.balanceOf(tester.address) await saUsdc.connect(tester).approve(rToken.address, saUsdcBal) - // get cUsdc + // get cUsdcVault await whileImpersonating( hre, whales[networkConfig['1'].tokens.USDC!.toLowerCase()], @@ -160,11 +184,12 @@ task('upgrade-checker', 'Mints all the tokens to an address') await usdc.connect(usdcSigner).approve(cUsdc.address, initialBal) await cUsdc.connect(usdcSigner).mint(initialBal) const bal = await cUsdc.balanceOf(usdcSigner.address) - await cUsdc.connect(usdcSigner).transfer(tester.address, bal) + await cUsdc.connect(usdcSigner).approve(cUsdcVault.address, bal) + await cUsdcVault.connect(usdcSigner).deposit(bal, tester.address) } ) - const cUsdcBal = await cUsdc.balanceOf(tester.address) - await cUsdc.connect(tester).approve(rToken.address, cUsdcBal) + const cUsdcVaultBal = await cUsdcVault.balanceOf(tester.address) + await cUsdcVault.connect(tester).approve(rToken.address, cUsdcVaultBal) console.log(`\nIssuing ${formatEther(issueAmount)} RTokens...`) await rToken.connect(tester).issue(issueAmount) @@ -180,46 +205,6 @@ task('upgrade-checker', 'Mints all the tokens to an address') console.log('successfully minted RTokens') - // get saUsdt - await whileImpersonating( - hre, - whales[networkConfig['1'].tokens.USDT!.toLowerCase()], - async (usdtSigner) => { - await usdt.connect(usdtSigner).approve(saUsdt.address, initialBal.mul(20)) - await saUsdt.connect(usdtSigner).deposit(usdtSigner.address, initialBal.mul(20), 0, true) - } - ) - - // get cUsdt - await whileImpersonating( - hre, - whales[networkConfig['1'].tokens.USDT!.toLowerCase()], - async (usdtSigner) => { - console.log(cUsdt.address, usdt.address, usdtSigner.address) - await usdt.connect(usdtSigner).approve(cUsdt.address, initialBal.mul(20)) - await cUsdt.connect(usdtSigner).mint(initialBal.mul(20)) - } - ) - - // get saUsdc - await whileImpersonating( - hre, - whales[networkConfig['1'].tokens.USDC!.toLowerCase()], - async (usdcSigner) => { - await usdc.connect(usdcSigner).approve(saUsdc.address, initialBal.mul(20)) - await saUsdc.connect(usdcSigner).deposit(usdcSigner.address, initialBal.mul(20), 0, true) - } - ) - - // get cUsdc - await whileImpersonating( - hre, - whales[networkConfig['1'].tokens.USDC!.toLowerCase()], - async (usdcSigner) => { - await usdc.connect(usdcSigner).approve(cUsdc.address, initialBal.mul(20)) - await cUsdc.connect(usdcSigner).mint(initialBal.mul(20)) - } - ) /* redeem @@ -228,8 +213,12 @@ task('upgrade-checker', 'Mints all the tokens to an address') const redeemAmount = fp('5e4') await redeemRTokens(hre, tester, params.rtoken, redeemAmount) - // 2. Run the 2.1.0 checks - await runChecks2_1_0(hre, params.rtoken, params.governor) + // 3. Run the 3.0.0 checks + await pushOraclesForward(hre, params.rtoken) + await runChecks3_0_0(hre, params.rtoken, params.governor) + + // we pushed the chain forward, so we need to keep the rToken SOUND + await pushOraclesForward(hre, params.rtoken) /* @@ -240,20 +229,49 @@ task('upgrade-checker', 'Mints all the tokens to an address') /* - switch basket and recollateralize + staking/unstaking + + */ + + // get RSR + const stakeAmount = fp('4e6') + const rsr = await hre.ethers.getContractAt('StRSRP1Votes', await main.rsr()) + await whileImpersonating( + hre, + whales[networkConfig['1'].tokens.RSR!.toLowerCase()], + async (rsrSigner) => { + await rsr.connect(rsrSigner).transfer(tester.address, stakeAmount) + } + ) + + const balPrevRSR = await rsr.balanceOf(stRSR.address) + const balPrevStRSR = await stRSR.balanceOf(tester.address) + + await stakeAndDelegateRsr(hre, rToken.address, tester.address) + + expect(await rsr.balanceOf(stRSR.address)).to.equal(balPrevRSR.add(stakeAmount)) + expect(await stRSR.balanceOf(tester.address)).to.be.gt(balPrevStRSR) + + /* + + switch basket and recollateralize - using Batch Auctions + Also check for custom redemption */ + + // we pushed the chain forward, so we need to keep the rToken SOUND await pushOraclesForward(hre, params.rtoken) const bas = await basketHandler.getPrimeBasket() console.log(bas.erc20s) + const prevNonce = await basketHandler.nonce() const governor = await hre.ethers.getContractAt('Governance', params.governor) const timelockAddress = await governor.timelock() await whileImpersonating(hre, timelockAddress, async (tl) => { await basketHandler .connect(tl) - .setPrimeBasket([saUsdtAddress, cUsdtAddress], [fp('0.5'), fp('0.5')]) + .setPrimeBasket([saUsdtAddress, cUsdtVaultAddress], [fp('0.5'), fp('0.5')]) await basketHandler.connect(tl).refreshBasket() const tradingDelay = await backingManager.tradingDelay() await advanceBlocks(hre, tradingDelay / 12 + 1) @@ -263,12 +281,25 @@ task('upgrade-checker', 'Mints all the tokens to an address') const b = await basketHandler.getPrimeBasket() console.log(b.erc20s) - await recollateralize(hre, rToken.address) + /* + custom redemption + */ + // Cannot do normal redeem + expect(await basketHandler.fullyCollateralized()).to.equal(false) + await expect(rToken.connect(tester).redeem(redeemAmount)).to.be.revertedWith( + 'partial redemption; use redeemCustom' + ) + + // Do custom redemption on previous basket + await customRedeemRTokens(hre, tester, params.rtoken, prevNonce, redeemAmount) + + // Recollateralize using Batch auctions + await recollateralize(hre, rToken.address, TradeKind.BATCH_AUCTION) }) task('propose', 'propose a gov action') .addParam('rtoken', 'the address of the RToken being upgraded') .addParam('governor', 'the address of the OWNER of the RToken being upgraded') .setAction(async (params, hre) => { - await proposeUpgrade(hre, params.rtoken, params.governor, proposal_2_1_0) + await proposeUpgrade(hre, params.rtoken, params.governor, proposal_3_0_0) }) From 5d04fbbb9c1ffca953d965190e1180dcb304a887 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 17:07:00 -0400 Subject: [PATCH 412/462] price(): fix possible overflow threat --- contracts/plugins/assets/Asset.sol | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/contracts/plugins/assets/Asset.sol b/contracts/plugins/assets/Asset.sol index 6f135ab342..7c52e64c1e 100644 --- a/contracts/plugins/assets/Asset.sol +++ b/contracts/plugins/assets/Asset.sol @@ -139,7 +139,7 @@ contract Asset is IAsset, VersionedAsset { _low = savedLowPrice; _high = savedHighPrice; } else if (delta >= oracleTimeout + priceTimeout) { - // use unpriced after a full timeout, incase 3x was not enough + // unpriced after a full timeout return (0, FIX_MAX); } else { // oracleTimeout <= delta <= oracleTimeout + priceTimeout @@ -147,13 +147,14 @@ contract Asset is IAsset, VersionedAsset { // Decay _low downwards from savedLowPrice to 0 // {UoA/tok} = {UoA/tok} * {1} _low = savedLowPrice.muluDivu(oracleTimeout + priceTimeout - delta, priceTimeout); + // during overflow should revert // Decay _high upwards to 3x savedHighPrice - _high = savedHighPrice.plus( - savedHighPrice.mul( - MAX_HIGH_PRICE_BUFFER.muluDivu(delta - oracleTimeout, priceTimeout) - ) - ); + // {UoA/tok} = {UoA/tok} * {1} + _high = savedHighPrice.safeMul( + FIX_ONE + MAX_HIGH_PRICE_BUFFER.muluDivu(delta - oracleTimeout, priceTimeout), + ROUND + ); // during overflow should not revert } } assert(_low <= _high); From 3a198ee77fc6c3e8abd8e823ec18aa46a00f1e3a Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Fri, 1 Sep 2023 18:13:22 -0300 Subject: [PATCH 413/462] Additional checks and logging (#929) --- .../testing/upgrade-checker-utils/rtokens.ts | 14 +- tasks/testing/upgrade-checker-utils/trades.ts | 1 + .../upgrade-checker-utils/upgrades/3_0_0.ts | 154 +++++++++++++++++- tasks/testing/upgrade-checker.ts | 7 +- 4 files changed, 157 insertions(+), 19 deletions(-) diff --git a/tasks/testing/upgrade-checker-utils/rtokens.ts b/tasks/testing/upgrade-checker-utils/rtokens.ts index b092d51a8f..8b76df7908 100644 --- a/tasks/testing/upgrade-checker-utils/rtokens.ts +++ b/tasks/testing/upgrade-checker-utils/rtokens.ts @@ -90,7 +90,6 @@ export const customRedeemRTokens = async ( ) => { console.log(`\nCustom Redeeming ${formatEther(redeemAmount)}...`) const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) - const main = await hre.ethers.getContractAt('IMain', await rToken.main()) const FacadeReadFactory: ContractFactory = await hre.ethers.getContractFactory('FacadeRead') const facadeRead = await FacadeReadFactory.deploy() @@ -105,13 +104,6 @@ export const customRedeemRTokens = async ( const expectedBalances: Balances = {} let log = '' for (const erc20 in expectedTokens) { - console.log('Token: ', expectedTokens[erc20]) - console.log( - 'Balance: ', - await ( - await hre.ethers.getContractAt('ERC20Mock', expectedTokens[erc20]) - ).balanceOf(await main.backingManager()) - ) expectedBalances[expectedTokens[erc20]] = expectedQuantities[erc20] log += `\n\t${expectedTokens[erc20]}: ${expectedQuantities[erc20]}` } @@ -197,11 +189,13 @@ const recollateralizeBatch = async (hre: HardhatRuntimeEnvironment, rtokenAddres await runBatchTrade(hre, backingManager, newSellToken, false) } + await advanceTime(hre, ONE_PERIOD.toString()) + // Set tradesRemain ;[tradesRemain, , ,] = await facadeAct.callStatic.nextRecollateralizationAuction( - backingManager.address + backingManager.address, + TradeKind.BATCH_AUCTION ) - await advanceTime(hre, ONE_PERIOD.toString()) } const basketStatus = await basketHandler.status() diff --git a/tasks/testing/upgrade-checker-utils/trades.ts b/tasks/testing/upgrade-checker-utils/trades.ts index 11062484bb..d45a377e54 100644 --- a/tasks/testing/upgrade-checker-utils/trades.ts +++ b/tasks/testing/upgrade-checker-utils/trades.ts @@ -77,6 +77,7 @@ export const runBatchTrade = async ( const lastTimestamp = await getLatestBlockTimestamp(hre) await advanceTime(hre, BigNumber.from(endTime).sub(lastTimestamp).toString()) await trader.settleTrade(tradeToken) + console.log(`Settled trade for ${logToken(buyTokenAddress)}.`) } diff --git a/tasks/testing/upgrade-checker-utils/upgrades/3_0_0.ts b/tasks/testing/upgrade-checker-utils/upgrades/3_0_0.ts index e7b772ca6a..49d5fc4753 100644 --- a/tasks/testing/upgrade-checker-utils/upgrades/3_0_0.ts +++ b/tasks/testing/upgrade-checker-utils/upgrades/3_0_0.ts @@ -5,7 +5,7 @@ import { Proposal } from '#/utils/subgraph' import { IImplementations, networkConfig } from '#/common/configuration' import { bn, fp, toBNDecimals } from '#/common/numbers' import { CollateralStatus, TradeKind, ZERO_ADDRESS } from '#/common/constants' -import { setOraclePrice } from '../oracles' +import { pushOraclesForward, setOraclePrice } from '../oracles' import { whileImpersonating } from '#/utils/impersonation' import { whales } from '../constants' import { getTokens, runDutchTrade } from '../trades' @@ -29,7 +29,12 @@ import { MainP1, RecollateralizationLibP1, } from '../../../../typechain' -import { advanceTime, getLatestBlockTimestamp, setNextBlockTimestamp } from '#/utils/time' +import { + advanceTime, + advanceToTimestamp, + getLatestBlockTimestamp, + setNextBlockTimestamp, +} from '#/utils/time' export default async ( hre: HardhatRuntimeEnvironment, @@ -54,11 +59,15 @@ export default async ( 'BackingManagerP1', await main.backingManager() ) + const broker = await hre.ethers.getContractAt('BrokerP1', await main.broker()) const furnace = await hre.ethers.getContractAt('FurnaceP1', await main.furnace()) const rsrTrader = await hre.ethers.getContractAt('RevenueTraderP1', await main.rsrTrader()) const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) const rsr = await hre.ethers.getContractAt('StRSRP1Votes', await main.rsr()) + // we pushed the chain forward, so we need to keep the rToken SOUND + await pushOraclesForward(hre, rTokenAddress) + /* Asset Registry - new getters */ @@ -67,6 +76,7 @@ export default async ( await assetRegistry.refresh() expect(await assetRegistry.lastRefresh()).to.equal(nextTimestamp) expect(await assetRegistry.size()).to.equal(16) + console.log(`successfully tested new AssetRegistry getters`) /* New Basket validations - units and weights @@ -114,6 +124,8 @@ export default async ( await assetRegistry.connect(tl).unregister(eurFiatCollateral.address) }) + console.log(`successfully tested validations of weights and units on basket switch`) + /* Main - Pausing issuance and trading */ @@ -140,12 +152,28 @@ export default async ( await expect(backingManager.connect(tester).forwardRevenue([])).to.not.be.reverted }) + console.log(`successfully tested issuance and trading pause`) + + /* + New setters for enabling auctions + */ + // Auction setters + await whileImpersonating(hre, timelockAddress, async (tl) => { + await broker.connect(tl).enableBatchTrade() + await broker.connect(tl).enableDutchTrade(rsr.address) + }) + + console.log(`successfully tested new auction setters`) + /* Dust Auctions */ + console.log(`testing dust auctions...`) + const minTrade = bn('1e18') const minTradePrev = await rsrTrader.minTradeVolume() await whileImpersonating(hre, timelockAddress, async (tl) => { + await broker.connect(tl).setDutchAuctionLength(1800) await rsrTrader.connect(tl).setMinTradeVolume(minTrade) }) await usdcFiatColl.refresh() @@ -164,11 +192,17 @@ export default async ( // Restore values await whileImpersonating(hre, timelockAddress, async (tl) => { await rsrTrader.connect(tl).setMinTradeVolume(minTradePrev) + await broker.connect(tl).setDutchAuctionLength(0) }) + console.log(`succesfully tested dust auctions`) + /* Warmup period */ + + console.log(`testing warmup period...`) + const usdcChainlinkFeed = await hre.ethers.getContractAt( 'AggregatorV3Interface', await usdcFiatColl.chainlinkFeed() @@ -188,21 +222,30 @@ export default async ( // Still cannot issue expect(await usdcFiatColl.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.isReady()).to.equal(false) - await expect(rToken.connect(tester).issue(fp('1'))).to.be.revertedWith('basket not ready') - // Move post warmup period - await advanceTime(hre, Number(await basketHandler.warmupPeriod()) + 1) + // If warmup period defined + if ((await basketHandler.warmupPeriod()) > 0) { + expect(await basketHandler.isReady()).to.equal(false) + await expect(rToken.connect(tester).issue(fp('1'))).to.be.revertedWith('basket not ready') + + // Move post warmup period + await advanceTime(hre, Number(await basketHandler.warmupPeriod()) + 1) + } // Can issue now - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) + expect(await usdcFiatColl.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.isReady()).to.equal(true) await expect(rToken.connect(tester).issue(fp('1'))).to.emit(rToken, 'Issuance') + console.log(`succesfully tested warmup period`) + + // we pushed the chain forward, so we need to keep the rToken SOUND + await pushOraclesForward(hre, rTokenAddress) /* Melting occurs when paused */ + await whileImpersonating(hre, timelockAddress, async (tl) => { await main.connect(tl).pauseIssuance() await main.connect(tl).pauseTrading() @@ -212,12 +255,14 @@ export default async ( await main.connect(tl).unpauseIssuance() await main.connect(tl).unpauseTrading() }) + console.log(`successfully tested melting during paused state`) /* Stake and delegate */ - const stakeAmount = fp('4e6') + console.log(`testing stakeAndDelegate...`) + const stakeAmount = fp('4e6') await whileImpersonating(hre, whales[networkConfig['1'].tokens.RSR!], async (rsrSigner) => { expect(await stRSR.delegates(rsrSigner.address)).to.equal(ZERO_ADDRESS) expect(await stRSR.balanceOf(rsrSigner.address)).to.equal(0) @@ -228,6 +273,99 @@ export default async ( expect(await stRSR.delegates(rsrSigner.address)).to.equal(rsrSigner.address) expect(await stRSR.balanceOf(rsrSigner.address)).to.be.gt(0) }) + console.log(`successfully tested stakeAndDelegate`) + + /* + Withdrawal leak + */ + + console.log(`testing withrawalLeak...`) + + // Decrease withdrawal leak to be able to test with previous stake + const withdrawalLeakPrev = await stRSR.withdrawalLeak() + const withdrawalLeak = withdrawalLeakPrev.eq(bn(0)) ? bn(0) : bn('1e5') + const unstakingDelay = await stRSR.unstakingDelay() + + await whileImpersonating(hre, timelockAddress, async (tl) => { + await stRSR.connect(tl).setWithdrawalLeak(withdrawalLeak) + }) + + await whileImpersonating(hre, whales[networkConfig['1'].tokens.RSR!], async (rsrSigner) => { + const withdrawal = stakeAmount + await stRSR.connect(rsrSigner).unstake(1) + await stRSR.connect(rsrSigner).unstake(withdrawal) + await stRSR.connect(rsrSigner).unstake(1) + + // Move forward past stakingWithdrawalDelay + await advanceToTimestamp(hre, Number(await getLatestBlockTimestamp(hre)) + unstakingDelay) + + // we pushed the chain forward, so we need to keep the rToken SOUND + await pushOraclesForward(hre, rTokenAddress) + + let lastRefresh = await assetRegistry.lastRefresh() + + // Should not refresh if withdrawal leak is applied + await stRSR.connect(rsrSigner).withdraw(rsrSigner.address, 1) + if (withdrawalLeak.gt(bn(0))) { + expect(await assetRegistry.lastRefresh()).to.eq(lastRefresh) + } + + // Should refresh + await stRSR.connect(rsrSigner).withdraw(rsrSigner.address, 2) + expect(await assetRegistry.lastRefresh()).to.be.gt(lastRefresh) + lastRefresh = await assetRegistry.lastRefresh() + + // Should not refresh + await stRSR.connect(rsrSigner).withdraw(rsrSigner.address, 3) + if (withdrawalLeak.gt(bn(0))) { + expect(await assetRegistry.lastRefresh()).to.eq(lastRefresh) + } + }) + + // Restore values + await whileImpersonating(hre, timelockAddress, async (tl) => { + await stRSR.connect(tl).setWithdrawalLeak(withdrawalLeakPrev) + }) + console.log(`successfully tested withrawalLeak`) + + // we pushed the chain forward, so we need to keep the rToken SOUND + await pushOraclesForward(hre, rTokenAddress) + + /* + RToken Asset + */ + console.log(`swapping RTokenAsset...`) + + const rTokenAsset = await hre.ethers.getContractAt( + 'TestIAsset', + await assetRegistry.toAsset(rToken.address) + ) + const maxTradeVolumePrev = await rTokenAsset.maxTradeVolume() + + const newRTokenAsset = await ( + await hre.ethers.getContractFactory('RTokenAsset') + ).deploy(rToken.address, maxTradeVolumePrev) + + // Swap RToken Asset + await whileImpersonating(hre, timelockAddress, async (tl) => { + await assetRegistry.connect(tl).swapRegistered(newRTokenAsset.address) + }) + await assetRegistry.refresh() + + // Check interface behaves properly + expect(await newRTokenAsset.isCollateral()).to.equal(false) + expect(await newRTokenAsset.erc20()).to.equal(rToken.address) + expect(await rToken.decimals()).to.equal(18) + expect(await newRTokenAsset.version()).to.equal('3.0.0') + expect(await newRTokenAsset.maxTradeVolume()).to.equal(maxTradeVolumePrev) + + const [lowPricePrev, highPricePrev] = await rTokenAsset.price() + const [lowPrice, highPrice] = await newRTokenAsset.price() + expect(lowPrice).to.equal(lowPricePrev) + expect(highPrice).to.equal(highPricePrev) + + await expect(rTokenAsset.claimRewards()).to.not.emit(rTokenAsset, 'RewardsClaimed') + console.log(`successfully tested RTokenAsset`) console.log('\n3.0.0 check succeeded!') } diff --git a/tasks/testing/upgrade-checker.ts b/tasks/testing/upgrade-checker.ts index aeb3dedf8e..9ee7df67a7 100644 --- a/tasks/testing/upgrade-checker.ts +++ b/tasks/testing/upgrade-checker.ts @@ -99,12 +99,17 @@ task('upgrade-checker', 'Mints all the tokens to an address') 'BackingManagerP1', await main.backingManager() ) + const broker = await hre.ethers.getContractAt('BrokerP1', await main.broker()) const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) // Move past trading delay await advanceTime(hre, (await backingManager.tradingDelay()) + 1) - await recollateralize(hre, rToken.address, TradeKind.DUTCH_AUCTION) // DUTCH_AUCTION + await recollateralize( + hre, + rToken.address, + (await broker.dutchAuctionLength()) > 0 ? TradeKind.DUTCH_AUCTION : TradeKind.BATCH_AUCTION + ) // 3. Run various checks const saUsdtAddress = '0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9'.toLowerCase() From 18f3b9d87953e62de2e8fb32b45155e0116ea8e4 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 17:18:36 -0400 Subject: [PATCH 414/462] enforce UNPRICED if high price overflows --- contracts/plugins/assets/Asset.sol | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/contracts/plugins/assets/Asset.sol b/contracts/plugins/assets/Asset.sol index 7c52e64c1e..bfc786dc68 100644 --- a/contracts/plugins/assets/Asset.sol +++ b/contracts/plugins/assets/Asset.sol @@ -144,17 +144,23 @@ contract Asset is IAsset, VersionedAsset { } else { // oracleTimeout <= delta <= oracleTimeout + priceTimeout - // Decay _low downwards from savedLowPrice to 0 - // {UoA/tok} = {UoA/tok} * {1} - _low = savedLowPrice.muluDivu(oracleTimeout + priceTimeout - delta, priceTimeout); - // during overflow should revert - // Decay _high upwards to 3x savedHighPrice // {UoA/tok} = {UoA/tok} * {1} _high = savedHighPrice.safeMul( FIX_ONE + MAX_HIGH_PRICE_BUFFER.muluDivu(delta - oracleTimeout, priceTimeout), ROUND ); // during overflow should not revert + + // if _high is FIX_MAX, leave at UNPRICED + if (_high != FIX_MAX) { + // Decay _low downwards from savedLowPrice to 0 + // {UoA/tok} = {UoA/tok} * {1} + _low = savedLowPrice.muluDivu( + oracleTimeout + priceTimeout - delta, + priceTimeout + ); + // during overflow should revert since a FIX_MAX _low breaks everything + } } } assert(_low <= _high); From 999c7e397ed63b428218a67339fc5b2b8978e7b3 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 18:18:45 -0400 Subject: [PATCH 415/462] add oracle helpers for testing decay/precise prices --- test/utils/oracles.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/utils/oracles.ts b/test/utils/oracles.ts index 944cf02dc4..81cbd3a0db 100644 --- a/test/utils/oracles.ts +++ b/test/utils/oracles.ts @@ -6,6 +6,13 @@ import { MAX_UINT192 } from '../../common/constants' const toleranceDivisor = bn('1e15') // 1 part in 1000 trillions +export const expectExactPrice = async (assetAddr: string, price: [BigNumber, BigNumber]) => { + const asset = await ethers.getContractAt('Asset', assetAddr) + const [lowPrice, highPrice] = await asset.price() + expect(lowPrice).to.equal(price[0]) + expect(highPrice).to.equal(price[1]) +} + // Expects a price around `avgPrice` assuming a consistent percentage oracle error // If near is truthy, allows a small error of 1 part in 1000 trillions export const expectPrice = async ( @@ -84,6 +91,15 @@ export const expectRTokenPrice = async ( expect(highPrice).to.be.gte(avgPrice) } +export const expectDecayedPrice = async (assetAddr: string) => { + const asset = await ethers.getContractAt('Asset', assetAddr) + const [lowPrice, highPrice] = await asset.price() + expect(lowPrice).to.be.gt(0) + expect(lowPrice).to.be.lt(await asset.savedLowPrice()) + expect(highPrice).to.be.gt(await asset.savedHighPrice()) + expect(highPrice).to.be.lt(MAX_UINT192) +} + // Expects an unpriced asset with low = 0 and high = FIX_MAX export const expectUnpriced = async (assetAddr: string) => { const asset = await ethers.getContractAt('Asset', assetAddr) From 53aeffdb44ce309bb4731bb6b2875abdda5ad941 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 18:19:03 -0400 Subject: [PATCH 416/462] decrease testing oracle timeout --- test/fixtures.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures.ts b/test/fixtures.ts index 15944a43b8..5bf2201390 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -71,7 +71,7 @@ export const SLOW = !!useEnv('SLOW') export const PRICE_TIMEOUT = bn('604800') // 1 week -export const ORACLE_TIMEOUT = bn('281474976710655').div(2) // type(uint48).max / 2 +export const ORACLE_TIMEOUT = bn('281474976710655').div(100) // type(uint48).max / 100 export const ORACLE_ERROR = fp('0.01') // 1% oracle error From 5289955d522d4482de5fcf1a06667727f3631f15 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 18:39:35 -0400 Subject: [PATCH 417/462] Asset.test.ts --- test/plugins/Asset.test.ts | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index 1077360a01..c83e6f41bb 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -260,11 +260,8 @@ describe('Assets contracts #fast', () => { await setOraclePrice(compAsset.address, bn('0')) await setOraclePrice(aaveAsset.address, bn('0')) await setOraclePrice(rsrAsset.address, bn('0')) - - // Should be unpriced - await expectUnpriced(rsrAsset.address) - await expectUnpriced(compAsset.address) - await expectUnpriced(aaveAsset.address) + await setOraclePrice(collateral0.address, bn(0)) + await setOraclePrice(collateral1.address, bn(0)) // Fallback prices should be initial prices let [lotLow, lotHigh] = await compAsset.price() @@ -276,18 +273,18 @@ describe('Assets contracts #fast', () => { ;[lotLow, lotHigh] = await aaveAsset.price() expect(lotLow).to.eq(aaveInitPrice[0]) expect(lotHigh).to.eq(aaveInitPrice[1]) - - // Update values of underlying tokens of RToken to 0 - await setOraclePrice(collateral0.address, bn(0)) - await setOraclePrice(collateral1.address, bn(0)) - - // RTokenAsset should be unpriced now - await expectUnpriced(rTokenAsset.address) - - // Should have initial lot price ;[lotLow, lotHigh] = await rTokenAsset.price() expect(lotLow).to.eq(rTokenInitPrice[0]) expect(lotHigh).to.eq(rTokenInitPrice[1]) + + // Advance past timeouts + await advanceTime(PRICE_TIMEOUT.add(ORACLE_TIMEOUT).toString()) + + // Should be unpriced now + await expectUnpriced(rsrAsset.address) + await expectUnpriced(compAsset.address) + await expectUnpriced(aaveAsset.address) + await expectUnpriced(rTokenAsset.address) }) it('Should return 0 price for RTokenAsset in full haircut scenario', async () => { From acc6722900cfe0615677d32878ec0aded6f91309 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 18:39:43 -0400 Subject: [PATCH 418/462] Collateral.test.ts --- test/plugins/Collateral.test.ts | 154 ++++++++++++++++++++++++-------- 1 file changed, 115 insertions(+), 39 deletions(-) diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index 979af9ad02..d62e3f7c53 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -39,6 +39,8 @@ import { } from '../utils/time' import snapshotGasCost from '../utils/snapshotGasCost' import { + expectDecayedPrice, + expectExactPrice, expectPrice, expectRTokenPrice, expectUnpriced, @@ -582,7 +584,7 @@ describe('Collateral contracts', () => { ) }) - it('Should become unpriced if price is zero', async () => { + it('Should handle prices correctly when price is zero', async () => { const compInitPrice = await tokenCollateral.price() const aaveInitPrice = await aTokenCollateral.price() const rsrInitPrice = await cTokenCollateral.price() @@ -590,11 +592,6 @@ describe('Collateral contracts', () => { // Update values in Oracles to 0 await setOraclePrice(tokenCollateral.address, bn('0')) - // Should be unpriced - await expectUnpriced(cTokenCollateral.address) - await expectUnpriced(tokenCollateral.address) - await expectUnpriced(aTokenCollateral.address) - // Fallback prices should be initial prices let [lotLow, lotHigh] = await tokenCollateral.price() expect(lotLow).to.eq(compInitPrice[0]) @@ -606,6 +603,14 @@ describe('Collateral contracts', () => { expect(lotLow).to.eq(aaveInitPrice[0]) expect(lotHigh).to.eq(aaveInitPrice[1]) + // Advance past timeouts + await advanceTime(PRICE_TIMEOUT.add(ORACLE_TIMEOUT).toString()) + + // Should be unpriced + await expectUnpriced(cTokenCollateral.address) + await expectUnpriced(tokenCollateral.address) + await expectUnpriced(aTokenCollateral.address) + // When refreshed, sets status to Unpriced await tokenCollateral.refresh() await aTokenCollateral.refresh() @@ -1259,6 +1264,8 @@ describe('Collateral contracts', () => { }) it('Should calculate prices correctly', async function () { + const initialPrice = await nonFiatCollateral.price() + // Check initial prices await expectPrice(nonFiatCollateral.address, fp('20000'), ORACLE_ERROR, true) @@ -1268,26 +1275,37 @@ describe('Collateral contracts', () => { // Check new prices await expectPrice(nonFiatCollateral.address, fp('22000'), ORACLE_ERROR, true) - // Unpriced if price is zero - Update Oracles and check prices + // Cached but IFFY if price is zero await targetUnitOracle.updateAnswer(bn('0')) - await expectUnpriced(nonFiatCollateral.address) - - // When refreshed, sets status to IFFY await nonFiatCollateral.refresh() expect(await nonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) + await expectExactPrice(nonFiatCollateral.address, initialPrice) + + // Should become disabled after just ORACLE_TIMEOUT + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + await targetUnitOracle.updateAnswer(bn('0')) + expect(await nonFiatCollateral.status()).to.equal(CollateralStatus.DISABLED) + await expectDecayedPrice(nonFiatCollateral.address) // Restore price await targetUnitOracle.updateAnswer(bn('20000e8')) + await referenceUnitOracle.updateAnswer(bn('1e8')) await nonFiatCollateral.refresh() - expect(await nonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) + await expectExactPrice(nonFiatCollateral.address, initialPrice) - // Check the other oracle + // Check the other oracle's impact await referenceUnitOracle.updateAnswer(bn('0')) - await expectUnpriced(nonFiatCollateral.address) + await expectExactPrice(nonFiatCollateral.address, initialPrice) - // When refreshed, sets status to IFFY - await nonFiatCollateral.refresh() - expect(await nonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) + // Advance past oracle timeout + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + await referenceUnitOracle.updateAnswer(bn('0')) + await expectDecayedPrice(nonFiatCollateral.address) + + // Advance past price timeout + await advanceTime(PRICE_TIMEOUT.toString()) + await referenceUnitOracle.updateAnswer(bn('0')) + await expectUnpriced(nonFiatCollateral.address) }) it('Reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { @@ -1550,47 +1568,64 @@ describe('Collateral contracts', () => { }) it('Should calculate prices correctly', async function () { + // Check initial prices await expectPrice(cTokenNonFiatCollateral.address, fp('400'), ORACLE_ERROR, true) - - // Check refPerTok initial values expect(await cTokenNonFiatCollateral.refPerTok()).to.equal(fp('0.02')) // Increase rate to double await cNonFiatTokenVault.setExchangeRate(fp(2)) await cTokenNonFiatCollateral.refresh() - // Check price doubled - await expectPrice(cTokenNonFiatCollateral.address, fp('800'), ORACLE_ERROR, true) - // RefPerTok also doubles in this case expect(await cTokenNonFiatCollateral.refPerTok()).to.equal(fp('0.04')) + // Check new prices + await expectPrice(cTokenNonFiatCollateral.address, fp('800'), ORACLE_ERROR, true) + // Update values in Oracle increase by 10% await targetUnitOracle.updateAnswer(bn('22000e8')) // $22k // Check new price await expectPrice(cTokenNonFiatCollateral.address, fp('880'), ORACLE_ERROR, true) - // Unpriced if price is zero - Update Oracles and check prices - await targetUnitOracle.updateAnswer(bn('0')) - await expectUnpriced(cTokenNonFiatCollateral.address) + // Should be SOUND + await cTokenNonFiatCollateral.refresh() + expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) - // When refreshed, sets status to IFFY + const initialPrice = await cTokenNonFiatCollateral.price() + + // Cached but IFFY when price becomes zero + await targetUnitOracle.updateAnswer(bn('0')) await cTokenNonFiatCollateral.refresh() expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) + await expectExactPrice(cTokenNonFiatCollateral.address, initialPrice) - // Restore + // Should become disabled after just ORACLE_TIMEOUT + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + await targetUnitOracle.updateAnswer(bn('0')) + expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.DISABLED) + await cTokenNonFiatCollateral.refresh() + await expectDecayedPrice(cTokenNonFiatCollateral.address) + + // Restore price await targetUnitOracle.updateAnswer(bn('22000e8')) + await referenceUnitOracle.updateAnswer(bn('1e8')) await cTokenNonFiatCollateral.refresh() - expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) + await expectExactPrice(cTokenNonFiatCollateral.address, initialPrice) - // Revert if price is zero - Update the other Oracle + // Check the other oracle's impact await referenceUnitOracle.updateAnswer(bn('0')) - await expectUnpriced(cTokenNonFiatCollateral.address) + await expectExactPrice(cTokenNonFiatCollateral.address, initialPrice) - // When refreshed, sets status to IFFY - await cTokenNonFiatCollateral.refresh() - expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) + // Advance past oracle timeout + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + await referenceUnitOracle.updateAnswer(bn('0')) + await expectDecayedPrice(cTokenNonFiatCollateral.address) + + // Advance past price timeout + await advanceTime(PRICE_TIMEOUT.toString()) + await referenceUnitOracle.updateAnswer(bn('0')) + await expectUnpriced(cTokenNonFiatCollateral.address) }) it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { @@ -1767,9 +1802,17 @@ describe('Collateral contracts', () => { // Check new prices await expectPrice(selfReferentialCollateral.address, fp('1.1'), ORACLE_ERROR, true) - // Unpriced if price is zero - Update Oracles and check prices + await selfReferentialCollateral.refresh() + const initialPrice = await selfReferentialCollateral.price() + + // Cached price if oracle price is zero await setOraclePrice(selfReferentialCollateral.address, bn(0)) - await expectUnpriced(selfReferentialCollateral.address) + await expectExactPrice(selfReferentialCollateral.address, initialPrice) + + // Decay starts after oracle timeout + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + await setOraclePrice(selfReferentialCollateral.address, bn(0)) + await expectDecayedPrice(selfReferentialCollateral.address) // When refreshed, sets status to IFFY await selfReferentialCollateral.refresh() @@ -1786,6 +1829,12 @@ describe('Collateral contracts', () => { // Another call would not change the state await selfReferentialCollateral.refresh() expect(await selfReferentialCollateral.status()).to.equal(CollateralStatus.DISABLED) + + // Final price checks + await expectDecayedPrice(selfReferentialCollateral.address) + await advanceTime(PRICE_TIMEOUT.toString()) + await setOraclePrice(selfReferentialCollateral.address, bn(0)) + await expectUnpriced(selfReferentialCollateral.address) }) it('Reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { @@ -1978,13 +2027,26 @@ describe('Collateral contracts', () => { // Check new prices await expectPrice(cTokenSelfReferentialCollateral.address, fp('0.044'), ORACLE_ERROR, true) - // Unpriced if price is zero - Update Oracles and check prices + await cTokenSelfReferentialCollateral.refresh() + const initialPrice = await cTokenSelfReferentialCollateral.price() await setOraclePrice(cTokenSelfReferentialCollateral.address, bn(0)) - await expectUnpriced(cTokenSelfReferentialCollateral.address) + await expectExactPrice(cTokenSelfReferentialCollateral.address, initialPrice) - // When refreshed, sets status to IFFY + // Decays if price is zero await cTokenSelfReferentialCollateral.refresh() expect(await cTokenSelfReferentialCollateral.status()).to.equal(CollateralStatus.IFFY) + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + await setOraclePrice(cTokenSelfReferentialCollateral.address, bn(0)) + await expectDecayedPrice(cTokenSelfReferentialCollateral.address) + + // Unpriced after price timeout + await advanceTime(PRICE_TIMEOUT.toString()) + await setOraclePrice(cTokenSelfReferentialCollateral.address, bn(0)) + await expectUnpriced(cTokenSelfReferentialCollateral.address) + + // When refreshed, sets status to DISABLED + await cTokenSelfReferentialCollateral.refresh() + expect(await cTokenSelfReferentialCollateral.status()).to.equal(CollateralStatus.DISABLED) }) it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { @@ -2221,10 +2283,12 @@ describe('Collateral contracts', () => { // Check new prices await expectPrice(eurFiatCollateral.address, fp('2'), ORACLE_ERROR, true) + await eurFiatCollateral.refresh() + const initialPrice = await eurFiatCollateral.price() - // Unpriced if price is zero - Update Oracles and check prices + // Decays if price is zero await referenceUnitOracle.updateAnswer(bn('0')) - await expectUnpriced(eurFiatCollateral.address) + await expectExactPrice(eurFiatCollateral.address, initialPrice) // When refreshed, sets status to IFFY await eurFiatCollateral.refresh() @@ -2239,6 +2303,18 @@ describe('Collateral contracts', () => { await targetUnitOracle.updateAnswer(bn('0')) await eurFiatCollateral.refresh() expect(await eurFiatCollateral.status()).to.equal(CollateralStatus.IFFY) + + // Decays if price is zero + await referenceUnitOracle.updateAnswer(bn('0')) + await expectExactPrice(eurFiatCollateral.address, initialPrice) + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + await referenceUnitOracle.updateAnswer(bn('0')) + await expectDecayedPrice(eurFiatCollateral.address) + + // After timeout, unpriced + await advanceTime(PRICE_TIMEOUT.toString()) + await referenceUnitOracle.updateAnswer(bn('0')) + await expectUnpriced(eurFiatCollateral.address) }) it('Reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { From e5ffdaf279f6b0c37dead45d409a285fbc578443 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 18:45:47 -0400 Subject: [PATCH 419/462] Asset.test.ts --- test/plugins/Asset.test.ts | 56 ++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index c83e6f41bb..a10b4ce63d 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -12,6 +12,8 @@ import { import { ZERO_ADDRESS, ONE_ADDRESS, MAX_UINT192 } from '../../common/constants' import { bn, fp } from '../../common/numbers' import { + expectDecayedPrice, + expectExactPrice, expectPrice, expectRTokenPrice, expectUnpriced, @@ -260,25 +262,45 @@ describe('Assets contracts #fast', () => { await setOraclePrice(compAsset.address, bn('0')) await setOraclePrice(aaveAsset.address, bn('0')) await setOraclePrice(rsrAsset.address, bn('0')) - await setOraclePrice(collateral0.address, bn(0)) - await setOraclePrice(collateral1.address, bn(0)) + await setOraclePrice(collateral0.address, bn('0')) + await setOraclePrice(collateral1.address, bn('0')) // Fallback prices should be initial prices - let [lotLow, lotHigh] = await compAsset.price() - expect(lotLow).to.eq(compInitPrice[0]) - expect(lotHigh).to.eq(compInitPrice[1]) - ;[lotLow, lotHigh] = await rsrAsset.price() - expect(lotLow).to.eq(rsrInitPrice[0]) - expect(lotHigh).to.eq(rsrInitPrice[1]) - ;[lotLow, lotHigh] = await aaveAsset.price() - expect(lotLow).to.eq(aaveInitPrice[0]) - expect(lotHigh).to.eq(aaveInitPrice[1]) - ;[lotLow, lotHigh] = await rTokenAsset.price() - expect(lotLow).to.eq(rTokenInitPrice[0]) - expect(lotHigh).to.eq(rTokenInitPrice[1]) - - // Advance past timeouts - await advanceTime(PRICE_TIMEOUT.add(ORACLE_TIMEOUT).toString()) + await expectExactPrice(compAsset.address, compInitPrice) + await expectExactPrice(rsrAsset.address, rsrInitPrice) + await expectExactPrice(aaveAsset.address, aaveInitPrice) + await expectExactPrice(rTokenAsset.address, rTokenInitPrice) + + // Advance past oracle timeout + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + await setOraclePrice(compAsset.address, bn('0')) + await setOraclePrice(aaveAsset.address, bn('0')) + await setOraclePrice(rsrAsset.address, bn('0')) + await setOraclePrice(collateral0.address, bn('0')) + await setOraclePrice(collateral1.address, bn('0')) + await compAsset.refresh() + await rsrAsset.refresh() + await aaveAsset.refresh() + await collateral0.refresh() + await collateral1.refresh() + + // Prices should be decaying + await expectDecayedPrice(compAsset.address) + await expectDecayedPrice(rsrAsset.address) + await expectDecayedPrice(aaveAsset.address) + const p = await rTokenAsset.price() + expect(p[0]).to.be.gt(0) + expect(p[0]).to.be.lt(rTokenInitPrice[0]) + expect(p[1]).to.be.gt(rTokenInitPrice[1]) + expect(p[1]).to.be.lt(MAX_UINT192) + + // After price timeout, should be unpriced + await advanceTime(PRICE_TIMEOUT.toString()) + await setOraclePrice(compAsset.address, bn('0')) + await setOraclePrice(aaveAsset.address, bn('0')) + await setOraclePrice(rsrAsset.address, bn('0')) + await setOraclePrice(collateral0.address, bn('0')) + await setOraclePrice(collateral1.address, bn('0')) // Should be unpriced now await expectUnpriced(rsrAsset.address) From 4c80697a5258ccdedc3fe972e85922a89c6af39f Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 19:36:50 -0400 Subject: [PATCH 420/462] test/integration/AssetPlugins.test.ts --- contracts/plugins/assets/Asset.sol | 2 +- contracts/plugins/mocks/AssetMock.sol | 2 +- test/integration/AssetPlugins.test.ts | 193 +++++++++++++++++++------- 3 files changed, 144 insertions(+), 53 deletions(-) diff --git a/contracts/plugins/assets/Asset.sol b/contracts/plugins/assets/Asset.sol index bfc786dc68..ed98e9a1d7 100644 --- a/contracts/plugins/assets/Asset.sol +++ b/contracts/plugins/assets/Asset.sol @@ -40,7 +40,7 @@ contract Asset is IAsset, VersionedAsset { /// @param oracleError_ {1} The % the oracle feed can be off by /// @param maxTradeVolume_ {UoA} The max trade volume, in UoA /// @param oracleTimeout_ {s} The number of seconds until a oracle value becomes invalid - /// @dev oracleTimeout_ is also used as the timeout value in lotPrice(), should be highest of + /// @dev oracleTimeout_ is also used as the timeout value in price(), should be highest of /// all assets' oracleTimeout in a collateral if there are multiple oracles constructor( uint48 priceTimeout_, diff --git a/contracts/plugins/mocks/AssetMock.sol b/contracts/plugins/mocks/AssetMock.sol index b6abe6fffb..c1b495380f 100644 --- a/contracts/plugins/mocks/AssetMock.sol +++ b/contracts/plugins/mocks/AssetMock.sol @@ -12,7 +12,7 @@ contract AssetMock is Asset { /// @param oracleError_ {1} The % the oracle feed can be off by /// @param maxTradeVolume_ {UoA} The max trade volume, in UoA /// @param oracleTimeout_ {s} The number of seconds until a oracle value becomes invalid - /// @dev oracleTimeout_ is also used as the timeout value in lotPrice(), should be highest of + /// @dev oracleTimeout_ is also used as the timeout value in price(), should be highest of /// all assets' oracleTimeout in a collateral if there are multiple oracles constructor( uint48 priceTimeout_, diff --git a/test/integration/AssetPlugins.test.ts b/test/integration/AssetPlugins.test.ts index 15f4b2236d..ccb0089359 100644 --- a/test/integration/AssetPlugins.test.ts +++ b/test/integration/AssetPlugins.test.ts @@ -19,7 +19,14 @@ import { expectEvents } from '../../common/events' import { bn, fp, toBNDecimals } from '../../common/numbers' import { advanceBlocks, advanceTime } from '../utils/time' import { whileImpersonating } from '../utils/impersonation' -import { expectPrice, expectRTokenPrice, expectUnpriced, setOraclePrice } from '../utils/oracles' +import { + expectDecayedPrice, + expectExactPrice, + expectPrice, + expectRTokenPrice, + expectUnpriced, + setOraclePrice, +} from '../utils/oracles' import forkBlockNumber from './fork-block-numbers' import { Asset, @@ -39,6 +46,7 @@ import { MockV3Aggregator, NonFiatCollateral, RTokenAsset, + SelfReferentialCollateral, StaticATokenLM, TestIBackingManager, TestIBasketHandler, @@ -969,7 +977,6 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, cTokenAddress: networkConfig[chainId].tokens.cETH || '', cTokenCollateral: cETHCollateral, price: fp('1859.17'), // approx price June 6, 2022 - refPerTok: fp('0.020064224962890636'), // for weth on June 2022 targetName: 'ETH', }, ] @@ -1114,13 +1121,24 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, ORACLE_ERROR, networkConfig[chainId].tokens.stkAAVE || '', config.rTokenMaxTradeVolume, - MAX_ORACLE_TIMEOUT + ORACLE_TIMEOUT ) ) + await setOraclePrice(zeroPriceAsset.address, bn('1e10')) + await zeroPriceAsset.refresh() + + const initialPrice = await zeroPriceAsset.price() + await setOraclePrice(zeroPriceAsset.address, bn(0)) + await expectExactPrice(zeroPriceAsset.address, initialPrice) + // After oracle timeout, begins decay + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) await setOraclePrice(zeroPriceAsset.address, bn(0)) + await expectDecayedPrice(zeroPriceAsset.address) - // Unpriced + // After price timeout, unpriced + await advanceTime(PRICE_TIMEOUT.toString()) + await setOraclePrice(zeroPriceAsset.address, bn(0)) await expectUnpriced(zeroPriceAsset.address) }) @@ -1183,19 +1201,30 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: dai.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, }) + await setOraclePrice(zeroFiatCollateral.address, bn('1e8')) await zeroFiatCollateral.refresh() + expect(await zeroFiatCollateral.status()).to.equal(CollateralStatus.SOUND) + const initialPrice = await zeroFiatCollateral.price() await setOraclePrice(zeroFiatCollateral.address, bn(0)) + await expectExactPrice(zeroFiatCollateral.address, initialPrice) - // Unpriced + // After oracle timeout, begins decay + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + await setOraclePrice(zeroFiatCollateral.address, bn(0)) + await expectDecayedPrice(zeroFiatCollateral.address) + + // After price timeout, unpriced + await advanceTime(PRICE_TIMEOUT.toString()) + await setOraclePrice(zeroFiatCollateral.address, bn(0)) await expectUnpriced(zeroFiatCollateral.address) - // Refresh should mark status IFFY + // Marked IFFY after refresh await zeroFiatCollateral.refresh() expect(await zeroFiatCollateral.status()).to.equal(CollateralStatus.IFFY) }) @@ -1258,18 +1287,29 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, }, REVENUE_HIDING ) + await setOraclePrice(zeropriceCtokenCollateral.address, bn('1e8')) await zeropriceCtokenCollateral.refresh() + expect(await zeropriceCtokenCollateral.status()).to.equal(CollateralStatus.SOUND) + + const initialPrice = await zeropriceCtokenCollateral.price() + await setOraclePrice(zeropriceCtokenCollateral.address, bn(0)) + await expectExactPrice(zeropriceCtokenCollateral.address, initialPrice) + // After oracle timeout, begins decay + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) await setOraclePrice(zeropriceCtokenCollateral.address, bn(0)) + await expectDecayedPrice(zeropriceCtokenCollateral.address) - // Unpriced + // After price timeout, unpriced + await advanceTime(PRICE_TIMEOUT.toString()) + await setOraclePrice(zeropriceCtokenCollateral.address, bn(0)) await expectUnpriced(zeropriceCtokenCollateral.address) // Refresh should mark status IFFY @@ -1335,18 +1375,29 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: stataDai.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, }, REVENUE_HIDING ) + await setOraclePrice(zeroPriceAtokenCollateral.address, bn('1e8')) await zeroPriceAtokenCollateral.refresh() + expect(await zeroPriceAtokenCollateral.status()).to.equal(CollateralStatus.SOUND) + + const initialPrice = await zeroPriceAtokenCollateral.price() + await setOraclePrice(zeroPriceAtokenCollateral.address, bn(0)) + await expectExactPrice(zeroPriceAtokenCollateral.address, initialPrice) + // After oracle timeout, begins decay + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) await setOraclePrice(zeroPriceAtokenCollateral.address, bn(0)) + await expectDecayedPrice(zeroPriceAtokenCollateral.address) - // Unpriced + // After price timeout, unpriced + await advanceTime(PRICE_TIMEOUT.toString()) + await setOraclePrice(zeroPriceAtokenCollateral.address, bn(0)) await expectUnpriced(zeroPriceAtokenCollateral.address) // Refresh should mark status IFFY @@ -1403,27 +1454,30 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: wbtc.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold, delayUntilDefault, }, mockChainlinkFeed.address, - MAX_ORACLE_TIMEOUT + ORACLE_TIMEOUT ) + await setOraclePrice(zeroPriceNonFiatCollateral.address, bn('1e10')) await zeroPriceNonFiatCollateral.refresh() - // Set price = 0 - const chainlinkFeedAddr = await zeroPriceNonFiatCollateral.chainlinkFeed() - const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) - await v3Aggregator.updateAnswer(bn(0)) + const initialPrice = await zeroPriceNonFiatCollateral.price() + await setOraclePrice(zeroPriceNonFiatCollateral.address, bn(0)) + await expectExactPrice(zeroPriceNonFiatCollateral.address, initialPrice) - // Unpriced - await expectUnpriced(zeroPriceNonFiatCollateral.address) + // After oracle timeout, begins decay + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + await setOraclePrice(zeroPriceNonFiatCollateral.address, bn(0)) + await expectDecayedPrice(zeroPriceNonFiatCollateral.address) - // Refresh should mark status IFFY - await zeroPriceNonFiatCollateral.refresh() - expect(await zeroPriceNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) + // After price timeout, unpriced + await advanceTime(PRICE_TIMEOUT.toString()) + await setOraclePrice(zeroPriceNonFiatCollateral.address, bn(0)) + await expectUnpriced(zeroPriceNonFiatCollateral.address) }) it('Should handle invalid/stale Price - Collateral - CTokens Non-Fiat', async () => { @@ -1480,29 +1534,32 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: cWBTCVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold, delayUntilDefault, }, mockChainlinkFeed.address, - MAX_ORACLE_TIMEOUT, + ORACLE_TIMEOUT, REVENUE_HIDING ) ) + await setOraclePrice(zeropriceCtokenNonFiatCollateral.address, bn('1e10')) await zeropriceCtokenNonFiatCollateral.refresh() - // Set price = 0 - const chainlinkFeedAddr = await zeropriceCtokenNonFiatCollateral.targetUnitChainlinkFeed() - const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) - await v3Aggregator.updateAnswer(bn(0)) + const initialPrice = await zeropriceCtokenNonFiatCollateral.price() + await setOraclePrice(zeropriceCtokenNonFiatCollateral.address, bn(0)) + await expectExactPrice(zeropriceCtokenNonFiatCollateral.address, initialPrice) - // Unpriced - await expectUnpriced(zeropriceCtokenNonFiatCollateral.address) + // After oracle timeout, begins decay + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + await setOraclePrice(zeropriceCtokenNonFiatCollateral.address, bn(0)) + await expectDecayedPrice(zeropriceCtokenNonFiatCollateral.address) - // Refresh should mark status IFFY - await zeropriceCtokenNonFiatCollateral.refresh() - expect(await zeropriceCtokenNonFiatCollateral.status()).to.equal(CollateralStatus.IFFY) + // After price timeout, unpriced + await advanceTime(PRICE_TIMEOUT.toString()) + await setOraclePrice(zeropriceCtokenNonFiatCollateral.address, bn(0)) + await expectUnpriced(zeropriceCtokenNonFiatCollateral.address) }) it('Should handle invalid/stale Price - Collateral - Self-Referential', async () => { @@ -1518,8 +1575,10 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await wethCollateral.status()).to.equal(CollateralStatus.IFFY) // Self referential collateral with no price - const nonpriceSelfReferentialCollateral: FiatCollateral = await ( - await ethers.getContractFactory('FiatCollateral') + const nonpriceSelfReferentialCollateral: SelfReferentialCollateral = < + SelfReferentialCollateral + >await ( + await ethers.getContractFactory('SelfReferentialCollateral') ).deploy({ priceTimeout: PRICE_TIMEOUT, chainlinkFeed: NO_PRICE_DATA_FEED, @@ -1540,28 +1599,40 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, expect(await nonpriceSelfReferentialCollateral.status()).to.equal(CollateralStatus.SOUND) // Self referential collateral with zero price - const zeroPriceSelfReferentialCollateral: FiatCollateral = await ( - await ethers.getContractFactory('FiatCollateral') + const zeroPriceSelfReferentialCollateral: SelfReferentialCollateral = < + SelfReferentialCollateral + >await ( + await ethers.getContractFactory('SelfReferentialCollateral') ).deploy({ priceTimeout: PRICE_TIMEOUT, chainlinkFeed: mockChainlinkFeed.address, oracleError: ORACLE_ERROR, erc20: weth.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: bn('0'), delayUntilDefault, }) + await setOraclePrice(zeroPriceSelfReferentialCollateral.address, bn('1e10')) await zeroPriceSelfReferentialCollateral.refresh() + expect(await zeroPriceSelfReferentialCollateral.status()).to.equal(CollateralStatus.SOUND) + + const initialPrice = await zeroPriceSelfReferentialCollateral.price() + await setOraclePrice(zeroPriceSelfReferentialCollateral.address, bn(0)) + await expectExactPrice(zeroPriceSelfReferentialCollateral.address, initialPrice) - // Set price = 0 + // After oracle timeout, begins decay + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) await setOraclePrice(zeroPriceSelfReferentialCollateral.address, bn(0)) + await expectDecayedPrice(zeroPriceSelfReferentialCollateral.address) - // Unpriced + // After price timeout, unpriced + await advanceTime(PRICE_TIMEOUT.toString()) + await setOraclePrice(zeroPriceSelfReferentialCollateral.address, bn(0)) await expectUnpriced(zeroPriceSelfReferentialCollateral.address) - // Refresh should mark status IFFY + // Refresh should mark status DISABLED await zeroPriceSelfReferentialCollateral.refresh() expect(await zeroPriceSelfReferentialCollateral.status()).to.equal(CollateralStatus.IFFY) }) @@ -1621,7 +1692,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: cETHVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: bn('0'), delayUntilDefault, @@ -1629,12 +1700,24 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, REVENUE_HIDING, await weth.decimals() ) + await setOraclePrice(zeroPriceCtokenSelfReferentialCollateral.address, bn('1e10')) await zeroPriceCtokenSelfReferentialCollateral.refresh() + expect(await zeroPriceCtokenSelfReferentialCollateral.status()).to.equal( + CollateralStatus.SOUND + ) - // Set price = 0 + const initialPrice = await zeroPriceCtokenSelfReferentialCollateral.price() await setOraclePrice(zeroPriceCtokenSelfReferentialCollateral.address, bn(0)) + await expectExactPrice(zeroPriceCtokenSelfReferentialCollateral.address, initialPrice) - // Unpriced + // After oracle timeout, begins decay + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + await setOraclePrice(zeroPriceCtokenSelfReferentialCollateral.address, bn(0)) + await expectDecayedPrice(zeroPriceCtokenSelfReferentialCollateral.address) + + // After price timeout, unpriced + await advanceTime(PRICE_TIMEOUT.toString()) + await setOraclePrice(zeroPriceCtokenSelfReferentialCollateral.address, bn(0)) await expectUnpriced(zeroPriceCtokenSelfReferentialCollateral.address) // Refresh should mark status IFFY @@ -1692,22 +1775,30 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: eurt.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: MAX_ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold, delayUntilDefault, }, mockChainlinkFeed.address, - MAX_ORACLE_TIMEOUT + ORACLE_TIMEOUT ) + await setOraclePrice(invalidPriceEURCollateral.address, bn('1e10')) await invalidPriceEURCollateral.refresh() + expect(await invalidPriceEURCollateral.status()).to.equal(CollateralStatus.SOUND) + + const initialPrice = await invalidPriceEURCollateral.price() + await setOraclePrice(invalidPriceEURCollateral.address, bn(0)) + await expectExactPrice(invalidPriceEURCollateral.address, initialPrice) - // Set price = 0 - const chainlinkFeedAddr = await invalidPriceEURCollateral.targetUnitChainlinkFeed() - const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) - await v3Aggregator.updateAnswer(bn(0)) + // After oracle timeout, begins decay + await advanceTime(ORACLE_TIMEOUT.add(1).toString()) + await setOraclePrice(invalidPriceEURCollateral.address, bn(0)) + await expectDecayedPrice(invalidPriceEURCollateral.address) - // With zero price + // After price timeout, unpriced + await advanceTime(PRICE_TIMEOUT.toString()) + await setOraclePrice(invalidPriceEURCollateral.address, bn(0)) await expectUnpriced(invalidPriceEURCollateral.address) // Refresh should mark status IFFY From 4b1fcf75ca37519fcb0a887b1fea8452b659f63e Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 19:45:02 -0400 Subject: [PATCH 421/462] cap maxTradeSell using low price, not high --- contracts/p0/mixins/TradingLib.sol | 2 +- contracts/p1/mixins/TradeLib.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index d8638e915f..72ae48ad53 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -60,7 +60,7 @@ library TradingLibP0 { ); // Cap sell amount - uint192 maxSell = maxTradeSize(trade.sell, trade.buy, trade.prices.sellHigh); // {sellTok} + uint192 maxSell = maxTradeSize(trade.sell, trade.buy, trade.prices.sellLow); // {sellTok} uint192 s = trade.sellAmount > maxSell ? maxSell : trade.sellAmount; // {sellTok} // Calculate equivalent buyAmount within [0, FIX_MAX] diff --git a/contracts/p1/mixins/TradeLib.sol b/contracts/p1/mixins/TradeLib.sol index f0921dd511..8d3c8e01c9 100644 --- a/contracts/p1/mixins/TradeLib.sol +++ b/contracts/p1/mixins/TradeLib.sol @@ -62,7 +62,7 @@ library TradeLib { ); // Cap sell amount - uint192 maxSell = maxTradeSize(trade.sell, trade.buy, trade.prices.sellHigh); // {sellTok} + uint192 maxSell = maxTradeSize(trade.sell, trade.buy, trade.prices.sellLow); // {sellTok} uint192 s = trade.sellAmount > maxSell ? maxSell : trade.sellAmount; // {sellTok} // Calculate equivalent buyAmount within [0, FIX_MAX] From 4641d0f514c3a06a82d75d8c2bbc9d569613d164 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 19:48:22 -0400 Subject: [PATCH 422/462] EasyAuction.test.ts --- test/integration/EasyAuction.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index 3d63f9e5b8..af2f439042 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -551,10 +551,11 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function }) it('should be able to scoop entire auction cheaply when minBuyAmount = 0', async () => { - // Make collateral0 lotPrice (0, 0) + // Make collateral0 price (0, FIX_MAX) await setOraclePrice(collateral0.address, bn('0')) await collateral0.refresh() await advanceTime(PRICE_TIMEOUT.add(ORACLE_TIMEOUT).toString()) + await setOraclePrice(collateral0.address, bn('0')) await setOraclePrice(await assetRegistry.toAsset(rsr.address), bn('1e8')) // force a revenue dust auction From 1e5e647d3175d1e89f9a8e7573f5aefac43a447d Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 20:02:02 -0400 Subject: [PATCH 423/462] test/plugins/individual-collateral/**/*.test.ts --- .../individual-collateral/collateralTests.ts | 32 ++++++++++++++++--- .../curve/collateralTests.ts | 28 +++++++++++++--- .../CrvStableRTokenMetapoolTestSuite.test.ts | 4 +-- .../CvxStableRTokenMetapoolTestSuite.test.ts | 4 +-- 4 files changed, 54 insertions(+), 14 deletions(-) diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 49e4cec608..7be0cd4520 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -25,7 +25,12 @@ import { CollateralTestSuiteFixtures, CollateralStatus, } from './pluginTestTypes' -import { expectPrice, expectUnpriced } from '../../utils/oracles' +import { + expectDecayedPrice, + expectExactPrice, + expectPrice, + expectUnpriced, +} from '../../utils/oracles' import snapshotGasCost from '../../utils/snapshotGasCost' import { IMPLEMENTATION, Implementation } from '../../fixtures' @@ -259,17 +264,34 @@ export default function fn( expect(newHigh).to.be.gt(initHigh) }) - it('returns unpriced for 0-valued oracle', async () => { + it('decays for 0-valued oracle', async () => { + const initialPrice = await collateral.price() + // Set price of underlying to 0 const updateAnswerTx = await chainlinkFeed.updateAnswer(0) await updateAnswerTx.wait() - // (0, FIX_MAX) is returned + // Price remains same at first, though IFFY + await collateral.refresh() + await expectExactPrice(collateral.address, initialPrice) + expect(await collateral.status()).to.equal(CollateralStatus.IFFY) + + // After oracle timeout decay begins + const oracleTimeout = await collateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(1 + oracleTimeout / 12) + await collateral.refresh() + await expectDecayedPrice(collateral.address) + + // After price timeout it becomes unpriced + const priceTimeout = await collateral.priceTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + priceTimeout) + await advanceBlocks(1 + priceTimeout / 12) await expectUnpriced(collateral.address) - // When refreshed, sets status to Unpriced + // When refreshed, sets status to DISABLED await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) + expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) }) it('does not revert in case of invalid timestamp', async () => { diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index 215814db2b..33285835f7 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -12,7 +12,7 @@ import { MAX_UINT48, MAX_UINT192, ZERO_ADDRESS, ONE_ADDRESS } from '../../../../ import { expect } from 'chai' import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' import { useEnv } from '#/utils/env' -import { expectUnpriced } from '../../../utils/oracles' +import { expectDecayedPrice, expectExactPrice, expectUnpriced } from '../../../utils/oracles' import { advanceBlocks, advanceTime, @@ -393,17 +393,35 @@ export default function fn( } }) - it('returns unpriced for 0-valued oracle', async () => { + it('decays for 0-valued oracle', async () => { + const initialPrice = await ctx.collateral.price() + + // Set price of underlyings to 0 for (const feed of ctx.feeds) { await feed.updateAnswer(0).then((e) => e.wait()) } - // (0, FIX_MAX) is returned + // Price remains same at first, though IFFY + await ctx.collateral.refresh() + await expectExactPrice(ctx.collateral.address, initialPrice) + expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) + + // After oracle timeout decay begins + const oracleTimeout = await ctx.collateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(1 + oracleTimeout / 12) + await ctx.collateral.refresh() + await expectDecayedPrice(ctx.collateral.address) + + // After price timeout it becomes unpriced + const priceTimeout = await ctx.collateral.priceTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + priceTimeout) + await advanceBlocks(1 + priceTimeout / 12) await expectUnpriced(ctx.collateral.address) - // When refreshed, sets status to IFFY + // When refreshed, sets status to DISABLED await ctx.collateral.refresh() - expect(await ctx.collateral.status()).to.equal(CollateralStatus.IFFY) + expect(await ctx.collateral.status()).to.equal(CollateralStatus.DISABLED) }) it('does not revert in case of invalid timestamp', async () => { diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts index 091863c246..0075e261f6 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts @@ -234,8 +234,8 @@ const collateralSpecificStatusTests = () => { // Should be unpriced await expectUnpriced(collateral.address) - // Lot price should be initial price - const lotP = await collateral.lotPrice() + // Price should be initial price + const lotP = await collateral.price() expect(lotP[0]).to.eq(initialPrice[0]) expect(lotP[1]).to.eq(initialPrice[1]) }) diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts index e1646193a8..aa7f2c9dd7 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts @@ -236,8 +236,8 @@ const collateralSpecificStatusTests = () => { // Should be unpriced await expectUnpriced(collateral.address) - // Lot price should be initial price - const lotP = await collateral.lotPrice() + // Price should be initial price + const lotP = await collateral.price() expect(lotP[0]).to.eq(initialPrice[0]) expect(lotP[1]).to.eq(initialPrice[1]) }) From 53f24f71e8e1692f17cd3f40b91e2709a1959179 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 20:14:24 -0400 Subject: [PATCH 424/462] fix AssetPlugins integration tests --- test/integration/AssetPlugins.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/AssetPlugins.test.ts b/test/integration/AssetPlugins.test.ts index ccb0089359..1729c21c9b 100644 --- a/test/integration/AssetPlugins.test.ts +++ b/test/integration/AssetPlugins.test.ts @@ -977,6 +977,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, cTokenAddress: networkConfig[chainId].tokens.cETH || '', cTokenCollateral: cETHCollateral, price: fp('1859.17'), // approx price June 6, 2022 + refPerTok: fp('0.020064224962890636'), // for weth on June 2022 targetName: 'ETH', }, ] From 7a1b16f8ce65d07701129e3ccde733a791f6a883 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 20:23:20 -0400 Subject: [PATCH 425/462] Revenues.test.ts --- test/Revenues.test.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 9581f3fe61..6fc7a8ec71 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -1106,26 +1106,26 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { it('Should only be able to start a dust auction BATCH_AUCTION (and not DUTCH_AUCTION) if oracle has failed', async () => { const minTrade = bn('1e18') - await rTokenTrader.connect(owner).setMinTradeVolume(minTrade) + await rsrTrader.connect(owner).setMinTradeVolume(minTrade) const dustAmount = bn('1e17') - await token0.connect(addr1).transfer(rTokenTrader.address, dustAmount) + await token0.connect(addr1).transfer(rsrTrader.address, dustAmount) - const p1RevenueTrader = await ethers.getContractAt('RevenueTraderP1', rTokenTrader.address) await setOraclePrice(collateral0.address, bn(0)) await collateral0.refresh() await advanceTime(PRICE_TIMEOUT.add(ORACLE_TIMEOUT).toString()) - await setOraclePrice(collateral1.address, bn('1e8')) + await setOraclePrice(rsrAsset.address, bn('1e8')) - const p = await collateral0.lotPrice() + const p = await collateral0.price() expect(p[0]).to.equal(0) - expect(p[1]).to.equal(0) - await expect( - p1RevenueTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) - ).to.revertedWith('bad sell pricing') + expect(p[1]).to.equal(MAX_UINT192) await expect( - p1RevenueTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION]) - ).to.emit(rTokenTrader, 'TradeStarted') + rsrTrader.manageTokens([token0.address], [TradeKind.DUTCH_AUCTION]) + ).to.revertedWith('dutch auctions require live prices') + await expect(rsrTrader.manageTokens([token0.address], [TradeKind.BATCH_AUCTION])).to.emit( + rsrTrader, + 'TradeStarted' + ) }) it('Should not launch an auction for 1 qTok', async () => { @@ -1481,7 +1481,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(furnace.address)).to.equal(0) // Expected values based on Prices between AAVE and RSR = 1 to 1 (for simplification) - const sellAmt: BigNumber = fp('1').mul(100).div(101) // due to oracle error + const sellAmt: BigNumber = fp('1').mul(100).div(99) // due to oracle error const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) // Run auctions @@ -1663,7 +1663,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Collect revenue // Expected values based on Prices between AAVE and RToken = 1 (for simplification) - const sellAmt: BigNumber = fp('1').mul(100).div(101) // due to high price setting trade size + const sellAmt: BigNumber = fp('1').mul(100).div(99) // due to high price setting trade size const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) await expectEvents(backingManager.claimRewards(), [ @@ -1861,7 +1861,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { // Collect revenue // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) - const sellAmt: BigNumber = fp('1').mul(100).div(101) // due to high price setting trade size + const sellAmt: BigNumber = fp('1').mul(100).div(99) // due to high price setting trade size const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) const sellAmtRToken: BigNumber = rewardAmountAAVE.mul(20).div(100) // All Rtokens can be sold - 20% of total comp based on f From f720f27eddfe8385390d7de5750bcd3223b46967 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 20:29:58 -0400 Subject: [PATCH 426/462] Recollateralization.test.ts --- test/Recollateralization.test.ts | 88 +++---------------- .../aave/ATokenFiatCollateral.test.ts | 7 +- 2 files changed, 20 insertions(+), 75 deletions(-) diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 4e05b74a6a..37cfe6955e 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -2209,7 +2209,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { ).div(2) const sellAmt = sellAmtBeforeSlippage .mul(BN_SCALE_FACTOR) - .div(BN_SCALE_FACTOR.add(ORACLE_ERROR)) + .div(BN_SCALE_FACTOR.sub(ORACLE_ERROR)) const minBuyAmt = await toMinBuyAmt(sellAmt, fp('0.5'), fp('1')) await expect(facadeTest.runAuctionsForAllTraders(rToken.address)) @@ -2252,64 +2252,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { await advanceTime(config.batchAuctionLength.add(100).toString()) // Run auctions - will end current, and will open a new auction for the same amount - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: backingManager, - name: 'TradeSettled', - args: [anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt], - emitted: true, - }, - { - contract: backingManager, - name: 'TradeStarted', - args: [anyValue, token0.address, backupToken1.address, sellAmt, minBuyAmt], - emitted: true, - }, - ]) - const leftoverSellAmt = issueAmount.sub(sellAmt.mul(2)) - - // Check new auction - // Token0 -> Backup Token Auction - await expectTrade(backingManager, { - sell: token0.address, - buy: backupToken1.address, - endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), - externalId: bn('1'), - }) - - // Check state - expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) - expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - minBuyAmt.add(leftoverSellAmt.div(2)) - ) - expect(await token0.balanceOf(backingManager.address)).to.equal(leftoverSellAmt) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(minBuyAmt) - expect(await rToken.totalSupply()).to.equal(issueAmount) - - // Check price in USD of the current RToken - await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) - - // Perform Mock Bids (addr1 has balance) - // Pay at worst-case price - await backupToken1.connect(addr1).approve(gnosis.address, minBuyAmt) - await gnosis.placeBid(1, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: minBuyAmt, - }) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // Check staking situation remains unchanged - expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) - expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // Run auctions - will end current, and will open a new auction for the same amount + const leftoverSellAmt = issueAmount.sub(sellAmt) const leftoverMinBuyAmt = await toMinBuyAmt(leftoverSellAmt, fp('0.5'), fp('1')) await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ { @@ -2338,17 +2281,14 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { sell: token0.address, buy: backupToken1.address, endTime: (await getLatestBlockTimestamp()) + Number(config.batchAuctionLength), - externalId: bn('2'), + externalId: bn('1'), }) // Check state expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(false) - expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - minBuyAmt.mul(2) - ) expect(await token0.balanceOf(backingManager.address)).to.equal(0) - expect(await backupToken1.balanceOf(backingManager.address)).to.equal(minBuyAmt.mul(2)) + expect(await backupToken1.balanceOf(backingManager.address)).to.equal(minBuyAmt) expect(await rToken.totalSupply()).to.equal(issueAmount) // Check price in USD of the current RToken @@ -2357,7 +2297,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Perform Mock Bids (addr1 has balance) // Pay at worst-case price await backupToken1.connect(addr1).approve(gnosis.address, minBuyAmt) - await gnosis.placeBid(2, { + await gnosis.placeBid(1, { bidder: addr1.address, sellAmount: leftoverSellAmt, buyAmount: leftoverMinBuyAmt, @@ -2370,11 +2310,11 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await rsr.balanceOf(stRSR.address)).to.equal(stakeAmount) expect(await stRSR.balanceOf(addr1.address)).to.equal(stakeAmount) - // End current auction, should start a new one to sell RSR for collateral - // ~51e18 Tokens left to buy - Sets Buy amount as independent value - const buyAmtBidRSR: BigNumber = issueAmount - .sub(minBuyAmt.mul(2).add(leftoverMinBuyAmt)) - .add(1) + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Run auctions - will end current, and will open a new auction for the same amount + const buyAmtBidRSR: BigNumber = issueAmount.sub(minBuyAmt.add(leftoverMinBuyAmt)).add(1) await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ { contract: backingManager, @@ -2404,7 +2344,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { sell: rsr.address, buy: backupToken1.address, endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('3'), + externalId: bn('2'), }) const t = await getTrade(backingManager, rsr.address) @@ -2415,11 +2355,11 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { expect(await basketHandler.status()).to.equal(CollateralStatus.SOUND) expect(await basketHandler.fullyCollateralized()).to.equal(false) expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( - minBuyAmt.mul(2).add(leftoverMinBuyAmt) + minBuyAmt.add(leftoverMinBuyAmt) ) expect(await token0.balanceOf(backingManager.address)).to.equal(0) expect(await backupToken1.balanceOf(backingManager.address)).to.equal( - minBuyAmt.mul(2).add(leftoverMinBuyAmt) + minBuyAmt.add(leftoverMinBuyAmt) ) expect(await rToken.totalSupply()).to.equal(issueAmount) @@ -2432,7 +2372,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { // Perform Mock Bids for RSR (addr1 has balance) // Pay at worst-case price await backupToken1.connect(addr1).approve(gnosis.address, buyAmtBidRSR) - await gnosis.placeBid(3, { + await gnosis.placeBid(2, { bidder: addr1.address, sellAmount: sellAmtRSR, buyAmount: buyAmtBidRSR, diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index c05faee8ac..8e38035704 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -31,7 +31,12 @@ import { import { expectEvents, expectInIndirectReceipt } from '../../../../common/events' import { bn, fp } from '../../../../common/numbers' import { whileImpersonating } from '../../../utils/impersonation' -import { expectPrice, expectRTokenPrice, setOraclePrice } from '../../../utils/oracles' +import { + expectPrice, + expectRTokenPrice, + setOraclePrice, + expectUnpriced, +} from '../../../utils/oracles' import { advanceBlocks, advanceTime, From 1deef71f86b66e9d9d26a5aa6d7b4e6c48c8422b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 22:42:50 -0400 Subject: [PATCH 427/462] CTokenFiatCollateral.test.ts --- .../compoundv2/CTokenFiatCollateral.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index effbdef07d..d3d0af540e 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -31,7 +31,12 @@ import { import { expectEvents, expectInIndirectReceipt } from '../../../../common/events' import { bn, fp, toBNDecimals } from '../../../../common/numbers' import { whileImpersonating } from '../../../utils/impersonation' -import { expectPrice, expectRTokenPrice, setOraclePrice } from '../../../utils/oracles' +import { + expectPrice, + expectRTokenPrice, + expectUnpriced, + setOraclePrice, +} from '../../../utils/oracles' import { advanceBlocks, advanceTime, From 8cd631744bdc32b9b168439d739a40a2cd6d901c Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 22:42:56 -0400 Subject: [PATCH 428/462] ComplexBasket.test.ts --- test/scenario/ComplexBasket.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/scenario/ComplexBasket.test.ts b/test/scenario/ComplexBasket.test.ts index e97f5bea25..9dd384a82c 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -1598,8 +1598,8 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // Running auctions will trigger recollateralization - cETHVault partial sale for weth // Will sell about 841K of cETHVault, expect to receive 8167 wETH (minimum) // We would still have about 438K to sell of cETHVault - let [, high] = await cETHVaultCollateral.price() - const sellAmtUnscaled = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(high) + let [low] = await cETHVaultCollateral.price() + const sellAmtUnscaled = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(low) const sellAmt = toBNDecimals(sellAmtUnscaled, 8) const sellAmtRemainder = (await cETHVault.balanceOf(backingManager.address)).sub(sellAmt) // Price for cETHVault = 1200 / 50 = $24 at rate 50% = $12 @@ -1744,8 +1744,8 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { // 13K wETH @ 1200 = 15,600,000 USD of value, in RSR ~= 156,000 RSR (@100 usd) // We exceed maxTradeVolume so we need two auctions - Will first sell 10M in value // Sells about 101K RSR, for 8167 WETH minimum - ;[, high] = await rsrAsset.price() - const sellAmtRSR1 = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(high) + ;[low] = await rsrAsset.price() + const sellAmtRSR1 = MAX_TRADE_VOLUME.mul(BN_SCALE_FACTOR).div(low) const buyAmtBidRSR1 = toMinBuyAmt( sellAmtRSR1, rsrPrice, From dd696791451d65573e5d63fd855e673ac7da89b9 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 22:54:02 -0400 Subject: [PATCH 429/462] Facade.test.ts --- test/Facade.test.ts | 175 +++++++++++++++++++++++--------------------- 1 file changed, 92 insertions(+), 83 deletions(-) diff --git a/test/Facade.test.ts b/test/Facade.test.ts index 6082e986d0..9da2b8398a 100644 --- a/test/Facade.test.ts +++ b/test/Facade.test.ts @@ -483,6 +483,10 @@ describe('FacadeRead + FacadeAct contracts', () => { // Set price to 0 await setOraclePrice(rsrAsset.address, bn(0)) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await setOraclePrice(tokenAsset.address, bn('1e8')) + await setOraclePrice(usdcAsset.address, bn('1e8')) + await assetRegistry.refresh() const [backing2, overCollateralization2] = await facade.callStatic.backingOverview( rToken.address @@ -556,97 +560,98 @@ describe('FacadeRead + FacadeAct contracts', () => { }) it('Should return revenue + chain into FacadeAct.runRevenueAuctions', async () => { - const traders = [rTokenTrader, rsrTrader] + // Set low to 0 == revenueOverview() should not revert + const minTradeVolume = await rsrTrader.minTradeVolume() + const auctionLength = await broker.dutchAuctionLength() + const tokenSurplus = bn('0.5e18') + await token.connect(addr1).transfer(rsrTrader.address, tokenSurplus) - // Set lotLow to 0 == revenueOverview() should not revert await setOraclePrice(usdcAsset.address, bn('0')) - await usdcAsset.refresh() - for (let traderIndex = 0; traderIndex < traders.length; traderIndex++) { - const trader = traders[traderIndex] - - const minTradeVolume = await trader.minTradeVolume() - const auctionLength = await broker.dutchAuctionLength() - const tokenSurplus = bn('0.5e18') - await token.connect(addr1).transfer(trader.address, tokenSurplus) - - const [low] = await usdcAsset.price() - expect(low).to.equal(0) - - // revenue - let [erc20s, canStart, surpluses, minTradeAmounts] = - await facadeAct.callStatic.revenueOverview(trader.address) - expect(erc20s.length).to.equal(8) // should be full set of registered ERC20s - - const erc20sToStart = [] - for (let i = 0; i < 8; i++) { - if (erc20s[i] == token.address) { - erc20sToStart.push(erc20s[i]) - expect(canStart[i]).to.equal(true) - expect(surpluses[i]).to.equal(tokenSurplus) - } else { - expect(canStart[i]).to.equal(false) - expect(surpluses[i]).to.equal(0) - } - const asset = await ethers.getContractAt('IAsset', await assetRegistry.toAsset(erc20s[i])) - const [low] = await asset.price() - expect(minTradeAmounts[i]).to.equal( - low.gt(0) ? minTradeVolume.mul(bn('10').pow(await asset.erc20Decimals())).div(low) : 0 - ) // 1% oracleError - } + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await setOraclePrice(tokenAsset.address, bn('1e8')) + await setOraclePrice(rsrAsset.address, bn('1e8')) + await assetRegistry.refresh() - // Run revenue auctions via multicall - const funcSig = ethers.utils.id('runRevenueAuctions(address,address[],address[],uint8[])') - const args = ethers.utils.defaultAbiCoder.encode( - ['address', 'address[]', 'address[]', 'uint8[]'], - [trader.address, [], erc20sToStart, [TradeKind.DUTCH_AUCTION]] - ) - const data = funcSig.substring(0, 10) + args.slice(2) - await expect(facadeAct.multicall([data])).to.emit(trader, 'TradeStarted') - - // Another call to revenueOverview should not propose any auction - ;[erc20s, canStart, surpluses, minTradeAmounts] = - await facadeAct.callStatic.revenueOverview(trader.address) - expect(canStart).to.eql(Array(8).fill(false)) - - // Nothing should be settleable - expect((await facade.auctionsSettleable(trader.address)).length).to.equal(0) - - // Advance time till auction is over - await advanceBlocks(2 + auctionLength / 12) - - // Now should be settleable - const settleable = await facade.auctionsSettleable(trader.address) - expect(settleable.length).to.equal(1) - expect(settleable[0]).to.equal(token.address) - - // Another call to revenueOverview should settle and propose new auction - ;[erc20s, canStart, surpluses, minTradeAmounts] = - await facadeAct.callStatic.revenueOverview(trader.address) - - // Should repeat the same auctions - for (let i = 0; i < 8; i++) { - if (erc20s[i] == token.address) { - expect(canStart[i]).to.equal(true) - expect(surpluses[i]).to.equal(tokenSurplus) - } else { - expect(canStart[i]).to.equal(false) - expect(surpluses[i]).to.equal(0) - } + const [low] = await usdcAsset.price() + expect(low).to.equal(0) + + // revenue + let [erc20s, canStart, surpluses, minTradeAmounts] = + await facadeAct.callStatic.revenueOverview(rsrTrader.address) + expect(erc20s.length).to.equal(8) // should be full set of registered ERC20s + + const erc20sToStart = [] + for (let i = 0; i < 8; i++) { + if (erc20s[i] == token.address) { + erc20sToStart.push(erc20s[i]) + expect(canStart[i]).to.equal(true) + expect(surpluses[i]).to.equal(tokenSurplus) + } else { + expect(canStart[i]).to.equal(false) + expect(surpluses[i]).to.equal(0) } + const asset = await ethers.getContractAt('IAsset', await assetRegistry.toAsset(erc20s[i])) + const [low] = await asset.price() + expect(minTradeAmounts[i]).to.equal( + low.gt(0) ? minTradeVolume.mul(bn('10').pow(await asset.erc20Decimals())).div(low) : 0 + ) // 1% oracleError + } - // Settle and start new auction - await facadeAct.runRevenueAuctions(trader.address, erc20sToStart, erc20sToStart, [ - TradeKind.DUTCH_AUCTION, - ]) + // Run revenue auctions via multicall + const funcSig = ethers.utils.id('runRevenueAuctions(address,address[],address[],uint8[])') + const args = ethers.utils.defaultAbiCoder.encode( + ['address', 'address[]', 'address[]', 'uint8[]'], + [rsrTrader.address, [], erc20sToStart, [TradeKind.DUTCH_AUCTION]] + ) + const data = funcSig.substring(0, 10) + args.slice(2) + await expect(facadeAct.multicall([data])).to.emit(rsrTrader, 'TradeStarted') - // Send additional revenues - await token.connect(addr1).transfer(trader.address, tokenSurplus) + // Another call to revenueOverview should not propose any auction + ;[erc20s, canStart, surpluses, minTradeAmounts] = await facadeAct.callStatic.revenueOverview( + rsrTrader.address + ) + expect(canStart).to.eql(Array(8).fill(false)) - // Call revenueOverview, cannot open new auctions - ;[erc20s, canStart, surpluses, minTradeAmounts] = - await facadeAct.callStatic.revenueOverview(trader.address) - expect(canStart).to.eql(Array(8).fill(false)) + // Nothing should be settleable + expect((await facade.auctionsSettleable(rsrTrader.address)).length).to.equal(0) + + // Advance time till auction is over + await advanceBlocks(2 + auctionLength / 12) + + // Now should be settleable + const settleable = await facade.auctionsSettleable(rsrTrader.address) + expect(settleable.length).to.equal(1) + expect(settleable[0]).to.equal(token.address) + + // Another call to revenueOverview should settle and propose new auction + ;[erc20s, canStart, surpluses, minTradeAmounts] = await facadeAct.callStatic.revenueOverview( + rsrTrader.address + ) + + // Should repeat the same auctions + for (let i = 0; i < 8; i++) { + if (erc20s[i] == token.address) { + expect(canStart[i]).to.equal(true) + expect(surpluses[i]).to.equal(tokenSurplus) + } else { + expect(canStart[i]).to.equal(false) + expect(surpluses[i]).to.equal(0) + } } + + // Settle and start new auction + await facadeAct.runRevenueAuctions(rsrTrader.address, erc20sToStart, erc20sToStart, [ + TradeKind.DUTCH_AUCTION, + ]) + + // Send additional revenues + await token.connect(addr1).transfer(rsrTrader.address, tokenSurplus) + + // Call revenueOverview, cannot open new auctions + ;[erc20s, canStart, surpluses, minTradeAmounts] = await facadeAct.callStatic.revenueOverview( + rsrTrader.address + ) + expect(canStart).to.eql(Array(8).fill(false)) }) itP1('Should handle invalid versions when running revenueOverview', async () => { @@ -896,7 +901,11 @@ describe('FacadeRead + FacadeAct contracts', () => { ) // set price of dai to 0 await chainlinkFeed.updateAnswer(0) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await setOraclePrice(usdcAsset.address, bn('1e8')) + await assetRegistry.refresh() await main.connect(owner).pauseTrading() + const [erc20s, breakdown, targets] = await facade.callStatic.basketBreakdown(rToken.address) expect(erc20s.length).to.equal(4) expect(breakdown.length).to.equal(4) From 722fea63e9fd30802b1278bc9763c9b0fed80d71 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 1 Sep 2023 22:54:11 -0400 Subject: [PATCH 430/462] Main.test.ts --- test/Main.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Main.test.ts b/test/Main.test.ts index 95fba1d958..7556200088 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -2815,6 +2815,8 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { // Set price = 0, which hits 3 of our 4 collateral in the basket await setOraclePrice(newColl2.address, bn('0')) + await advanceTime(ORACLE_TIMEOUT.add(PRICE_TIMEOUT).toString()) + await setOraclePrice(collateral1.address, bn('1e8')) // Check status and price again const p = await basketHandler.price() From 843a9c66482424e2a4996b1dec0f591b66ab9325 Mon Sep 17 00:00:00 2001 From: Patrick McKelvy Date: Sun, 3 Sep 2023 11:06:16 -0400 Subject: [PATCH 431/462] fix plugin tests. --- .../individual-collateral/collateralTests.ts | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 7be0cd4520..1b7f94a9fb 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -264,36 +264,6 @@ export default function fn( expect(newHigh).to.be.gt(initHigh) }) - it('decays for 0-valued oracle', async () => { - const initialPrice = await collateral.price() - - // Set price of underlying to 0 - const updateAnswerTx = await chainlinkFeed.updateAnswer(0) - await updateAnswerTx.wait() - - // Price remains same at first, though IFFY - await collateral.refresh() - await expectExactPrice(collateral.address, initialPrice) - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - - // After oracle timeout decay begins - const oracleTimeout = await collateral.oracleTimeout() - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) - await advanceBlocks(1 + oracleTimeout / 12) - await collateral.refresh() - await expectDecayedPrice(collateral.address) - - // After price timeout it becomes unpriced - const priceTimeout = await collateral.priceTimeout() - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + priceTimeout) - await advanceBlocks(1 + priceTimeout / 12) - await expectUnpriced(collateral.address) - - // When refreshed, sets status to DISABLED - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - }) - it('does not revert in case of invalid timestamp', async () => { await chainlinkFeed.setInvalidTimestamp() @@ -373,6 +343,36 @@ export default function fn( await advanceTime(priceTimeout / 2) await expectUnpriced(collateral.address) }) + + it('decays for 0-valued oracle', async () => { + const initialPrice = await collateral.price() + + // Set price of underlying to 0 + const updateAnswerTx = await chainlinkFeed.updateAnswer(0) + await updateAnswerTx.wait() + + // Price remains same at first, though IFFY + await collateral.refresh() + await expectExactPrice(collateral.address, initialPrice) + expect(await collateral.status()).to.equal(CollateralStatus.IFFY) + + // After oracle timeout decay begins + const oracleTimeout = await collateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(1 + oracleTimeout / 12) + await collateral.refresh() + await expectDecayedPrice(collateral.address) + + // After price timeout it becomes unpriced + const priceTimeout = await collateral.priceTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + priceTimeout) + await advanceBlocks(1 + priceTimeout / 12) + await expectUnpriced(collateral.address) + + // When refreshed, sets status to DISABLED + await collateral.refresh() + expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) + }) }) describe('status', () => { From 86aa66b640e1312b9b7ec74549b93118b154ce23 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 5 Sep 2023 11:45:51 -0400 Subject: [PATCH 432/462] fix final failing tests --- .../CrvStableRTokenMetapoolTestSuite.test.ts | 18 ++++++++++-------- .../CvxStableRTokenMetapoolTestSuite.test.ts | 18 ++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts index 0075e261f6..a97702d5af 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts @@ -7,13 +7,14 @@ import { import { makeWeUSDFraxBP, mintWeUSDFraxBP, resetFork } from './helpers' import { ethers } from 'hardhat' import { ContractFactory, BigNumberish } from 'ethers' -import { expectUnpriced } from '../../../../utils/oracles' +import { expectDecayedPrice, expectExactPrice, expectUnpriced } from '../../../../utils/oracles' import { ERC20Mock, MockV3Aggregator, MockV3Aggregator__factory, TestICollateral, } from '../../../../../typechain' +import { advanceTime } from '../../../../utils/time' import { bn } from '../../../../../common/numbers' import { ZERO_ADDRESS, ONE_ADDRESS, MAX_UINT192 } from '../../../../../common/constants' import { expect } from 'chai' @@ -227,17 +228,18 @@ const collateralSpecificStatusTests = () => { // Set RTokenAsset to unpriced // Would be the price under a stale oracle timeout for a poorly-coded RTokenAsset await mockRTokenAsset.setPrice(0, MAX_UINT192) + await expectExactPrice(collateral.address, initialPrice) - // refresh() should not revert - await collateral.refresh() + // Should decay after oracle timeout + await advanceTime(await collateral.oracleTimeout()) + await expectDecayedPrice(collateral.address) - // Should be unpriced + // Should be unpriced after price timeout + await advanceTime(await collateral.priceTimeout()) await expectUnpriced(collateral.address) - // Price should be initial price - const lotP = await collateral.price() - expect(lotP[0]).to.eq(initialPrice[0]) - expect(lotP[1]).to.eq(initialPrice[1]) + // refresh() should not revert + await collateral.refresh() }) } diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts index aa7f2c9dd7..bfb1f30180 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts @@ -7,13 +7,14 @@ import { import { makeWeUSDFraxBP, mintWeUSDFraxBP, resetFork } from './helpers' import { ethers } from 'hardhat' import { ContractFactory, BigNumberish } from 'ethers' -import { expectUnpriced } from '../../../../utils/oracles' +import { expectDecayedPrice, expectExactPrice, expectUnpriced } from '../../../../utils/oracles' import { ERC20Mock, MockV3Aggregator, MockV3Aggregator__factory, TestICollateral, } from '../../../../../typechain' +import { advanceTime } from '../../../../utils/time' import { bn } from '../../../../../common/numbers' import { ZERO_ADDRESS, ONE_ADDRESS, MAX_UINT192 } from '../../../../../common/constants' import { expect } from 'chai' @@ -229,17 +230,18 @@ const collateralSpecificStatusTests = () => { // Set RTokenAsset to unpriced // Would be the price under a stale oracle timeout for a poorly-coded RTokenAsset await mockRTokenAsset.setPrice(0, MAX_UINT192) + await expectExactPrice(collateral.address, initialPrice) - // refresh() should not revert - await collateral.refresh() + // Should decay after oracle timeout + await advanceTime(await collateral.oracleTimeout()) + await expectDecayedPrice(collateral.address) - // Should be unpriced + // Should be unpriced after price timeout + await advanceTime(await collateral.priceTimeout()) await expectUnpriced(collateral.address) - // Price should be initial price - const lotP = await collateral.price() - expect(lotP[0]).to.eq(initialPrice[0]) - expect(lotP[1]).to.eq(initialPrice[1]) + // refresh() should not revert + await collateral.refresh() }) } From b7c958104d109678ad6ba960d5ebff7b704ccb2b Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 11 Sep 2023 09:24:09 -0400 Subject: [PATCH 433/462] advance oracles to prevent timeouts (#933) --- .../ankr/AnkrEthCollateralTestSuite.test.ts | 4 ++ .../cbeth/CBETHCollateral.test.ts | 5 ++ .../individual-collateral/collateralTests.ts | 60 +++++++++---------- .../compoundv3/CometTestSuite.test.ts | 4 ++ .../dsr/SDaiCollateralTestSuite.test.ts | 5 ++ .../flux-finance/FTokenFiatCollateral.test.ts | 4 ++ .../frax-eth/SFrxEthTestSuite.test.ts | 5 ++ .../lido/LidoStakedEthTestSuite.test.ts | 6 ++ .../MorphoAAVEFiatCollateral.test.ts | 4 ++ .../MorphoAAVENonFiatCollateral.test.ts | 5 ++ ...orphoAAVESelfReferentialCollateral.test.ts | 4 ++ .../RethCollateralTestSuite.test.ts | 6 ++ .../stargate/StargateUSDCTestSuite.test.ts | 4 ++ test/utils/oracles.ts | 34 ++++++++++- 14 files changed, 119 insertions(+), 31 deletions(-) diff --git a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts index 79f063c586..0635aba4c3 100644 --- a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts @@ -10,6 +10,7 @@ import { TestICollateral, IAnkrETH, } from '../../../../typechain' +import { pushOracleForward } from '../../../utils/oracles' import { bn, fp } from '../../../../common/numbers' import { ZERO_ADDRESS } from '../../../../common/constants' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' @@ -100,6 +101,9 @@ export const deployCollateral = async ( ) await collateral.deployed() + // Push forward chainlink feed + await pushOracleForward(opts.chainlinkFeed!) + // sometimes we are trying to test a negative test case and we want this to fail silently // fortunately this syntax fails silently because our tools are terrible await expect(collateral.refresh()) diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts index fa6340bfb4..47ca9b8da1 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts @@ -12,6 +12,7 @@ import { ORACLE_TIMEOUT, PRICE_TIMEOUT, } from './constants' +import { pushOracleForward } from '../../../utils/oracles' import { BigNumber, BigNumberish, ContractFactory } from 'ethers' import { bn, fp } from '#/common/numbers' import { TestICollateral } from '@typechain/TestICollateral' @@ -60,6 +61,10 @@ export const deployCollateral = async ( ) await collateral.deployed() + // Push forward chainlink feeds + await pushOracleForward(opts.chainlinkFeed!) + await pushOracleForward(opts.targetPerTokChainlinkFeed ?? CBETH_ETH_PRICE_FEED) + await expect(collateral.refresh()) return collateral diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 1b7f94a9fb..7be0cd4520 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -264,6 +264,36 @@ export default function fn( expect(newHigh).to.be.gt(initHigh) }) + it('decays for 0-valued oracle', async () => { + const initialPrice = await collateral.price() + + // Set price of underlying to 0 + const updateAnswerTx = await chainlinkFeed.updateAnswer(0) + await updateAnswerTx.wait() + + // Price remains same at first, though IFFY + await collateral.refresh() + await expectExactPrice(collateral.address, initialPrice) + expect(await collateral.status()).to.equal(CollateralStatus.IFFY) + + // After oracle timeout decay begins + const oracleTimeout = await collateral.oracleTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) + await advanceBlocks(1 + oracleTimeout / 12) + await collateral.refresh() + await expectDecayedPrice(collateral.address) + + // After price timeout it becomes unpriced + const priceTimeout = await collateral.priceTimeout() + await setNextBlockTimestamp((await getLatestBlockTimestamp()) + priceTimeout) + await advanceBlocks(1 + priceTimeout / 12) + await expectUnpriced(collateral.address) + + // When refreshed, sets status to DISABLED + await collateral.refresh() + expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) + }) + it('does not revert in case of invalid timestamp', async () => { await chainlinkFeed.setInvalidTimestamp() @@ -343,36 +373,6 @@ export default function fn( await advanceTime(priceTimeout / 2) await expectUnpriced(collateral.address) }) - - it('decays for 0-valued oracle', async () => { - const initialPrice = await collateral.price() - - // Set price of underlying to 0 - const updateAnswerTx = await chainlinkFeed.updateAnswer(0) - await updateAnswerTx.wait() - - // Price remains same at first, though IFFY - await collateral.refresh() - await expectExactPrice(collateral.address, initialPrice) - expect(await collateral.status()).to.equal(CollateralStatus.IFFY) - - // After oracle timeout decay begins - const oracleTimeout = await collateral.oracleTimeout() - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + oracleTimeout) - await advanceBlocks(1 + oracleTimeout / 12) - await collateral.refresh() - await expectDecayedPrice(collateral.address) - - // After price timeout it becomes unpriced - const priceTimeout = await collateral.priceTimeout() - await setNextBlockTimestamp((await getLatestBlockTimestamp()) + priceTimeout) - await advanceBlocks(1 + priceTimeout / 12) - await expectUnpriced(collateral.address) - - // When refreshed, sets status to DISABLED - await collateral.refresh() - expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) - }) }) describe('status', () => { diff --git a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts index 3ea997da0d..22365aaab9 100644 --- a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts @@ -22,6 +22,7 @@ import { CometMock__factory, TestICollateral, } from '../../../../typechain' +import { pushOracleForward } from '../../../utils/oracles' import { bn, fp } from '../../../../common/numbers' import { MAX_UINT48 } from '../../../../common/constants' import { expect } from 'chai' @@ -118,6 +119,9 @@ export const deployCollateral = async ( ) await collateral.deployed() + // Push forward chainlink feed + await pushOracleForward(opts.chainlinkFeed!) + // sometimes we are trying to test a negative test case and we want this to fail silently // fortunately this syntax fails silently because our tools are terrible await expect(collateral.refresh()) diff --git a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts index 7ee5c7dc01..43b5f8593a 100644 --- a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts @@ -12,6 +12,7 @@ import { PotMock, TestICollateral, } from '../../../../typechain' +import { pushOracleForward } from '../../../utils/oracles' import { bn, fp } from '../../../../common/numbers' import { ZERO_ADDRESS } from '../../../../common/constants' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' @@ -69,6 +70,10 @@ export const deployCollateral = async (opts: CollateralOpts = {}): Promise { ) await collateral.deployed() + // Push forward chainlink feed + await pushOracleForward(opts.chainlinkFeed!) + // sometimes we are trying to test a negative test case and we want this to fail silently // fortunately this syntax fails silently because our tools are terrible await expect(collateral.refresh()) diff --git a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts index d47e23919f..701789f733 100644 --- a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts @@ -12,6 +12,7 @@ import { TestICollateral, IsfrxEth, } from '../../../../typechain' +import { pushOracleForward } from '../../../utils/oracles' import { bn, fp } from '../../../../common/numbers' import { CollateralStatus } from '../../../../common/constants' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' @@ -84,6 +85,10 @@ export const deployCollateral = async (opts: CollateralOpts = {}): Promise { const v3Aggregator = await ethers.getContractAt('MockV3Aggregator', chainlinkFeedAddr) await v3Aggregator.setInvalidAnsweredRound() } + +// === Pushing oracles (real or mock) forward === + +export const overrideOracle = async (oracleAddress: string): Promise => { + const oracle = await ethers.getContractAt( + 'contracts/plugins/mocks/EACAggregatorProxyMock.sol:EACAggregatorProxy', + oracleAddress + ) + const aggregator = await oracle.aggregator() + const accessController = await oracle.accessController() + const initPrice = await oracle.latestAnswer() + const mockOracleFactory = await ethers.getContractFactory('EACAggregatorProxyMock') + const mockOracle = await mockOracleFactory.deploy(aggregator, accessController, initPrice) + const bytecode = await network.provider.send('eth_getCode', [mockOracle.address]) + await setCode(oracleAddress, bytecode) + return ethers.getContractAt('EACAggregatorProxyMock', oracleAddress) +} + +export const pushOracleForward = async (chainlinkAddr: string) => { + const chainlinkFeed = await ethers.getContractAt('MockV3Aggregator', await chainlinkAddr) + const initPrice = await chainlinkFeed.latestAnswer() + try { + // Try to update as if it's a mock already + await chainlinkFeed.updateAnswer(initPrice) + } catch { + // Not a mock; need to override the oracle first + const oracle = await overrideOracle(chainlinkFeed.address) + await oracle.updateAnswer(initPrice) + } +} From 632fe66e82c4c5ec60958b7dc8630ac340898905 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 2 Oct 2023 19:08:17 -0400 Subject: [PATCH 434/462] merge 3.0.0 in --- .env.example | 10 +- .github/workflows/docgen-netlify.yml | 52 + .github/workflows/tests.yml | 39 +- .openzeppelin/base_8453.json | 3149 +++++++++++++++++ .openzeppelin/mainnet.json | 3143 ++++++++++++++++ CHANGELOG.md | 118 + README.md | 67 +- ... - Reserve Audit Report - 3.0.0 Release.md | 1553 ++++++++ book.toml | 15 + common/configuration.ts | 67 +- contracts/facade/FacadeAct.sol | 2 + contracts/facade/FacadeWrite.sol | 2 +- contracts/interfaces/IDeployer.sol | 5 + contracts/p0/Deployer.sol | 9 +- contracts/p0/mixins/TradingLib.sol | 3 + contracts/p1/Broker.sol | 1 + contracts/p1/Deployer.sol | 9 +- .../p1/mixins/RecollateralizationLib.sol | 7 +- contracts/plugins/assets/L2LSDCollateral.sol | 108 + .../assets/aave-v3/AaveV3FiatCollateral.sol | 40 + .../aave-v3/mock/MockStaticATokenV3.sol | 27 + .../plugins/assets/aave-v3/vendor/ERC20.sol | 237 ++ .../vendor/RayMathExplicitRounding.sol | 46 + .../aave-v3/vendor/StaticATokenErrors.sol | 14 + .../aave-v3/vendor/StaticATokenV3LM.sol | 753 ++++ .../aave-v3/vendor/interfaces/IAToken.sol | 22 + .../aave-v3/vendor/interfaces/IERC4626.sol | 245 ++ .../IInitializableStaticATokenLM.sol | 41 + .../vendor/interfaces/IStaticATokenV3LM.sol | 220 ++ .../plugins/assets/cbeth/CBETHCollateral.sol | 13 +- .../assets/cbeth/CBETHCollateralL2.sol | 76 + contracts/plugins/assets/cbeth/README.md | 4 +- .../plugins/assets/cbeth/vendor/ICBEth.sol | 14 + .../compoundv2/CTokenFiatCollateral.sol | 24 +- .../curve/cvx/vendor/ConvexStakingWrapper.sol | 7 +- .../morpho-aave/MorphoFiatCollateral.sol | 2 +- .../morpho-aave/MorphoNonFiatCollateral.sol | 8 +- .../MorphoSelfReferentialCollateral.sol | 2 +- contracts/plugins/trading/DutchTrade.sol | 44 +- docs/collateral.md | 200 +- docs/deployed-addresses/1-ETH+.md | 24 + docs/deployed-addresses/1-assets-2.1.0.md | 44 + docs/deployed-addresses/1-components-2.1.0.md | 31 + docs/deployed-addresses/1-eUSD.md | 24 + docs/deployed-addresses/1-hyUSD.md | 24 + docs/deployment-variables.md | 4 +- docs/mev.md | 2 + docs/plugin-addresses.md | 84 +- docs/solidity-style.md | 2 +- docs/system-design.md | 172 +- hardhat.config.ts | 25 +- package.json | 2 + .../84531-tmp-assets-collateral.json | 6 +- scripts/addresses/84531-tmp-deployments.json | 44 +- .../8453-tmp-assets-collateral.json | 22 + .../base-3.0.0/8453-tmp-deployments.json | 35 + .../1-tmp-assets-collateral.json | 100 + .../mainnet-3.0.0/1-tmp-deployments.json | 44 +- scripts/collateral-params.ts | 6 +- scripts/deploy.ts | 79 +- .../phase1-common/1_deploy_libraries.ts | 16 +- .../phase2-assets/1_deploy_assets.ts | 28 +- .../phase2-assets/2_deploy_collateral.ts | 1322 +++---- .../collaterals/deploy_aave_v3_usdbc.ts | 110 + .../collaterals/deploy_aave_v3_usdc.ts | 111 + .../collaterals/deploy_cbeth_collateral.ts | 92 +- .../deploy_ctokenv3_usdbc_collateral.ts | 104 + .../deploy_ctokenv3_usdc_collateral.ts | 21 +- .../collaterals/deploy_curve_stable_plugin.ts | 5 +- .../collaterals/deploy_dsr_sdai.ts | 4 +- .../deploy_flux_finance_collateral.ts | 67 +- .../deploy_lido_wsteth_collateral.ts | 2 +- .../deploy_morpho_aavev2_plugin.ts | 26 +- .../deploy_rocket_pool_reth_collateral.ts | 6 +- scripts/deployment/utils.ts | 2 +- scripts/verification/6_verify_collateral.ts | 45 +- .../verify_aave_v3_usdbc.ts | 71 + .../collateral-plugins/verify_aave_v3_usdc.ts | 69 + .../collateral-plugins/verify_cbeth.ts | 74 +- .../verify_convex_stable.ts | 8 +- .../verify_convex_stable_metapool.ts | 2 +- .../verify_convex_stable_rtoken_metapool.ts | 4 +- .../collateral-plugins/verify_curve_stable.ts | 2 +- .../verify_curve_stable_rtoken_metapool.ts | 2 +- .../collateral-plugins/verify_cusdbcv3.ts | 81 + .../collateral-plugins/verify_cusdcv3.ts | 13 +- .../collateral-plugins/verify_morpho.ts | 140 + .../collateral-plugins/verify_reth.ts | 5 +- .../collateral-plugins/verify_wsteth.ts | 2 +- scripts/verify_etherscan.ts | 49 +- tasks/deployment/get-addresses.ts | 269 ++ tasks/index.ts | 1 + .../upgrade-checker-utils/constants.ts | 6 +- tasks/testing/upgrade-checker-utils/logs.ts | 4 +- tasks/testing/upgrade-checker-utils/trades.ts | 5 +- .../upgrade-checker-utils/upgrades/3_0_0.ts | 347 +- tasks/testing/upgrade-checker.ts | 6 +- test/Broker.test.ts | 10 +- test/Deployer.test.ts | 4 +- test/FacadeWrite.test.ts | 6 + test/Governance.test.ts | 21 +- test/Main.test.ts | 29 + test/RTokenExtremes.test.ts | 2 + test/Revenues.test.ts | 2 + test/ZTradingExtremes.test.ts | 2 + test/__snapshots__/Broker.test.ts.snap | 8 +- test/__snapshots__/FacadeWrite.test.ts.snap | 4 +- test/__snapshots__/Main.test.ts.snap | 8 +- test/__snapshots__/RToken.test.ts.snap | 6 +- .../Recollateralization.test.ts.snap | 12 +- test/__snapshots__/Revenues.test.ts.snap | 8 +- test/__snapshots__/ZZStRSR.test.ts.snap | 6 +- test/plugins/Asset.test.ts | 6 + test/plugins/Collateral.test.ts | 64 + test/plugins/__snapshots__/Asset.test.ts.snap | 4 +- .../__snapshots__/Collateral.test.ts.snap | 22 +- .../aave-v3/AaveV3FiatCollateral.test.ts | 219 ++ .../AaveV3FiatCollateral.test.ts.snap | 25 + .../aave-v3/constants.ts | 2 + .../ATokenFiatCollateral.test.ts.snap | 24 +- .../AnkrEthCollateralTestSuite.test.ts.snap | 24 +- .../cbeth/CBETHCollateral.test.ts | 6 +- .../cbeth/CBETHCollateralL2.test.ts | 287 ++ .../CBETHCollateral.test.ts.snap | 24 +- .../individual-collateral/cbeth/constants.ts | 9 + .../individual-collateral/cbeth/helpers.ts | 14 +- .../individual-collateral/collateralTests.ts | 41 +- .../CTokenFiatCollateral.test.ts.snap | 24 +- .../__snapshots__/CometTestSuite.test.ts.snap | 24 +- .../CrvStableMetapoolSuite.test.ts.snap | 24 +- ...StableRTokenMetapoolTestSuite.test.ts.snap | 24 +- .../CrvStableTestSuite.test.ts.snap | 24 +- .../curve/cvx/CvxStableTestSuite.test.ts | 53 +- .../CvxStableMetapoolSuite.test.ts.snap | 24 +- ...StableRTokenMetapoolTestSuite.test.ts.snap | 24 +- .../CvxStableTestSuite.test.ts.snap | 24 +- .../curve/cvx/helpers.ts | 9 +- .../curve/pluginTestTypes.ts | 3 +- .../SDaiCollateralTestSuite.test.ts.snap | 24 +- .../flux-finance/FTokenFiatCollateral.test.ts | 20 +- .../FTokenFiatCollateral.test.ts.snap | 96 +- .../SFrxEthTestSuite.test.ts.snap | 12 +- test/plugins/individual-collateral/helpers.ts | 3 +- .../LidoStakedEthTestSuite.test.ts.snap | 24 +- .../MorphoAAVENonFiatCollateral.test.ts | 5 +- .../MorphoAAVEFiatCollateral.test.ts.snap | 72 +- .../MorphoAAVENonFiatCollateral.test.ts.snap | 48 +- ...AAVESelfReferentialCollateral.test.ts.snap | 16 +- .../individual-collateral/pluginTestTypes.ts | 6 + .../RethCollateralTestSuite.test.ts | 50 - .../RethCollateralTestSuite.test.ts.snap | 24 +- .../StargateETHTestSuite.test.ts.snap | 24 +- .../__snapshots__/MaxBasketSize.test.ts.snap | 16 +- test/utils/oracles.ts | 2 +- test/utils/trades.ts | 14 +- tools/docgen/custom.css | 116 + tools/docgen/foundry.toml | 7 + utils/env.ts | 4 +- utils/fork.ts | 12 + yarn.lock | 18 + 160 files changed, 14150 insertions(+), 1912 deletions(-) create mode 100644 .github/workflows/docgen-netlify.yml create mode 100644 .openzeppelin/base_8453.json create mode 100644 audits/Code4rena - Reserve Audit Report - 3.0.0 Release.md create mode 100644 book.toml create mode 100644 contracts/plugins/assets/L2LSDCollateral.sol create mode 100644 contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol create mode 100644 contracts/plugins/assets/aave-v3/mock/MockStaticATokenV3.sol create mode 100644 contracts/plugins/assets/aave-v3/vendor/ERC20.sol create mode 100644 contracts/plugins/assets/aave-v3/vendor/RayMathExplicitRounding.sol create mode 100644 contracts/plugins/assets/aave-v3/vendor/StaticATokenErrors.sol create mode 100644 contracts/plugins/assets/aave-v3/vendor/StaticATokenV3LM.sol create mode 100644 contracts/plugins/assets/aave-v3/vendor/interfaces/IAToken.sol create mode 100644 contracts/plugins/assets/aave-v3/vendor/interfaces/IERC4626.sol create mode 100644 contracts/plugins/assets/aave-v3/vendor/interfaces/IInitializableStaticATokenLM.sol create mode 100644 contracts/plugins/assets/aave-v3/vendor/interfaces/IStaticATokenV3LM.sol create mode 100644 contracts/plugins/assets/cbeth/CBETHCollateralL2.sol create mode 100644 contracts/plugins/assets/cbeth/vendor/ICBEth.sol create mode 100644 docs/deployed-addresses/1-ETH+.md create mode 100644 docs/deployed-addresses/1-assets-2.1.0.md create mode 100644 docs/deployed-addresses/1-components-2.1.0.md create mode 100644 docs/deployed-addresses/1-eUSD.md create mode 100644 docs/deployed-addresses/1-hyUSD.md create mode 100644 scripts/addresses/base-3.0.0/8453-tmp-assets-collateral.json create mode 100644 scripts/addresses/base-3.0.0/8453-tmp-deployments.json create mode 100644 scripts/addresses/mainnet-3.0.0/1-tmp-assets-collateral.json create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdbc.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdc.ts create mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts create mode 100644 scripts/verification/collateral-plugins/verify_aave_v3_usdbc.ts create mode 100644 scripts/verification/collateral-plugins/verify_aave_v3_usdc.ts create mode 100644 scripts/verification/collateral-plugins/verify_cusdbcv3.ts create mode 100644 scripts/verification/collateral-plugins/verify_morpho.ts create mode 100644 tasks/deployment/get-addresses.ts create mode 100644 test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts create mode 100644 test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap create mode 100644 test/plugins/individual-collateral/aave-v3/constants.ts create mode 100644 test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts create mode 100644 tools/docgen/custom.css create mode 100644 tools/docgen/foundry.toml create mode 100644 utils/fork.ts diff --git a/.env.example b/.env.example index 3ee518afba..98f4a01a35 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,9 @@ BASE_GOERLI_RPC_URL="https://goerli.base.org" # Mainnet URL, used for Mainnet forking (Alchemy) MAINNET_RPC_URL="https://eth-mainnet.alchemyapi.io/v2/your_mainnet_api_key" +# Base Mainnet URL, used for Base Mainnet forking (Alchemy) +BASE_RPC_URL="https://base-mainnet.g.alchemy.com/v2/your_base_mainnet_api_key" + # Mnemonic, first address will be used for deployments MNEMONIC='copy here your mnemonic words' @@ -29,8 +32,11 @@ ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1 # Run tests and scripts using Mainnet forking - required for integration tests # FORK=1 -# Block to use as default for mainnet forking -MAINNET_BLOCK=14916729 +# Network to fork when forking for hardhat +# FORK_NETWORK='mainnet' + +# Block to use as default for forking +# FORK_BLOCK=4446300 # Select Protocol implementation to run tests for, 0 (Prototype) or 1 (Production) PROTO_IMPL=0 diff --git a/.github/workflows/docgen-netlify.yml b/.github/workflows/docgen-netlify.yml new file mode 100644 index 0000000000..7326b95c60 --- /dev/null +++ b/.github/workflows/docgen-netlify.yml @@ -0,0 +1,52 @@ +name: Generate and Deploy Reserve Docs to Netlify + +on: + push: + branches: + - master + +env: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_ARTORIAS_AUTH_TOKEN }} + NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DOCS_SITE_ID }} + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: yarn + + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + with: + version: nightly + + - name: Install node dependencies + run: yarn install + shell: bash + + - name: Setup Forge config + run: forge config --config-path tools/docgen/foundry.toml + shell: bash + + - name: Generate docs + run: forge doc --build --out tools/docgen + shell: bash + + - name: Deploy to Netlify + uses: jsmrcaga/action-netlify-deploy@v2.0.0 + with: + NETLIFY_AUTH_TOKEN: ${{ env.NETLIFY_AUTH_TOKEN }} + NETLIFY_SITE_ID: ${{ env.NETLIFY_SITE_ID }} + NETLIFY_DEPLOY_MESSAGE: "Prod deploy v${{ github.ref }}" + NETLIFY_DEPLOY_TO_PROD: true + build_directory: tools/docgen/book + install_command: "echo Skipping installing the dependencies" + build_command: "echo Skipping building the web files" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a0215996f0..ca7fc0ee67 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,7 +21,9 @@ jobs: - run: yarn install --immutable - run: yarn devchain & env: - MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} + MAINNET_RPC_URL: https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 + FORK_BLOCK: 18114118 + FORK_NETWORK: mainnet - run: yarn deploy:run --network localhost env: SKIP_PROMPT: 1 @@ -38,8 +40,8 @@ jobs: - run: yarn install --immutable - run: yarn lint - plugin-tests: - name: 'Plugin Tests' + plugin-tests-mainnet: + name: 'Plugin Tests (Mainnet)' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -62,6 +64,35 @@ jobs: NODE_OPTIONS: '--max-old-space-size=8192' TS_NODE_SKIP_IGNORE: true MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} + FORK_NETWORK: mainnet + + plugin-tests-base: + name: 'Plugin Tests (Base)' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16.x + cache: 'yarn' + - run: yarn install --immutable + - name: 'Cache hardhat network fork' + uses: actions/cache@v3 + with: + path: cache/hardhat-network-fork + key: hardhat-network-fork-${{ runner.os }}-${{ hashFiles('test/integration/fork-block-numbers.ts') }} + restore-keys: | + hardhat-network-fork-${{ runner.os }}- + hardhat-network-fork- + - run: npx hardhat test ./test/plugins/individual-collateral/cbeth/*.test.ts + env: + NODE_OPTIONS: '--max-old-space-size=8192' + TS_NODE_SKIP_IGNORE: true + BASE_RPC_URL: https://base-mainnet.infura.io/v3/${{ secrets.INFURA_BASE_KEY }} + FORK_NETWORK: base + FORK_BLOCK: 4446300 + FORK: 1 + PROTO_IMPL: 1 p0-tests: name: 'P0 tests' @@ -129,6 +160,7 @@ jobs: NODE_OPTIONS: '--max-old-space-size=8192' TS_NODE_SKIP_IGNORE: true MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} + FORK_NETWORK: mainnet integration-tests: name: 'Integration Tests' @@ -155,3 +187,4 @@ jobs: NODE_OPTIONS: '--max-old-space-size=8192' TS_NODE_SKIP_IGNORE: true MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }} + FORK_NETWORK: mainnet diff --git a/.openzeppelin/base_8453.json b/.openzeppelin/base_8453.json new file mode 100644 index 0000000000..6c4c4a3671 --- /dev/null +++ b/.openzeppelin/base_8453.json @@ -0,0 +1,3149 @@ +{ + "manifestVersion": "3.2", + "proxies": [], + "impls": { + "9ff12d14530d638900db7dd2f55b43196fd6d1fe09e9c635a8da175a62a5cda5": { + "address": "0x1D6d0B74E7A701aE5C2E11967b242E9861275143", + "txHash": "0x4843fca0d0fb070bfe72919ca1f530e5ac723964e47aea2feb2fd8bf59797401", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC165Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41" + }, + { + "label": "_roles", + "offset": 0, + "slot": "101", + "type": "t_mapping(t_bytes32,t_struct(RoleData)3741_storage)", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:61" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:259" + }, + { + "label": "longFreezes", + "offset": 0, + "slot": "151", + "type": "t_mapping(t_address,t_uint256)", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:36" + }, + { + "label": "unfreezeAt", + "offset": 0, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:38" + }, + { + "label": "shortFreeze", + "offset": 6, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:39" + }, + { + "label": "longFreeze", + "offset": 12, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:40" + }, + { + "label": "tradingPaused", + "offset": 18, + "slot": "152", + "type": "t_bool", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:45", + "renamedFrom": "paused" + }, + { + "label": "issuancePaused", + "offset": 19, + "slot": "152", + "type": "t_bool", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:46" + }, + { + "label": "__gap", + "offset": 0, + "slot": "153", + "type": "t_array(t_uint256)48_storage", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:225" + }, + { + "label": "rToken", + "offset": 0, + "slot": "201", + "type": "t_contract(IRToken)26835", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:34" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "202", + "type": "t_contract(IStRSR)27176", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:42" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "203", + "type": "t_contract(IAssetRegistry)24822", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:50" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "204", + "type": "t_contract(IBasketHandler)25131", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:58" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "205", + "type": "t_contract(IBackingManager)24884", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:66" + }, + { + "label": "distributor", + "offset": 0, + "slot": "206", + "type": "t_contract(IDistributor)25643", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:74" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "207", + "type": "t_contract(IRevenueTrader)26964", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:82" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "208", + "type": "t_contract(IRevenueTrader)26964", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:90" + }, + { + "label": "furnace", + "offset": 0, + "slot": "209", + "type": "t_contract(IFurnace)26171", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:98" + }, + { + "label": "broker", + "offset": 0, + "slot": "210", + "type": "t_contract(IBroker)25280", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:106" + }, + { + "label": "__gap", + "offset": 0, + "slot": "211", + "type": "t_array(t_uint256)40_storage", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:119" + }, + { + "label": "__gap", + "offset": 0, + "slot": "251", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "301", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "rsr", + "offset": 0, + "slot": "351", + "type": "t_contract(IERC20)15339", + "contract": "MainP1", + "src": "contracts/p1/Main.sol:19" + }, + { + "label": "__gap", + "offset": 0, + "slot": "352", + "type": "t_array(t_uint256)49_storage", + "contract": "MainP1", + "src": "contracts/p1/Main.sol:71" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)40_storage": { + "label": "uint256[40]", + "numberOfBytes": "1280" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)24822": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)24884": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)25131": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IBroker)25280": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)25643": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)15339": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)26171": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IRToken)26835": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)26964": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)27176": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)3741_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(RoleData)3741_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "fe0f2dec194b882efa0d8220d324cba3ec32136c7c6322c221bd90103690d736": { + "address": "0x9c387fc258061bd3E02c851F36aE227DB03a396C", + "txHash": "0x931576bc01d5984558263d8d5facbbd4748e81f53f5cdb4cc732215000dcbbb6", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26615", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "201", + "type": "t_contract(IBasketHandler)25131", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:19" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "202", + "type": "t_contract(IBackingManager)24884", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:20" + }, + { + "label": "_erc20s", + "offset": 0, + "slot": "203", + "type": "t_struct(AddressSet)21587_storage", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:23" + }, + { + "label": "assets", + "offset": 0, + "slot": "205", + "type": "t_mapping(t_contract(IERC20)15339,t_contract(IAsset)24579)", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:26" + }, + { + "label": "lastRefresh", + "offset": 0, + "slot": "206", + "type": "t_uint48", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:30" + }, + { + "label": "__gap", + "offset": 0, + "slot": "207", + "type": "t_array(t_uint256)46_storage", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:233" + } + ], + "types": { + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAsset)24579": { + "label": "contract IAsset", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)24884": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)25131": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IERC20)15339": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)26615": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)15339,t_contract(IAsset)24579)": { + "label": "mapping(contract IERC20 => contract IAsset)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)21587_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)21286_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Set)21286_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "48661fdf6237c49a10304321f297c21ad2799eb111db81c7380ea05da78601f2": { + "address": "0x63e12c3b2DBCaeF1835Bb99Ac1Fdb0Ebe1bE69bE", + "txHash": "0x05c4ce9b2e15eac4b03c76588ae104e9d43308a385d3d03eaeae82942c4c02a2", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26615", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_status", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:74" + }, + { + "label": "broker", + "offset": 0, + "slot": "251", + "type": "t_contract(IBroker)25280", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:27" + }, + { + "label": "trades", + "offset": 0, + "slot": "252", + "type": "t_mapping(t_contract(IERC20)15339,t_contract(ITrade)27311)", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:30" + }, + { + "label": "tradesOpen", + "offset": 0, + "slot": "253", + "type": "t_uint48", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:31" + }, + { + "label": "maxTradeSlippage", + "offset": 6, + "slot": "253", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:34" + }, + { + "label": "minTradeVolume", + "offset": 0, + "slot": "254", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:35" + }, + { + "label": "tradesNonce", + "offset": 0, + "slot": "255", + "type": "t_uint256", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "256", + "type": "t_array(t_uint256)45_storage", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:155" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "301", + "type": "t_contract(IAssetRegistry)24822", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:30" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "302", + "type": "t_contract(IBasketHandler)25131", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:31" + }, + { + "label": "distributor", + "offset": 0, + "slot": "303", + "type": "t_contract(IDistributor)25643", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:32" + }, + { + "label": "rToken", + "offset": 0, + "slot": "304", + "type": "t_contract(IRToken)26835", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:33" + }, + { + "label": "rsr", + "offset": 0, + "slot": "305", + "type": "t_contract(IERC20)15339", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:34" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "306", + "type": "t_contract(IStRSR)27176", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:35" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "307", + "type": "t_contract(IRevenueTrader)26964", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:36" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "308", + "type": "t_contract(IRevenueTrader)26964", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:37" + }, + { + "label": "tradingDelay", + "offset": 20, + "slot": "308", + "type": "t_uint48", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:41" + }, + { + "label": "backingBuffer", + "offset": 0, + "slot": "309", + "type": "t_uint192", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:42" + }, + { + "label": "furnace", + "offset": 0, + "slot": "310", + "type": "t_contract(IFurnace)26171", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:45" + }, + { + "label": "tradeEnd", + "offset": 0, + "slot": "311", + "type": "t_mapping(t_enum(TradeKind)25153,t_uint48)", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:46" + }, + { + "label": "__gap", + "offset": 0, + "slot": "312", + "type": "t_array(t_uint256)39_storage", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:310" + } + ], + "types": { + "t_array(t_uint256)39_storage": { + "label": "uint256[39]", + "numberOfBytes": "1248" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IAssetRegistry)24822": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)25131": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IBroker)25280": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)25643": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)15339": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)26171": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)26615": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)26835": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)26964": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)27176": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_contract(ITrade)27311": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_enum(TradeKind)25153": { + "label": "enum TradeKind", + "members": ["DUTCH_AUCTION", "BATCH_AUCTION"], + "numberOfBytes": "1" + }, + "t_mapping(t_contract(IERC20)15339,t_contract(ITrade)27311)": { + "label": "mapping(contract IERC20 => contract ITrade)", + "numberOfBytes": "32" + }, + "t_mapping(t_enum(TradeKind)25153,t_uint48)": { + "label": "mapping(enum TradeKind => uint48)", + "numberOfBytes": "32" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "4e2cfd07678877b4bd048e05bb2382697948cb5ed20e4611cda549bf9f1c5541": { + "address": "0x25E92785C1AC01B397224E0534f3D626868A1Cbf", + "txHash": "0x6d83e8e952d340e41f0e83e6db70f0f92e89ec87c1a9a1e3a3fd75215ee1ad97", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26615", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "201", + "type": "t_contract(IAssetRegistry)24822", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:34" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "202", + "type": "t_contract(IBackingManager)24884", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:35" + }, + { + "label": "rsr", + "offset": 0, + "slot": "203", + "type": "t_contract(IERC20)15339", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:36" + }, + { + "label": "rToken", + "offset": 0, + "slot": "204", + "type": "t_contract(IRToken)26835", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:37" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "205", + "type": "t_contract(IStRSR)27176", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:38" + }, + { + "label": "config", + "offset": 0, + "slot": "206", + "type": "t_struct(BasketConfig)54599_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:42" + }, + { + "label": "basket", + "offset": 0, + "slot": "210", + "type": "t_struct(Basket)54609_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:46" + }, + { + "label": "nonce", + "offset": 0, + "slot": "212", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:48" + }, + { + "label": "timestamp", + "offset": 6, + "slot": "212", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:49" + }, + { + "label": "disabled", + "offset": 12, + "slot": "212", + "type": "t_bool", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:53" + }, + { + "label": "_targetNames", + "offset": 0, + "slot": "213", + "type": "t_struct(Bytes32Set)21480_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:59" + }, + { + "label": "_newBasket", + "offset": 0, + "slot": "215", + "type": "t_struct(Basket)54609_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:60" + }, + { + "label": "warmupPeriod", + "offset": 0, + "slot": "217", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:66" + }, + { + "label": "lastStatusTimestamp", + "offset": 6, + "slot": "217", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:70" + }, + { + "label": "lastStatus", + "offset": 12, + "slot": "217", + "type": "t_enum(CollateralStatus)24611", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:71" + }, + { + "label": "basketHistory", + "offset": 0, + "slot": "218", + "type": "t_mapping(t_uint48,t_struct(Basket)54609_storage)", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:77" + }, + { + "label": "_targetAmts", + "offset": 0, + "slot": "219", + "type": "t_struct(Bytes32ToUintMap)21092_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:80" + }, + { + "label": "__gap", + "offset": 0, + "slot": "222", + "type": "t_array(t_uint256)37_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:673" + } + ], + "types": { + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_contract(IERC20)15339)dyn_storage": { + "label": "contract IERC20[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)37_storage": { + "label": "uint256[37]", + "numberOfBytes": "1184" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)24822": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)24884": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IERC20)15339": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)26615": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)26835": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)27176": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_enum(CollateralStatus)24611": { + "label": "enum CollateralStatus", + "members": ["SOUND", "IFFY", "DISABLED"], + "numberOfBytes": "1" + }, + "t_mapping(t_bytes32,t_bytes32)": { + "label": "mapping(bytes32 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(BackupConfig)54579_storage)": { + "label": "mapping(bytes32 => struct BackupConfig)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)15339,t_bytes32)": { + "label": "mapping(contract IERC20 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)15339,t_uint192)": { + "label": "mapping(contract IERC20 => uint192)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint48,t_struct(Basket)54609_storage)": { + "label": "mapping(uint48 => struct Basket)", + "numberOfBytes": "32" + }, + "t_struct(BackupConfig)54579_storage": { + "label": "struct BackupConfig", + "members": [ + { + "label": "max", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)15339)dyn_storage", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Basket)54609_storage": { + "label": "struct Basket", + "members": [ + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)15339)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "refAmts", + "type": "t_mapping(t_contract(IERC20)15339,t_uint192)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(BasketConfig)54599_storage": { + "label": "struct BasketConfig", + "members": [ + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)15339)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "targetAmts", + "type": "t_mapping(t_contract(IERC20)15339,t_uint192)", + "offset": 0, + "slot": "1" + }, + { + "label": "targetNames", + "type": "t_mapping(t_contract(IERC20)15339,t_bytes32)", + "offset": 0, + "slot": "2" + }, + { + "label": "backups", + "type": "t_mapping(t_bytes32,t_struct(BackupConfig)54579_storage)", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_struct(Bytes32Set)21480_storage": { + "label": "struct EnumerableSet.Bytes32Set", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)21286_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Bytes32ToBytes32Map)20169_storage": { + "label": "struct EnumerableMap.Bytes32ToBytes32Map", + "members": [ + { + "label": "_keys", + "type": "t_struct(Bytes32Set)21480_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_values", + "type": "t_mapping(t_bytes32,t_bytes32)", + "offset": 0, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(Bytes32ToUintMap)21092_storage": { + "label": "struct EnumerableMap.Bytes32ToUintMap", + "members": [ + { + "label": "_inner", + "type": "t_struct(Bytes32ToBytes32Map)20169_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "96" + }, + "t_struct(Set)21286_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "eb36ed3a59a82baec4bd56fa27434f627d80b31df3a101e16165d869c850512c": { + "address": "0x12c3BB1B0da85fDaE0137aE8fDe901F7D0e106ba", + "txHash": "0x5fe697334dd0eef6a26b080b6245022a5cf7837ef03c1b7af54c9f060d2aab12", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26615", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "201", + "type": "t_contract(IBackingManager)24884", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:31" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "202", + "type": "t_contract(IRevenueTrader)26964", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:32" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "203", + "type": "t_contract(IRevenueTrader)26964", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:33" + }, + { + "label": "batchTradeImplementation", + "offset": 0, + "slot": "204", + "type": "t_contract(ITrade)27311", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:37", + "renamedFrom": "tradeImplementation" + }, + { + "label": "gnosis", + "offset": 0, + "slot": "205", + "type": "t_contract(IGnosis)26271", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:40" + }, + { + "label": "batchAuctionLength", + "offset": 20, + "slot": "205", + "type": "t_uint48", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:44", + "renamedFrom": "auctionLength" + }, + { + "label": "batchTradeDisabled", + "offset": 26, + "slot": "205", + "type": "t_bool", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:50", + "renamedFrom": "disabled" + }, + { + "label": "trades", + "offset": 0, + "slot": "206", + "type": "t_mapping(t_address,t_bool)", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:53" + }, + { + "label": "dutchTradeImplementation", + "offset": 0, + "slot": "207", + "type": "t_contract(ITrade)27311", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:58" + }, + { + "label": "dutchAuctionLength", + "offset": 20, + "slot": "207", + "type": "t_uint48", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:61" + }, + { + "label": "dutchTradeDisabled", + "offset": 0, + "slot": "208", + "type": "t_mapping(t_contract(IERC20Metadata)15364,t_bool)", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:64" + }, + { + "label": "__gap", + "offset": 0, + "slot": "209", + "type": "t_array(t_uint256)42_storage", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:278" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)42_storage": { + "label": "uint256[42]", + "numberOfBytes": "1344" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IBackingManager)24884": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IERC20Metadata)15364": { + "label": "contract IERC20Metadata", + "numberOfBytes": "20" + }, + "t_contract(IGnosis)26271": { + "label": "contract IGnosis", + "numberOfBytes": "20" + }, + "t_contract(IMain)26615": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)26964": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(ITrade)27311": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20Metadata)15364,t_bool)": { + "label": "mapping(contract IERC20Metadata => bool)", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "ba4e753809f0c7a43fa0a310c6445dbc18238bc5e8e399210a08bce936c83763": { + "address": "0xd31de64957b79435bfc702044590ac417e02c19B", + "txHash": "0x3fd4499d73d2c4bcd3b07bf2cd73b8f4c9ccfd2c1b30354308f852e2d9fa8bcb", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26615", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "destinations", + "offset": 0, + "slot": "201", + "type": "t_struct(AddressSet)21587_storage", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:17" + }, + { + "label": "distribution", + "offset": 0, + "slot": "203", + "type": "t_mapping(t_address,t_struct(RevenueShare)25581_storage)", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:18" + }, + { + "label": "rsr", + "offset": 0, + "slot": "204", + "type": "t_contract(IERC20)15339", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:36" + }, + { + "label": "rToken", + "offset": 0, + "slot": "205", + "type": "t_contract(IERC20)15339", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:37" + }, + { + "label": "furnace", + "offset": 0, + "slot": "206", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:38" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "207", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:39" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "208", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:40" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "209", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:41" + }, + { + "label": "__gap", + "offset": 0, + "slot": "210", + "type": "t_array(t_uint256)44_storage", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:206" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)44_storage": { + "label": "uint256[44]", + "numberOfBytes": "1408" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IERC20)15339": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)26615": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_struct(RevenueShare)25581_storage)": { + "label": "mapping(address => struct RevenueShare)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)21587_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)21286_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(RevenueShare)25581_storage": { + "label": "struct RevenueShare", + "members": [ + { + "label": "rTokenDist", + "type": "t_uint16", + "offset": 0, + "slot": "0" + }, + { + "label": "rsrDist", + "type": "t_uint16", + "offset": 2, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Set)21286_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "546202a50d79a0dd7cfcb3f9e0e3a2794622502a556be156f418d865aa4d7d04": { + "address": "0x45D7dFE976cdF80962d863A66918346a457b87Bd", + "txHash": "0x0be38d9b586b534dc0afe9089b00aedcf6045c46930becd5dc34283bb4f38d02", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26615", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "rToken", + "offset": 0, + "slot": "201", + "type": "t_contract(IRToken)26835", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:21" + }, + { + "label": "ratio", + "offset": 0, + "slot": "202", + "type": "t_uint192", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:24" + }, + { + "label": "lastPayout", + "offset": 24, + "slot": "202", + "type": "t_uint48", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:27" + }, + { + "label": "lastPayoutBal", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:28" + }, + { + "label": "__gap", + "offset": 0, + "slot": "204", + "type": "t_array(t_uint256)47_storage", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:112" + } + ], + "types": { + "t_array(t_uint256)47_storage": { + "label": "uint256[47]", + "numberOfBytes": "1504" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IMain)26615": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)26835": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "632dbf8b9744ed3b4bedeca088a559a83fb7354de5b593aefa39ab7993ce04bf": { + "address": "0xAeA6BD7b231C0eC7f35C2bdf47A76053D09dbD09", + "txHash": "0xa366202f823e4a4cf49e8e3ab5c69193088302464785962f31dc22778e3a822b", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26615", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_status", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:74" + }, + { + "label": "broker", + "offset": 0, + "slot": "251", + "type": "t_contract(IBroker)25280", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:27" + }, + { + "label": "trades", + "offset": 0, + "slot": "252", + "type": "t_mapping(t_contract(IERC20)15339,t_contract(ITrade)27311)", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:30" + }, + { + "label": "tradesOpen", + "offset": 0, + "slot": "253", + "type": "t_uint48", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:31" + }, + { + "label": "maxTradeSlippage", + "offset": 6, + "slot": "253", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:34" + }, + { + "label": "minTradeVolume", + "offset": 0, + "slot": "254", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:35" + }, + { + "label": "tradesNonce", + "offset": 0, + "slot": "255", + "type": "t_uint256", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "256", + "type": "t_array(t_uint256)45_storage", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:155" + }, + { + "label": "tokenToBuy", + "offset": 0, + "slot": "301", + "type": "t_contract(IERC20)15339", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:19" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "302", + "type": "t_contract(IAssetRegistry)24822", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:20" + }, + { + "label": "distributor", + "offset": 0, + "slot": "303", + "type": "t_contract(IDistributor)25643", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:21" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "304", + "type": "t_contract(IBackingManager)24884", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:22" + }, + { + "label": "furnace", + "offset": 0, + "slot": "305", + "type": "t_contract(IFurnace)26171", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:23" + }, + { + "label": "rToken", + "offset": 0, + "slot": "306", + "type": "t_contract(IRToken)26835", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:24" + }, + { + "label": "rsr", + "offset": 0, + "slot": "307", + "type": "t_contract(IERC20)15339", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:25" + }, + { + "label": "__gap", + "offset": 0, + "slot": "308", + "type": "t_array(t_uint256)43_storage", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:190" + } + ], + "types": { + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IAssetRegistry)24822": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)24884": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBroker)25280": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)25643": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)15339": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)26171": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)26615": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)26835": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(ITrade)27311": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_mapping(t_contract(IERC20)15339,t_contract(ITrade)27311)": { + "label": "mapping(contract IERC20 => contract ITrade)", + "numberOfBytes": "32" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "654ae924bb358aa1208293e52f8ad1216a89fcc33581dfea5664a9666450057b": { + "address": "0xA42850A760151bb3ACF17E7f8643EB4d864bF7a6", + "txHash": "0x20a2299b0aa2cce8b168ef6e0583addce5dadf5f12fcef83be3ec367a44320f2", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26615", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_balances", + "offset": 0, + "slot": "201", + "type": "t_mapping(t_address,t_uint256)", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:37" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "202", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:39" + }, + { + "label": "_totalSupply", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:41" + }, + { + "label": "_name", + "offset": 0, + "slot": "204", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:43" + }, + { + "label": "_symbol", + "offset": 0, + "slot": "205", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:44" + }, + { + "label": "__gap", + "offset": 0, + "slot": "206", + "type": "t_array(t_uint256)45_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:394" + }, + { + "label": "_HASHED_NAME", + "offset": 0, + "slot": "251", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:32" + }, + { + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "252", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:33" + }, + { + "label": "__gap", + "offset": 0, + "slot": "253", + "type": "t_array(t_uint256)50_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:120" + }, + { + "label": "_nonces", + "offset": 0, + "slot": "303", + "type": "t_mapping(t_address,t_struct(Counter)6400_storage)", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:37" + }, + { + "label": "_PERMIT_TYPEHASH_DEPRECATED_SLOT", + "offset": 0, + "slot": "304", + "type": "t_bytes32", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:51", + "renamedFrom": "_PERMIT_TYPEHASH" + }, + { + "label": "__gap", + "offset": 0, + "slot": "305", + "type": "t_array(t_uint256)48_storage", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:129" + }, + { + "label": "mandate", + "offset": 0, + "slot": "353", + "type": "t_string_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:44" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "354", + "type": "t_contract(IAssetRegistry)24822", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:47" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "355", + "type": "t_contract(IBasketHandler)25131", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:48" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "356", + "type": "t_contract(IBackingManager)24884", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:49" + }, + { + "label": "furnace", + "offset": 0, + "slot": "357", + "type": "t_contract(IFurnace)26171", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:50" + }, + { + "label": "basketsNeeded", + "offset": 0, + "slot": "358", + "type": "t_uint192", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:55" + }, + { + "label": "issuanceThrottle", + "offset": 0, + "slot": "359", + "type": "t_struct(Throttle)29644_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:58" + }, + { + "label": "redemptionThrottle", + "offset": 0, + "slot": "363", + "type": "t_struct(Throttle)29644_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:59" + }, + { + "label": "__gap", + "offset": 0, + "slot": "367", + "type": "t_array(t_uint256)42_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:536" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)42_storage": { + "label": "uint256[42]", + "numberOfBytes": "1344" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)24822": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)24884": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)25131": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)26171": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)26615": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Counter)6400_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Counter)6400_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Params)29636_storage": { + "label": "struct ThrottleLib.Params", + "members": [ + { + "label": "amtRate", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "pctRate", + "type": "t_uint192", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Throttle)29644_storage": { + "label": "struct ThrottleLib.Throttle", + "members": [ + { + "label": "params", + "type": "t_struct(Params)29636_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "lastTimestamp", + "type": "t_uint48", + "offset": 0, + "slot": "2" + }, + { + "label": "lastAvailable", + "type": "t_uint256", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "2ea71a5ba1eac5bb0c264e453e5886b4b378edf1164bf5292a0b3e6989933c2b": { + "address": "0x53321f03A7cce52413515DFD0527e0163ec69A46", + "txHash": "0xd1d7036ee0562557797266d49b3260261a198212725e596b185c62533f847c3e", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26615", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_HASHED_NAME", + "offset": 0, + "slot": "201", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:32" + }, + { + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "202", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:33" + }, + { + "label": "__gap", + "offset": 0, + "slot": "203", + "type": "t_array(t_uint256)50_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:120" + }, + { + "label": "name", + "offset": 0, + "slot": "253", + "type": "t_string_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:48" + }, + { + "label": "symbol", + "offset": 0, + "slot": "254", + "type": "t_string_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:49" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "255", + "type": "t_contract(IAssetRegistry)24822", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:54" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "256", + "type": "t_contract(IBackingManager)24884", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:55" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "257", + "type": "t_contract(IBasketHandler)25131", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:56" + }, + { + "label": "rsr", + "offset": 0, + "slot": "258", + "type": "t_contract(IERC20)15339", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:57" + }, + { + "label": "era", + "offset": 0, + "slot": "259", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:62" + }, + { + "label": "stakes", + "offset": 0, + "slot": "260", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:66" + }, + { + "label": "totalStakes", + "offset": 0, + "slot": "261", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:67" + }, + { + "label": "stakeRSR", + "offset": 0, + "slot": "262", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:68" + }, + { + "label": "stakeRate", + "offset": 0, + "slot": "263", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:69" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "264", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_mapping(t_address,t_uint256)))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:74" + }, + { + "label": "draftEra", + "offset": 0, + "slot": "265", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:79" + }, + { + "label": "draftQueues", + "offset": 0, + "slot": "266", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(CumulativeDraft)51598_storage)dyn_storage))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:87" + }, + { + "label": "firstRemainingDraft", + "offset": 0, + "slot": "267", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:88" + }, + { + "label": "totalDrafts", + "offset": 0, + "slot": "268", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:89" + }, + { + "label": "draftRSR", + "offset": 0, + "slot": "269", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:90" + }, + { + "label": "draftRate", + "offset": 0, + "slot": "270", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:91" + }, + { + "label": "_nonces", + "offset": 0, + "slot": "271", + "type": "t_mapping(t_address,t_struct(Counter)6400_storage)", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:129" + }, + { + "label": "_delegationNonces", + "offset": 0, + "slot": "272", + "type": "t_mapping(t_address,t_struct(Counter)6400_storage)", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:131" + }, + { + "label": "unstakingDelay", + "offset": 0, + "slot": "273", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:141" + }, + { + "label": "rewardRatio", + "offset": 6, + "slot": "273", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:142" + }, + { + "label": "payoutLastPaid", + "offset": 0, + "slot": "274", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:153" + }, + { + "label": "rsrRewardsAtLastPayout", + "offset": 0, + "slot": "275", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:156" + }, + { + "label": "leaked", + "offset": 0, + "slot": "276", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:162" + }, + { + "label": "lastWithdrawRefresh", + "offset": 24, + "slot": "276", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:163" + }, + { + "label": "withdrawalLeak", + "offset": 0, + "slot": "277", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:164" + }, + { + "label": "__gap", + "offset": 0, + "slot": "278", + "type": "t_array(t_uint256)28_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:1001" + }, + { + "label": "_delegates", + "offset": 0, + "slot": "306", + "type": "t_mapping(t_address,t_address)", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:31" + }, + { + "label": "_eras", + "offset": 0, + "slot": "307", + "type": "t_array(t_struct(Checkpoint)53832_storage)dyn_storage", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:34" + }, + { + "label": "_checkpoints", + "offset": 0, + "slot": "308", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(Checkpoint)53832_storage)dyn_storage))", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:38" + }, + { + "label": "_totalSupplyCheckpoints", + "offset": 0, + "slot": "309", + "type": "t_mapping(t_uint256,t_array(t_struct(Checkpoint)53832_storage)dyn_storage)", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:40" + }, + { + "label": "__gap", + "offset": 0, + "slot": "310", + "type": "t_array(t_uint256)46_storage", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:243" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_struct(Checkpoint)53832_storage)dyn_storage": { + "label": "struct StRSRP1Votes.Checkpoint[]", + "numberOfBytes": "32" + }, + "t_array(t_struct(CumulativeDraft)51598_storage)dyn_storage": { + "label": "struct StRSRP1.CumulativeDraft[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)28_storage": { + "label": "uint256[28]", + "numberOfBytes": "896" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)24822": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)24884": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)25131": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IERC20)15339": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)26615": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_address)": { + "label": "mapping(address => address)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_array(t_struct(Checkpoint)53832_storage)dyn_storage)": { + "label": "mapping(address => struct StRSRP1Votes.Checkpoint[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_array(t_struct(CumulativeDraft)51598_storage)dyn_storage)": { + "label": "mapping(address => struct StRSRP1.CumulativeDraft[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Counter)6400_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_array(t_struct(Checkpoint)53832_storage)dyn_storage)": { + "label": "mapping(uint256 => struct StRSRP1Votes.Checkpoint[])", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(Checkpoint)53832_storage)dyn_storage))": { + "label": "mapping(uint256 => mapping(address => struct StRSRP1Votes.Checkpoint[]))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(CumulativeDraft)51598_storage)dyn_storage))": { + "label": "mapping(uint256 => mapping(address => struct StRSRP1.CumulativeDraft[]))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_mapping(t_address,t_uint256)))": { + "label": "mapping(uint256 => mapping(address => mapping(address => uint256)))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_uint256))": { + "label": "mapping(uint256 => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Checkpoint)53832_storage": { + "label": "struct StRSRP1Votes.Checkpoint", + "members": [ + { + "label": "fromBlock", + "type": "t_uint48", + "offset": 0, + "slot": "0" + }, + { + "label": "val", + "type": "t_uint224", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Counter)6400_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(CumulativeDraft)51598_storage": { + "label": "struct StRSRP1.CumulativeDraft", + "members": [ + { + "label": "drafts", + "type": "t_uint176", + "offset": 0, + "slot": "0" + }, + { + "label": "availableAt", + "type": "t_uint64", + "offset": 22, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_uint176": { + "label": "uint176", + "numberOfBytes": "22" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint224": { + "label": "uint224", + "numberOfBytes": "28" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + } + } +} diff --git a/.openzeppelin/mainnet.json b/.openzeppelin/mainnet.json index ebff644d82..cb33be3859 100644 --- a/.openzeppelin/mainnet.json +++ b/.openzeppelin/mainnet.json @@ -2882,6 +2882,3149 @@ } } } + }, + "9ff12d14530d638900db7dd2f55b43196fd6d1fe09e9c635a8da175a62a5cda5": { + "address": "0xF5366f67FF66A3CefcB18809a762D5b5931FebF8", + "txHash": "0x25b5d397ea11b22100bc2b7bf21813104fe8b84c77118aa1d1c6fb39ba1558ef", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC165Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41" + }, + { + "label": "_roles", + "offset": 0, + "slot": "101", + "type": "t_mapping(t_bytes32,t_struct(RoleData)3741_storage)", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:61" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:259" + }, + { + "label": "longFreezes", + "offset": 0, + "slot": "151", + "type": "t_mapping(t_address,t_uint256)", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:36" + }, + { + "label": "unfreezeAt", + "offset": 0, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:38" + }, + { + "label": "shortFreeze", + "offset": 6, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:39" + }, + { + "label": "longFreeze", + "offset": 12, + "slot": "152", + "type": "t_uint48", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:40" + }, + { + "label": "tradingPaused", + "offset": 18, + "slot": "152", + "type": "t_bool", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:45", + "renamedFrom": "paused" + }, + { + "label": "issuancePaused", + "offset": 19, + "slot": "152", + "type": "t_bool", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:46" + }, + { + "label": "__gap", + "offset": 0, + "slot": "153", + "type": "t_array(t_uint256)48_storage", + "contract": "Auth", + "src": "contracts/mixins/Auth.sol:225" + }, + { + "label": "rToken", + "offset": 0, + "slot": "201", + "type": "t_contract(IRToken)26675", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:34" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "202", + "type": "t_contract(IStRSR)27016", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:42" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "203", + "type": "t_contract(IAssetRegistry)24671", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:50" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "204", + "type": "t_contract(IBasketHandler)24980", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:58" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "205", + "type": "t_contract(IBackingManager)24733", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:66" + }, + { + "label": "distributor", + "offset": 0, + "slot": "206", + "type": "t_contract(IDistributor)25483", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:74" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "207", + "type": "t_contract(IRevenueTrader)26804", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:82" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "208", + "type": "t_contract(IRevenueTrader)26804", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:90" + }, + { + "label": "furnace", + "offset": 0, + "slot": "209", + "type": "t_contract(IFurnace)26011", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:98" + }, + { + "label": "broker", + "offset": 0, + "slot": "210", + "type": "t_contract(IBroker)25129", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:106" + }, + { + "label": "__gap", + "offset": 0, + "slot": "211", + "type": "t_array(t_uint256)40_storage", + "contract": "ComponentRegistry", + "src": "contracts/mixins/ComponentRegistry.sol:119" + }, + { + "label": "__gap", + "offset": 0, + "slot": "251", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "301", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "rsr", + "offset": 0, + "slot": "351", + "type": "t_contract(IERC20)15191", + "contract": "MainP1", + "src": "contracts/p1/Main.sol:19" + }, + { + "label": "__gap", + "offset": 0, + "slot": "352", + "type": "t_array(t_uint256)49_storage", + "contract": "MainP1", + "src": "contracts/p1/Main.sol:71" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)40_storage": { + "label": "uint256[40]", + "numberOfBytes": "1280" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)24671": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)24733": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)24980": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IBroker)25129": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)25483": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)15191": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)26011": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IRToken)26675": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)26804": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)27016": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)3741_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(RoleData)3741_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "fe0f2dec194b882efa0d8220d324cba3ec32136c7c6322c221bd90103690d736": { + "address": "0x773cf50adCF1730964D4A9b664BaEd4b9FFC2450", + "txHash": "0xbdbde4636b24e254b21d5506964fdc019f5369421fcaa90116ed6f882193cdf9", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26455", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "201", + "type": "t_contract(IBasketHandler)24980", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:19" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "202", + "type": "t_contract(IBackingManager)24733", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:20" + }, + { + "label": "_erc20s", + "offset": 0, + "slot": "203", + "type": "t_struct(AddressSet)21439_storage", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:23" + }, + { + "label": "assets", + "offset": 0, + "slot": "205", + "type": "t_mapping(t_contract(IERC20)15191,t_contract(IAsset)24428)", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:26" + }, + { + "label": "lastRefresh", + "offset": 0, + "slot": "206", + "type": "t_uint48", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:30" + }, + { + "label": "__gap", + "offset": 0, + "slot": "207", + "type": "t_array(t_uint256)46_storage", + "contract": "AssetRegistryP1", + "src": "contracts/p1/AssetRegistry.sol:233" + } + ], + "types": { + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAsset)24428": { + "label": "contract IAsset", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)24733": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)24980": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IERC20)15191": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)26455": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)15191,t_contract(IAsset)24428)": { + "label": "mapping(contract IERC20 => contract IAsset)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)21439_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)21138_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Set)21138_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "27fae0bad0c3da7d638bee52dc88d1e4fb2a82b065d5718429785fd97e33174f": { + "address": "0x0A388FC05AA017b31fb084e43e7aEaFdBc043080", + "txHash": "0x01087c684030e2a84a29d2b20e098461c1a7cba6214f32e5906ee58bb3926306", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26455", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_status", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:74" + }, + { + "label": "broker", + "offset": 0, + "slot": "251", + "type": "t_contract(IBroker)25129", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:27" + }, + { + "label": "trades", + "offset": 0, + "slot": "252", + "type": "t_mapping(t_contract(IERC20)15191,t_contract(ITrade)27151)", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:30" + }, + { + "label": "tradesOpen", + "offset": 0, + "slot": "253", + "type": "t_uint48", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:31" + }, + { + "label": "maxTradeSlippage", + "offset": 6, + "slot": "253", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:34" + }, + { + "label": "minTradeVolume", + "offset": 0, + "slot": "254", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:35" + }, + { + "label": "tradesNonce", + "offset": 0, + "slot": "255", + "type": "t_uint256", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "256", + "type": "t_array(t_uint256)45_storage", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:155" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "301", + "type": "t_contract(IAssetRegistry)24671", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:30" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "302", + "type": "t_contract(IBasketHandler)24980", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:31" + }, + { + "label": "distributor", + "offset": 0, + "slot": "303", + "type": "t_contract(IDistributor)25483", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:32" + }, + { + "label": "rToken", + "offset": 0, + "slot": "304", + "type": "t_contract(IRToken)26675", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:33" + }, + { + "label": "rsr", + "offset": 0, + "slot": "305", + "type": "t_contract(IERC20)15191", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:34" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "306", + "type": "t_contract(IStRSR)27016", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:35" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "307", + "type": "t_contract(IRevenueTrader)26804", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:36" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "308", + "type": "t_contract(IRevenueTrader)26804", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:37" + }, + { + "label": "tradingDelay", + "offset": 20, + "slot": "308", + "type": "t_uint48", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:41" + }, + { + "label": "backingBuffer", + "offset": 0, + "slot": "309", + "type": "t_uint192", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:42" + }, + { + "label": "furnace", + "offset": 0, + "slot": "310", + "type": "t_contract(IFurnace)26011", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:45" + }, + { + "label": "tradeEnd", + "offset": 0, + "slot": "311", + "type": "t_mapping(t_enum(TradeKind)25002,t_uint48)", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:46" + }, + { + "label": "__gap", + "offset": 0, + "slot": "312", + "type": "t_array(t_uint256)39_storage", + "contract": "BackingManagerP1", + "src": "contracts/p1/BackingManager.sol:310" + } + ], + "types": { + "t_array(t_uint256)39_storage": { + "label": "uint256[39]", + "numberOfBytes": "1248" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IAssetRegistry)24671": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)24980": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IBroker)25129": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)25483": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)15191": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)26011": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)26455": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)26675": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)26804": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)27016": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_contract(ITrade)27151": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_enum(TradeKind)25002": { + "label": "enum TradeKind", + "members": ["DUTCH_AUCTION", "BATCH_AUCTION"], + "numberOfBytes": "1" + }, + "t_mapping(t_contract(IERC20)15191,t_contract(ITrade)27151)": { + "label": "mapping(contract IERC20 => contract ITrade)", + "numberOfBytes": "32" + }, + "t_mapping(t_enum(TradeKind)25002,t_uint48)": { + "label": "mapping(enum TradeKind => uint48)", + "numberOfBytes": "32" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "3932e3a18a08667478426126d6b91d5a6c9550a08ed3d33006f251ce889a369c": { + "address": "0x5ccca36CbB66a4E4033B08b4F6D7bAc96bA55cDc", + "txHash": "0x69e22cd8dbb7da095140555f5bd8b96842a95dbd3800106eaa52f50b31120153", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26455", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "201", + "type": "t_contract(IAssetRegistry)24671", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:34" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "202", + "type": "t_contract(IBackingManager)24733", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:35" + }, + { + "label": "rsr", + "offset": 0, + "slot": "203", + "type": "t_contract(IERC20)15191", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:36" + }, + { + "label": "rToken", + "offset": 0, + "slot": "204", + "type": "t_contract(IRToken)26675", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:37" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "205", + "type": "t_contract(IStRSR)27016", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:38" + }, + { + "label": "config", + "offset": 0, + "slot": "206", + "type": "t_struct(BasketConfig)53516_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:42" + }, + { + "label": "basket", + "offset": 0, + "slot": "210", + "type": "t_struct(Basket)53526_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:46" + }, + { + "label": "nonce", + "offset": 0, + "slot": "212", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:48" + }, + { + "label": "timestamp", + "offset": 6, + "slot": "212", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:49" + }, + { + "label": "disabled", + "offset": 12, + "slot": "212", + "type": "t_bool", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:53" + }, + { + "label": "_targetNames", + "offset": 0, + "slot": "213", + "type": "t_struct(Bytes32Set)21332_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:59" + }, + { + "label": "_newBasket", + "offset": 0, + "slot": "215", + "type": "t_struct(Basket)53526_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:60" + }, + { + "label": "warmupPeriod", + "offset": 0, + "slot": "217", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:66" + }, + { + "label": "lastStatusTimestamp", + "offset": 6, + "slot": "217", + "type": "t_uint48", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:70" + }, + { + "label": "lastStatus", + "offset": 12, + "slot": "217", + "type": "t_enum(CollateralStatus)24460", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:71" + }, + { + "label": "basketHistory", + "offset": 0, + "slot": "218", + "type": "t_mapping(t_uint48,t_struct(Basket)53526_storage)", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:77" + }, + { + "label": "_targetAmts", + "offset": 0, + "slot": "219", + "type": "t_struct(Bytes32ToUintMap)20944_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:80" + }, + { + "label": "__gap", + "offset": 0, + "slot": "222", + "type": "t_array(t_uint256)37_storage", + "contract": "BasketHandlerP1", + "src": "contracts/p1/BasketHandler.sol:673" + } + ], + "types": { + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_contract(IERC20)15191)dyn_storage": { + "label": "contract IERC20[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)37_storage": { + "label": "uint256[37]", + "numberOfBytes": "1184" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)24671": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)24733": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IERC20)15191": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)26455": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)26675": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(IStRSR)27016": { + "label": "contract IStRSR", + "numberOfBytes": "20" + }, + "t_enum(CollateralStatus)24460": { + "label": "enum CollateralStatus", + "members": ["SOUND", "IFFY", "DISABLED"], + "numberOfBytes": "1" + }, + "t_mapping(t_bytes32,t_bytes32)": { + "label": "mapping(bytes32 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(BackupConfig)53496_storage)": { + "label": "mapping(bytes32 => struct BackupConfig)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)15191,t_bytes32)": { + "label": "mapping(contract IERC20 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20)15191,t_uint192)": { + "label": "mapping(contract IERC20 => uint192)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint48,t_struct(Basket)53526_storage)": { + "label": "mapping(uint48 => struct Basket)", + "numberOfBytes": "32" + }, + "t_struct(BackupConfig)53496_storage": { + "label": "struct BackupConfig", + "members": [ + { + "label": "max", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)15191)dyn_storage", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Basket)53526_storage": { + "label": "struct Basket", + "members": [ + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)15191)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "refAmts", + "type": "t_mapping(t_contract(IERC20)15191,t_uint192)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(BasketConfig)53516_storage": { + "label": "struct BasketConfig", + "members": [ + { + "label": "erc20s", + "type": "t_array(t_contract(IERC20)15191)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "targetAmts", + "type": "t_mapping(t_contract(IERC20)15191,t_uint192)", + "offset": 0, + "slot": "1" + }, + { + "label": "targetNames", + "type": "t_mapping(t_contract(IERC20)15191,t_bytes32)", + "offset": 0, + "slot": "2" + }, + { + "label": "backups", + "type": "t_mapping(t_bytes32,t_struct(BackupConfig)53496_storage)", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_struct(Bytes32Set)21332_storage": { + "label": "struct EnumerableSet.Bytes32Set", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)21138_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Bytes32ToBytes32Map)20021_storage": { + "label": "struct EnumerableMap.Bytes32ToBytes32Map", + "members": [ + { + "label": "_keys", + "type": "t_struct(Bytes32Set)21332_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_values", + "type": "t_mapping(t_bytes32,t_bytes32)", + "offset": 0, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(Bytes32ToUintMap)20944_storage": { + "label": "struct EnumerableMap.Bytes32ToUintMap", + "members": [ + { + "label": "_inner", + "type": "t_struct(Bytes32ToBytes32Map)20021_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "96" + }, + "t_struct(Set)21138_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "eb36ed3a59a82baec4bd56fa27434f627d80b31df3a101e16165d869c850512c": { + "address": "0x9A5F8A9bB91a868b7501139eEdB20dC129D28F04", + "txHash": "0x65f97beea44073eaa104d688c41656b1bbd0ddb240e5fc0c208562c84d32ede1", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26455", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "201", + "type": "t_contract(IBackingManager)24733", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:31" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "202", + "type": "t_contract(IRevenueTrader)26804", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:32" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "203", + "type": "t_contract(IRevenueTrader)26804", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:33" + }, + { + "label": "batchTradeImplementation", + "offset": 0, + "slot": "204", + "type": "t_contract(ITrade)27151", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:37", + "renamedFrom": "tradeImplementation" + }, + { + "label": "gnosis", + "offset": 0, + "slot": "205", + "type": "t_contract(IGnosis)26111", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:40" + }, + { + "label": "batchAuctionLength", + "offset": 20, + "slot": "205", + "type": "t_uint48", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:44", + "renamedFrom": "auctionLength" + }, + { + "label": "batchTradeDisabled", + "offset": 26, + "slot": "205", + "type": "t_bool", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:50", + "renamedFrom": "disabled" + }, + { + "label": "trades", + "offset": 0, + "slot": "206", + "type": "t_mapping(t_address,t_bool)", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:53" + }, + { + "label": "dutchTradeImplementation", + "offset": 0, + "slot": "207", + "type": "t_contract(ITrade)27151", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:58" + }, + { + "label": "dutchAuctionLength", + "offset": 20, + "slot": "207", + "type": "t_uint48", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:61" + }, + { + "label": "dutchTradeDisabled", + "offset": 0, + "slot": "208", + "type": "t_mapping(t_contract(IERC20Metadata)15216,t_bool)", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:64" + }, + { + "label": "__gap", + "offset": 0, + "slot": "209", + "type": "t_array(t_uint256)42_storage", + "contract": "BrokerP1", + "src": "contracts/p1/Broker.sol:278" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)42_storage": { + "label": "uint256[42]", + "numberOfBytes": "1344" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IBackingManager)24733": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IERC20Metadata)15216": { + "label": "contract IERC20Metadata", + "numberOfBytes": "20" + }, + "t_contract(IGnosis)26111": { + "label": "contract IGnosis", + "numberOfBytes": "20" + }, + "t_contract(IMain)26455": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRevenueTrader)26804": { + "label": "contract IRevenueTrader", + "numberOfBytes": "20" + }, + "t_contract(ITrade)27151": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_contract(IERC20Metadata)15216,t_bool)": { + "label": "mapping(contract IERC20Metadata => bool)", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "ba4e753809f0c7a43fa0a310c6445dbc18238bc5e8e399210a08bce936c83763": { + "address": "0x0e8439a17bA5cBb2D9823c03a02566B9dd5d96Ac", + "txHash": "0x6eeb691ccce0b8f3af94d4d11d9f9ef3df58515c35970972c98a3a5eed7ec5db", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26455", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "destinations", + "offset": 0, + "slot": "201", + "type": "t_struct(AddressSet)21439_storage", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:17" + }, + { + "label": "distribution", + "offset": 0, + "slot": "203", + "type": "t_mapping(t_address,t_struct(RevenueShare)25421_storage)", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:18" + }, + { + "label": "rsr", + "offset": 0, + "slot": "204", + "type": "t_contract(IERC20)15191", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:36" + }, + { + "label": "rToken", + "offset": 0, + "slot": "205", + "type": "t_contract(IERC20)15191", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:37" + }, + { + "label": "furnace", + "offset": 0, + "slot": "206", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:38" + }, + { + "label": "stRSR", + "offset": 0, + "slot": "207", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:39" + }, + { + "label": "rTokenTrader", + "offset": 0, + "slot": "208", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:40" + }, + { + "label": "rsrTrader", + "offset": 0, + "slot": "209", + "type": "t_address", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:41" + }, + { + "label": "__gap", + "offset": 0, + "slot": "210", + "type": "t_array(t_uint256)44_storage", + "contract": "DistributorP1", + "src": "contracts/p1/Distributor.sol:206" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)44_storage": { + "label": "uint256[44]", + "numberOfBytes": "1408" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IERC20)15191": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)26455": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_struct(RevenueShare)25421_storage)": { + "label": "mapping(address => struct RevenueShare)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)21439_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)21138_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(RevenueShare)25421_storage": { + "label": "struct RevenueShare", + "members": [ + { + "label": "rTokenDist", + "type": "t_uint16", + "offset": 0, + "slot": "0" + }, + { + "label": "rsrDist", + "type": "t_uint16", + "offset": 2, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Set)21138_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "546202a50d79a0dd7cfcb3f9e0e3a2794622502a556be156f418d865aa4d7d04": { + "address": "0x99580Fc649c02347eBc7750524CAAe5cAcf9d34c", + "txHash": "0xa65b956104e3f80aa39ab5307b85799a2709f020987a65a3292762a64dd94dd4", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26455", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "rToken", + "offset": 0, + "slot": "201", + "type": "t_contract(IRToken)26675", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:21" + }, + { + "label": "ratio", + "offset": 0, + "slot": "202", + "type": "t_uint192", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:24" + }, + { + "label": "lastPayout", + "offset": 24, + "slot": "202", + "type": "t_uint48", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:27" + }, + { + "label": "lastPayoutBal", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:28" + }, + { + "label": "__gap", + "offset": 0, + "slot": "204", + "type": "t_array(t_uint256)47_storage", + "contract": "FurnaceP1", + "src": "contracts/p1/Furnace.sol:112" + } + ], + "types": { + "t_array(t_uint256)47_storage": { + "label": "uint256[47]", + "numberOfBytes": "1504" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IMain)26455": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)26675": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "632dbf8b9744ed3b4bedeca088a559a83fb7354de5b593aefa39ab7993ce04bf": { + "address": "0x1cCa3FBB11C4b734183f997679d52DeFA74b613A", + "txHash": "0x63f37f08a7bc49c077875e288998abad28f3514099a7a308651723d37cff7a02", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26455", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_status", + "offset": 0, + "slot": "201", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:74" + }, + { + "label": "broker", + "offset": 0, + "slot": "251", + "type": "t_contract(IBroker)25129", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:27" + }, + { + "label": "trades", + "offset": 0, + "slot": "252", + "type": "t_mapping(t_contract(IERC20)15191,t_contract(ITrade)27151)", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:30" + }, + { + "label": "tradesOpen", + "offset": 0, + "slot": "253", + "type": "t_uint48", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:31" + }, + { + "label": "maxTradeSlippage", + "offset": 6, + "slot": "253", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:34" + }, + { + "label": "minTradeVolume", + "offset": 0, + "slot": "254", + "type": "t_uint192", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:35" + }, + { + "label": "tradesNonce", + "offset": 0, + "slot": "255", + "type": "t_uint256", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "256", + "type": "t_array(t_uint256)45_storage", + "contract": "TradingP1", + "src": "contracts/p1/mixins/Trading.sol:155" + }, + { + "label": "tokenToBuy", + "offset": 0, + "slot": "301", + "type": "t_contract(IERC20)15191", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:19" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "302", + "type": "t_contract(IAssetRegistry)24671", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:20" + }, + { + "label": "distributor", + "offset": 0, + "slot": "303", + "type": "t_contract(IDistributor)25483", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:21" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "304", + "type": "t_contract(IBackingManager)24733", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:22" + }, + { + "label": "furnace", + "offset": 0, + "slot": "305", + "type": "t_contract(IFurnace)26011", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:23" + }, + { + "label": "rToken", + "offset": 0, + "slot": "306", + "type": "t_contract(IRToken)26675", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:24" + }, + { + "label": "rsr", + "offset": 0, + "slot": "307", + "type": "t_contract(IERC20)15191", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:25" + }, + { + "label": "__gap", + "offset": 0, + "slot": "308", + "type": "t_array(t_uint256)43_storage", + "contract": "RevenueTraderP1", + "src": "contracts/p1/RevenueTrader.sol:190" + } + ], + "types": { + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IAssetRegistry)24671": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)24733": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBroker)25129": { + "label": "contract IBroker", + "numberOfBytes": "20" + }, + "t_contract(IDistributor)25483": { + "label": "contract IDistributor", + "numberOfBytes": "20" + }, + "t_contract(IERC20)15191": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)26011": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)26455": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_contract(IRToken)26675": { + "label": "contract IRToken", + "numberOfBytes": "20" + }, + "t_contract(ITrade)27151": { + "label": "contract ITrade", + "numberOfBytes": "20" + }, + "t_mapping(t_contract(IERC20)15191,t_contract(ITrade)27151)": { + "label": "mapping(contract IERC20 => contract ITrade)", + "numberOfBytes": "32" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "654ae924bb358aa1208293e52f8ad1216a89fcc33581dfea5664a9666450057b": { + "address": "0xb6f01Aa21defA4a4DE33Bed16BcC06cfd23b6A6F", + "txHash": "0x6f13a2b27846ae2866c301b7d60fd62f6df7ddd4f2a6676889c5472250686669", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26455", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_balances", + "offset": 0, + "slot": "201", + "type": "t_mapping(t_address,t_uint256)", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:37" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "202", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:39" + }, + { + "label": "_totalSupply", + "offset": 0, + "slot": "203", + "type": "t_uint256", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:41" + }, + { + "label": "_name", + "offset": 0, + "slot": "204", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:43" + }, + { + "label": "_symbol", + "offset": 0, + "slot": "205", + "type": "t_string_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:44" + }, + { + "label": "__gap", + "offset": 0, + "slot": "206", + "type": "t_array(t_uint256)45_storage", + "contract": "ERC20Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:394" + }, + { + "label": "_HASHED_NAME", + "offset": 0, + "slot": "251", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:32" + }, + { + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "252", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:33" + }, + { + "label": "__gap", + "offset": 0, + "slot": "253", + "type": "t_array(t_uint256)50_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:120" + }, + { + "label": "_nonces", + "offset": 0, + "slot": "303", + "type": "t_mapping(t_address,t_struct(Counter)6400_storage)", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:37" + }, + { + "label": "_PERMIT_TYPEHASH_DEPRECATED_SLOT", + "offset": 0, + "slot": "304", + "type": "t_bytes32", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:51", + "renamedFrom": "_PERMIT_TYPEHASH" + }, + { + "label": "__gap", + "offset": 0, + "slot": "305", + "type": "t_array(t_uint256)48_storage", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/vendor/ERC20PermitUpgradeable.sol:129" + }, + { + "label": "mandate", + "offset": 0, + "slot": "353", + "type": "t_string_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:44" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "354", + "type": "t_contract(IAssetRegistry)24671", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:47" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "355", + "type": "t_contract(IBasketHandler)24980", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:48" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "356", + "type": "t_contract(IBackingManager)24733", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:49" + }, + { + "label": "furnace", + "offset": 0, + "slot": "357", + "type": "t_contract(IFurnace)26011", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:50" + }, + { + "label": "basketsNeeded", + "offset": 0, + "slot": "358", + "type": "t_uint192", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:55" + }, + { + "label": "issuanceThrottle", + "offset": 0, + "slot": "359", + "type": "t_struct(Throttle)29484_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:58" + }, + { + "label": "redemptionThrottle", + "offset": 0, + "slot": "363", + "type": "t_struct(Throttle)29484_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:59" + }, + { + "label": "__gap", + "offset": 0, + "slot": "367", + "type": "t_array(t_uint256)42_storage", + "contract": "RTokenP1", + "src": "contracts/p1/RToken.sol:536" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)42_storage": { + "label": "uint256[42]", + "numberOfBytes": "1344" + }, + "t_array(t_uint256)45_storage": { + "label": "uint256[45]", + "numberOfBytes": "1440" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)24671": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)24733": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)24980": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IFurnace)26011": { + "label": "contract IFurnace", + "numberOfBytes": "20" + }, + "t_contract(IMain)26455": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Counter)6400_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Counter)6400_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Params)29476_storage": { + "label": "struct ThrottleLib.Params", + "members": [ + { + "label": "amtRate", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "pctRate", + "type": "t_uint192", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Throttle)29484_storage": { + "label": "struct ThrottleLib.Throttle", + "members": [ + { + "label": "params", + "type": "t_struct(Params)29476_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "lastTimestamp", + "type": "t_uint48", + "offset": 0, + "slot": "2" + }, + { + "label": "lastAvailable", + "type": "t_uint256", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "2ea71a5ba1eac5bb0c264e453e5886b4b378edf1164bf5292a0b3e6989933c2b": { + "address": "0xC98eaFc9F249D90e3E35E729e3679DD75A899c10", + "txHash": "0xd0188111deb4fe412a62db6ef61561b237f7b13f2332c87006e348cfca9e7915", + "layout": { + "solcVersion": "0.8.19", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:211" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:107" + }, + { + "label": "main", + "offset": 0, + "slot": "151", + "type": "t_contract(IMain)26455", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:21" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "ComponentP1", + "src": "contracts/p1/mixins/Component.sol:69" + }, + { + "label": "_HASHED_NAME", + "offset": 0, + "slot": "201", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:32" + }, + { + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "202", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:33" + }, + { + "label": "__gap", + "offset": 0, + "slot": "203", + "type": "t_array(t_uint256)50_storage", + "contract": "EIP712Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol:120" + }, + { + "label": "name", + "offset": 0, + "slot": "253", + "type": "t_string_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:48" + }, + { + "label": "symbol", + "offset": 0, + "slot": "254", + "type": "t_string_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:49" + }, + { + "label": "assetRegistry", + "offset": 0, + "slot": "255", + "type": "t_contract(IAssetRegistry)24671", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:54" + }, + { + "label": "backingManager", + "offset": 0, + "slot": "256", + "type": "t_contract(IBackingManager)24733", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:55" + }, + { + "label": "basketHandler", + "offset": 0, + "slot": "257", + "type": "t_contract(IBasketHandler)24980", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:56" + }, + { + "label": "rsr", + "offset": 0, + "slot": "258", + "type": "t_contract(IERC20)15191", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:57" + }, + { + "label": "era", + "offset": 0, + "slot": "259", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:62" + }, + { + "label": "stakes", + "offset": 0, + "slot": "260", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:66" + }, + { + "label": "totalStakes", + "offset": 0, + "slot": "261", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:67" + }, + { + "label": "stakeRSR", + "offset": 0, + "slot": "262", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:68" + }, + { + "label": "stakeRate", + "offset": 0, + "slot": "263", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:69" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "264", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_mapping(t_address,t_uint256)))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:74" + }, + { + "label": "draftEra", + "offset": 0, + "slot": "265", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:79" + }, + { + "label": "draftQueues", + "offset": 0, + "slot": "266", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(CumulativeDraft)50515_storage)dyn_storage))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:87" + }, + { + "label": "firstRemainingDraft", + "offset": 0, + "slot": "267", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:88" + }, + { + "label": "totalDrafts", + "offset": 0, + "slot": "268", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:89" + }, + { + "label": "draftRSR", + "offset": 0, + "slot": "269", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:90" + }, + { + "label": "draftRate", + "offset": 0, + "slot": "270", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:91" + }, + { + "label": "_nonces", + "offset": 0, + "slot": "271", + "type": "t_mapping(t_address,t_struct(Counter)6400_storage)", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:129" + }, + { + "label": "_delegationNonces", + "offset": 0, + "slot": "272", + "type": "t_mapping(t_address,t_struct(Counter)6400_storage)", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:131" + }, + { + "label": "unstakingDelay", + "offset": 0, + "slot": "273", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:141" + }, + { + "label": "rewardRatio", + "offset": 6, + "slot": "273", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:142" + }, + { + "label": "payoutLastPaid", + "offset": 0, + "slot": "274", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:153" + }, + { + "label": "rsrRewardsAtLastPayout", + "offset": 0, + "slot": "275", + "type": "t_uint256", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:156" + }, + { + "label": "leaked", + "offset": 0, + "slot": "276", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:162" + }, + { + "label": "lastWithdrawRefresh", + "offset": 24, + "slot": "276", + "type": "t_uint48", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:163" + }, + { + "label": "withdrawalLeak", + "offset": 0, + "slot": "277", + "type": "t_uint192", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:164" + }, + { + "label": "__gap", + "offset": 0, + "slot": "278", + "type": "t_array(t_uint256)28_storage", + "contract": "StRSRP1", + "src": "contracts/p1/StRSR.sol:1001" + }, + { + "label": "_delegates", + "offset": 0, + "slot": "306", + "type": "t_mapping(t_address,t_address)", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:31" + }, + { + "label": "_eras", + "offset": 0, + "slot": "307", + "type": "t_array(t_struct(Checkpoint)52749_storage)dyn_storage", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:34" + }, + { + "label": "_checkpoints", + "offset": 0, + "slot": "308", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(Checkpoint)52749_storage)dyn_storage))", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:38" + }, + { + "label": "_totalSupplyCheckpoints", + "offset": 0, + "slot": "309", + "type": "t_mapping(t_uint256,t_array(t_struct(Checkpoint)52749_storage)dyn_storage)", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:40" + }, + { + "label": "__gap", + "offset": 0, + "slot": "310", + "type": "t_array(t_uint256)46_storage", + "contract": "StRSRP1Votes", + "src": "contracts/p1/StRSRVotes.sol:243" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_struct(Checkpoint)52749_storage)dyn_storage": { + "label": "struct StRSRP1Votes.Checkpoint[]", + "numberOfBytes": "32" + }, + "t_array(t_struct(CumulativeDraft)50515_storage)dyn_storage": { + "label": "struct StRSRP1.CumulativeDraft[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)28_storage": { + "label": "uint256[28]", + "numberOfBytes": "896" + }, + "t_array(t_uint256)46_storage": { + "label": "uint256[46]", + "numberOfBytes": "1472" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAssetRegistry)24671": { + "label": "contract IAssetRegistry", + "numberOfBytes": "20" + }, + "t_contract(IBackingManager)24733": { + "label": "contract IBackingManager", + "numberOfBytes": "20" + }, + "t_contract(IBasketHandler)24980": { + "label": "contract IBasketHandler", + "numberOfBytes": "20" + }, + "t_contract(IERC20)15191": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IMain)26455": { + "label": "contract IMain", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_address)": { + "label": "mapping(address => address)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_array(t_struct(Checkpoint)52749_storage)dyn_storage)": { + "label": "mapping(address => struct StRSRP1Votes.Checkpoint[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_array(t_struct(CumulativeDraft)50515_storage)dyn_storage)": { + "label": "mapping(address => struct StRSRP1.CumulativeDraft[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Counter)6400_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_array(t_struct(Checkpoint)52749_storage)dyn_storage)": { + "label": "mapping(uint256 => struct StRSRP1Votes.Checkpoint[])", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(Checkpoint)52749_storage)dyn_storage))": { + "label": "mapping(uint256 => mapping(address => struct StRSRP1Votes.Checkpoint[]))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_array(t_struct(CumulativeDraft)50515_storage)dyn_storage))": { + "label": "mapping(uint256 => mapping(address => struct StRSRP1.CumulativeDraft[]))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_mapping(t_address,t_uint256)))": { + "label": "mapping(uint256 => mapping(address => mapping(address => uint256)))", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_uint256))": { + "label": "mapping(uint256 => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Checkpoint)52749_storage": { + "label": "struct StRSRP1Votes.Checkpoint", + "members": [ + { + "label": "fromBlock", + "type": "t_uint48", + "offset": 0, + "slot": "0" + }, + { + "label": "val", + "type": "t_uint224", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Counter)6400_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(CumulativeDraft)50515_storage": { + "label": "struct StRSRP1.CumulativeDraft", + "members": [ + { + "label": "drafts", + "type": "t_uint176", + "offset": 0, + "slot": "0" + }, + { + "label": "availableAt", + "type": "t_uint64", + "offset": 22, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_uint176": { + "label": "uint176", + "numberOfBytes": "22" + }, + "t_uint192": { + "label": "uint192", + "numberOfBytes": "24" + }, + "t_uint224": { + "label": "uint224", + "numberOfBytes": "28" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } } } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 45c59ff34f..dfe4bb03c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ Call the following functions, once it is desired to turn on the new features: - `BasketHandler.setWarmupPeriod()` - `StRSR.setWithdrawalLeak()` - `Broker.setDutchAuctionLength()` +- `Broker.setDutchTradeImplementation()` It is acceptable to leave these function calls out of the initial upgrade tx and follow up with them later. The protocol will continue to function, just without dutch auctions, RSR unstaking gas-savings, and the warmup period. @@ -178,6 +179,7 @@ It is acceptable to leave these function calls out of the initial upgrade tx and - Add `UnstakingCancelled()` event - Allow payout of (already acquired) RSR rewards while frozen - Add ability for governance to `resetStakes()` when stake rate falls outside (1e12, 1e24) + <<<<<<< HEAD - `StRSRVotes` [+0 slots] - Add `stakeAndDelegate(uint256 rsrAmount, address delegate)` function to encourage people to receive voting weight upon staking @@ -291,6 +293,122 @@ Across all collateral, `tryPrice()` was updated to exclude revenueHiding conside - Add EasyAuction regression test for Broker false positive (observed during USDC de-peg) - Add EasyAuction extreme tests +======= + +- `StRSRVotes` [+0 slots] + - Add `stakeAndDelegate(uint256 rsrAmount, address delegate)` function to encourage people to receive voting weight upon staking + +### Facades + +Remove `FacadeMonitor` - now redundant with `nextRecollateralizationAuction()` and `revenueOverview()` + +- `FacadeAct` + Summary: Remove unused `getActCalldata()` and add way to run revenue auctions + + - Remove `getActCalldata(..)` + - Remove `canRunRecollateralizationAuctions(..)` + - Remove `runRevenueAuctions(..)` + - Add `revenueOverview(IRevenueTrader) returns ( IERC20[] memory erc20s, bool[] memory canStart, uint256[] memory surpluses, uint256[] memory minTradeAmounts)` + - Add `nextRecollateralizationAuction(..) returns (bool canStart, IERC20 sell, IERC20 buy, uint256 sellAmount)` + - Modify all functions to work on both 3.0.0 and 2.1.0 RTokens + +- `FacadeRead` + Summary: Add new data summary views frontends may be interested in + + - Remove `basketNonce` from `redeem(.., uint48 basketNonce)` + - Add `redeemCustom(.., uint48[] memory basketNonces, uint192[] memory portions)` callstatic to simulate multi-basket redemptions + - Remove `traderBalances(..)` + - Add `balancesAcrossAllTraders(IBackingManager) returns (IERC20[] memory erc20s, uint256[] memory balances, uint256[] memory balancesNeededByBackingManager)` + +- `FacadeWrite` + Summary: More expressive and fine-grained control over the set of pausers and freezers + + - Do not automatically grant Guardian PAUSER/SHORT_FREEZER/LONG_FREEZER + - Do not automatically grant Owner PAUSER/SHORT_FREEZER/LONG_FREEZER + - Add ability to initialize with multiple pausers, short freezers, and long freezers + - Modify `setupGovernance(.., address owner, address guardian, address pauser)` -> `setupGovernance(.., GovernanceRoles calldata govRoles)` + +## Plugins + +### DutchTrade + +A cheaper, simpler, trading method. Intended to be the new dominant trading method, with GnosisTrade (batch auctions) available as a backup option. Generally speaking the batch auction length can be kept shorter than the dutch auction length. + +DutchTrade implements a four-stage, single-lot, falling price dutch auction: + +1. In the first 20% of the auction, the price falls from 1000x the best price to the best price in a geometric/exponential decay as a price manipulation defense mechanism. Bids are not expected to occur (but note: unlike the GnosisTrade batch auction, this mechanism is not resistant to _arbitrary_ price manipulation). If a bid occurs, then trading for the pair of tokens is disabled as long as the trade was started by the BackingManager. +2. Between 20% and 45%, the price falls linearly from 1.5x the best price to the best price. +3. Between 45% and 95%, the price falls linearly from the best price to the worst price. +4. Over the last 5% of the auction, the price remains constant at the worst price. + +Duration: 30 min (default) + +### Assets and Collateral + +- Add `version() return (string)` getter to pave way for separation of asset versioning and core protocol versioning +- Deprecate `claimRewards()` +- Add `lastSave()` to `RTokenAsset` +- Remove `CurveVolatileCollateral` +- Switch `CToken*Collateral` (Compound V2) to using a CTokenVault ERC20 rather than the raw cToken +- Bugfix: `lotPrice()` now begins at 100% the lastSavedPrice, instead of below 100%. It can be at 100% for up to the oracleTimeout in the worst-case. +- Bugfix: Handle oracle deprecation as indicated by the `aggregator()` being set to the zero address +- Bugfix: `AnkrStakedETHCollateral`/`CBETHCollateral`/`RethCollateral` now correctly detects soft default (note that Ankr still requires a new oracle before it can be deployed) +- Bugfix: Adjust `Curve*Collateral` and `RTokenAsset` to treat FIX_MAX correctly as +inf +- Bugfix: Continue updating cached price after collateral default (impacts all appreciating collateral) + +# 2.1.0 + +### Core protocol contracts + +- `BasketHandler` + - Bugfix for `getPrimeBasket()` view + - Minor change to `_price()` rounding + - Minor natspec improvement to `refreshBasket()` +- `Broker` + - Fix `GnosisTrade` trade implemention to treat defensive rounding by EasyAuction correctly + - Add `setGnosis()` and `setTradeImplementation()` governance functions +- `RToken` + - Minor gas optimization added to `redeemTo` to use saved `assetRegistry` variable +- `StRSR` + - Expose RSR variables via `getDraftRSR()`, `getStakeRSR()`, and `getTotalDrafts()` views + +### Facades + +- `FacadeRead` + - Extend `issue()` to return the estimated USD value of deposits as `depositsUoA` + - Add `traderBalances()` + - Add `auctionsSettleable()` + - Add `nextRecollateralizationAuction()` + - Modify `backingOverview() to handle unpriced cases` +- `FacadeAct` + - Add `runRevenueAuctions()` + +### Plugins + +#### Assets and Collateral + +Across all collateral, `tryPrice()` was updated to exclude revenueHiding considerations + +- Deploy CRV + CVX plugins +- Add `AnkrStakedEthCollateral` + tests + deployment/verification scripts for ankrETH +- Add FluxFinance collateral tests + deployment/verification scripts for fUSDC, fUSDT, fDAI, and fFRAX +- Add CompoundV3 `CTokenV3Collateral` + tests + deployment/verification scripts for cUSDCV3 +- Add Convex `CvxStableCollateral` + tests + deployment/verification scripts for 3Pool +- Add Convex `CvxVolatileCollateral` + tests + deployment/verification scripts for Tricrypto +- Add Convex `CvxStableMetapoolCollateral` + tests + deployment/verification scripts for MIM/3Pool +- Add Convex `CvxStableRTokenMetapoolCollateral` + tests + deployment/verification scripts for eUSD/fraxBP +- Add Frax `SFraxEthCollateral` + tests + deployment/verification scripts for sfrxETH +- Add Lido `LidoStakedEthCollateral` + tests + deployment/verification scripts for wstETH +- Add RocketPool `RethCollateral` + tests + deployment/verification scripts for rETH + +### Testing + +- Add generic collateral testing suite at `test/plugins/individual-collateral/collateralTests.ts` +- Add EasyAuction regression test for Broker false positive (observed during USDC de-peg) +- Add EasyAuction extreme tests + +> > > > > > > master + ### Documentation - Add `docs/plugin-addresses.md` as well as accompanying script for generation at `scripts/collateral-params.ts` diff --git a/README.md b/README.md index 0fb2726242..1c569eb492 100644 --- a/README.md +++ b/README.md @@ -18,45 +18,46 @@ For a much more detailed explanation of the economic design, including an hour-l ## Further Documentation -- [Development Environment](docs/dev-env.md): Setup and usage of our dev environment. How to compile, autoformat, lint, and test our code. - - [Testing with Echidna](docs/using-echidna.md): Notes so far on setup and usage of Echidna (which is decidedly an integration-in-progress!) - - [Deployment](docs/deployment.md): How to do test deployments in our environment. -- [System Design](docs/system-design.md): The overall architecture of our system, and some detailed descriptions about what our protocol is _intended_ to do. -- [Deployment Variables](docs/deployment-variables.md) A detailed description of the governance variables of the protocol. -- [Our Solidity Style](docs/solidity-style.md): Common practices, details, and conventions relevant to reading and writing our Solidity source code, estpecially where those go beyond standard practice. -- [Writing Collateral Plugins](docs/collateral.md): An overview of how to develop collateral plugins and the concepts / questions involved. -- [Building on Top](docs/build-on-top.md): How to build on top of Reserve, including information about long-lived fork environments. -- [MEV](docs/mev.md): A resource for MEV searchers and others looking to interact with the deployed protocol programatically. -- [Rebalancing Algorithm](docs/recollateralization.md): Description of our trading algorithm during the recollateralization process -- [Changelog](CHANGELOG.md): Release changelog - -## Mainnet Addresses (v2.1.0) +- [Development Environment](https://github.com/reserve-protocol/protocol/blob/master/docs/dev-env.md): Setup and usage of our dev environment. How to compile, autoformat, lint, and test our code. + - [Testing with Echidna](https://github.com/reserve-protocol/protocol/blob/master/docs/using-echidna.md): Notes so far on setup and usage of Echidna (which is decidedly an integration-in-progress!) + - [Deployment](https://github.com/reserve-protocol/protocol/blob/master/docs/deployment.md): How to do test deployments in our environment. +- [System Design](https://github.com/reserve-protocol/protocol/blob/master/docs/system-design.md): The overall architecture of our system, and some detailed descriptions about what our protocol is _intended_ to do. +- [Deployment Variables](https://github.com/reserve-protocol/protocol/blob/master/docs/deployment-variables.md) A detailed description of the governance variables of the protocol. +- [Our Solidity Style](https://github.com/reserve-protocol/protocol/blob/master/docs/solidity-style.md): Common practices, details, and conventions relevant to reading and writing our Solidity source code, estpecially where those go beyond standard practice. +- [Writing Collateral Plugins](https://github.com/reserve-protocol/protocol/blob/master/docs/collateral.md): An overview of how to develop collateral plugins and the concepts / questions involved. +- [Building on Top](https://github.com/reserve-protocol/protocol/blob/master/docs/build-on-top.md): How to build on top of Reserve, including information about long-lived fork environments. +- [MEV](https://github.com/reserve-protocol/protocol/blob/master/docs/mev.md): A resource for MEV searchers and others looking to interact with the deployed protocol programatically. +- [Rebalancing Algorithm](https://github.com/reserve-protocol/protocol/blob/master/docs/recollateralization.md): Description of our trading algorithm during the recollateralization process +- [Changelog](https://github.com/reserve-protocol/protocol/blob/master/CHANGELOG.md): Release changelog + +## Mainnet Addresses (v3.0.0) | Implementation Contracts | Address | | ------------------------ | --------------------------------------------------------------------------------------------------------------------- | -| tradingLib | [0x81b19Af39ab589D0Ca211DC3Dee4cfF7072eb478](https://etherscan.io/address/0x81b19Af39ab589D0Ca211DC3Dee4cfF7072eb478) | -| facadeRead | [0xf535Cab96457558eE3eeAF1402fCA6441E832f08](https://etherscan.io/address/0xf535Cab96457558eE3eeAF1402fCA6441E832f08) | -| facadeAct | [0x933c5DBdA80f03C102C560e9ed0c29812998fA78](https://etherscan.io/address/0x933c5DBdA80f03C102C560e9ed0c29812998fA78) | -| facadeWriteLib | [0xe33cEF9f56F0d8d2b683c6E1F6afcd1e43b77ea8](https://etherscan.io/address/0xe33cEF9f56F0d8d2b683c6E1F6afcd1e43b77ea8) | -| facadeWrite | [0x1656D8aAd7Ee892582B9D5c2E9992d9f94ff3629](https://etherscan.io/address/0x1656D8aAd7Ee892582B9D5c2E9992d9f94ff3629) | -| deployer | [0x5c46b718Cd79F2BBA6869A3BeC13401b9a4B69bB](https://etherscan.io/address/0x5c46b718Cd79F2BBA6869A3BeC13401b9a4B69bB) | -| rsrAsset | [0x9cd0F8387672fEaaf7C269b62c34C53590d7e948](https://etherscan.io/address/0x9cd0F8387672fEaaf7C269b62c34C53590d7e948) | -| main | [0x143C35bFe04720394eBd18AbECa83eA9D8BEdE2F](https://etherscan.io/address/0x143C35bFe04720394eBd18AbECa83eA9D8BEdE2F) | -| trade | [0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439](https://etherscan.io/address/0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439) | -| assetRegistry | [0x5a004F70b2450E909B4048050c585549Ab8afeB8](https://etherscan.io/address/0x5a004F70b2450E909B4048050c585549Ab8afeB8) | -| backingManager | [0xa0D4b6aD503E776457dBF4695d462DdF8621A1CC](https://etherscan.io/address/0xa0D4b6aD503E776457dBF4695d462DdF8621A1CC) | -| basketHandler | [0x5c13b3b6f40aD4bF7aa4793F844BA24E85482030](https://etherscan.io/address/0x5c13b3b6f40aD4bF7aa4793F844BA24E85482030) | -| broker | [0x89209a52d085D975b14555F3e828F43fb7EaF3B7](https://etherscan.io/address/0x89209a52d085D975b14555F3e828F43fb7EaF3B7) | -| distributor | [0xc78c5a84F30317B5F7D87170Ec21DC73Df38d569](https://etherscan.io/address/0xc78c5a84F30317B5F7D87170Ec21DC73Df38d569) | -| furnace | [0x393002573ea4A3d74A80F3B1Af436a3ee3A30c96](https://etherscan.io/address/0x393002573ea4A3d74A80F3B1Af436a3ee3A30c96) | -| rsrTrader | [0xE5bD2249118b6a4B39Be195951579dC9Af05029a](https://etherscan.io/address/0xE5bD2249118b6a4B39Be195951579dC9Af05029a) | -| rTokenTrader | [0xE5bD2249118b6a4B39Be195951579dC9Af05029a](https://etherscan.io/address/0xE5bD2249118b6a4B39Be195951579dC9Af05029a) | -| rToken | [0x5643D5AC6b79ae8467Cf2F416da6D465d8e7D9C1](https://etherscan.io/address/0x5643D5AC6b79ae8467Cf2F416da6D465d8e7D9C1) | -| stRSR | [0xfDa8C62d86E426D5fB653B6c44a455Bb657b693f](https://etherscan.io/address/0xfDa8C62d86E426D5fB653B6c44a455Bb657b693f) | +| tradingLib | [0xB81a1fa9A497953CEC7f370CACFA5cc364871A73](https://etherscan.io/address/0xB81a1fa9A497953CEC7f370CACFA5cc364871A73) | +| facadeRead | [0x81b9Ae0740CcA7cDc5211b2737de735FBC4BeB3C](https://etherscan.io/address/0x81b9Ae0740CcA7cDc5211b2737de735FBC4BeB3C) | +| facadeAct | [0x801fF27bacc7C00fBef17FC901504c79D59E845C](https://etherscan.io/address/0x801fF27bacc7C00fBef17FC901504c79D59E845C) | +| facadeWriteLib | [0x0776Ad71Ae99D759354B3f06fe17454b94837B0D](https://etherscan.io/address/0x0776Ad71Ae99D759354B3f06fe17454b94837B0D) | +| facadeWrite | [0x41edAFFB50CA1c2FEC86C629F845b8490ced8A2c](https://etherscan.io/address/0x41edAFFB50CA1c2FEC86C629F845b8490ced8A2c) | +| deployer | [0x15480f5B5ED98A94e1d36b52Dd20e9a35453A38e](https://etherscan.io/address/0x15480f5B5ED98A94e1d36b52Dd20e9a35453A38e) | +| rsrAsset | [0x7edD40933DfdA0ecEe1ad3E61a5044962284e1A6](https://etherscan.io/address/0x7edD40933DfdA0ecEe1ad3E61a5044962284e1A6) | +| main | [0xF5366f67FF66A3CefcB18809a762D5b5931FebF8](https://etherscan.io/address/0xF5366f67FF66A3CefcB18809a762D5b5931FebF8) | +| gnosisTrade | [0xe416Db92A1B27c4e28D5560C1EEC03f7c582F630](https://etherscan.io/address/0xe416Db92A1B27c4e28D5560C1EEC03f7c582F630) | +| dutchTrade | [0x2387C22727ACb91519b80A15AEf393ad40dFdb2F](https://etherscan.io/address/0x2387C22727ACb91519b80A15AEf393ad40dFdb2F) | +| assetRegistry | [0x773cf50adCF1730964D4A9b664BaEd4b9FFC2450](https://etherscan.io/address/0x773cf50adCF1730964D4A9b664BaEd4b9FFC2450) | +| backingManager | [0x0A388FC05AA017b31fb084e43e7aEaFdBc043080](https://etherscan.io/address/0x0A388FC05AA017b31fb084e43e7aEaFdBc043080) | +| basketHandler | [0x5ccca36CbB66a4E4033B08b4F6D7bAc96bA55cDc](https://etherscan.io/address/0x5ccca36CbB66a4E4033B08b4F6D7bAc96bA55cDc) | +| broker | [0x9A5F8A9bB91a868b7501139eEdB20dC129D28F04](https://etherscan.io/address/0x9A5F8A9bB91a868b7501139eEdB20dC129D28F04) | +| distributor | [0x0e8439a17bA5cBb2D9823c03a02566B9dd5d96Ac](https://etherscan.io/address/0x0e8439a17bA5cBb2D9823c03a02566B9dd5d96Ac) | +| furnace | [0x99580Fc649c02347eBc7750524CAAe5cAcf9d34c](https://etherscan.io/address/0x99580Fc649c02347eBc7750524CAAe5cAcf9d34c) | +| rsrTrader | [0x1cCa3FBB11C4b734183f997679d52DeFA74b613A](https://etherscan.io/address/0x1cCa3FBB11C4b734183f997679d52DeFA74b613A) | +| rTokenTrader | [0x1cCa3FBB11C4b734183f997679d52DeFA74b613A](https://etherscan.io/address/0x1cCa3FBB11C4b734183f997679d52DeFA74b613A) | +| rToken | [0xb6f01Aa21defA4a4DE33Bed16BcC06cfd23b6A6F](https://etherscan.io/address/0xb6f01Aa21defA4a4DE33Bed16BcC06cfd23b6A6F) | +| stRSR | [0xC98eaFc9F249D90e3E35E729e3679DD75A899c10](https://etherscan.io/address/0xC98eaFc9F249D90e3E35E729e3679DD75A899c10) | The DeployerRegistry, which contains a link to all official releases via their Deployer contracts, can be found [here](https://etherscan.io/address/0xD85Fac03804a3e44D29c494f3761D11A2262cBBe). -Deployed collateral plugin addresses and their configuration parameters can be found [here](/docs/plugin-addresses.md). +Deployed collateral plugin addresses and their configuration parameters can be found [here](https://github.com/reserve-protocol/protocol/blob/master/docs/plugin-addresses.md). ## Parallel Prototypes diff --git a/audits/Code4rena - Reserve Audit Report - 3.0.0 Release.md b/audits/Code4rena - Reserve Audit Report - 3.0.0 Release.md new file mode 100644 index 0000000000..396da93633 --- /dev/null +++ b/audits/Code4rena - Reserve Audit Report - 3.0.0 Release.md @@ -0,0 +1,1553 @@ +--- +sponsor: "Reserve" +slug: "2023-06-reserve" +date: "2023-09-⭕" # the date this report is published to the C4 website +title: "Reserve Protocol - Invitational" +findings: "https://github.com/code-423n4/2023-06-reserve-findings/issues" +contest: 248 +--- + +# Overview + +## About C4 + +Code4rena (C4) is an open organization consisting of security researchers, auditors, developers, and individuals with domain expertise in smart contracts. + +A C4 audit is an event in which community participants, referred to as Wardens, review, audit, or analyze smart contract logic in exchange for a bounty provided by sponsoring projects. + +During the audit outlined in this document, C4 conducted an analysis of the Reserve Protocol smart contract system written in Solidity. The audit took place between June 15—June 29 2023. + +Following the C4 audit, 3 wardens (0xA5DF, ronnyx2017, and rvierdiiev) reviewed the mitigations for all identified issues; the [mitigation review report](#mitigation-review) is appended below the audit report. + +## Wardens + +In Code4rena's Invitational audits, the competition is limited to a small group of wardens; for this audit, 6 wardens contributed reports: + + 1. 0xA5DF + 2. ronnyx2017 + 3. rvierdiiev + 4. RaymondFam + 5. [carlitox477](https://twitter.com/carlitox477) + 6. [hihen](https://twitter.com/henryxf3) + +This Audit was judged by [0xean](https://github.com/0xean). + +Final report assembled by PaperParachute and [liveactionllama](https://twitter.com/liveactionllama). + +# Summary + +The C4 analysis yielded an aggregated total of 14 unique vulnerabilities. Of these vulnerabilities, 2 received a risk rating in the category of HIGH severity and 12 received a risk rating in the category of MEDIUM severity. + +Additionally, C4 analysis included 6 reports detailing issues with a risk rating of LOW severity or non-critical. There were also 4 reports recommending gas optimizations. + +All of the issues presented here are linked back to their original finding. + +# Scope + +The code under review can be found within the [C4 Reserve Protocol repository](https://github.com/code-423n4/2023-06-reserve), and is composed of 12 smart contracts written in the Solidity programming language and includes 2126 lines of Solidity code. + +# Severity Criteria + +C4 assesses the severity of disclosed vulnerabilities based on three primary risk categories: high, medium, and low/non-critical. + +High-level considerations for vulnerabilities span the following key areas when conducting assessments: + +- Malicious Input Handling +- Escalation of privileges +- Arithmetic +- Gas use + +For more information regarding the severity criteria referenced throughout the submission review process, please refer to the documentation provided on [the C4 website](https://code4rena.com), specifically our section on [Severity Categorization](https://docs.code4rena.com/awarding/judging-criteria/severity-categorization). + +# High Risk Findings (2) +## [[H-01] Custom redemption might revert if old assets were unregistered](https://github.com/code-423n4/2023-06-reserve-findings/issues/4) +*Submitted by [0xA5DF](https://github.com/code-423n4/2023-06-reserve-findings/issues/4)* + +`quoteCustomRedemption()` works under the assumption that the maximum size of the `erc20sAll` should be `assetRegistry.size()`, however there can be cases where an asset was unregistered but still exists in an old basket, making the size of the old basket greater than `assetRegistry.size()`. In that case the function will revert with an index out of bounds error. + +### Impact + +Users might not be able to use `redeemCustom` when needed. + +I think this should be considered high severity, since being able to redeem the token at all time is an essential feature for the protocol that's allowed also while frozen. +Not being able to redeem can result in a depeg or in governance becoming malicious and stealing RToken collateral. + +### Proof of Concept + +Consider the following scenario: + +* RToken deployed with 0.9 USDC, 0.05 USDT, 0.05 DAI +* Governance passed a vote to change it to 0.9 DAI and 0.1 USDC and un-register USDT +* Trading is paused before execution, so the basket switch occurs but the re-balance can't be executed. Meaning the actual assets that the backing manager holds are in accordance with the old basket +* A user wants to redeem using the old basket, but custom redemption reverts + +As for the revert: + +* `erc20sAll` is created [here](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/BasketHandler.sol#L391-L392) with the length of `assetRegistry.size()`, which is 2 in our case. +* Then in [this loop](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/BasketHandler.sol#L397-L428) the function tries to push 3 assets into `erc20sAll` which will result in an index-out-of-bonds error + +(the function doesn't include in the final results assets that aren't registered, but it does push them too into `erc20sAll`) + +### Recommended Mitigation Steps + +Allow the user to specify the length of the array `erc20sAll` to avoid this revert + +**[0xean (judge) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/4#issuecomment-1586269794):** + > I believe this to be a stretch for high severity. It has several pre-conditions to end up in the proposed state and I do believe it would be entirely possible for governance to change back to the original state (USDC, USDT, DAI), so assets wouldn't be lost and the impact would more be along the lines of a temporary denial of service. +> +> Look forward to warden and sponsor comments. + +**[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/4#issuecomment-1587948710):** + > @0xA5DF - nice find! Thoughts on an alternative mitigation? +> - Could move L438 to just after L417, so that `erc20sAll` never includes unregistered ERC20s +> - Would probably have to cache the assets as `assetsAll` for re-use around L438 +> - Has side-effect of making the ERC20 return list never include unregistered ERC20s. Current implementation can return a 0 value for an unregistered ERC20. This is properly handled by the RToken contract, but still, nice-to-have. + +**[0xA5DF (warden) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/4#issuecomment-1587988163):** + > Hey @tbrent -
+> That can work as well, the only downside I can think of is that in case there's an asset that's not registered and is repeated across different baskets - the `toAsset()` would be called multiple times for that asset (while under the current implementation and under the mitigation I've suggested it'll be called only once), this would cost about 300 gas units per additional call (100 for the call, 2 `sload`s to a warm slot inside the call itself) + +**[tbrent (Reserve) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/4#issuecomment-1588023172):** +> @0xA5DF - Noted, good point. + +**[tbrent (Reserve) confirmed](https://github.com/code-423n4/2023-06-reserve-findings/issues/4#issuecomment-1620824425)** + +**[0xean (judge) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/4#issuecomment-1632948695):** + > @tbrent - do you care to comment on your thoughts on severity? I am leaning towards M on this, but it sounds like you believe it is correct as labeled (high). + +**[tbrent (Reserve) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/4#issuecomment-1632984393):** + > @0xean - Correct, I think high is appropriate. + +**[Reserve mitigated](https://github.com/code-423n4/2023-08-reserve-mitigation#individual-prs):** +> Fix `redeemCustom`.
+> PR: https://github.com/reserve-protocol/protocol/pull/857 + +**Status:** Mitigation confirmed. Full details in reports from [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/7), [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/30), and [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/3) - and also shared below in the [Mitigation Review](#mitigation-review) section. + + + +*** + +## [[H-02] A new era might be triggered despite a significant value being held in the previous era](https://github.com/code-423n4/2023-06-reserve-findings/issues/2) +*Submitted by [0xA5DF](https://github.com/code-423n4/2023-06-reserve-findings/issues/2)* + +
+ +When RSR seizure occurs the staking and drafting rate is adjusted accordingly, if any of those rates is above some threshold then a new era begins (draft or staking era accordingly), wiping out all of the holdings of the current era. +The assumption is that if the rate is above the threshold then there's not much staking or drafts left after the seizure (and therefore it makes sense to begin a new era). +However, there might be a case where a previous seizure has increased the staking/draft rate close to the threshold, and then even a small seizure would make it cross this threshold. In that case the total value of staking or drafts can be very high, and they will all be wiped out by starting a new era. + +### Impact + +Stakers will lose their holdings or pending drafts. + +### Proof of Concept + +Consider the following scenario: + +* Max stake rate is 1e9 +* A seizure occurs and the new rate is now 91e7 +* Not much staking is left after the seizure, but as time passes users keep staking bring back the total stakes to a significant value +* A 10% seizure occurs, this causes the staking rate to cross the threshold (getting to 1.01e9) and start a new era + +This means the stakings were wiped out despite holding a significant amount of value, causing a loss for the holders. + +### Recommended Mitigation Steps + +This one is a bit difficult to mitigate. +One way I can think of is to add a 'migration' feature, where in such cases a new era would be created but users would be able to transfer the funds that they held in the previous era into the new era. But this would require some significant code changes and checking that this doesn't break anything or introduces new bugs. + + +**[tbrent (Reserve) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/2#issuecomment-1588260840):** + > @0xA5DF thoughts on a governance function that requires the ratio be out of bounds, that does `beginEra()` and/or `beginDraftEra()`? +> +> The idea is that stakers can mostly withdraw, and since governance thresholds are all percentage, vote to immolate themselves and re-start the staking pool. I think it should treat `beginEra()` and `beginDraftEra()` separately, but I'm not confident in that yet. + +**[tbrent (Reserve) acknowledged and commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/2#issuecomment-1620824773):** + > We're still not sure how to mitigate this one. Agree it should be considered HIGH and a new issue. + +**[Reserve mitigated](https://github.com/code-423n4/2023-08-reserve-mitigation#individual-prs):** +> Adds governance function to manually push the era forward.
+> PR: https://github.com/reserve-protocol/protocol/pull/888 + +**Status:** Mitigation confirmed. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/31), [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/8), and [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/11) - and also shared below in the [Mitigation Review](#mitigation-review) section. + + + +*** + + +# Medium Risk Findings (12) +## [[M-01] A Dutch trade could end up with an unintended lower closing price](https://github.com/code-423n4/2023-06-reserve-findings/issues/48) +*Submitted by [RaymondFam](https://github.com/code-423n4/2023-06-reserve-findings/issues/48)* + +

+ +`notTradingPausedOrFrozen` that is turned on and off during an open Dutch trade could have the auction closed with a lower price depending on the timimg, leading to lesser capability to boost the Rtoken and/or stRSR exchange rates as well as a weakened recollaterization. + +### Proof of Concept + +Here's the scenario: + +1. A 30 minute Dutch trade is opened by the Revenue trader selling a suplus token for Rtoken. + +2. Shortly after the price begins to decrease linearly, Alice calls [`bid()`](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/plugins/trading/DutchTrade.sol#L146-L164). As can be seen in line 160 of the code block below, `settleTrade()` is externally called on the `origin`, RevenueTrader.sol in this case: + +```solidity + function bid() external returns (uint256 amountIn) { + require(bidder == address(0), "bid already received"); + + // {qBuyTok} + amountIn = bidAmount(uint48(block.timestamp)); // enforces auction ongoing + + // Transfer in buy tokens + bidder = msg.sender; + buy.safeTransferFrom(bidder, address(this), amountIn); + + // status must begin OPEN + assert(status == TradeStatus.OPEN); + + // settle() via callback +160: origin.settleTrade(sell); + + // confirm callback succeeded + assert(status == TradeStatus.CLOSED); + } +``` + +3. However, her call is preceded by [`pauseTrading()`](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/mixins/Auth.sol#L169-L172) invoked by a `PAUSER`, and denied on line 46 of the function below: + + + +```solidity + function settleTrade(IERC20 sell) + public + override(ITrading, TradingP1) +46: notTradingPausedOrFrozen + returns (ITrade trade) + { + trade = super.settleTrade(sell); // nonReentrant + distributeTokenToBuy(); + // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely + } +``` + +4. As the auction is nearing to `endTime`, the `PAUSER` calls [`unpauseIssuance()`](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/mixins/Auth.sol#L176-L179). + +5. Bob, the late comer, upon seeing this, proceeds to calling `bid()` and gets the sell token for a price much lower than he would initially expect before the trading pause. + + +### Recommended Mitigation Steps + +Consider removing `notTradingPausedOrFrozen` from the function visibility of `RevenueTrader.settleTrade` and `BackingManager.settleTrade`. This will also have a good side effect of allowing the settling of a Gnosis trade if need be. Collectively, the settled trades could at least proceed to helping boost the RToken and/or stRSR exchange rates that is conducive to the token holders redeeming and withdrawing. The same shall apply to enhancing recollaterization, albeit future tradings will be halted if the trading pause is still enabled. + +**[0xean (judge) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/48#issuecomment-1613819858):** + > This also seems like QA. It outlines a very specific set of events that are very unlikely to occur during production scenarios and would additionally come down to admin misconfiguration / mismanagement. will wait for sponsor comment, but most likely downgrade to QA. + + > > - The PAUSER role should be assigned to an address that is able to act quickly in response to off-chain events, such as a Chainlink feed failing. It is acceptable for there to be false positives, since redemption remains enabled. +> +> It is good to consider this quote from the documentation stating that pausing may have false positives. + +**[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/48#issuecomment-1618926905):** + > @0xean - We believe a malicious pauser attack vector is dangerous enough that the issue is Medium and deserves a mitigation. Agree with suggested mitigation. + +**[Reserve mitigated](https://github.com/code-423n4/2023-08-reserve-mitigation#individual-prs):** +> Allow settle trade when paused or frozen.
+> PR: https://github.com/reserve-protocol/protocol/pull/876 + +**Status:** Mitigation confirmed. Full details in reports from [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/5), [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/32), and [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/9) - and also shared below in the [Mitigation Review](#mitigation-review) section. + + + +*** + +## [[M-02] The broker should not be fully disabled by GnosisTrade.reportViolation](https://github.com/code-423n4/2023-06-reserve-findings/issues/47) +*Submitted by [RaymondFam](https://github.com/code-423n4/2023-06-reserve-findings/issues/47)* + +
+ +GnosisTrade and DutchTrade are two separate auction systems where the failing of either system should not affect the other one. The current design will have `Broker.sol` disabled when `reportViolation` is invoked by `GnosisTrade.settle()` if the auction's clearing price was below what we assert it should be. + + + +```solidity + broker.reportViolation(); +``` + + + +```solidity + function reportViolation() external notTradingPausedOrFrozen { + require(trades[_msgSender()], "unrecognized trade contract"); + emit DisabledSet(disabled, true); + disabled = true; + } +``` + +Consequently, both `BackingManager` and `RevenueTrader (rsrTrader and rTokenTrader)` will not be able to call `openTrade()`: + + + +```soliidty + function openTrade(TradeKind kind, TradeRequest memory req) external returns (ITrade) { + require(!disabled, "broker disabled"); + ... +``` + +till it's resolved by the governance: + + + +```solidity + function setDisabled(bool disabled_) external governance { + emit DisabledSet(disabled, disabled_); + disabled = disabled_; + } +``` + +### Proof of Concept + +The following `Trading.trytrade()` as inherited by `BackingManager` and `RevenueTrader` will be denied on line 121, deterring recollaterization and boosting of Rtoken and stRSR exchange rate. The former deterrence will have [`Rtoken.redeemTo`](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/RToken.sol#L190) and [`StRSR.withdraw`](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/StRSR.sol#L335) (both [requiring](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/RToken.sol#L190) [`fullyCollateralized`](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/StRSR.sol#L335)) denied whereas the latter will have the Rtoken and stRSR holders divested of intended gains. + + + +```solidty + function tryTrade(TradeKind kind, TradeRequest memory req) internal returns (ITrade trade) { + /* */ + IERC20 sell = req.sell.erc20(); + assert(address(trades[sell]) == address(0)); + + IERC20Upgradeable(address(sell)).safeApprove(address(broker), 0); + IERC20Upgradeable(address(sell)).safeApprove(address(broker), req.sellAmount); + +121: trade = broker.openTrade(kind, req); + trades[sell] = trade; + tradesOpen++; + + emit TradeStarted(trade, sell, req.buy.erc20(), req.sellAmount, req.minBuyAmount); + } +``` + +### Recommended Mitigation Steps + +Consider having the affected code refactored as follows: + + + +```diff + function openTrade(TradeKind kind, TradeRequest memory req) external returns (ITrade) { +- require(!disabled, "broker disabled"); + + address caller = _msgSender(); + require( + caller == address(backingManager) || + caller == address(rsrTrader) || + caller == address(rTokenTrader), + "only traders" + ); + + // Must be updated when new TradeKinds are created + if (kind == TradeKind.BATCH_AUCTION) { ++ require(!disabled, "Gnosis Trade disabled"); + return newBatchAuction(req, caller); + } + return newDutchAuction(req, ITrading(caller)); + } +``` + +This will have the Gnosis Trade conditionally denied while still allowing the opening of Dutch Trade. + +**[0xean (judge) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/47#issuecomment-1613867516):** + > This currently mostly reads like a design suggestion. I can see the merits of disabling the entire broker in the scenario where the invariant has been violated. Probably best as QA, but will allow for sponsor comment before downgrading. + +**[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/47#issuecomment-1618919354):** + > @0xean - We think this should be kept as Medium. It's a good design suggestion that otherwise could lead to the protocol not trading for the length of the governance cycle. This matters when it comes to selling defaulted collateral. + +**[Reserve mitigated](https://github.com/code-423n4/2023-08-reserve-mitigation#individual-prs):** +> Disable dutch auctions on a per-collateral basis, use 4-step dutch trade curve.
+> PRs:
+> - https://github.com/reserve-protocol/protocol/pull/873
+> - https://github.com/reserve-protocol/protocol/pull/869
+ +**Status:** Two mitigation errors. Full details in reports from [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/20) and [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/40) - and also shared below in the [Mitigation Review](#mitigation-review) section. + + + +*** + +## [[M-03] In case `Distributor.setDistribution` use, revenue from rToken RevenueTrader and rsr token RevenueTrader should be distributed](https://github.com/code-423n4/2023-06-reserve-findings/issues/34) +*Submitted by [rvierdiiev](https://github.com/code-423n4/2023-06-reserve-findings/issues/34)* + +In case Distributor.setDistribution use, revenue from rToken RevenueTrader and rsr token RevenueTrader should be distributed. Otherwise wrong distribution will be used. + +### Proof of Concept + +`BackingManager.forwardRevenue` function sends revenue amount to the `rsrTrader` and `rTokenTrader` contracts, [according to the distribution inside `Distributor` contract](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/BackingManager.sol#L236-L249). For example it can `50%`/`50%`. In case if we have 2 destinations in Distributor: strsr and furnace, that means that half of revenue will be received by strsr stakers as rewards. + +This distribution [can be changed](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/Distributor.sol#L61-L65) at any time.
+The job of `RevenueTrader` is to sell provided token for a `tokenToBuy` and then distribute it using `Distributor.distribute` function. There are 2 ways of auction that are used: dutch and gnosis. Dutch auction will call `RevenueTrader.settleTrade`, which [will initiate distribution](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/RevenueTrader.sol#L50). But Gnosis trade will not do that and user should call `distributeTokenToBuy` manually, after auction is settled. + +The problem that I want to discuss is next.
+Suppose, that governance at the beginning set distribution as 50/50 between 2 destinations: strsr and furnace. And then later `forwardRevenue` sent some tokens to the rsrTrader and rTokenTrader. Then, when trade was active to exchange some token to rsr token, `Distributor.setDistribution` was set in order to make strsr share to 0, so now everything goes to Furnace only. As result, when trade will be finished in the rsrTrader and `Distributor.distribute` will be called, then those tokens will not be sent to the strsr contract, because their share is 0 now. +They will be stuck inside rsrTrader. + +Another problem here is that strsr holders should receive all revenue from the time, where they distribution were created. What i mean is if in time 0, rsr share was 50% and in time 10 rsr share is 10%, then `BackingManager.forwardRevenue` should be called for all tokens that has surplus, because if that will be done after changing to 10%, then strsr stakers will receive less revenue. + +### Tools Used + +VsCode + +### Recommended Mitigation Steps + +You need to think how to guarantee fair distribution to the strsr stakers, when distribution params are changed. + +**[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/34#issuecomment-1620796074):** + > This is a good find. The mitigation we have in mind is adding a new function to the `RevenueTrader` that allows anyone to transfer a registered ERC20 back to the `BackingManager`, as long as the current distribution for that `tokenToBuy` is 0%. + +**[Reserve mitigated](https://github.com/code-423n4/2023-08-reserve-mitigation#individual-prs):** +> Distribute revenue in `setDistribution`.
+> PR: https://github.com/reserve-protocol/protocol/pull/878 + +**Status:** Mitigation error. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/36) and [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/10) - and also shared below in the [Mitigation Review](#mitigation-review) section. + + + +*** + +## [[M-04] FurnaceP1.setRatio will work incorrectly after call when frozen ](https://github.com/code-423n4/2023-06-reserve-findings/issues/29) +*Submitted by [rvierdiiev](https://github.com/code-423n4/2023-06-reserve-findings/issues/29)* + +`FurnaceP1.setRatio` will not update `lastPayout` when called in frozen state, which means that after component will be unfrozen, melting will be incorrect. + +### Proof of Concept + +`melt` function should burn some amount of tokens from `lastPayoutBal`. It depends of `lastPayout` and `ratio` variables. The more time has passed, the more tokens will be burnt. + +When `setRatio` function is called, then `melt` function [is tried to be executed](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/Furnace.sol#L86), because new ratio is provided and it should not be used for previous time ranges. +In case if everything is ok, then `lastPayout` and `lastPayoutBal` will be updated, so it's safe to update `ratio` now. +But it's possible that `melt` function will revert in case if `notFrozen` modifier is not passed. As result `lastPayout` and `lastPayoutBal` will not be updated, but ratio will be. Because of that, when `Furnace` will be unfrozen, then melting rate can be much more, then it should be, because `lastPayout` wasn't updated. + +### Tools Used + +VsCode + +### Recommended Mitigation Steps + +In case of `catch` case, you can update `lastPayout` and `lastPayoutBal`. + +**[tbrent (Reserve) confirmed](https://github.com/code-423n4/2023-06-reserve-findings/issues/29#issuecomment-1620802303)** + +**[Reserve mitigated](https://github.com/code-423n4/2023-08-reserve-mitigation#individual-prs):** +> Update payout variables if melt fails during `setRatio`.
+> PR: https://github.com/reserve-protocol/protocol/pull/885 + +**Status:** Mitigation error. Full details in report from [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/37) - and also shared below in the [Mitigation Review](#mitigation-review) section. + + + +*** + +## [[M-05] Lack of claimRewards when manageToken in RevenueTrader](https://github.com/code-423n4/2023-06-reserve-findings/issues/16) +*Submitted by [ronnyx2017](https://github.com/code-423n4/2023-06-reserve-findings/issues/16)* + +There is a dev comment in the Assert.sol: + + DEPRECATED: claimRewards() will be removed from all assets and collateral plugins + +The claimRewards is moved to the `TradingP1.claimRewards/claimRewardsSingle`. + +But when the `RevenueTraderP1` trade and distribute revenues by `manageToken`, it only calls the refresh function of the asserts: + + if (erc20 != IERC20(address(rToken)) && tokenToBuy != IERC20(address(rToken))) { + IAsset sell_ = assetRegistry.toAsset(erc20); + IAsset buy_ = assetRegistry.toAsset(tokenToBuy); + if (sell_.lastSave() != uint48(block.timestamp)) sell_.refresh(); + if (buy_.lastSave() != uint48(block.timestamp)) buy_.refresh(); + } + +The claimRewards is left out. + +### Impact + +Potential loss of rewards. + +### Recommended Mitigation Steps + +Add claimRewardsSingle when refresh assert in the `manageToken`. + + +**[tbrent (Reserve) disputed and commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/16#issuecomment-1588019948):** + > This is similar to an (unmitigated) issue from an earlier contest: https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/22 +> +> However in this case it has to do with `RevenueTraderP1.manageToken()`, as opposed to `BackingManagerP1.manageTokens()`. +> +> I think that difference matters, because the loss of the rewards _for this auction_ does not have serious long-term consequences. This is not like the BackingManager where it's important that all capital always be available else an unnecessarily large haircut could occur. Instead, the worst that can happen is for the revenue auction to complete at high slippage, and for a second reward token revenue auction to complete afterwards at high slippage yet again, when it could have been a single revenue auction with less slippage. +> +> The recommended mitigation would not succeed, because recall, we may be selling token X but any number of additional assets could have token X as a reward token. We would need to call `claimRewards()`, which is simply too gas-costly to do everytime for revenue auctions. + +**[0xean (judge) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/16#issuecomment-1633111555):** + > > Instead, the worst that can happen is for the revenue auction to complete at high slippage, and for a second reward token revenue auction to complete afterwards at high slippage yet again, when it could have been a single revenue auction with less slippage. +> +> @tbrent - The impact sounds like a "leak of value" and therefore I think Medium is the correct severity per the c4 docs. (cc @tbrent - open to additional comment here) + + + +*** + +## [[M-06] Oracle timeout at rebalance will result in a sell-off of all RSRs at 0 price](https://github.com/code-423n4/2023-06-reserve-findings/issues/15) +*Submitted by [ronnyx2017](https://github.com/code-423n4/2023-06-reserve-findings/issues/15)* + +When creating the trade for rebalance, the `RecollateralizationLibP1.nextTradePair` uses `(uint192 low, uint192 high) = rsrAsset.price(); // {UoA/tok}` to get the rsr sell price. And the rsr assert is a pure Assert contract, which `price()` function will just return (0, FIX\_MAX) if oracle is timeout: + + function price() public view virtual returns (uint192, uint192) { + try this.tryPrice() returns (uint192 low, uint192 high, uint192) { + assert(low <= high); + return (low, high); + } catch (bytes memory errData) { + ... + return (0, FIX_MAX); + } + } + +The `trade.sellAmount` will be all the rsr in the `BackingManager` and `stRSR`: + + uint192 rsrAvailable = rsrAsset.bal(address(ctx.bm)).plus( + rsrAsset.bal(address(ctx.stRSR)) + ); + trade.sellAmount = rsrAvailable; + +It will be cut down to a normal amount fit for buying UoA amount in the `trade.prepareTradeToCoverDeficit` function. + +But if the rsr oracle is timeout and returns a 0 low price. The trade req will be made by `trade.prepareTradeSell`, which will sell all the available rsr at 0 price. + +Note that the SOUND colls won't be affected by the issue because the sell amount has already been cut down by basketsNeeded. + +Loss huge amount of rsr in the auction. When huge amounts of assets are auctioned off at zero, panic and insufficient liquidity make the outcome unpredictable. + +### Proof of Concept + +POC git diff test/Recollateralization.test.ts + +```patch +diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts +index 86cd3e88..15639916 100644 +--- a/test/Recollateralization.test.ts ++++ b/test/Recollateralization.test.ts +@@ -51,7 +51,7 @@ import { + import snapshotGasCost from './utils/snapshotGasCost' + import { expectTrade, getTrade, dutchBuyAmount } from './utils/trades' + import { withinQuad } from './utils/matchers' +-import { expectRTokenPrice, setOraclePrice } from './utils/oracles' ++import { expectRTokenPrice, setInvalidOracleTimestamp, setOraclePrice } from './utils/oracles' + import { useEnv } from '#/utils/env' + import { mintCollaterals } from './utils/tokens' + +@@ -797,6 +797,166 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { + }) + + describe('Recollateralization', function () { ++ context('With simple Basket - Two stablecoins', function () { ++ let issueAmount: BigNumber ++ let stakeAmount: BigNumber ++ ++ beforeEach(async function () { ++ // Issue some RTokens to user ++ issueAmount = bn('100e18') ++ stakeAmount = bn('10000e18') ++ ++ // Setup new basket with token0 & token1 ++ await basketHandler.connect(owner).setPrimeBasket([token0.address, token1.address], [fp('0.5'), fp('0.5')]) ++ await basketHandler.connect(owner).refreshBasket() ++ ++ // Provide approvals ++ await token0.connect(addr1).approve(rToken.address, initialBal) ++ await token1.connect(addr1).approve(rToken.address, initialBal) ++ ++ // Issue rTokens ++ await rToken.connect(addr1).issue(issueAmount) ++ ++ // Stake some RSR ++ await rsr.connect(owner).mint(addr1.address, initialBal) ++ await rsr.connect(addr1).approve(stRSR.address, stakeAmount) ++ await stRSR.connect(addr1).stake(stakeAmount) ++ }) ++ ++ it('C4M7', async () => { ++ // Register Collateral ++ await assetRegistry.connect(owner).register(backupCollateral1.address) ++ ++ // Set backup configuration - USDT as backup ++ await basketHandler ++ .connect(owner) ++ .setBackupConfig(ethers.utils.formatBytes32String('USD'), bn(1), [backupToken1.address]) ++ ++ // Set Token0 to default - 50% price reduction ++ await setOraclePrice(collateral0.address, bn('0.5e8')) ++ ++ // Mark default as probable ++ await assetRegistry.refresh() ++ // Advance time post collateral's default delay ++ await advanceTime((await collateral0.delayUntilDefault()).toString()) ++ ++ // Confirm default and trigger basket switch ++ await basketHandler.refreshBasket() ++ ++ // Advance time post warmup period - SOUND just regained ++ await advanceTime(Number(config.warmupPeriod) + 1) ++ ++ const initToken1B = await token1.balanceOf(backingManager.address); ++ // rebalance ++ const token1Decimal = 6; ++ const sellAmt: BigNumber = await token0.balanceOf(backingManager.address) ++ const buyAmt: BigNumber = sellAmt.div(2) ++ await facadeTest.runAuctionsForAllTraders(rToken.address); ++ // bid ++ await backupToken1.connect(addr1).approve(gnosis.address, sellAmt) ++ await gnosis.placeBid(0, { ++ bidder: addr1.address, ++ sellAmount: sellAmt, ++ buyAmount: buyAmt, ++ }) ++ await advanceTime(config.batchAuctionLength.add(100).toString()) ++ // await facadeTest.runAuctionsForAllTraders(rToken.address); ++ const rsrAssert = await assetRegistry.callStatic.toAsset(rsr.address); ++ await setInvalidOracleTimestamp(rsrAssert); ++ await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ ++ { ++ contract: backingManager, ++ name: 'TradeSettled', ++ args: [anyValue, token0.address, backupToken1.address, sellAmt, buyAmt], ++ emitted: true, ++ }, ++ { ++ contract: backingManager, ++ name: 'TradeStarted', ++ args: [anyValue, rsr.address, backupToken1.address, stakeAmount, anyValue], // sell 25762677277828792981 ++ emitted: true, ++ }, ++ ]) ++ ++ // check ++ console.log(await token0.balanceOf(backingManager.address)); ++ const currentToken1B = await token1.balanceOf(backingManager.address); ++ console.log(currentToken1B); ++ console.log(await backupToken1.balanceOf(backingManager.address)); ++ const rsrB = await rsr.balanceOf(stRSR.address); ++ console.log(rsrB); ++ ++ // expect ++ expect(rsrB).to.eq(0); ++ }) ++ }) ++ + context('With very simple Basket - Single stablecoin', function () { + let issueAmount: BigNumber + let stakeAmount: BigNumber + +``` + +run test: + + PROTO_IMPL=1 npx hardhat test --grep 'C4M7' test/Recollateralization.test.ts + +log: + + Recollateralization - P1 + Recollateralization + With simple Basket - Two stablecoins + BigNumber { value: "0" } + BigNumber { value: "50000000" } + BigNumber { value: "25000000000000000000" } + BigNumber { value: "0" } + +### Recommended Mitigation Steps + +Using lotPrice or just revert for rsr oracle timeout might be a good idea. + + +**[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/15#issuecomment-1588048139):** + > Hmm, interesting case. +> +> There are two types of auctions that can occur: batch auctions via `GnosisTrade`, and dutch auctions via `DutchTrade`. +> +> Batch auctions via `GnosisTrade` are good at discovering prices when price is unknown. It would require self-interested parties to be offline for the entire duration of the batch auction (default: 15 minutes) in order for someone to get away with buying the RSR for close to 0. +> +> Dutch auctions via `DutchTrade` do not have this problem because of an assert that reverts at the top of the contract. +> +> I'm inclined to dispute validity, but I also agree it might be strictly better to use the `lotPrice()`. When trading out backing collateral it is important to sell it quickly and not have to wait for `lotPrice()` to decay sufficiently, but this is not true with RSR. For RSR it might be fine to wait as long as a week for the `lotPrice()` to fall to near 0. +> +> This would then allow dutch auctions via `DutchTrade` to be used when RSR's oracle is offline. + +**[Reserve mitigated](https://github.com/code-423n4/2023-08-reserve-mitigation#individual-prs):** +> Use `lotPrice()`.
+> PR: https://github.com/reserve-protocol/protocol-private/pull/15 + +**Status:** Mitigation confirmed. Full details in reports from [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/13), [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/41), and [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/23) - and also shared below in the [Mitigation Review](#mitigation-review) section. + + + +*** + +## [[M-07] Sell reward `rTokens` at low price because of skiping `furnace.melt`](https://github.com/code-423n4/2023-06-reserve-findings/issues/13) +*Submitted by [ronnyx2017](https://github.com/code-423n4/2023-06-reserve-findings/issues/13)* + +The reward rToken sent to RevenueTrader will be sold at a low price. RSR stakers will lose some of their profits. + +### Proof of Concept + +`RevenueTraderP1.manageToken` function is used to launch auctions for any erc20 tokens sent to it. For the RevenueTrader of the rsr stake, the `tokenToBuy` is rsr and the token to sell is reward rtoken. + +There is the refresh code in the `manageToken` function: + + } else if (assetRegistry.lastRefresh() != uint48(block.timestamp)) { + // Refresh everything only if RToken is being traded + assetRegistry.refresh(); + furnace.melt(); + } + +It refreshes only when the assetRegistry has not been refreshed in the same block. + +So if the actor calls the `assetRegistry.refresh()` before calling `manageToken` function, the `furnace.melt()` won't been called. And the BU exchange rate of the RToken will be lower than actual value. So the sellPrice is also going to be smaller. + + (uint192 sellPrice, ) = sell.price(); // {UoA/tok} + + TradeInfo memory trade = TradeInfo({ + sell: sell, + buy: buy, + sellAmount: sell.bal(address(this)), + buyAmount: 0, + sellPrice: sellPrice, + buyPrice: buyPrice + }); + +### Recommended Mitigation Steps + +Refresh everything before sell rewards. + +**[tbrent (Reserve) confirmed](https://github.com/code-423n4/2023-06-reserve-findings/issues/13#issuecomment-1620818933)** + +**[Reserve mitigated](https://github.com/code-423n4/2023-08-reserve-mitigation#individual-prs):** +> Refresh before selling rewards, refactor revenue & distro.
+> PR: https://github.com/reserve-protocol/protocol-private/pull/7 + +**Status:** Mitigation confirmed. Full details in reports from [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/24), [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/34), and [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/14) - and also shared below in the [Mitigation Review](#mitigation-review) section. + + + +*** + +## [[M-08] Stake before unfreeze can take away most of rsr rewards in the freeze period](https://github.com/code-423n4/2023-06-reserve-findings/issues/11) +*Submitted by [ronnyx2017](https://github.com/code-423n4/2023-06-reserve-findings/issues/11), also found by [rvierdiiev](https://github.com/code-423n4/2023-06-reserve-findings/issues/43) and [0xA5DF](https://github.com/code-423n4/2023-05-reserve-findings/issues/24)* + +If the system is frozen, the only allowed operation is `stRST.stake`. And the `_payoutRewards` is not called during freeze period: + + if (!main.frozen()) _payoutRewards(); + + function payoutRewards() external { + requireNotFrozen(); + _payoutRewards(); + } + +So the `payoutLastPaid` stays before the freeze period. But when the system is unfreezed, accumulated rewards will be released all at once because the block.timestamp leapt the whole freeze period. + +### Impact + +A front runner can stake huge proportion rsr before admin unfreezes the system. And the attacker can get most of rsr rewards in the next block. And he only takes the risk of the `unstakingDelay` period. + +### Proof of Concept + +Assumption: there are 2000 rsr stake in the stRSR, and there are 1000 rsr rewards in the `rsrRewardsAtLastPayout` with a 1 year half-life period. + +And at present, the LONG_FREEZER `freezeLong` system for 1 year(default). + +After 1 year, at the unfreeze point, a front runner stake 2000 rsr into stRSR. And then the system is unfreeze. And in the next blcok,the front runner unstakes all the stRSR he has for `2250 rsr = 2000 principal + 1000 / 2 / 2 rsr rewards`. + +The only risk he took is `unstakingDelay`. The original rsr stakers took the risk of the whole freeze period + `unstakingDelay` but only got a part of rewards back. + +### Recommended Mitigation Steps + +payoutRewards before freeze and update payoutLastPaid before unfreeze. + +**[tbrent (Reserve) confirmed via duplicate issue #24](https://github.com/code-423n4/2023-06-reserve-findings/issues/24)** + +**[Reserve mitigated](https://github.com/code-423n4/2023-08-reserve-mitigation#individual-prs):** +> `payoutRewards` before freeze and update `payoutLastPaid` before unfreeze.
+> PR: https://github.com/reserve-protocol/protocol/pull/857 + +**Status:** Mitigation confirmed. Full details in reports from [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/15), [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/38), and [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/25) - and also shared below in the [Mitigation Review](#mitigation-review) section. + + + +*** + +## [[M-09] `cancelUnstake` lack `payoutRewards` before mint shares](https://github.com/code-423n4/2023-06-reserve-findings/issues/10) +*Submitted by [ronnyx2017](https://github.com/code-423n4/2023-06-reserve-findings/issues/10), also found by [rvierdiiev](https://github.com/code-423n4/2023-06-reserve-findings/issues/39) and [0xA5DF](https://github.com/code-423n4/2023-05-reserve-findings/issues/5)* + +`cancelUnstake` will cancel the withdrawal request in the queue can mint shares as the current `stakeRate`. But it doesn't `payoutRewards` before `mintStakes`. Therefor it will mint stRsr as a lower rate, which means it will get more rsr. + +### Impact + +Withdrawers in the unstake queue can `cancelUnstake` without calling `payoutRewards` to get more rsr rewards that should not belong to them. + +### Proof of Concept + +POC test/ZZStRSR.test.ts git patch + +```patch +diff --git a/test/ZZStRSR.test.ts b/test/ZZStRSR.test.ts +index ecc31f68..b2809129 100644 +--- a/test/ZZStRSR.test.ts ++++ b/test/ZZStRSR.test.ts +@@ -1333,6 +1333,46 @@ describe(`StRSRP${IMPLEMENTATION} contract`, () => { + expect(await stRSR.exchangeRate()).to.be.gt(initialRate) + }) + ++ it('cancelUnstake', async () => { ++ const amount: BigNumber = bn('10e18') ++ ++ // Stake ++ await rsr.connect(addr1).approve(stRSR.address, amount) ++ await stRSR.connect(addr1).stake(amount) ++ await rsr.connect(addr2).approve(stRSR.address, amount) ++ await stRSR.connect(addr2).stake(amount) ++ await rsr.connect(addr3).approve(stRSR.address, amount) ++ await stRSR.connect(addr3).stake(amount) ++ ++ const initExchangeRate = await stRSR.exchangeRate(); ++ console.log(initExchangeRate); ++ ++ // Unstake addr2 & addr3 at same time (Although in different blocks, but timestamp only 1s) ++ await stRSR.connect(addr2).unstake(amount) ++ await stRSR.connect(addr3).unstake(amount) ++ ++ // skip 1000 block PERIOD / 12000s ++ await setNextBlockTimestamp(Number(ONE_PERIOD.mul(1000).add(await getLatestBlockTimestamp()))) ++ ++ // Let's cancel the unstake in normal ++ await expect(stRSR.connect(addr2).cancelUnstake(1)).to.emit(stRSR, 'UnstakingCancelled') ++ let exchangeRate = await stRSR.exchangeRate(); ++ expect(exchangeRate).to.equal(initExchangeRate) ++ ++ // addr3 cancelUnstake after payoutRewards ++ await stRSR.payoutRewards() ++ await expect(stRSR.connect(addr3).cancelUnstake(1)).to.emit(stRSR, 'UnstakingCancelled') ++ ++ // Check balances addr2 & addr3 ++ exchangeRate = await stRSR.exchangeRate(); ++ expect(exchangeRate).to.be.gt(initExchangeRate) ++ const addr2NowAmount = exchangeRate.mul(await stRSR.balanceOf(addr2.address)).div(bn('1e18')); ++ console.log("addr2", addr2NowAmount.toString()); ++ const addr3NowAmount = exchangeRate.mul(await stRSR.balanceOf(addr3.address)).div(bn('1e18')); ++ console.log("addr3",addr3NowAmount.toString()); ++ expect(addr2NowAmount).to.gt(addr3NowAmount) ++ }) ++ + it('Rewards should not be handed out when paused but staking should still work', async () => { + await main.connect(owner).pauseTrading() + await setNextBlockTimestamp(Number(ONE_PERIOD.add(await getLatestBlockTimestamp()))) + +``` + +The test simulates two users unstake and cancelUnstake operations at the same time.But the addr2 calls payoutRewards after his cancelUnstake. And addr3 calls cancelUnstake after payoutRewards. Addr2 gets more rsr than addr3 in the end. + +run test: + + PROTO_IMPL=1 npx hardhat test --grep cancelUnstake test/ZZStRSR.test.ts + +log: + + StRSRP1 contract + Add RSR / Rewards + BigNumber { value: "1000000000000000000" } + addr2 10005345501258588240 + addr3 10000000000000000013 + +### Recommended Mitigation Steps + +Call `_payoutRewards` before mint shares. + +**[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/10#issuecomment-1589913989):** + > Agree with severity and proposed mitigation. + +**[Reserve mitigated](https://github.com/code-423n4/2023-08-reserve-mitigation#individual-prs):** +> Payout rewards during cancelUnstake.
+> PR: https://github.com/reserve-protocol/protocol-private/pull/3 + +**Status:** Mitigation confirmed. Full details in reports from [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/16), [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/33), and [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/26) - and also shared below in the [Mitigation Review](#mitigation-review) section. + + + +*** + +## [[M-10] An oracle deprecation might lead the protocol to sell assets for a low price](https://github.com/code-423n4/2023-06-reserve-findings/issues/8) +*Submitted by [0xA5DF](https://github.com/code-423n4/2023-06-reserve-findings/issues/8)* + +During a Dutch Auction, if a user places a bid, the trade is settled in the same transaction. As part of this process, the backing manager tries to call the `rebalance()` function again. +The call to `rebalance()` is wrapped in a try-catch block, if an error occurs and the error data is empty, the function will revert. + +The assumption is that if the error data is empty that means it was due to an out-of-gas error, this assumption isn't always true as mentioned in a [previous issue](https://github.com/code-423n4/2023-01-reserve-findings/issues/234) (that wasn't mitigated). +In the case of this issue, this can result in a case where users can't bid on an auction for some time, ending up selling an asset for a price lower than the market price. + +### Impact + +Protocol's assets will be auctioned for a price lower than the market price. + +### Proof of Concept + +Consider the following scenario: + +* Chainlink announces that an oracle will get deprecated +* Governance passes a proposal to update the asset registry with a new oracle +* A re-balancing is required and executed with a Dutch Auction +* The oracle deprecation happens before the auction price reaches a reasonable value +* Any bid while the oracle is deprecated will revert +* Right before the auction ends the proposal to update the asset becomes available for execution (after the timelock delay has passed). Somebody executes it, bids, and enjoys the low price of the auction. + +### Recommended Mitigation Steps + +On top of checking that the error data is empty, compare the gas before and after to ensure this is an out-of-gas error. + +**[0xean (judge) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/8#issuecomment-1586331887):** + > On the fence on this one, it is based off a known issue from a previous Audit but does show a new problem stemming from the same problem of oracle deprecation. +> +> Look forward to sponsor comment. + +**[tbrent (Reserve) disputed and commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/8#issuecomment-1589910605):** + > The PoC does not function as specified. Specifically, [bidding on an auction](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/plugins/trading/DutchTrade.sol#L146) does not involve the price at the time of the tx. The price is set at the beginning of the dutch auction in the `init()` function. Therefore, it is the starting of new auctions that will revert while the oracle is deprecated, while bids will succeed and simply fail to start the next auction. + +**[0xA5DF (warden) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/8#issuecomment-1633780455):** + > > Therefore, it is the starting of new auctions that will revert while the oracle is deprecated, while bids will succeed and simply fail to start the next auction. +> +> Hey @tbrent - +> I didn't quite understand the dispute here, if starting the next auction will fail/revert then the bid will revert too.
+> `bid()` calls `origin.settleTrade()` and `settleTrade()` calls `rebalance()`.
+> If `rebalance()` reverts due to a deprecated oracle then `settleTrade()` will revert too (`rebalance()` will revert with empty data, and therefore the catch block will trigger a revert [here](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/BackingManager.sol#L94)). + +**[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/8#issuecomment-1636182107):** + > @0xA5DF - Ah, understood now. Agree this is Medium and think it should be counted as a new finding since the consequence (dutch auction economics break) is novel. + +**[tbrent (Reserve) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/8#issuecomment-1662674844):** + > Hey @0xa5df -- we're having some confusion around exactly what happens when a chainlink oracle is deprecated. Do you have details to share about what this ends up looking like? +> +> We're having trouble finding documentation on this, and it feels like the aggregator contract should just stay there and return a stale value. Is that not right? Has this happened in the past or has Chainlink committed to a particular approach for deprecating? + +**[0xA5DF (warden) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/8#issuecomment-1662876091):** + > Hey - It's a bit difficult to track deprecated Chainlink oracles since Chainlink removes the announcement once they're deprecated.
+> I was able to track one Oracle that was deprecated during the first contest, from the original issue this seems to be [this one](https://polygonscan.com/address/0x2E5B04aDC0A3b7dB5Fd34AE817c7D0993315A8a6#readContract#F10).
+> It seems that what happens is that Chainlink sets the aggregator address to the zero address, which makes the call to `latestRoundData()` to revert without any data (I guess this is due to the way Solidity handles calls to a non-contract address).
+> See also the PoC in the [original issue](https://github.com/code-423n4/2023-01-reserve-findings/issues/234) in the January contest. + +**[tbrent (Reserve) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/8#issuecomment-1662939816):** + > Got it, checks out. Thanks! + +**[Reserve mitigated](https://github.com/code-423n4/2023-08-reserve-mitigation#individual-prs):** +> Add oracle deprecation check.
+> PR: https://github.com/reserve-protocol/protocol/pull/886 + +**Status:** Mitigation confirmed. Full details in reports from [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/35), [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/28), and [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/17) - and also shared below in the [Mitigation Review](#mitigation-review) section. + + + +*** + +## [[M-11] Attacker can disable basket during un-registration, which can cause an unnecessary trade in some cases](https://github.com/code-423n4/2023-06-reserve-findings/issues/7) +*Submitted by [0xA5DF](https://github.com/code-423n4/2023-06-reserve-findings/issues/7), also found by [rvierdiiev](https://github.com/code-423n4/2023-06-reserve-findings/issues/35)* + +
+ +At the mitigation contest there was an issue regarding the `basketHandler.quantity()` call at the unregistration process taking up all gas. +As a mitigation to that issue the devs set aside some gas and use the remaining to do that call. +This opens up to a new kind of attack, where a attacker can cause the call to revert by not supplying enough gas to it. + +### Impact + +This can cause the basket to get disabled, which would require a basket refresh. + +After a basket refresh is done, an additional warmup period has to pass for some functionality to be available again (issuance, rebalancing and forwarding revenue). + +In some cases this might trigger a basket switch that would require the protocol to rebalance via trading, trading can have some slippage which can cause a loss for the protocol. + +### Proof of Concept + +The `quantity()` function is being called with the amount of gas that `_reserveGas()` returns + +If an attacker causes the gas to be just right above `GAS_TO_RESERVE` the function would be called with 1 unit of gas, causing it to revert: + +```solidity + function _reserveGas() private view returns (uint256) { + uint256 gas = gasleft(); + require(gas > GAS_TO_RESERVE, "not enough gas to unregister safely"); + return gas - GAS_TO_RESERVE; + } +``` + +Regarding the unnecessary trade, consider the following scenario: + +* The basket has USDC as the main asset and DAI as a backup token +* A proposal to replace the backup token with USDT was raised +* A proposal to unregister BUSD (which isn't part of the basket) was raised too +* USDC defaults and DAI kicks in as the backup token +* Both proposals are now ready to execute and the attacker executes the backup proposal first, then the unregister while disabling the basket using the bug in question +* Now, when the basket is refreshed DAI will be replaced with USDT, making the protocol to trade DAI for USDT + +The refresh was unnecessary and therefore the trade too. + +### Recommended Mitigation Steps + +Reserve gas for the call as well: + +```diff + function _reserveGas() private view returns (uint256) { + uint256 gas = gasleft(); +- require(gas > GAS_TO_RESERVE, "not enough gas to unregister safely"); ++ require(gas >= GAS_TO_RESERVE + MIN_GAS_FOR_EXECUTION, "not enough gas to unregister safely"); + return gas - GAS_TO_RESERVE; + } +``` + +Disclosure: this issue was [mentioned in the comments](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73#issuecomment-1435483929) to the issue in the mitigation contest; however, since this wasn't noticed by the devs and isn't part of the submission, I don't think this should be considered a known issue. + +**[0xean (judge) commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/7#issuecomment-1586332703):** + > Applaud @0xA5DF for highlighting this on their own issue. +> +> > *Disclosure: this issue was [mentioned in the comments](https://github.com/code-423n4/2023-02-reserve-mitigation-contest-findings/issues/73#issuecomment-1435483929) to the issue in the mitigation contest, however since this wasn't noticed by the devs and isn't part of the submission I don't think this should be considered a known issue* +> +> Look forward to discussion with sponsor. + +**[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/7#issuecomment-1589904155):** + > We've discussed and agree with with the warden that this should not be considered a known issue. + +**[Reserve mitigated](https://github.com/code-423n4/2023-08-reserve-mitigation#individual-prs):** +> Change gas reservation policy in `AssetRegistry`.
+> PR: https://github.com/reserve-protocol/protocol/pull/857 + +**Status:** Mitigation confirmed. Full details in reports from [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/27), [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/39), and [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/18) - and also shared below in the [Mitigation Review](#mitigation-review) section. + + + +*** + +## [[M-12] Custom redemption can be used to get more than RToken value, when an upwards depeg occurs](https://github.com/code-423n4/2023-06-reserve-findings/issues/6) +*Submitted by [0xA5DF](https://github.com/code-423n4/2023-06-reserve-findings/issues/6)* + +
+ +Custom redemption allows to redeem RToken in exchange of a mix of previous baskets (as long as it's not more than the prorata share of the redeemer). +The assumption is that previous baskets aren't worth more than the target value of the basket. +However, a previous basket can contain a collateral that depegged upwards and is now actually worth more than its target. + +### Impact + +Funds that are supposed to go revenue traders would be taken by an attacker redeeming RToken. + +### Proof of Concept + +The [following code](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/plugins/assets/FiatCollateral.sol#L142-L148) shows that when a depeg occurs the collateral becomes IFFY (which means it'll be disabled after a certain delay): + +```solidity + // If the price is below the default-threshold price, default eventually + // uint192(+/-) is the same as Fix.plus/minus + if (pegPrice < pegBottom || pegPrice > pegTop || low == 0) { + markStatus(CollateralStatus.IFFY); + } else { + markStatus(CollateralStatus.SOUND); + } +``` + +Consider the following scenario: + +* Basket contains token X which is supposed to be pegged to c +* Token X depegs upwards and is now worth 2c +* After `delayUntilDefault` passes the basket gets disabled +* A basket refresh is executed (can be done by anybody) and token Y kicks in as the backup token +* Half of token X is now traded for the required Y tokens +* The other half should go to revenue traders (rsr trader and Furnace), but before anyone calls ‘forewardRevenue’ the attacker calls custom redemption with half from the current basket and half of the previous one +* The user would get 0.5X+0.5Y per each RToken which is worth 1.5c + +### Recommended Mitigation Steps + +When doing custom redemption check that the collateral used is sound or at least check that the price isn't higher then the peg price. + +**[tbrent (Reserve) acknowledged, but disagreed with severity and commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/6#issuecomment-1620823067):** + > This is also true of normal redemption via `redeem()` until `refreshBasket()` is called after the collateral has cycled from IFFY to DISABLED. +> +> It only checks `basketHandler.fullyCollateralized()`, not `basketHandler.isReady()`. This is intended. It is important to not disallow redemption, and using USD prices to determine redemption quantities is opposite to the fundamental design of the protocol. +> + > Agree that the behavior is as the warden indicates. All alternatives seem worse, however. I think this is probably not HIGH. We do not expect to make any change to the behavior. + +**[0xean (judge) reduced severity to Medium and commented](https://github.com/code-423n4/2023-06-reserve-findings/issues/6#issuecomment-1632945825):** + > I think Medium seems like the correct severity here. There are pre-conditions for this to occur and in addition the likelihood doesn't seem very high. + + + +*** + +# Low Risk and Non-Critical Issues + +For this Audit, 6 reports were submitted by wardens detailing low risk and non-critical issues. The [report highlighted below](https://github.com/code-423n4/2023-06-reserve-findings/issues/25) by **0xA5DF** received the top score from the judge. + +*The following wardens also submitted reports: [hihen](https://github.com/code-423n4/2023-06-reserve-findings/issues/56), +[RaymondFam](https://github.com/code-423n4/2023-06-reserve-findings/issues/49), +[rvierdiiev](https://github.com/code-423n4/2023-06-reserve-findings/issues/28), +[carlitox477](https://github.com/code-423n4/2023-05-reserve-findings/issues/26), and +[ronnyx2017](https://github.com/code-423n4/2023-05-reserve-findings/issues/21).* + +## [01] A redeemer might get 'front-run' by a freezer +Before redemption `furnace.melt()` is called, which increases a bit the amount of assets the redeemer gets in return. +While frozen the `melt()` call will revert, but since the call is surrounded by a try-catch block the redemption will continue. + +This can lead to a case where a user sends out a redeem tx, expecting melt to be called by the redeem function, but before the tx is executed the protocol gets frozen. This means the user wouldn't get the additional value they expected to get from melting (and that they can get if they choose to wait till after the freeze is over). + +If the last time `melt()` was called wasn't long enough then the additional value from melting wouldn't be that significant. But there can be cases where it can be longer - e.g. low activity or after a freeze (meaning there are 2 freezes one after the other and a user is attempting to redeem between them). + +As a mitigation allow the user to specify if they're ok with skipping the call to `melt()`, if they didn't specifically allow it then revert. + +## [02] Leaky refresh math is incorrect +`leakyRefresh()` keeps track of the percentage that was withdrawn since last refresh by adding up the current percentage that's being withdrawn each time. +However, the current percentage is being calculated as part of the current `totalRSR`, which doesn't account for the already withdrawn drafts. + +This will trigger the `withdrawalLeak` threshold earlier than expected. +E.g. if the threshold is set to `25%` and 26 users try to withdraw `1%` each - the leaky refresh would be triggered by the 23rd person rather than by the 26th. + +## [03] Reorg attack +Reorg attacks aren't very common on mainnet (but more common on L2s if the protocol intends to ever launch on them), but they can still happen (there was [a 7 blocks reorg](https://decrypt.co/101390/ethereum-beacon-chain-blockchain-reorg) on the Beacon chain before the merge). +It can be relevant in the following cases (I've reported a med separately, the followings are the ones I consider low): +* RToken deployment - a user might mint right after the RToken was deployed, a reorg might be used to deploy a different RToken and trap the users' funds in it (since the deployer becomes the owner). +* Dutch Auction - a reorg might switch the addresses of 2 auctions, causing the user to bid on the wrong auction +* Gnosis auctions - this can also cause the user to bid on the wrong auction (it's more relevant for the `EasyAuction` contract which is OOS) + +As a mitigation - make sure to deploy all contracts with `create2` using a salt that's unique to the features of the contract, that will ensure that even in the case of a reorg it wouldn't be deployed to the same address as before. + +## [04] `distributeTokenToBuy()` can be called while paused/frozen +Due to the removal of `notPausedOrFrozen` from `Distributor.distribute()` it's now possible to execute `RevenueTrader.distributeTokenToBuy()` while paused or frozen. + +This is relevant when `tokensToBuy` is sent directly to the revenue trader: RSR sent to RSRTrader or RToken sent directly to RTokenTrader + +## [05] `redeemCustom` allows the use of the zero basket +The basket with nonce `#0` is an empty basket, `redeemCustom` allows to specify that basket for redemption, which will result in a loss for the redeemer. + +## [06] `refreshBasket` can be called before the first prime basket was set +This will result in an event being emitted but will not impact the contract's state. + +## [07] `MIN_AUCTION_LENGTH` seems too low +The current `MIN_AUCTION_LENGTH` is set to 2 blocks. +This seems a bit too low since the price is time-dependant that means there would be only 3 price options for the auctions, and the final price wouldn't necessarily be the optimal price for the protocol. + +## [08] If a token that yields RSR would be used as collateral then 100% of the yield would go to StRSR +This isn’t very likely to happen (currently the only token that yields RSR is StRSR of another RToken) but it’s worth keeping an eye on it. + +## [09] Protocol might not be able to compromise basket when needed +Consider the following scenario: +* Protocol suffers from some loss and compromises the basket to a 1.1e9 ratio +* Months pass by and users mint new tokens and increase the TVL +* A small compromise is required (12%), this brings the ratio to below 1e9 and reverts the compromise +* Protocol is now disabled despite holding a significant amount of value, and users can only redeem for their prorata share of the assets, + +This might be intended design, but worth taking this scenario into account. + + + +*** + +# Gas Optimizations + +For this Audit, 4 reports were submitted by wardens detailing gas optimizations. The [report highlighted below](https://github.com/code-423n4/2023-06-reserve-findings/issues/3) by **0xA5DF** received the top score from the judge. + +*The following wardens also submitted reports: [hihen](https://github.com/code-423n4/2023-06-reserve-findings/issues/55), +[RaymondFam](https://github.com/code-423n4/2023-06-reserve-findings/issues/50), and +[carlitox477](https://github.com/code-423n4/2023-05-reserve-findings/issues/27).* + +## [G-01] At `toColl()` and `toAsset()` use the mapping to check if asset exists +Savings: ~2.1K per call + +Notice this is a function that's being called frequently, and many times per tx. + +**Overall this can save a few thousands of gas units per tx for the most common txs (e.g. issuance, redemption)** + +https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/AssetRegistry.sol#L120-L132 + +At `toColl()` and `toAsset()` instead of using the EnumberableSet to check that the erc20 is registered just check that the value returned from the mapping isn't zero (this is supposed to be equivalent as long as the governance doesn't register the zero address as an asset contract - maybe add a check for that at `register()`). + +Proposed changes: +```solidity + + /// Return the Asset registered for erc20; revert if erc20 is not registered. + // checks: erc20 in assets + // returns: assets[erc20] + function toAsset(IERC20 erc20) external view returns (IAsset) { + IAsset asset = assets[erc20]; + require(asset != IAsset(address(0)), "erc20 unregistered"); + return asset; + } + + /// Return the Collateral registered for erc20; revert if erc20 is not registered as Collateral + // checks: erc20 in assets, assets[erc20].isCollateral() + // returns: assets[erc20] + function toColl(IERC20 erc20) external view returns (ICollateral) { + IAsset coll = assets[erc20]; + require(coll != IAsset(address(0)), "erc20 unregistered"); + require(coll.isCollateral(), "erc20 is not collateral"); + return ICollateral(address(coll)); + } +``` + +## [G-02] Get `targetIndex` from mapping instead of iterating +Gas savings: a few thousands (see below) + +The following code is used at the [BasketLib](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/mixins/BasketLib.sol#L196-L198) to find the index of a value inside an `EnumerableSet` +```solidity + for (targetIndex = 0; targetIndex < targetsLength; ++targetIndex) { + if (targetNames.at(targetIndex) == config.targetNames[config.erc20s[i]]) break; + } +``` + +However the index can be fetched directly from the `_indexed` mapping: + + +```diff +diff --git a/contracts/p1/mixins/BasketLib.sol b/contracts/p1/mixins/BasketLib.sol +index bc52d1c6..ce56c715 100644 +--- a/contracts/p1/mixins/BasketLib.sol ++++ b/contracts/p1/mixins/BasketLib.sol +@@ -192,10 +192,8 @@ library BasketLibP1 { + // For each prime collateral token: + for (uint256 i = 0; i < config.erc20s.length; ++i) { + // Find collateral's targetName index +- uint256 targetIndex; +- for (targetIndex = 0; targetIndex < targetsLength; ++targetIndex) { +- if (targetNames.at(targetIndex) == config.targetNames[config.erc20s[i]]) break; +- } ++ uint256 targetIndex = targetNames._inner._indexes[config.targetNames[config.erc20s[i]]] -1 ; ++ + assert(targetIndex < targetsLength); + // now, targetNames[targetIndex] == config.targetNames[erc20] + ``` + +Gas savings: +* The `_indexes` keys are considered warm since all values were inserted in the current tx +* Total saving is the sum of the index of the target names per each erc20 minus 1 +* on average (depends on the location of the target in the set for each erc20): `(config.erc20s.length)*(targetsLength-1)/2*100` + * E.g. for target length of 5 and 10 ERC20 that would save on average `10*4/2*100=2K` + +## [G-03] Use `furnace` instead of `main.furnace()` +Gas savings: ~2.6K + +Code: https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/RToken.sol#L184
+https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/RToken.sol#L257 + +At `RToken.redeemTo()` and `redeemCustom()` furnace is being called using `main.furnace()` instead of using the `furnace` variable. + +The call to `main.furnace()` costs both the cold storage variable read at `main.furnace()` and an external call to a cold address while using the `furnace` variable of the current contract costs only the cold storage read. + +The additional cold call would cost ~2.6K. + +To my understanding both of the values should be equal at all times so there shouldn't be an issue with the replacement. + +## [G-04] Deployer.implementations can be immutable +Gas saved: ~28K per RToken deployment + +The struct itself can’t be immutable, but you can save the values of the fields (and fields of the `components`) as immutable variables, and use an internal function to build the struct out of those immutable variables. + +This would save ~2.1K per field, with 13 fields that brings us to ~28K of units saved. + +## [G-05] Update `lastWithdrawRefresh` only if it has changed +Gas saved: ~100 + +At [`leakyRefresh()`](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/StRSR.sol#L674) `lastWithdrawRefresh` gets updated even if didn't change, that costs an additional 100 gas units. + +Proposed change: +```diff +- leaked = lastWithdrawRefresh != lastRefresh ? withdrawal : leaked + withdrawal; +- lastWithdrawRefresh = lastRefresh; ++ if(lastWithdrawRefresh != lastRefresh){ ++ leaked = withdrawal; ++ lastWithdrawRefresh = lastRefresh; ++ } else{ ++ leaked = leaked + withdrawal; ++ } +``` + +## [G-06] Require array to be sorted and use `sortedAndAllUnique` at `BackingManager.forwardRevenue()` +Estimated savings: `~n^2*10` where n is the length of the asset.
+For example for 20 assets that would save ~4K. + +## [G-07] Caching storage variable and function calls +This is one of the most common ways to save a nice amount of gas. Every additional read costs 100 gas units (when it comes to mapping or arrays there's additional cost), and each additional function call costs at least 100 gas units (usually much more). + +I've noticed a few instances where a storage variable read or a view-function call can be cached to memory to save gas, I'm pretty sure there are many more instances that I didn't notice. + +## [G-08]`BasketLib.nextBasket()` refactoring +Gas saved: a few thousand + +The following refactoring saves a few thousands of gas mostly by preventing: +1. Double call to `goodCollateral` +2. The second iteration over the whole `backup.erc20s` array +```diff +diff --git a/contracts/p1/mixins/BasketLib.sol b/contracts/p1/mixins/BasketLib.sol +index bc52d1c6..7ab9c48b 100644 +--- a/contracts/p1/mixins/BasketLib.sol ++++ b/contracts/p1/mixins/BasketLib.sol +@@ -192,10 +192,8 @@ library BasketLibP1 { + // For each prime collateral token: + for (uint256 i = 0; i < config.erc20s.length; ++i) { + // Find collateral's targetName index +- uint256 targetIndex; +- for (targetIndex = 0; targetIndex < targetsLength; ++targetIndex) { +- if (targetNames.at(targetIndex) == config.targetNames[config.erc20s[i]]) break; +- } ++ uint256 targetIndex = targetNames._inner._indexes[config.targetNames[config.erc20s[i]]] -1 ; ++ + assert(targetIndex < targetsLength); + // now, targetNames[targetIndex] == config.targetNames[erc20] + +@@ -244,32 +242,32 @@ library BasketLibP1 { + uint256 size = 0; // backup basket size + BackupConfig storage backup = config.backups[targetNames.at(i)]; + ++ IERC20[] memory backupsToUse = new IERC20[](backup.erc20s.length); ++ + // Find the backup basket size: min(backup.max, # of good backup collateral) + for (uint256 j = 0; j < backup.erc20s.length && size < backup.max; ++j) { +- if (goodCollateral(targetNames.at(i), backup.erc20s[j], assetRegistry)) size++; ++ if (goodCollateral(targetNames.at(i), backup.erc20s[j], assetRegistry)) ++ { ++ backupsToUse[size] = backup.erc20s[j]; ++ size++; ++ } + } + + // Now, size = len(backups(tgt)). If empty, fail. + if (size == 0) return false; + +- // Set backup basket weights... +- uint256 assigned = 0; + + // Loop: for erc20 in backups(tgt)... +- for (uint256 j = 0; j < backup.erc20s.length && assigned < size; ++j) { +- if (goodCollateral(targetNames.at(i), backup.erc20s[j], assetRegistry)) { +- // Across this .add(), targetWeight(newBasket',erc20) +- // = targetWeight(newBasket,erc20) + unsoundPrimeWt(tgt) / len(backups(tgt)) +- newBasket.add( +- backup.erc20s[j], +- totalWeights[i].minus(goodWeights[i]).div( +- // this div is safe: targetPerRef > 0: goodCollateral check +- assetRegistry.toColl(backup.erc20s[j]).targetPerRef().mulu(size), +- CEIL +- ) +- ); +- assigned++; +- } ++ for (uint256 j = 0; j < size; ++j) { ++ ++ newBasket.add( ++ backupsToUse[j], ++ totalWeights[i].minus(goodWeights[i]).div( ++ // this div is safe: targetPerRef > 0: goodCollateral check ++ assetRegistry.toColl(backupsToUse[j]).targetPerRef().mulu(size), ++ CEIL ++ ) ++ ); + } + // Here, targetWeight(newBasket, e) = primeWt(e) + backupWt(e) for all e targeting tgt + } +``` + +## [G-09] `BasketLib.nextBasket()` caching +On top of the above refactoring: +* `config.erc20s[i]` is being read a few times [here](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/mixins/BasketLib.sol#L193-L224) +* `config.erc20s.length` and `backup.erc20s.length` can be cached +* `targetNames.at(i)` is being read twice in the second loop (3 before the proposed refactoring) + + +## [G-10]`sellAmount` at `DutchTrade.settle()` +`sellAmount` is read here twice if greater than `sellBal` +```solidity + soldAmt = sellAmount > sellBal ? sellAmount - sellBal : 0; +``` + +## [G-11] `quoteCustomRedemption()` loop +### `nonce` +Savings: `100*basketNonces.length`
+[Here](https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/p1/BasketHandler.sol#L398) nonce is being read each iteration. +It can be cached outside of the loop. + +### `basketNonces.length` +Savings: `100*basketNonces.length` + +### `b.erc20s.length` +Savings: `100*(sum of `b.erc20s.length` for all baskets)` + +## [G-12] Use custom errors instead of string errors +This saves gas both for deployment and in case that the revert is triggered. + + + +*** + +# [Mitigation Review](#mitigation-review) + +## Introduction + +Following the C4 audit, 3 wardens (0xA5DF, ronnyx2017, and rvierdiiev) reviewed the mitigations for all identified issues. Additional details can be found within the [C4 Reserve Protocol Mitigation Review repository](https://github.com/code-423n4/2023-08-reserve-mitigation). + +## Mitigation Review Scope + +### Branch + +https://github.com/reserve-protocol/protocol/pull/882 (commit hash 99d9db72e04db29f8e80e50a78b16a0b475d79f3) + +### Individual PRs + +| URL | Mitigation of | Purpose | +| ----------------------------------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------ | +| https://github.com/reserve-protocol/protocol/pull/857 | H-01 | Fix redeemCustom | +| https://github.com/reserve-protocol/protocol/pull/888 | H-02 | Adds governance function to manually push the era forward | +| https://github.com/reserve-protocol/protocol/pull/876 | M-01 | Allow settle trade when paused or frozen | +| https://github.com/reserve-protocol/protocol/pull/873 & https://github.com/reserve-protocol/protocol/pull/869 | M-02 | Disable dutch auctions on a per-collateral basis, use 4-step dutch trade curve | +| https://github.com/reserve-protocol/protocol/pull/878 | M-03 | Distribute revenue in setDistribution | +| https://github.com/reserve-protocol/protocol/pull/885 | M-04 | Update payout variables if melt fails during setRatio | +| https://github.com/reserve-protocol/protocol-private/pull/15 | M-06 | Use lotPrice() | +| https://github.com/reserve-protocol/protocol-private/pull/7 | M-07 | Refresh before selling rewards, refactor revenue & distro | +| https://github.com/reserve-protocol/protocol/pull/857 | M-08 | payoutRewards before freeze and update payoutLastPaid before unfreeze | +| https://github.com/reserve-protocol/protocol-private/pull/3 | M-09 | Payout rewards during cancelUnstake | +| https://github.com/reserve-protocol/protocol/pull/886 | M-10 | Add oracle deprecation check | +| https://github.com/reserve-protocol/protocol/pull/857 | M-11 | Change gas reservation policy in AssetRegistry | + +### Out of Scope + +- [M-05: Lack of claimRewards when manageToken in RevenueTrader](https://github.com/code-423n4/2023-06-reserve-findings/issues/16) +- [M-12: Custom redemption can be used to get more than RToken value, when an upwards depeg occurs](https://github.com/code-423n4/2023-06-reserve-findings/issues/6) + +## Mitigation Review Summary + +| Original Issue | Status | Full Details | +| ----------- | ------------- | ----------- | +| H-01 | Mitigation Confirmed | Reports from [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/7), [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/30), and [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/3) | +| H-02 | Mitigation Confirmed | Reports from [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/31), [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/8), and [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/11) | +| M-01 | Mitigation Confirmed | Reports from [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/5), [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/32), and [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/9) | +| M-02 | Mitigation Errors | Reports from [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/20) and [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/40) - details also shared below | +| M-03 | Mitigation Error | Reports from [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/36) and [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/10) - details also shared below | +| M-04 | Mitigation Error | Report from [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/37) - details also shared below | +| M-05 | Sponsor Disputed | - | +| M-06 | Mitigation Confirmed | Reports from [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/13), [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/41), and [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/23) | +| M-07 | Mitigation Confirmed | Reports from [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/24), [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/34), and [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/14) | +| M-08 | Mitigation Confirmed | Reports from [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/15), [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/38), and [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/25) | +| M-09 | Mitigation Confirmed | Reports from [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/16), [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/33), and [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/26) | +| M-10 | Mitigation Confirmed | Reports from [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/35), [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/28), and [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/17) | +| M-11 | Mitigation Confirmed | Reports from [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/27), [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/39), and [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/18) | +| M-12 | Sponsor Acknowledged | - | + +**During their review, the wardens surfaced several mitigation errors. These consisted of 4 Medium severity issues. See below for details.** + +## [M-02 Mitigation Error: `dutchTradeDisabled[erc20]` gives governance an incentive to disable RSR auctions](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/20) +*Submitted by [ronnyx2017](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/20)* + +**Severity: Medium** + +Lines of Code: [Broker.sol#L213-L216](https://github.com/reserve-protocol/protocol/blob/3.0.0-rc5/contracts/p1/Broker.sol#L213-L216) + +The mitigation adds different disable flags for GnosisTrade and DutchTrade. It can disable dutch trades by specific collateral. But it has serious problem with overall economic model design. + +The traders Broker contract are under control of the governance. The governance proposals are voted by stRSR stakers. And if the RToken is undercollateralized, the staking RSR will be sold for collaterals. In order to prevent this from happening, the governance(stakers) have every incentive to block the rsr auction. Although governance also can set disable flag for trade broker in the previous version of mitigation, there is a difference made it impossible to do so in previous versions. + +In the pre version, there is only one disable flag that disables any trades for any token. So if the governance votes for disable trades, the RToken users will find that they can't derive any gain from RToken. So no one would try to issue RToken by their collateral. It is also unacceptable for governance. + +But after the mitigation, the governance can decide only disable the DutchTrade for RSR. And they can initiate a proposal about enable RSR trade -> open openTrade -> re-disable RSR trade to ensure their own gains. And most importantly, this behavior seems to do no harm to RToken holders just on the face of it, and it therefore does not threaten RToken issuance. + +So in order to prevent the undercollateralized case, dutchTradeDisabled\[erc20] gives governance every incentive to disable RSR auctions. + +### Impact + +When RToken is undercollateralized, disabling RSR trade will force users into redeeming from RToken baskets. It will lead to even greater depeg, and the RToken users will bear all the losses, but the RSR stakers can emerge unscathed. + +### Proof of Concept + +StRSR stakers can initiate such a proposal to prevent staking RSR auctions: + +1. Call `Broker.setBatchTradeDisabled(bool disabled)` to disable any GnosisTrade. + +2. And call `setDutchTradeDisabled(RSR_address, true)` to disable RSR DutchTrade. + +### Recommended Mitigation Steps + +The `dutchTradeDisabled` flag of RSR should not be set to true directly by governance in the `Broker.setDutchTradeDisabled` function. Add a check like that: + + require(!(disabled && rsrTrader.tokenToBuy()==erc20),"xxxxxxx"); + +### Assessed type + +Context + +**[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/20#issuecomment-1692159329):** + > Anticipating restricting governance to only be able to _enable_ batch trade, or dutch trade. + + + +*** + +## [M-02 Mitigation Error: Attacker might disable trading by faking a report violation](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/40) +*Submitted by [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/40)* + +**Severity: Medium** + +Lines of Code: [DutchTrade.sol#L212-L214](https://github.com/reserve-protocol/protocol/blob/99d9db72e04db29f8e80e50a78b16a0b475d79f3/contracts/plugins/trading/DutchTrade.sol#L212-L214) + +Dutch trade now creates a report violation whenever the price is x1.5 then the best price. + +The issue is that the attacker can fake a report violation by buying with the higher price. Since revenue traders don't have a minimum trade amount that can cost the attacker near zero funds. + +Mitigation might be to create violation report only if the price is high and the total value of the sell is above some threshold. + +**[tbrent (Reserve) confirmed](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/40#issuecomment-1692064874)** + + + +*** + +## [M-03 Mitigation Error: Funds aren't distributed before changing distribution](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/36) +*Submitted by [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/36), also found by [rvierdiiev](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/10)* + +**Severity: Medium** + +Lines of Code: [Distributor.sol#L59-L63](https://github.com/reserve-protocol/protocol/blob/99d9db72e04db29f8e80e50a78b16a0b475d79f3/contracts/p1/Distributor.sol#L59-L63) + +Mitigation does solve the issue; however there’s a wider issue here that funds aren’t distributed before set distribution is executed. + +Fully mitigating the issue might not be possible, as it’d require to send from the backing manager to revenue trader and sell all assets for the `tokenToBuy`. But we can at least distribute the current balance before changing the distribution. + +**[tbrent (Reserve) confirmed and commented](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/36#issuecomment-1692078866):** + > Anticipating adding a try-catch at the start of `setDistribution()` targeting `RevenueTrader.distributeTokenToBuy()` + + + +*** + +## [M-04 Mitigation Error: Furnace would melt less than intended](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/37) +*Submitted by [0xA5DF](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/37)* + +**Severity: Medium** + +Lines of Code: [Furnace.sol#L92-L105](https://github.com/reserve-protocol/protocol/blob/99d9db72e04db29f8e80e50a78b16a0b475d79f3/contracts/p1/Furnace.sol#L92-L105) + +We traded one problem with another here. The original issue was that in case `melt()` fails then the distribution would use the new rate for previous periods as well. + +The issue now is that in case of a failure (e.g. paused or frozen) we simply don’t melt for the previous period. Meaning RToken holders would get deprived of the melting they’re supposed to get. + +This is especially noticeable when the ratio has been decreased and the balance didn’t grow much, in that case we do more harm than good by updating `lastPayout` and `lastPayoutBal`. + +A better mitigation might be to update the `lastPayout` in a way that would reflect the melting that should be distributed. + +**[tbrent (Reserve) acknowledged and commented](https://github.com/code-423n4/2023-08-reserve-mitigation-findings/issues/37#issuecomment-1692068277):** + > I think this can only happen when frozen, not while paused. `Furnace.melt()` and `RToken.melt()` succeed while paused. + + + +*** + +# Disclosures + +C4 is an open organization governed by participants in the community. + +C4 Audits incentivize the discovery of exploits, vulnerabilities, and bugs in smart contracts. Security researchers are rewarded at an increasing rate for finding higher-risk issues. Audit submissions are judged by a knowledgeable security researcher and solidity developer and disclosed to sponsoring developers. C4 does not conduct formal verification regarding the provided code but instead provides final verification. + +C4 does not provide any guarantee or warranty regarding the security of this project. All smart contract software should be used at the sole risk and responsibility of users. diff --git a/book.toml b/book.toml new file mode 100644 index 0000000000..6b2acd5dbb --- /dev/null +++ b/book.toml @@ -0,0 +1,15 @@ +[book] +src = "src" +title = "Reserve Developer Documentation" + +[output.html] +no-section-label = true +additional-js = ["solidity.min.js"] +additional-css = [ + "book.css", + "custom.css", +] +git-repository-url = "https://github.com/reserve-protocol/protocol" + +[output.html.fold] +enable = true \ No newline at end of file diff --git a/common/configuration.ts b/common/configuration.ts index c12edfa2ce..c9b10de792 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -9,6 +9,7 @@ interface ICurrencies { export interface ITokens { DAI?: string USDC?: string + USDbC?: string USDT?: string USDP?: string TUSD?: string @@ -25,6 +26,10 @@ export interface ITokens { aWETH?: string aWBTC?: string aCRV?: string + aEthUSDC?: string + aBasUSDbC?: string + aWETHv3?: string + acbETHv3?: string cDAI?: string cUSDC?: string cUSDT?: string @@ -51,6 +56,7 @@ export interface ITokens { wstETH?: string rETH?: string cUSDCv3?: string + cUSDbCv3?: string ONDO?: string sDAI?: string cbETH?: string @@ -73,7 +79,8 @@ export interface ITokens { export interface IFeeds { stETHETH?: string stETHUSD?: string - wBTCBTC?: string + wstETHstETHexr?: string + cbETHETHexr?: string } export interface IPools { @@ -102,6 +109,9 @@ interface INetworkConfig { MORPHO_AAVE_CONTROLLER?: string MORPHO_REWARDS_DISTRIBUTOR?: string MORPHO_AAVE_LENS?: string + COMET_REWARDS?: string + AAVE_V3_INCENTIVES_CONTROLLER?: string + AAVE_V3_POOL?: string } export const networkConfig: { [key: string]: INetworkConfig } = { @@ -132,6 +142,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { aWETH: '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e', aWBTC: '0x9ff58f4ffb29fa2266ab25e75e2a8b3503311656', aCRV: '0x8dae6cb04688c62d939ed9b68d32bc62e49970b1', + aEthUSDC: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', cDAI: '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', cUSDC: '0x39AA39c021dfbaE8faC545936693aC917d5E7563', cUSDT: '0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9', @@ -192,7 +203,6 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH - wBTCBTC: '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', // "WBTC/BTC" }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5', @@ -205,15 +215,9 @@ export const networkConfig: { [key: string]: INetworkConfig } = { MORPHO_AAVE_LENS: '0x507fA343d0A90786d86C7cd885f5C49263A91FF4', MORPHO_AAVE_CONTROLLER: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', MORPHO_REWARDS_DISTRIBUTOR: '0x3b14e5c73e0a56d607a8688098326fd4b4292135', - }, - '3': { - name: 'ropsten', - tokens: { - USDC: '0x07865c6e87b9f70255377e024ace6630c1eaa37f', - RSR: '0x320623b8e4ff03373931769a31fc52a4e78b5d70', - }, - chainlinkFeeds: {}, - COMPTROLLER: '0xcfa7b0e37f5AC60f3ae25226F5e39ec59AD26152', + COMET_REWARDS: '0x1B0e765F6224C21223AeA2af16c1C46E38885a40', + AAVE_V3_POOL: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2', + AAVE_V3_INCENTIVES_CONTROLLER: '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb', }, '1': { name: 'mainnet', @@ -234,6 +238,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { aBUSD: '0xA361718326c15715591c299427c62086F69923D9', aUSDP: '0x2e8F4bdbE3d47d7d7DE490437AeA9915D930F1A3', aWETH: '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e', + aEthUSDC: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', cDAI: '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', cUSDC: '0x39AA39c021dfbaE8faC545936693aC917d5E7563', cUSDT: '0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9', @@ -296,7 +301,6 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH - wBTCBTC: '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', // "WBTC/BTC" }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', @@ -306,6 +310,9 @@ export const networkConfig: { [key: string]: INetworkConfig } = { MORPHO_AAVE_LENS: '0x507fA343d0A90786d86C7cd885f5C49263A91FF4', MORPHO_AAVE_CONTROLLER: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', MORPHO_REWARDS_DISTRIBUTOR: '0x3b14e5c73e0a56d607a8688098326fd4b4292135', + COMET_REWARDS: '0x1B0e765F6224C21223AeA2af16c1C46E38885a40', + AAVE_V3_POOL: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2', + AAVE_V3_INCENTIVES_CONTROLLER: '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb', }, '3': { name: 'tenderly', @@ -326,6 +333,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = { aBUSD: '0xA361718326c15715591c299427c62086F69923D9', aUSDP: '0x2e8F4bdbE3d47d7d7DE490437AeA9915D930F1A3', aWETH: '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e', + aEthUSDC: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', cDAI: '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', cUSDC: '0x39AA39c021dfbaE8faC545936693aC917d5E7563', cUSDT: '0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9', @@ -388,7 +396,6 @@ export const networkConfig: { [key: string]: INetworkConfig } = { stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH - wBTCBTC: '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', // "WBTC/BTC" }, AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', AAVE_RESERVE_TREASURY: '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c', @@ -398,6 +405,9 @@ export const networkConfig: { [key: string]: INetworkConfig } = { MORPHO_AAVE_LENS: '0x507fA343d0A90786d86C7cd885f5C49263A91FF4', MORPHO_AAVE_CONTROLLER: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', MORPHO_REWARDS_DISTRIBUTOR: '0x3b14e5c73e0a56d607a8688098326fd4b4292135', + COMET_REWARDS: '0x1B0e765F6224C21223AeA2af16c1C46E38885a40', + AAVE_V3_POOL: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2', + AAVE_V3_INCENTIVES_CONTROLLER: '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb', }, '5': { name: 'goerli', @@ -487,6 +497,37 @@ export const networkConfig: { [key: string]: INetworkConfig } = { }, GNOSIS_EASY_AUCTION: '0xcdf32E323e69090eCA17adDeF058A6A921c3e75A', // mock }, + '8453': { + name: 'base', + tokens: { + DAI: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', + USDbC: '0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA', + RSR: '0xaB36452DbAC151bE02b16Ca17d8919826072f64a', + COMP: '0x9e1028F5F1D5eDE59748FFceE5532509976840E0', + WETH: '0x4200000000000000000000000000000000000006', + cbETH: '0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22', + cUSDbCv3: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf', + aBasUSDbC: '0x0a1d576f3eFeF75b330424287a95A366e8281D54', + aWETHv3: '0xD4a0e0b9149BCee3C920d2E00b5dE09138fd8bb7', + acbETHv3: '0xcf3D55c10DB69f28fD1A75Bd73f3D8A2d9c595ad', + }, + chainlinkFeeds: { + DAI: '0x591e79239a7d679378ec8c847e5038150364c78f', // 0.3%, 24hr + ETH: '0x71041dddad3595f9ced3dccfbe3d1f4b0a16bb70', // 0.15%, 20min + WBTC: '0xccadc697c55bbb68dc5bcdf8d3cbe83cdd4e071e', // 0.5%, 24hr + USDC: '0x7e860098f58bbfc8648a4311b374b1d669a2bc6b', // 0.3%, 24hr + USDT: '0xf19d560eb8d2adf07bd6d13ed03e1d11215721f9', // 0.3%, 24hr + COMP: '0x9dda783de64a9d1a60c49ca761ebe528c35ba428', // 0.5%, 24hr + cbETH: '0x806b4ac04501c29769051e42783cf04dce41440b', // 0.5%, 24hr + RSR: '0xAa98aE504658766Dfe11F31c5D95a0bdcABDe0b1', // 2%, 24hr + wstETHstETHexr: '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', // 0.5%, 24hr + cbETHETHexr: '0x868a501e68F3D1E89CfC0D22F6b22E8dabce5F04', // 0.5%, 24hr + }, + GNOSIS_EASY_AUCTION: '0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02', // mock + COMET_REWARDS: '0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1', + AAVE_V3_POOL: '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5', + AAVE_V3_INCENTIVES_CONTROLLER: '0xf9cc4F0D883F1a1eb2c253bdb46c254Ca51E1F44', + }, } export const getNetworkConfig = (chainId: string) => { diff --git a/contracts/facade/FacadeAct.sol b/contracts/facade/FacadeAct.sol index cbe95038f6..37ec3dc4be 100644 --- a/contracts/facade/FacadeAct.sol +++ b/contracts/facade/FacadeAct.sol @@ -59,6 +59,8 @@ contract FacadeAct is IFacadeAct, Multicall { ); } + if (toStart.length == 0) return; + // Transfer revenue backingManager -> revenueTrader _forwardRevenue(revenueTrader.main().backingManager(), toStart); diff --git a/contracts/facade/FacadeWrite.sol b/contracts/facade/FacadeWrite.sol index 7d4a4c2c12..ed791cb244 100644 --- a/contracts/facade/FacadeWrite.sol +++ b/contracts/facade/FacadeWrite.sol @@ -165,7 +165,7 @@ contract FacadeWrite is IFacadeWrite { timelock.grantRole(timelock.PROPOSER_ROLE(), governance); // Gov only proposer // Set Guardian as canceller, if address(0) then no one can cancel timelock.grantRole(timelock.CANCELLER_ROLE(), govRoles.guardian); - timelock.grantRole(timelock.EXECUTOR_ROLE(), address(0)); // Anyone as executor + timelock.grantRole(timelock.EXECUTOR_ROLE(), governance); // Gov only executor timelock.revokeRole(timelock.TIMELOCK_ADMIN_ROLE(), address(this)); // Revoke admin role // Set new owner to timelock diff --git a/contracts/interfaces/IDeployer.sol b/contracts/interfaces/IDeployer.sol index 164c60a8c1..18c1cc4ddc 100644 --- a/contracts/interfaces/IDeployer.sol +++ b/contracts/interfaces/IDeployer.sol @@ -85,6 +85,11 @@ interface IDeployer is IVersioned { string version ); + /// Emitted when a new RTokenAsset is deployed during `deployRTokenAsset` + /// @param rToken The address of the RToken ERC20 + /// @param rTokenAsset The address of the RTokenAsset + event RTokenAssetCreated(IRToken indexed rToken, IAsset rTokenAsset); + // /// Deploys an instance of the entire system diff --git a/contracts/p0/Deployer.sol b/contracts/p0/Deployer.sol index 0b1ff0324a..c143ec9d62 100644 --- a/contracts/p0/Deployer.sol +++ b/contracts/p0/Deployer.sol @@ -162,7 +162,12 @@ contract DeployerP0 is IDeployer, Versioned { } /// @param maxTradeVolume {UoA} The maximum trade volume for the RTokenAsset - function deployRTokenAsset(IRToken rToken, uint192 maxTradeVolume) external returns (IAsset) { - return new RTokenAsset(rToken, maxTradeVolume); + /// @return rTokenAsset The address of the newly deployed RTokenAsset + function deployRTokenAsset(IRToken rToken, uint192 maxTradeVolume) + external + returns (IAsset rTokenAsset) + { + rTokenAsset = new RTokenAsset(rToken, maxTradeVolume); + emit RTokenAssetCreated(rToken, rTokenAsset); } } diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index 72ae48ad53..fd9ea5aa19 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -304,6 +304,9 @@ library TradingLibP0 { } (uint192 low, uint192 high) = asset.price(); // {UoA/tok} + // price() is better than lotPrice() here: it's important to not underestimate how + // much value could be in a token that is unpriced by using a decaying high lotPrice. + // price() will return [0, FIX_MAX] in this case, which is preferable. // Skip over dust-balance assets not in the basket // Intentionally include value of IFFY/DISABLED collateral diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index c5cdb649e8..a83603c226 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -46,6 +46,7 @@ contract BrokerP1 is ComponentP1, IBroker { // Whether Batch Auctions are disabled. // Initially false. Settable by OWNER. // A GnosisTrade clone can set it to true via reportViolation() + /// @custom:oz-renamed-from disabled bool public batchTradeDisabled; // The set of ITrade (clone) addresses this contract has created diff --git a/contracts/p1/Deployer.sol b/contracts/p1/Deployer.sol index 23f0b34ac1..2119420e43 100644 --- a/contracts/p1/Deployer.sol +++ b/contracts/p1/Deployer.sol @@ -251,7 +251,12 @@ contract DeployerP1 is IDeployer, Versioned { /// Deploys a new RTokenAsset instance. Not needed during normal deployment flow /// @param maxTradeVolume {UoA} The maximum trade volume for the RTokenAsset - function deployRTokenAsset(IRToken rToken, uint192 maxTradeVolume) external returns (IAsset) { - return new RTokenAsset(rToken, maxTradeVolume); + /// @return rTokenAsset The address of the newly deployed RTokenAsset + function deployRTokenAsset(IRToken rToken, uint192 maxTradeVolume) + external + returns (IAsset rTokenAsset) + { + rTokenAsset = new RTokenAsset(rToken, maxTradeVolume); + emit RTokenAssetCreated(rToken, rTokenAsset); } } diff --git a/contracts/p1/mixins/RecollateralizationLib.sol b/contracts/p1/mixins/RecollateralizationLib.sol index c6eff7ff4f..ac5deeb3f5 100644 --- a/contracts/p1/mixins/RecollateralizationLib.sol +++ b/contracts/p1/mixins/RecollateralizationLib.sol @@ -130,8 +130,7 @@ library RecollateralizationLibP1 { // Compute the target basket range // Algorithm intuition: Trade conservatively. Quantify uncertainty based on the proportion of // token balances requiring trading vs not requiring trading. Seek to decrease uncertainty - // the largest amount possible with each trade. As long as trades clear within the expected - // range of prices, the basket range should narrow with each iteration (under constant prices) + // the largest amount possible with each trade. // // How do we know this algorithm converges? // Assumption: constant oracle prices; monotonically increasing refPerTok() @@ -143,8 +142,8 @@ library RecollateralizationLibP1 { // run-to-run, but will never increase it // // Preconditions: - // - ctx is correctly populated, with current basketsHeld + quantities - // - reg contains erc20 + asset arrays in same order and without duplicates + // - ctx is correctly populated, with current basketsHeld.bottom + basketsHeld.top + // - reg contains erc20 + asset + quantities arrays in same order and without duplicates // Trading Strategy: // - We will not aim to hold more than rToken.basketsNeeded() BUs // - No double trades: capital converted from token A to token B should not go to token C diff --git a/contracts/plugins/assets/L2LSDCollateral.sol b/contracts/plugins/assets/L2LSDCollateral.sol new file mode 100644 index 0000000000..0fc8e40884 --- /dev/null +++ b/contracts/plugins/assets/L2LSDCollateral.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import { CEIL, FIX_MAX, FixLib, _safeWrap } from "../../libraries/Fixed.sol"; +import { AggregatorV3Interface, OracleLib } from "./OracleLib.sol"; +import { CollateralConfig, AppreciatingFiatCollateral } from "./AppreciatingFiatCollateral.sol"; +import { CollateralStatus } from "../../interfaces/IAsset.sol"; + +/** + * @title L2LSDCollateral + * @notice Base collateral plugin for LSDs on L2s. Inherited per collateral. + * @notice _underlyingRefPerTok uses a chainlink feed rather than direct contract calls. + */ +abstract contract L2LSDCollateral is AppreciatingFiatCollateral { + using OracleLib for AggregatorV3Interface; + using FixLib for uint192; + + AggregatorV3Interface public immutable exchangeRateChainlinkFeed; + uint48 public immutable exchangeRateChainlinkTimeout; + + /// @param config.chainlinkFeed {UoA/target} price of ETH in USD terms + /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide + /// @param _exchangeRateChainlinkFeed {target/tok} L1 LSD exchange rate, oraclized to L2 + /// @param _exchangeRateChainlinkTimeout {s} Timeout for L1 LSD exchange rate oracle + constructor( + CollateralConfig memory config, + uint192 revenueHiding, + AggregatorV3Interface _exchangeRateChainlinkFeed, + uint48 _exchangeRateChainlinkTimeout + ) AppreciatingFiatCollateral(config, revenueHiding) { + require(address(_exchangeRateChainlinkFeed) != address(0), "missing exchangeRate feed"); + require(_exchangeRateChainlinkTimeout != 0, "exchangeRateChainlinkTimeout zero"); + + exchangeRateChainlinkFeed = _exchangeRateChainlinkFeed; + exchangeRateChainlinkTimeout = _exchangeRateChainlinkTimeout; + } + + /// Should not revert + /// Refresh exchange rates and update default status. + /// @dev Should not need to override: can handle collateral with variable refPerTok() + function refresh() public virtual override { + CollateralStatus oldStatus = status(); + + // Check for hard default + // must happen before tryPrice() call since `refPerTok()` returns a stored value + + // revenue hiding: do not DISABLE if drawdown is small + // underlyingRefPerTok may fail call to chainlink oracle, need to catch + try this.getUnderlyingRefPerTok() returns (uint192 underlyingRefPerTok) { + // {ref/tok} = {ref/tok} * {1} + uint192 hiddenReferencePrice = underlyingRefPerTok.mul(revenueShowing); + + // uint192(<) is equivalent to Fix.lt + if (underlyingRefPerTok < exposedReferencePrice) { + exposedReferencePrice = hiddenReferencePrice; + markStatus(CollateralStatus.DISABLED); + } else if (hiddenReferencePrice > exposedReferencePrice) { + exposedReferencePrice = hiddenReferencePrice; + } + + // Check for soft default + save prices + try this.tryPrice() returns (uint192 low, uint192 high, uint192 pegPrice) { + // {UoA/tok}, {UoA/tok}, {target/ref} + // (0, 0) is a valid price; (0, FIX_MAX) is unpriced + + // Save prices if priced + if (high < FIX_MAX) { + savedLowPrice = low; + savedHighPrice = high; + lastSave = uint48(block.timestamp); + } else { + // must be unpriced + assert(low == 0); + } + + // If the price is below the default-threshold price, default eventually + // uint192(+/-) is the same as Fix.plus/minus + if (pegPrice < pegBottom || pegPrice > pegTop || low == 0) { + markStatus(CollateralStatus.IFFY); + } else { + markStatus(CollateralStatus.SOUND); + } + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + markStatus(CollateralStatus.IFFY); + } + } catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + markStatus(CollateralStatus.IFFY); + } + + CollateralStatus newStatus = status(); + if (oldStatus != newStatus) { + emit CollateralStatusChanged(oldStatus, newStatus); + } + } + + function getUnderlyingRefPerTok() public view returns (uint192) { + return _underlyingRefPerTok(); + } + + /// @return {ref/tok} Quantity of whole reference units per whole collateral tokens + function _underlyingRefPerTok() internal view override returns (uint192) { + return exchangeRateChainlinkFeed.price(exchangeRateChainlinkTimeout); + } +} diff --git a/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol b/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol new file mode 100644 index 0000000000..2edfd5d65b --- /dev/null +++ b/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../../../libraries/Fixed.sol"; +import "../AppreciatingFiatCollateral.sol"; + +import { StaticATokenV3LM } from "./vendor/StaticATokenV3LM.sol"; + +/** + * @title AaveV3FiatCollateral + * @notice Collateral plugin for an aToken for a UoA-pegged asset, like aUSDC or a aUSDP on Aave V3 + * Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} == {UoA} + */ +contract AaveV3FiatCollateral is AppreciatingFiatCollateral { + using OracleLib for AggregatorV3Interface; + using FixLib for uint192; + + // solhint-disable no-empty-blocks + /// @param config.chainlinkFeed Feed units: {UoA/ref} + /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide + constructor(CollateralConfig memory config, uint192 revenueHiding) + AppreciatingFiatCollateral(config, revenueHiding) + {} + + // solhint-enable no-empty-blocks + + /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens + function _underlyingRefPerTok() internal view override returns (uint192) { + uint256 rate = StaticATokenV3LM(address(erc20)).rate(); // {ray ref/tok} + + return shiftl_toFix(rate, -27); // {ray -> wad} + } + + /// Claim rewards earned by holding a balance of the ERC20 token + /// delegatecall + /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins + function claimRewards() external virtual override(Asset, IRewardable) { + StaticATokenV3LM(address(erc20)).claimRewards(); + } +} diff --git a/contracts/plugins/assets/aave-v3/mock/MockStaticATokenV3.sol b/contracts/plugins/assets/aave-v3/mock/MockStaticATokenV3.sol new file mode 100644 index 0000000000..33fd031d93 --- /dev/null +++ b/contracts/plugins/assets/aave-v3/mock/MockStaticATokenV3.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +import { StaticATokenV3LM, IPool, IRewardsController } from "../vendor/StaticATokenV3LM.sol"; + +contract MockStaticATokenV3LM is StaticATokenV3LM { + uint256 public customRate; + + /* solhint-disable no-empty-blocks */ + constructor(IPool pool, IRewardsController rewardsController) + StaticATokenV3LM(pool, rewardsController) + {} + + /* solhint-enable no-empty-blocks */ + + function rate() public view override returns (uint256) { + if (customRate != 0) { + return customRate; + } + + return POOL.getReserveNormalizedIncome(_aTokenUnderlying); + } + + function mockSetCustomRate(uint256 _customRate) external { + customRate = _customRate; + } +} diff --git a/contracts/plugins/assets/aave-v3/vendor/ERC20.sol b/contracts/plugins/assets/aave-v3/vendor/ERC20.sol new file mode 100644 index 0000000000..1cfa9889f1 --- /dev/null +++ b/contracts/plugins/assets/aave-v3/vendor/ERC20.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +/* solhint-disable */ + +/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. +/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol) +/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) +/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. +abstract contract ERC20 { + /* ////////////////////////////////////////////////////////////// + EVENTS + ////////////////////////////////////////////////////////////// */ + + event Transfer(address indexed from, address indexed to, uint256 amount); + + event Approval(address indexed owner, address indexed spender, uint256 amount); + + /* ////////////////////////////////////////////////////////////// + METADATA STORAGE + ////////////////////////////////////////////////////////////// */ + + string public name; + + string public symbol; + + uint8 public decimals; + + /* ////////////////////////////////////////////////////////////// + ERC20 STORAGE + ////////////////////////////////////////////////////////////// */ + + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + + mapping(address => mapping(address => uint256)) public allowance; + + /* ////////////////////////////////////////////////////////////// + EIP-2612 STORAGE + ////////////////////////////////////////////////////////////// */ + + uint256 internal immutable INITIAL_CHAIN_ID; + + bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; + + mapping(address => uint256) public nonces; + + /* ////////////////////////////////////////////////////////////// + CONSTRUCTOR + ////////////////////////////////////////////////////////////// */ + + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals + ) { + name = _name; + symbol = _symbol; + decimals = _decimals; + + INITIAL_CHAIN_ID = block.chainid; + INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); + } + + /* ////////////////////////////////////////////////////////////// + ERC20 LOGIC + ////////////////////////////////////////////////////////////// */ + + function approve(address spender, uint256 amount) public virtual returns (bool) { + allowance[msg.sender][spender] = amount; + + emit Approval(msg.sender, spender, amount); + + return true; + } + + function transfer(address to, uint256 amount) public virtual returns (bool) { + _beforeTokenTransfer(msg.sender, to, amount); + balanceOf[msg.sender] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(msg.sender, to, amount); + + return true; + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual returns (bool) { + _beforeTokenTransfer(from, to, amount); + uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; + + balanceOf[from] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(from, to, amount); + + return true; + } + + /* ////////////////////////////////////////////////////////////// + EIP-2612 LOGIC + ////////////////////////////////////////////////////////////// */ + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); + + // Unchecked because the only math done is incrementing + // the owner's nonce which cannot realistically overflow. + unchecked { + address recoveredAddress = ecrecover( + keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR(), + keccak256( + abi.encode( + keccak256( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" + ), + owner, + spender, + value, + nonces[owner]++, + deadline + ) + ) + ) + ), + v, + r, + s + ); + + require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); + + allowance[recoveredAddress][spender] = value; + } + + emit Approval(owner, spender, value); + } + + function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { + return + block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); + } + + function computeDomainSeparator() internal view virtual returns (bytes32) { + return + keccak256( + abi.encode( + keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ), + keccak256(bytes(name)), + keccak256("1"), + block.chainid, + address(this) + ) + ); + } + + /* ////////////////////////////////////////////////////////////// + INTERNAL MINT/BURN LOGIC + ////////////////////////////////////////////////////////////// */ + + function _mint(address to, uint256 amount) internal virtual { + _beforeTokenTransfer(address(0), to, amount); + totalSupply += amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(address(0), to, amount); + } + + function _burn(address from, uint256 amount) internal virtual { + _beforeTokenTransfer(from, address(0), amount); + balanceOf[from] -= amount; + + // Cannot underflow because a user's balance + // will never be larger than the total supply. + unchecked { + totalSupply -= amount; + } + + emit Transfer(from, address(0), amount); + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be to transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} +} + +/* solhint-enable */ diff --git a/contracts/plugins/assets/aave-v3/vendor/RayMathExplicitRounding.sol b/contracts/plugins/assets/aave-v3/vendor/RayMathExplicitRounding.sol new file mode 100644 index 0000000000..b95df0c04d --- /dev/null +++ b/contracts/plugins/assets/aave-v3/vendor/RayMathExplicitRounding.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.10; + +/* solhint-disable max-line-length */ + +enum Rounding { + UP, + DOWN +} + +/** + * Simplified version of RayMath that instead of half-up rounding does explicit rounding in a specified direction. + * This is needed to have a 4626 complient implementation, that always predictable rounds in favor of the vault / static a token. + */ +library RayMathExplicitRounding { + uint256 internal constant RAY = 1e27; + uint256 internal constant WAD_RAY_RATIO = 1e9; + + function rayMulRoundDown(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0 || b == 0) { + return 0; + } + return (a * b) / RAY; + } + + function rayMulRoundUp(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0 || b == 0) { + return 0; + } + return ((a * b) + RAY - 1) / RAY; + } + + function rayDivRoundDown(uint256 a, uint256 b) internal pure returns (uint256) { + return (a * RAY) / b; + } + + function rayDivRoundUp(uint256 a, uint256 b) internal pure returns (uint256) { + return ((a * RAY) + b - 1) / b; + } + + function rayToWadRoundDown(uint256 a) internal pure returns (uint256) { + return a / WAD_RAY_RATIO; + } +} + +/* solhint-enable max-line-length */ diff --git a/contracts/plugins/assets/aave-v3/vendor/StaticATokenErrors.sol b/contracts/plugins/assets/aave-v3/vendor/StaticATokenErrors.sol new file mode 100644 index 0000000000..ccd554d757 --- /dev/null +++ b/contracts/plugins/assets/aave-v3/vendor/StaticATokenErrors.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.10; + +library StaticATokenErrors { + string public constant INVALID_OWNER = "1"; + string public constant INVALID_EXPIRATION = "2"; + string public constant INVALID_SIGNATURE = "3"; + string public constant INVALID_DEPOSITOR = "4"; + string public constant INVALID_RECIPIENT = "5"; + string public constant INVALID_CLAIMER = "6"; + string public constant ONLY_ONE_AMOUNT_FORMAT_ALLOWED = "7"; + string public constant INVALID_ZERO_AMOUNT = "8"; + string public constant REWARD_NOT_INITIALIZED = "9"; +} diff --git a/contracts/plugins/assets/aave-v3/vendor/StaticATokenV3LM.sol b/contracts/plugins/assets/aave-v3/vendor/StaticATokenV3LM.sol new file mode 100644 index 0000000000..0b0654cac6 --- /dev/null +++ b/contracts/plugins/assets/aave-v3/vendor/StaticATokenV3LM.sol @@ -0,0 +1,753 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +/* solhint-disable */ + +import { IPool } from "@aave/core-v3/contracts/interfaces/IPool.sol"; +import { DataTypes, ReserveConfiguration } from "@aave/core-v3/contracts/protocol/libraries/configuration/ReserveConfiguration.sol"; +import { IScaledBalanceToken } from "@aave/core-v3/contracts/interfaces/IScaledBalanceToken.sol"; +import { IRewardsController } from "@aave/periphery-v3/contracts/rewards/interfaces/IRewardsController.sol"; +import { WadRayMath } from "@aave/core-v3/contracts/protocol/libraries/math/WadRayMath.sol"; +import { MathUtils } from "@aave/core-v3/contracts/protocol/libraries/math/MathUtils.sol"; + +import { IStaticATokenV3LM } from "./interfaces/IStaticATokenV3LM.sol"; +import { IAToken } from "./interfaces/IAToken.sol"; +import { IInitializableStaticATokenLM } from "./interfaces/IInitializableStaticATokenLM.sol"; +import { StaticATokenErrors } from "./StaticATokenErrors.sol"; +import { RayMathExplicitRounding, Rounding } from "./RayMathExplicitRounding.sol"; + +import { IERC4626 } from "./interfaces/IERC4626.sol"; +import { ERC20 } from "./ERC20.sol"; + +import { IERC20Metadata, IERC20 } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import { IRewardable } from "../../../../interfaces/IRewardable.sol"; + +/** + * @title StaticATokenLM + * @notice Wrapper smart contract that allows to deposit tokens on the Aave protocol and receive + * a token which balance doesn't increase automatically, but uses an ever-increasing exchange rate. + * It supports claiming liquidity mining rewards from the Aave system. + * @author BGD Labs + * From https://github.com/bgd-labs/static-a-token-v3/blob/b9f6f86b6d89c7407eeb0013af248d3c5f4d09c8/src/StaticATokenLM.sol + * Original source was formally verified + * https://github.com/bgd-labs/static-a-token-v3/blob/b9f6f86b6d89c7407eeb0013af248d3c5f4d09c8/audits/Formal_Verification_Report_staticAToken.pdf + * @dev This contract has been further modified by Reserve to include the claimRewards() function. This is the only change. + */ +contract StaticATokenV3LM is + Initializable, + ERC20("STATIC__aToken_IMPL", "STATIC__aToken_IMPL", 18), + IStaticATokenV3LM, + IERC4626, + IRewardable +{ + using SafeERC20 for IERC20; + using SafeCast for uint256; + using WadRayMath for uint256; + using RayMathExplicitRounding for uint256; + + bytes32 public constant METADEPOSIT_TYPEHASH = + keccak256( + "Deposit(address depositor,address receiver,uint256 assets,uint16 referralCode,bool depositToAave,uint256 nonce,uint256 deadline,PermitParams permit)" + ); + bytes32 public constant METAWITHDRAWAL_TYPEHASH = + keccak256( + "Withdraw(address owner,address receiver,uint256 shares,uint256 assets,bool withdrawFromAave,uint256 nonce,uint256 deadline)" + ); + + uint256 public constant STATIC__ATOKEN_LM_REVISION = 2; + + IPool public immutable POOL; + IRewardsController public immutable INCENTIVES_CONTROLLER; + + IERC20 internal _aToken; + address internal _aTokenUnderlying; + address[] internal _rewardTokens; + mapping(address => RewardIndexCache) internal _startIndex; + mapping(address => mapping(address => UserRewardsData)) internal _userRewardsData; + + constructor(IPool pool, IRewardsController rewardsController) { + POOL = pool; + INCENTIVES_CONTROLLER = rewardsController; + } + + ///@inheritdoc IInitializableStaticATokenLM + function initialize( + address newAToken, + string calldata staticATokenName, + string calldata staticATokenSymbol + ) external initializer { + require(IAToken(newAToken).POOL() == address(POOL)); + _aToken = IERC20(newAToken); + + name = staticATokenName; + symbol = staticATokenSymbol; + decimals = IERC20Metadata(newAToken).decimals(); + + _aTokenUnderlying = IAToken(newAToken).UNDERLYING_ASSET_ADDRESS(); + IERC20(_aTokenUnderlying).safeApprove(address(POOL), type(uint256).max); + + if (INCENTIVES_CONTROLLER != IRewardsController(address(0))) { + refreshRewardTokens(); + } + + emit InitializedStaticATokenLM(newAToken, staticATokenName, staticATokenSymbol); + } + + ///@inheritdoc IStaticATokenV3LM + function refreshRewardTokens() public override { + address[] memory rewards = INCENTIVES_CONTROLLER.getRewardsByAsset(address(_aToken)); + for (uint256 i = 0; i < rewards.length; i++) { + _registerRewardToken(rewards[i]); + } + } + + ///@inheritdoc IStaticATokenV3LM + function isRegisteredRewardToken(address reward) public view override returns (bool) { + return _startIndex[reward].isRegistered; + } + + ///@inheritdoc IStaticATokenV3LM + function deposit( + uint256 assets, + address receiver, + uint16 referralCode, + bool depositToAave + ) external returns (uint256) { + (uint256 shares, ) = _deposit(msg.sender, receiver, 0, assets, referralCode, depositToAave); + return shares; + } + + ///@inheritdoc IStaticATokenV3LM + function metaDeposit( + address depositor, + address receiver, + uint256 assets, + uint16 referralCode, + bool depositToAave, + uint256 deadline, + PermitParams calldata permit, + SignatureParams calldata sigParams + ) external returns (uint256) { + require(depositor != address(0), StaticATokenErrors.INVALID_DEPOSITOR); + //solium-disable-next-line + require(deadline >= block.timestamp, StaticATokenErrors.INVALID_EXPIRATION); + uint256 nonce = nonces[depositor]; + + // Unchecked because the only math done is incrementing + // the owner's nonce which cannot realistically overflow. + unchecked { + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR(), + keccak256( + abi.encode( + METADEPOSIT_TYPEHASH, + depositor, + receiver, + assets, + referralCode, + depositToAave, + nonce, + deadline, + permit + ) + ) + ) + ); + nonces[depositor] = nonce + 1; + require( + depositor == ecrecover(digest, sigParams.v, sigParams.r, sigParams.s), + StaticATokenErrors.INVALID_SIGNATURE + ); + } + // assume if deadline 0 no permit was supplied + if (permit.deadline != 0) { + IERC20Permit(depositToAave ? address(_aTokenUnderlying) : address(_aToken)).permit( + depositor, + address(this), + permit.value, + permit.deadline, + permit.v, + permit.r, + permit.s + ); + } + (uint256 shares, ) = _deposit(depositor, receiver, 0, assets, referralCode, depositToAave); + return shares; + } + + ///@inheritdoc IStaticATokenV3LM + function metaWithdraw( + address owner, + address receiver, + uint256 shares, + uint256 assets, + bool withdrawFromAave, + uint256 deadline, + SignatureParams calldata sigParams + ) external returns (uint256, uint256) { + require(owner != address(0), StaticATokenErrors.INVALID_OWNER); + //solium-disable-next-line + require(deadline >= block.timestamp, StaticATokenErrors.INVALID_EXPIRATION); + uint256 nonce = nonces[owner]; + // Unchecked because the only math done is incrementing + // the owner's nonce which cannot realistically overflow. + unchecked { + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR(), + keccak256( + abi.encode( + METAWITHDRAWAL_TYPEHASH, + owner, + receiver, + shares, + assets, + withdrawFromAave, + nonce, + deadline + ) + ) + ) + ); + nonces[owner] = nonce + 1; + require( + owner == ecrecover(digest, sigParams.v, sigParams.r, sigParams.s), + StaticATokenErrors.INVALID_SIGNATURE + ); + } + return _withdraw(owner, receiver, shares, assets, withdrawFromAave); + } + + ///@inheritdoc IERC4626 + function previewRedeem(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Rounding.DOWN); + } + + ///@inheritdoc IERC4626 + function previewMint(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Rounding.UP); + } + + ///@inheritdoc IERC4626 + function previewWithdraw(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Rounding.UP); + } + + ///@inheritdoc IERC4626 + function previewDeposit(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Rounding.DOWN); + } + + ///@inheritdoc IStaticATokenV3LM + function rate() public view virtual returns (uint256) { + return POOL.getReserveNormalizedIncome(_aTokenUnderlying); + } + + ///@inheritdoc IStaticATokenV3LM + function collectAndUpdateRewards(address reward) public returns (uint256) { + if (reward == address(0)) { + return 0; + } + + address[] memory assets = new address[](1); + assets[0] = address(_aToken); + + return INCENTIVES_CONTROLLER.claimRewards(assets, type(uint256).max, address(this), reward); + } + + ///@inheritdoc IStaticATokenV3LM + function claimRewardsOnBehalf( + address onBehalfOf, + address receiver, + address[] memory rewards + ) external { + require( + msg.sender == onBehalfOf || msg.sender == INCENTIVES_CONTROLLER.getClaimer(onBehalfOf), + StaticATokenErrors.INVALID_CLAIMER + ); + _claimRewardsOnBehalf(onBehalfOf, receiver, rewards); + } + + ///@inheritdoc IStaticATokenV3LM + function claimRewards(address receiver, address[] memory rewards) external { + _claimRewardsOnBehalf(msg.sender, receiver, rewards); + } + + /// @dev Added by Reserve + function claimRewards() external { + address[] memory rewardsList = INCENTIVES_CONTROLLER.getRewardsList(); + + for (uint256 i = 0; i < rewardsList.length; i++) { + address currentReward = rewardsList[i]; + + uint256 prevBalance = IERC20(currentReward).balanceOf(msg.sender); + + address[] memory rewardsToCollect = new address[](1); + rewardsToCollect[0] = currentReward; + _claimRewardsOnBehalf(msg.sender, msg.sender, rewardsToCollect); + + emit RewardsClaimed( + IERC20(currentReward), + IERC20(currentReward).balanceOf(msg.sender) - prevBalance + ); + } + } + + ///@inheritdoc IStaticATokenV3LM + function claimRewardsToSelf(address[] memory rewards) external { + _claimRewardsOnBehalf(msg.sender, msg.sender, rewards); + } + + ///@inheritdoc IStaticATokenV3LM + function getCurrentRewardsIndex(address reward) public view returns (uint256) { + if (address(reward) == address(0)) { + return 0; + } + (, uint256 nextIndex) = INCENTIVES_CONTROLLER.getAssetIndex(address(_aToken), reward); + return nextIndex; + } + + ///@inheritdoc IStaticATokenV3LM + function getTotalClaimableRewards(address reward) external view returns (uint256) { + if (reward == address(0)) { + return 0; + } + + address[] memory assets = new address[](1); + assets[0] = address(_aToken); + uint256 freshRewards = INCENTIVES_CONTROLLER.getUserRewards(assets, address(this), reward); + return IERC20(reward).balanceOf(address(this)) + freshRewards; + } + + ///@inheritdoc IStaticATokenV3LM + function getClaimableRewards(address user, address reward) external view returns (uint256) { + return _getClaimableRewards(user, reward, balanceOf[user], getCurrentRewardsIndex(reward)); + } + + ///@inheritdoc IStaticATokenV3LM + function getUnclaimedRewards(address user, address reward) external view returns (uint256) { + return _userRewardsData[user][reward].unclaimedRewards; + } + + ///@inheritdoc IERC4626 + function asset() external view returns (address) { + return address(_aTokenUnderlying); + } + + ///@inheritdoc IStaticATokenV3LM + function aToken() external view returns (IERC20) { + return _aToken; + } + + ///@inheritdoc IStaticATokenV3LM + function rewardTokens() external view returns (address[] memory) { + return _rewardTokens; + } + + ///@inheritdoc IERC4626 + function totalAssets() external view returns (uint256) { + return _aToken.balanceOf(address(this)); + } + + ///@inheritdoc IERC4626 + function convertToShares(uint256 assets) external view returns (uint256) { + return _convertToShares(assets, Rounding.DOWN); + } + + ///@inheritdoc IERC4626 + function convertToAssets(uint256 shares) external view returns (uint256) { + return _convertToAssets(shares, Rounding.DOWN); + } + + ///@inheritdoc IERC4626 + function maxMint(address) public view virtual returns (uint256) { + uint256 assets = maxDeposit(address(0)); + return _convertToShares(assets, Rounding.DOWN); + } + + ///@inheritdoc IERC4626 + function maxWithdraw(address owner) public view virtual returns (uint256) { + uint256 shares = maxRedeem(owner); + return _convertToAssets(shares, Rounding.DOWN); + } + + ///@inheritdoc IERC4626 + function maxRedeem(address owner) public view virtual returns (uint256) { + address cachedATokenUnderlying = _aTokenUnderlying; + DataTypes.ReserveData memory reserveData = POOL.getReserveData(cachedATokenUnderlying); + + // if paused or inactive users cannot withdraw underlying + if ( + !ReserveConfiguration.getActive(reserveData.configuration) || + ReserveConfiguration.getPaused(reserveData.configuration) + ) { + return 0; + } + + // otherwise users can withdraw up to the available amount + uint256 underlyingTokenBalanceInShares = _convertToShares( + IERC20(cachedATokenUnderlying).balanceOf(reserveData.aTokenAddress), + Rounding.DOWN + ); + uint256 cachedUserBalance = balanceOf[owner]; + return + underlyingTokenBalanceInShares >= cachedUserBalance + ? cachedUserBalance + : underlyingTokenBalanceInShares; + } + + ///@inheritdoc IERC4626 + function maxDeposit(address) public view virtual returns (uint256) { + DataTypes.ReserveData memory reserveData = POOL.getReserveData(_aTokenUnderlying); + + // if inactive, paused or frozen users cannot deposit underlying + if ( + !ReserveConfiguration.getActive(reserveData.configuration) || + ReserveConfiguration.getPaused(reserveData.configuration) || + ReserveConfiguration.getFrozen(reserveData.configuration) + ) { + return 0; + } + + uint256 supplyCap = ReserveConfiguration.getSupplyCap(reserveData.configuration) * + (10**ReserveConfiguration.getDecimals(reserveData.configuration)); + // if no supply cap deposit is unlimited + if (supplyCap == 0) return type(uint256).max; + // return remaining supply cap margin + uint256 currentSupply = (IAToken(reserveData.aTokenAddress).scaledTotalSupply() + + reserveData.accruedToTreasury) + .rayMulRoundUp(_getNormalizedIncome(reserveData)); + return currentSupply > supplyCap ? 0 : supplyCap - currentSupply; + } + + ///@inheritdoc IERC4626 + function deposit(uint256 assets, address receiver) external virtual returns (uint256) { + (uint256 shares, ) = _deposit(msg.sender, receiver, 0, assets, 0, true); + return shares; + } + + ///@inheritdoc IERC4626 + function mint(uint256 shares, address receiver) external virtual returns (uint256) { + (, uint256 assets) = _deposit(msg.sender, receiver, shares, 0, 0, true); + + return assets; + } + + ///@inheritdoc IERC4626 + function withdraw( + uint256 assets, + address receiver, + address owner + ) external virtual returns (uint256) { + (uint256 shares, ) = _withdraw(owner, receiver, 0, assets, true); + + return shares; + } + + ///@inheritdoc IERC4626 + function redeem( + uint256 shares, + address receiver, + address owner + ) external virtual returns (uint256) { + (, uint256 assets) = _withdraw(owner, receiver, shares, 0, true); + + return assets; + } + + ///@inheritdoc IStaticATokenV3LM + function redeem( + uint256 shares, + address receiver, + address owner, + bool withdrawFromAave + ) external virtual returns (uint256, uint256) { + return _withdraw(owner, receiver, shares, 0, withdrawFromAave); + } + + function _deposit( + address depositor, + address receiver, + uint256 _shares, + uint256 _assets, + uint16 referralCode, + bool depositToAave + ) internal returns (uint256, uint256) { + require(receiver != address(0), StaticATokenErrors.INVALID_RECIPIENT); + require(_shares == 0 || _assets == 0, StaticATokenErrors.ONLY_ONE_AMOUNT_FORMAT_ALLOWED); + + uint256 assets = _assets; + uint256 shares = _shares; + if (shares > 0) { + if (depositToAave) { + require(shares <= maxMint(receiver), "ERC4626: mint more than max"); + } + assets = previewMint(shares); + } else { + if (depositToAave) { + require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max"); + } + shares = previewDeposit(assets); + } + require(shares != 0, StaticATokenErrors.INVALID_ZERO_AMOUNT); + + if (depositToAave) { + address cachedATokenUnderlying = _aTokenUnderlying; + IERC20(cachedATokenUnderlying).safeTransferFrom(depositor, address(this), assets); + POOL.deposit(cachedATokenUnderlying, assets, address(this), referralCode); + } else { + _aToken.safeTransferFrom(depositor, address(this), assets); + } + + _mint(receiver, shares); + + emit Deposit(depositor, receiver, assets, shares); + + return (shares, assets); + } + + function _withdraw( + address owner, + address receiver, + uint256 _shares, + uint256 _assets, + bool withdrawFromAave + ) internal returns (uint256, uint256) { + require(receiver != address(0), StaticATokenErrors.INVALID_RECIPIENT); + require(_shares == 0 || _assets == 0, StaticATokenErrors.ONLY_ONE_AMOUNT_FORMAT_ALLOWED); + require(_shares != _assets, StaticATokenErrors.INVALID_ZERO_AMOUNT); + + uint256 assets = _assets; + uint256 shares = _shares; + + if (shares > 0) { + if (withdrawFromAave) { + require(shares <= maxRedeem(owner), "ERC4626: redeem more than max"); + } + assets = previewRedeem(shares); + } else { + if (withdrawFromAave) { + require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max"); + } + shares = previewWithdraw(assets); + } + + if (msg.sender != owner) { + uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares; + } + + _burn(owner, shares); + + emit Withdraw(msg.sender, receiver, owner, assets, shares); + + if (withdrawFromAave) { + POOL.withdraw(_aTokenUnderlying, assets, receiver); + } else { + _aToken.safeTransfer(receiver, assets); + } + + return (shares, assets); + } + + /** + * @notice Updates rewards for senders and receiver in a transfer (not updating rewards for address(0)) + * @param from The address of the sender of tokens + * @param to The address of the receiver of tokens + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 + ) internal override { + for (uint256 i = 0; i < _rewardTokens.length; i++) { + address rewardToken = address(_rewardTokens[i]); + uint256 rewardsIndex = getCurrentRewardsIndex(rewardToken); + if (from != address(0)) { + _updateUser(from, rewardsIndex, rewardToken); + } + if (to != address(0) && from != to) { + _updateUser(to, rewardsIndex, rewardToken); + } + } + } + + /** + * @notice Adding the pending rewards to the unclaimed for specific user and updating user index + * @param user The address of the user to update + * @param currentRewardsIndex The current rewardIndex + * @param rewardToken The address of the reward token + */ + function _updateUser( + address user, + uint256 currentRewardsIndex, + address rewardToken + ) internal { + uint256 balance = balanceOf[user]; + if (balance > 0) { + _userRewardsData[user][rewardToken].unclaimedRewards = _getClaimableRewards( + user, + rewardToken, + balance, + currentRewardsIndex + ).toUint128(); + } + _userRewardsData[user][rewardToken].rewardsIndexOnLastInteraction = currentRewardsIndex + .toUint128(); + } + + /** + * @notice Compute the pending in WAD. Pending is the amount to add (not yet unclaimed) rewards in WAD. + * @param balance The balance of the user + * @param rewardsIndexOnLastInteraction The index which was on the last interaction of the user + * @param currentRewardsIndex The current rewards index in the system + * @param assetUnit One unit of asset (10**decimals) + * @return The amount of pending rewards in WAD + */ + function _getPendingRewards( + uint256 balance, + uint256 rewardsIndexOnLastInteraction, + uint256 currentRewardsIndex, + uint256 assetUnit + ) internal pure returns (uint256) { + if (balance == 0) { + return 0; + } + return (balance * (currentRewardsIndex - rewardsIndexOnLastInteraction)) / assetUnit; + } + + /** + * @notice Compute the claimable rewards for a user + * @param user The address of the user + * @param reward The address of the reward + * @param balance The balance of the user in WAD + * @param currentRewardsIndex The current rewards index + * @return The total rewards that can be claimed by the user (if `fresh` flag true, after updating rewards) + */ + function _getClaimableRewards( + address user, + address reward, + uint256 balance, + uint256 currentRewardsIndex + ) internal view returns (uint256) { + RewardIndexCache memory rewardsIndexCache = _startIndex[reward]; + require(rewardsIndexCache.isRegistered == true, StaticATokenErrors.REWARD_NOT_INITIALIZED); + UserRewardsData memory currentUserRewardsData = _userRewardsData[user][reward]; + uint256 assetUnit = 10**decimals; + return + currentUserRewardsData.unclaimedRewards + + _getPendingRewards( + balance, + currentUserRewardsData.rewardsIndexOnLastInteraction == 0 + ? rewardsIndexCache.lastUpdatedIndex + : currentUserRewardsData.rewardsIndexOnLastInteraction, + currentRewardsIndex, + assetUnit + ); + } + + /** + * @notice Claim rewards on behalf of a user and send them to a receiver + * @param onBehalfOf The address to claim on behalf of + * @param rewards The addresses of the rewards + * @param receiver The address to receive the rewards + */ + function _claimRewardsOnBehalf( + address onBehalfOf, + address receiver, + address[] memory rewards + ) internal { + for (uint256 i = 0; i < rewards.length; i++) { + if (address(rewards[i]) == address(0)) { + continue; + } + uint256 currentRewardsIndex = getCurrentRewardsIndex(rewards[i]); + uint256 balance = balanceOf[onBehalfOf]; + uint256 userReward = _getClaimableRewards( + onBehalfOf, + rewards[i], + balance, + currentRewardsIndex + ); + uint256 totalRewardTokenBalance = IERC20(rewards[i]).balanceOf(address(this)); + uint256 unclaimedReward = 0; + + if (userReward > totalRewardTokenBalance) { + totalRewardTokenBalance += collectAndUpdateRewards(address(rewards[i])); + } + + if (userReward > totalRewardTokenBalance) { + unclaimedReward = userReward - totalRewardTokenBalance; + userReward = totalRewardTokenBalance; + } + if (userReward > 0) { + _userRewardsData[onBehalfOf][rewards[i]].unclaimedRewards = unclaimedReward + .toUint128(); + _userRewardsData[onBehalfOf][rewards[i]] + .rewardsIndexOnLastInteraction = currentRewardsIndex.toUint128(); + IERC20(rewards[i]).safeTransfer(receiver, userReward); + } + } + } + + function _convertToShares(uint256 assets, Rounding rounding) internal view returns (uint256) { + if (rounding == Rounding.UP) return assets.rayDivRoundUp(rate()); + return assets.rayDivRoundDown(rate()); + } + + function _convertToAssets(uint256 shares, Rounding rounding) internal view returns (uint256) { + if (rounding == Rounding.UP) return shares.rayMulRoundUp(rate()); + return shares.rayMulRoundDown(rate()); + } + + /** + * @notice Initializes a new rewardToken + * @param reward The reward token to be registered + */ + function _registerRewardToken(address reward) internal { + if (isRegisteredRewardToken(reward)) return; + uint256 startIndex = getCurrentRewardsIndex(reward); + + _rewardTokens.push(reward); + _startIndex[reward] = RewardIndexCache(true, startIndex.toUint240()); + + emit RewardTokenRegistered(reward, startIndex); + } + + /** + * @notice Returns the ongoing normalized income for the reserve. + * @dev A value of 1e27 means there is no income. As time passes, the income is accrued + * @dev A value of 2*1e27 means for each unit of asset one unit of income has been accrued + * @param reserve The reserve object + * @return The normalized income, expressed in ray + */ + function _getNormalizedIncome(DataTypes.ReserveData memory reserve) + internal + view + returns (uint256) + { + uint40 timestamp = reserve.lastUpdateTimestamp; + + //solium-disable-next-line + if (timestamp == block.timestamp) { + //if the index was updated in the same block, no need to perform any calculation + return reserve.liquidityIndex; + } else { + return + MathUtils.calculateLinearInterest(reserve.currentLiquidityRate, timestamp).rayMul( + reserve.liquidityIndex + ); + } + } +} + +/* solhint-enable */ diff --git a/contracts/plugins/assets/aave-v3/vendor/interfaces/IAToken.sol b/contracts/plugins/assets/aave-v3/vendor/interfaces/IAToken.sol new file mode 100644 index 0000000000..76943cd02e --- /dev/null +++ b/contracts/plugins/assets/aave-v3/vendor/interfaces/IAToken.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.10; + +/* solhint-disable */ + +import { IAaveIncentivesController } from "@aave/core-v3/contracts/interfaces/IAaveIncentivesController.sol"; + +interface IAToken { + function POOL() external view returns (address); + + function getIncentivesController() external view returns (address); + + function UNDERLYING_ASSET_ADDRESS() external view returns (address); + + /** + * @notice Returns the scaled total supply of the scaled balance token. Represents sum(debt/index) + * @return The scaled total supply + */ + function scaledTotalSupply() external view returns (uint256); +} + +/* solhint-enable */ diff --git a/contracts/plugins/assets/aave-v3/vendor/interfaces/IERC4626.sol b/contracts/plugins/assets/aave-v3/vendor/interfaces/IERC4626.sol new file mode 100644 index 0000000000..0444cac97b --- /dev/null +++ b/contracts/plugins/assets/aave-v3/vendor/interfaces/IERC4626.sol @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (interfaces/IERC4626.sol) + +pragma solidity ^0.8.10; + +/* solhint-disable max-line-length */ + +/** + * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in + * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. + * + * _Available since v4.7._ + */ +interface IERC4626 { + event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + + event Withdraw( + address indexed sender, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares + ); + + /** + * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + * + * - MUST be an ERC-20 token contract. + * - MUST NOT revert. + */ + function asset() external view returns (address assetTokenAddress); + + /** + * @dev Returns the total amount of the underlying asset that is “managed” by Vault. + * + * - SHOULD include any compounding that occurs from yield. + * - MUST be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT revert. + */ + function totalAssets() external view returns (uint256 totalManagedAssets); + + /** + * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, + * through a deposit call. + * While deposit of aToken is not affected by aave pool configrations, deposit of the aTokenUnderlying will need to deposit to aave + * so it is affected by current aave pool configuration. + * Reference: https://github.com/aave/aave-v3-core/blob/29ff9b9f89af7cd8255231bc5faf26c3ce0fb7ce/contracts/protocol/libraries/logic/ValidationLogic.sol#L57 + * - MUST return a limited value if receiver is subject to some deposit limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. + * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. + */ + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + * in the same transaction. + * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + * deposit would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * deposit execution, and are accounted for during deposit. + * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + * - MUST return a limited value if receiver is subject to some mint limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. + * - MUST NOT revert. + */ + function maxMint(address receiver) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + * same transaction. + * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + * would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by minting. + */ + function previewMint(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + * execution, and are accounted for during mint. + * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the + * Vault, through a withdraw call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + * called + * in the same transaction. + * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + * the withdrawal would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * withdraw execution, and are accounted for during withdraw. + * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function withdraw( + uint256 assets, + address receiver, + address owner + ) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, + * through a redeem call to the aToken underlying. + * While redeem of aToken is not affected by aave pool configrations, redeeming of the aTokenUnderlying will need to redeem from aave + * so it is affected by current aave pool configuration. + * Reference: https://github.com/aave/aave-v3-core/blob/29ff9b9f89af7cd8255231bc5faf26c3ce0fb7ce/contracts/protocol/libraries/logic/ValidationLogic.sol#L87 + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxRedeem(address owner) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + * same transaction. + * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + * redemption would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by redeeming. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * redeem execution, and are accounted for during redeem. + * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function redeem( + uint256 shares, + address receiver, + address owner + ) external returns (uint256 assets); +} + +/* solhint-enable max-line-length */ diff --git a/contracts/plugins/assets/aave-v3/vendor/interfaces/IInitializableStaticATokenLM.sol b/contracts/plugins/assets/aave-v3/vendor/interfaces/IInitializableStaticATokenLM.sol new file mode 100644 index 0000000000..7e1eb865ea --- /dev/null +++ b/contracts/plugins/assets/aave-v3/vendor/interfaces/IInitializableStaticATokenLM.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.10; + +/* solhint-disable max-line-length */ + +import { IPool } from "@aave/core-v3/contracts/interfaces/IPool.sol"; +import { IAaveIncentivesController } from "@aave/core-v3/contracts/interfaces/IAaveIncentivesController.sol"; + +/** + * @title IInitializableStaticATokenLM + * @notice Interface for the initialize function on StaticATokenLM + * @author Aave + **/ +interface IInitializableStaticATokenLM { + /** + * @dev Emitted when a StaticATokenLM is initialized + * @param aToken The address of the underlying aToken (aWETH) + * @param staticATokenName The name of the Static aToken + * @param staticATokenSymbol The symbol of the Static aToken + * @dev Used to be `Initialized` but changed to avoid duplicate events + **/ + event InitializedStaticATokenLM( + address indexed aToken, + string staticATokenName, + string staticATokenSymbol + ); + + /** + * @dev Initializes the StaticATokenLM + * @param aToken The address of the underlying aToken (aWETH) + * @param staticATokenName The name of the Static aToken + * @param staticATokenSymbol The symbol of the Static aToken + */ + function initialize( + address aToken, + string calldata staticATokenName, + string calldata staticATokenSymbol + ) external; +} + +/* solhint-enable max-line-length */ diff --git a/contracts/plugins/assets/aave-v3/vendor/interfaces/IStaticATokenV3LM.sol b/contracts/plugins/assets/aave-v3/vendor/interfaces/IStaticATokenV3LM.sol new file mode 100644 index 0000000000..074affbeff --- /dev/null +++ b/contracts/plugins/assets/aave-v3/vendor/interfaces/IStaticATokenV3LM.sol @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.10; + +/* solhint-disable max-line-length */ + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IPool } from "@aave/core-v3/contracts/interfaces/IPool.sol"; +import { IAaveIncentivesController } from "@aave/core-v3/contracts/interfaces/IAaveIncentivesController.sol"; +import { IInitializableStaticATokenLM } from "./IInitializableStaticATokenLM.sol"; + +interface IStaticATokenV3LM is IInitializableStaticATokenLM { + struct SignatureParams { + uint8 v; + bytes32 r; + bytes32 s; + } + + struct PermitParams { + address owner; + address spender; + uint256 value; + uint256 deadline; + uint8 v; + bytes32 r; + bytes32 s; + } + + struct UserRewardsData { + uint128 rewardsIndexOnLastInteraction; // (in RAYs) + uint128 unclaimedRewards; // (in RAYs) + } + + struct RewardIndexCache { + bool isRegistered; + uint248 lastUpdatedIndex; + } + + event RewardTokenRegistered(address indexed reward, uint256 startIndex); + + /** + * @notice Burns `amount` of static aToken, with receiver receiving the corresponding amount of `ASSET` + * @param shares The amount to withdraw, in static balance of StaticAToken + * @param receiver The address that will receive the amount of `ASSET` withdrawn from the Aave protocol + * @param withdrawFromAave bool + * - `true` for the receiver to get underlying tokens (e.g. USDC) + * - `false` for the receiver to get aTokens (e.g. aUSDC) + * @return amountToBurn: StaticATokens burnt, static balance + * @return amountToWithdraw: underlying/aToken send to `receiver`, dynamic balance + **/ + function redeem( + uint256 shares, + address receiver, + address owner, + bool withdrawFromAave + ) external returns (uint256, uint256); + + /** + * @notice Deposits `ASSET` in the Aave protocol and mints static aTokens to msg.sender + * @param assets The amount of underlying `ASSET` to deposit (e.g. deposit of 100 USDC) + * @param receiver The address that will receive the static aTokens + * @param referralCode Code used to register the integrator originating the operation, for potential rewards. + * 0 if the action is executed directly by the user, without any middle-man + * @param depositToAave bool + * - `true` if the msg.sender comes with underlying tokens (e.g. USDC) + * - `false` if the msg.sender comes already with aTokens (e.g. aUSDC) + * @return uint256 The amount of StaticAToken minted, static balance + **/ + function deposit( + uint256 assets, + address receiver, + uint16 referralCode, + bool depositToAave + ) external returns (uint256); + + /** + * @notice Allows to deposit on Aave via meta-transaction + * https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md + * @param depositor Address from which the funds to deposit are going to be pulled + * @param receiver Address that will receive the staticATokens, in the average case, same as the `depositor` + * @param assets The amount to deposit + * @param referralCode Code used to register the integrator originating the operation, for potential rewards. + * 0 if the action is executed directly by the user, without any middle-man + * @param depositToAave bool + * - `true` if the msg.sender comes with underlying tokens (e.g. USDC) + * - `false` if the msg.sender comes already with aTokens (e.g. aUSDC) + * @param deadline The deadline timestamp, type(uint256).max for max deadline + * @param sigParams Signature params: v,r,s + * @return uint256 The amount of StaticAToken minted, static balance + */ + function metaDeposit( + address depositor, + address receiver, + uint256 assets, + uint16 referralCode, + bool depositToAave, + uint256 deadline, + PermitParams calldata permit, + SignatureParams calldata sigParams + ) external returns (uint256); + + /** + * @notice Allows to withdraw from Aave via meta-transaction + * https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md + * @param owner Address owning the staticATokens + * @param receiver Address that will receive the underlying withdrawn from Aave + * @param shares The amount of staticAToken to withdraw. If > 0, `assets` needs to be 0 + * @param assets The amount of underlying/aToken to withdraw. If > 0, `shares` needs to be 0 + * @param withdrawFromAave bool + * - `true` for the receiver to get underlying tokens (e.g. USDC) + * - `false` for the receiver to get aTokens (e.g. aUSDC) + * @param deadline The deadline timestamp, type(uint256).max for max deadline + * @param sigParams Signature params: v,r,s + * @return amountToBurn: StaticATokens burnt, static balance + * @return amountToWithdraw: underlying/aToken send to `receiver`, dynamic balance + */ + function metaWithdraw( + address owner, + address receiver, + uint256 shares, + uint256 assets, + bool withdrawFromAave, + uint256 deadline, + SignatureParams calldata sigParams + ) external returns (uint256, uint256); + + /** + * @notice Returns the Aave liquidity index of the underlying aToken, denominated rate here + * as it can be considered as an ever-increasing exchange rate + * @return The liquidity index + **/ + function rate() external view returns (uint256); + + /** + * @notice Claims rewards from `INCENTIVES_CONTROLLER` and updates internal accounting of rewards. + * @param reward The reward to claim + * @return uint256 Amount collected + */ + function collectAndUpdateRewards(address reward) external returns (uint256); + + /** + * @notice Claim rewards on behalf of a user and send them to a receiver + * @dev Only callable by if sender is onBehalfOf or sender is approved claimer + * @param onBehalfOf The address to claim on behalf of + * @param receiver The address to receive the rewards + * @param rewards The rewards to claim + */ + function claimRewardsOnBehalf( + address onBehalfOf, + address receiver, + address[] memory rewards + ) external; + + /** + * @notice Claim rewards and send them to a receiver + * @param receiver The address to receive the rewards + * @param rewards The rewards to claim + */ + function claimRewards(address receiver, address[] memory rewards) external; + + /** + * @notice Claim rewards + * @param rewards The rewards to claim + */ + function claimRewardsToSelf(address[] memory rewards) external; + + /** + * @notice Get the total claimable rewards of the contract. + * @param reward The reward to claim + * @return uint256 The current balance + pending rewards from the `_incentivesController` + */ + function getTotalClaimableRewards(address reward) external view returns (uint256); + + /** + * @notice Get the total claimable rewards for a user in WAD + * @param user The address of the user + * @param reward The reward to claim + * @return uint256 The claimable amount of rewards in WAD + */ + function getClaimableRewards(address user, address reward) external view returns (uint256); + + /** + * @notice The unclaimed rewards for a user in WAD + * @param user The address of the user + * @param reward The reward to claim + * @return uint256 The unclaimed amount of rewards in WAD + */ + function getUnclaimedRewards(address user, address reward) external view returns (uint256); + + /** + * @notice The underlying asset reward index in RAY + * @param reward The reward to claim + * @return uint256 The underlying asset reward index in RAY + */ + function getCurrentRewardsIndex(address reward) external view returns (uint256); + + /** + * @notice The aToken used inside the 4626 vault. + * @return IERC20 The aToken IERC20. + */ + function aToken() external view returns (IERC20); + + /** + * @notice The IERC20s that are currently rewarded to addresses of the vault via LM on incentivescontroller. + * @return IERC20 The IERC20s of the rewards. + */ + function rewardTokens() external view returns (address[] memory); + + /** + * @notice Fetches all rewardTokens from the incentivecontroller and registers the missing ones. + */ + function refreshRewardTokens() external; + + /** + * @notice Checks if the passed token is a registered reward. + * @return bool signaling if token is a registered reward. + */ + function isRegisteredRewardToken(address reward) external view returns (bool); +} + +/* solhint-enable max-line-length */ diff --git a/contracts/plugins/assets/cbeth/CBETHCollateral.sol b/contracts/plugins/assets/cbeth/CBETHCollateral.sol index f9aec23f54..5c190e6050 100644 --- a/contracts/plugins/assets/cbeth/CBETHCollateral.sol +++ b/contracts/plugins/assets/cbeth/CBETHCollateral.sol @@ -5,16 +5,7 @@ import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/I import { CEIL, FixLib, _safeWrap } from "../../../libraries/Fixed.sol"; import { AggregatorV3Interface, OracleLib } from "../OracleLib.sol"; import { CollateralConfig, AppreciatingFiatCollateral } from "../AppreciatingFiatCollateral.sol"; - -interface CBEth is IERC20Metadata { - function mint(address account, uint256 amount) external returns (bool); - - function updateExchangeRate(uint256 exchangeRate) external; - - function configureMinter(address minter, uint256 minterAllowedAmount) external returns (bool); - - function exchangeRate() external view returns (uint256 _exchangeRate); -} +import { ICBEth } from "./vendor/ICBEth.sol"; /** * @title CBEthCollateral @@ -76,6 +67,6 @@ contract CBEthCollateral is AppreciatingFiatCollateral { /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens function _underlyingRefPerTok() internal view override returns (uint192) { - return _safeWrap(CBEth(address(erc20)).exchangeRate()); + return _safeWrap(ICBEth(address(erc20)).exchangeRate()); } } diff --git a/contracts/plugins/assets/cbeth/CBETHCollateralL2.sol b/contracts/plugins/assets/cbeth/CBETHCollateralL2.sol new file mode 100644 index 0000000000..b745028f54 --- /dev/null +++ b/contracts/plugins/assets/cbeth/CBETHCollateralL2.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { CEIL, FixLib, _safeWrap } from "../../../libraries/Fixed.sol"; +import { AggregatorV3Interface, OracleLib } from "../OracleLib.sol"; +import { CollateralConfig, AppreciatingFiatCollateral } from "../AppreciatingFiatCollateral.sol"; +import { L2LSDCollateral } from "../L2LSDCollateral.sol"; + +/** + * @title CBEthCollateral + * @notice Collateral plugin for Coinbase's staked ETH + * tok = cbETH + * ref = ETH2 + * tar = ETH + * UoA = USD + */ +contract CBEthCollateralL2 is L2LSDCollateral { + using OracleLib for AggregatorV3Interface; + using FixLib for uint192; + + AggregatorV3Interface public immutable targetPerTokChainlinkFeed; // {target/tok} + uint48 public immutable targetPerTokChainlinkTimeout; + + /// @param config.chainlinkFeed {UoA/target} price of ETH in USD terms + /// @param _targetPerTokChainlinkFeed {target/tok} price of cbETH in ETH terms + constructor( + CollateralConfig memory config, + uint192 revenueHiding, + AggregatorV3Interface _targetPerTokChainlinkFeed, + uint48 _targetPerTokChainlinkTimeout, + AggregatorV3Interface _exchangeRateChainlinkFeed, + uint48 _exchangeRateChainlinkTimeout + ) + L2LSDCollateral( + config, + revenueHiding, + _exchangeRateChainlinkFeed, + _exchangeRateChainlinkTimeout + ) + { + require(address(_targetPerTokChainlinkFeed) != address(0), "missing targetPerTok feed"); + require(_targetPerTokChainlinkTimeout != 0, "targetPerTokChainlinkTimeout zero"); + + targetPerTokChainlinkFeed = _targetPerTokChainlinkFeed; + targetPerTokChainlinkTimeout = _targetPerTokChainlinkTimeout; + } + + /// 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 + /// @return pegPrice {target/ref} The actual price observed in the peg + function tryPrice() + external + view + override + returns ( + uint192 low, + uint192 high, + uint192 pegPrice + ) + { + uint192 targetPerTok = targetPerTokChainlinkFeed.price(targetPerTokChainlinkTimeout); + + // {UoA/tok} = {UoA/target} * {target/tok} + uint192 p = chainlinkFeed.price(oracleTimeout).mul(targetPerTok); + uint192 err = p.mul(oracleError, CEIL); + + high = p + err; + low = p - err; + // assert(low <= high); obviously true just by inspection + + // {target/ref} = {target/tok} / {ref/tok} + pegPrice = targetPerTok.div(_underlyingRefPerTok()); + } +} diff --git a/contracts/plugins/assets/cbeth/README.md b/contracts/plugins/assets/cbeth/README.md index aa654f92eb..351074009d 100644 --- a/contracts/plugins/assets/cbeth/README.md +++ b/contracts/plugins/assets/cbeth/README.md @@ -16,4 +16,6 @@ This plugin allows `CBETH` holders to use their tokens as collateral in the Rese #### refPerTok {ref/tok} -`return _safeWrap(token.exchange_rate());` +The L1 implementation (CBETHCollateral.sol) uses `token.exchange_rate()` to get the cbETH/ETH {ref/tok} contract exchange rate. + +The L2 implementation (CBETHCollateralL2.sol) uses the relevant chainlink oracle to get the cbETH/ETH {ref/tok} contract exchange rate (oraclized from the L1). diff --git a/contracts/plugins/assets/cbeth/vendor/ICBEth.sol b/contracts/plugins/assets/cbeth/vendor/ICBEth.sol new file mode 100644 index 0000000000..a842a59f3e --- /dev/null +++ b/contracts/plugins/assets/cbeth/vendor/ICBEth.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +interface ICBEth is IERC20Metadata { + function mint(address account, uint256 amount) external returns (bool); + + function updateExchangeRate(uint256 exchangeRate) external; + + function configureMinter(address minter, uint256 minterAllowedAmount) external returns (bool); + + function exchangeRate() external view returns (uint256 _exchangeRate); +} diff --git a/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol index 78d37b5bcb..a60744893a 100644 --- a/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol @@ -12,7 +12,7 @@ import "./ICToken.sol"; * @title CTokenFiatCollateral * @notice Collateral plugin for a cToken of fiat collateral, like cUSDC or cUSDP * Expected: {tok} != {ref}, {ref} is pegged to {target} unless defaulting, {target} == {UoA} - * Also used for FluxFinance + * Also used for FluxFinance. Flexible enough to work with and without CTokenWrapper. */ contract CTokenFiatCollateral is AppreciatingFiatCollateral { using OracleLib for AggregatorV3Interface; @@ -24,13 +24,25 @@ contract CTokenFiatCollateral is AppreciatingFiatCollateral { ICToken public immutable cToken; // gas-optimization: access underlying cToken directly - /// @param config.erc20 Should be a CTokenWrapper + /// @param config.erc20 May be a CTokenWrapper or the cToken itself /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide constructor(CollateralConfig memory config, uint192 revenueHiding) AppreciatingFiatCollateral(config, revenueHiding) { - cToken = ICToken(address(RewardableERC20Wrapper(address(config.erc20)).underlying())); - referenceERC20Decimals = IERC20Metadata(cToken.underlying()).decimals(); + ICToken _cToken = ICToken(address(config.erc20)); + address _underlying = _cToken.underlying(); + uint8 _referenceERC20Decimals; + + // _underlying might be a wrapper at this point, try to go one level further + try ICToken(_underlying).underlying() returns (address _mostUnderlying) { + _cToken = ICToken(_underlying); + _referenceERC20Decimals = IERC20Metadata(_mostUnderlying).decimals(); + } catch { + _referenceERC20Decimals = IERC20Metadata(_underlying).decimals(); + } + + cToken = _cToken; + referenceERC20Decimals = _referenceERC20Decimals; require(referenceERC20Decimals > 0, "referenceERC20Decimals missing"); } @@ -67,6 +79,8 @@ contract CTokenFiatCollateral is AppreciatingFiatCollateral { /// Claim rewards earned by holding a balance of the ERC20 token /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins function claimRewards() external virtual override(Asset, IRewardable) { - IRewardable(address(erc20)).claimRewards(); + // solhint-ignore-next-line no-empty-blocks + try IRewardable(address(erc20)).claimRewards() {} catch {} + // erc20 may not be a CTokenWrapper } } diff --git a/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol b/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol index fb6b12065a..4d7b92ebac 100644 --- a/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol +++ b/contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol @@ -289,15 +289,14 @@ contract ConvexStakingWrapper is ERC20, ReentrancyGuard { } function _checkpoint(address[2] memory _accounts) internal nonReentrant { - //if shutdown, no longer checkpoint in case there are problems - if (isShutdown()) return; - uint256 supply = _getTotalSupply(); uint256[2] memory depositedBalance; depositedBalance[0] = _getDepositedBalance(_accounts[0]); depositedBalance[1] = _getDepositedBalance(_accounts[1]); - IRewardStaking(convexPool).getReward(address(this), true); + if (!isShutdown()) { + IRewardStaking(convexPool).getReward(address(this), true); + } _claimExtras(); diff --git a/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol b/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol index 778725866c..5959b944ec 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol @@ -21,8 +21,8 @@ contract MorphoFiatCollateral is AppreciatingFiatCollateral { uint256 private immutable oneShare; int8 private immutable refDecimals; - /// @param config Configuration of this collateral /// config.erc20 must be a MorphoTokenisedDeposit + /// @param config.chainlinkFeed Feed units: {UoA/ref} /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide constructor(CollateralConfig memory config, uint192 revenueHiding) AppreciatingFiatCollateral(config, revenueHiding) diff --git a/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol b/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol index b37db03207..27449c2883 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol @@ -19,8 +19,8 @@ contract MorphoNonFiatCollateral is MorphoFiatCollateral { AggregatorV3Interface public immutable targetUnitChainlinkFeed; // {target/ref} uint48 public immutable targetUnitOracleTimeout; // {s} - /// @param config Configuration of this collateral. - /// config.erc20 must be a MorphoTokenisedDeposit + /// @dev config.erc20 must be a MorphoTokenisedDeposit + /// @param config.chainlinkFeed Feed units: {UoA/target} /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide /// @param targetUnitChainlinkFeed_ Feed units: {target/ref} /// @param targetUnitOracleTimeout_ {s} oracle timeout to use for targetUnitChainlinkFeed @@ -51,8 +51,8 @@ contract MorphoNonFiatCollateral is MorphoFiatCollateral { // {tar/ref} Get current market peg pegPrice = targetUnitChainlinkFeed.price(targetUnitOracleTimeout); - // {UoA/tok} = {UoA/ref} * {ref/tok} - uint192 p = chainlinkFeed.price(oracleTimeout).mul(_underlyingRefPerTok()); + // {UoA/tok} = {UoA/target} * {target/ref} * {ref/tok} + uint192 p = chainlinkFeed.price(oracleTimeout).mul(pegPrice).mul(_underlyingRefPerTok()); uint192 err = p.mul(oracleError, CEIL); high = p + err; diff --git a/contracts/plugins/assets/morpho-aave/MorphoSelfReferentialCollateral.sol b/contracts/plugins/assets/morpho-aave/MorphoSelfReferentialCollateral.sol index 941320bcdf..e1f1ec17b3 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoSelfReferentialCollateral.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoSelfReferentialCollateral.sol @@ -22,8 +22,8 @@ contract MorphoSelfReferentialCollateral is AppreciatingFiatCollateral { uint256 private immutable oneShare; int8 private immutable refDecimals; - /// @param config Configuration of this collateral. /// config.erc20 must be a MorphoTokenisedDeposit + /// @param config.chainlinkFeed Feed units: {UoA/ref} /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide constructor(CollateralConfig memory config, uint192 revenueHiding) AppreciatingFiatCollateral(config, revenueHiding) diff --git a/contracts/plugins/trading/DutchTrade.sol b/contracts/plugins/trading/DutchTrade.sol index ba647e95a8..dc4a563dc6 100644 --- a/contracts/plugins/trading/DutchTrade.sol +++ b/contracts/plugins/trading/DutchTrade.sol @@ -56,9 +56,7 @@ uint192 constant ONE_POINT_FIVE = 150e16; // {1} 1.5 * exists to handle cases where prices change after the auction is started, naturally. * * Case 3: Over the next 50% of the auction the price falls from the best plausible price to the - * worst price, linearly. The worst price is further discounted by the maxTradeSlippage as a - * fraction of how far from minTradeVolume to maxTradeVolume the trade lies. - * At maxTradeVolume, no additonal discount beyond the oracle errors is applied. + * worst price, linearly. The worst price is further discounted by the maxTradeSlippage. * This is the phase of the auction where bids will typically occur. * * Case 4: Lastly the price stays at the worst price for the final 5% of the auction to allow @@ -96,7 +94,6 @@ contract DutchTrade is ITrade { uint192 public bestPrice; // {buyTok/sellTok} The best plausible price based on oracle data uint192 public worstPrice; // {buyTok/sellTok} The worst plausible price based on oracle data - // and further discounted by a fraction of maxTradeSlippage based on auction volume. // === Bid === address public bidder; @@ -172,17 +169,14 @@ contract DutchTrade is ITrade { uint256 _endBlock = _startBlock + auctionLength / ONE_BLOCK; // FLOOR; endBlock is inclusive endBlock = _endBlock; // gas-saver - endTime = uint48(block.timestamp + ONE_BLOCK * (_endBlock - _startBlock)); - - // {1} - uint192 slippage = _slippage( - sellAmount.mul(prices.sellHigh, FLOOR), // auctionVolume - origin.minTradeVolume(), // minTradeVolume - fixMin(sell_.maxTradeVolume(), buy_.maxTradeVolume()) // maxTradeVolume - ); + endTime = uint48(block.timestamp + ONE_BLOCK * (_endBlock - _startBlock + 1)); // {buyTok/sellTok} = {UoA/sellTok} * {1} / {UoA/buyTok} - uint192 _worstPrice = prices.sellLow.mulDiv(FIX_ONE - slippage, prices.buyHigh, FLOOR); + uint192 _worstPrice = prices.sellLow.mulDiv( + FIX_ONE - origin.maxTradeSlippage(), + prices.buyHigh, + FLOOR + ); uint192 _bestPrice = prices.sellHigh.div(prices.buyLow, CEIL); // no additional slippage assert(_worstPrice <= _bestPrice); worstPrice = _worstPrice; // gas-saver @@ -260,30 +254,6 @@ contract DutchTrade is ITrade { // === Private === - /// Return a sliding % from 0 (at maxTradeVolume) to maxTradeSlippage (at minTradeVolume) - /// @param auctionVolume {UoA} The actual auction volume - /// @param minTradeVolume {UoA} The minimum trade volume - /// @param maxTradeVolume {UoA} The maximum trade volume - /// @return slippage {1} The fraction of auctionVolume that should be permitted as slippage - function _slippage( - uint192 auctionVolume, - uint192 minTradeVolume, - uint192 maxTradeVolume - ) private view returns (uint192 slippage) { - slippage = origin.maxTradeSlippage(); // {1} - if (maxTradeVolume <= minTradeVolume || auctionVolume < minTradeVolume) return slippage; - - // untestable: - // auctionVolume already sized based on maxTradeVolume, so this will not be true - if (auctionVolume > maxTradeVolume) return 0; // 0% slippage beyond maxTradeVolume - - // {1} = {1} * ({UoA} - {UoA}} / ({UoA} - {UoA}) - return - slippage.mul( - FIX_ONE - divuu(auctionVolume - minTradeVolume, maxTradeVolume - minTradeVolume) - ); - } - /// Return the price of the auction at a particular timestamp /// @param blockNumber {block} The block number to get price for /// @return {buyTok/sellTok} diff --git a/docs/collateral.md b/docs/collateral.md index a2fb80adc4..b5e367555c 100644 --- a/docs/collateral.md +++ b/docs/collateral.md @@ -11,17 +11,19 @@ The core protocol depends on two plugin types: 2. _Trading_ (not discussed here) `contracts/plugins/trading` -In our inheritance tree, Collateral is a subtype of Asset (i.e. `ICollateral is IAsset`). An Asset describes how to interact with and price an ERC20 token. An instance of the Reserve Protocol can use an ERC20 token if and only if its `AssetRegistry` contains an asset modeling that token. An Asset provides the Reserve Protocol with information about the token: +In our inheritance tree, Collateral is a subtype of Asset (i.e. `ICollateral is IAsset`). An Asset describes how to treat and price an ERC20 token, allowing the protocol to buy and sell the token. An instance of the Reserve Protocol can use an ERC20 token iff its `AssetRegistry` contains an asset modeling that token. An Asset provides the Reserve Protocol with: -- How to get its price +- How to get its (USD) price - A maximum volume per trade +- A `refresh()` mutator function -A Collateral contract is a subtype of Asset (i.e. `ICollateral is IAsset`), so it does everything as Asset does. Beyond that, a Collateral plugin provides the Reserve Protocol with the information it needs to use its token as collateral -- as backing, held in the RToken's basket. +A Collateral contract is a subtype of Asset (i.e. `ICollateral is IAsset`), so it does everything as Asset does. Beyond that, a Collateral plugin provides the Reserve Protocol with the information it needs to use its token as collateral -- as backing, held in the RToken's basket. Mainly this involves the addition of 2 exchange rates and a `Collateral Status`. + +For a collateral: - Its ERC20 token can be used to back an RToken, not just be bought and sold -- A Collateral has a `refresh()` method that is called at the start of any significant system interaction (i.e. `@custom:interaction`). - A Collateral has a `status()` view that returns a `CollateralStatus` value, which is one of `SOUND`, `IFFY`, or `DISABLED`. -- A Collateral provides 3 exchange rates in addition to the `{UoA/tok}` prices provided by an Asset: `{ref/tok}`, `{target/ref}`, and `{UoA/target}`. A large part of designing a collateral plugin is deciding how these exchange rates should be computed. This is discussed below, under [Accounting Units and Exchange Rates](#Accounting_Units_and_Exchange_Rates). If this notation for units is entirely new to you, first read [our explanation of this unit notation](solidity-style.md#Units-in-comments). +- A Collateral provides 2 exchange rates in addition to the `{UoA/tok}` price provided by an Asset: `{ref/tok}` and `{target/ref}` (to understand this notation, see: [here](solidity-style.md#Units-in-comments). A large part of designing a collateral plugin is deciding how these exchange rates should be computed. This is discussed further below, under [Accounting Units and Exchange Rates](#Accounting_Units_and_Exchange_Rates). The IAsset and ICollateral interfaces, from `IAsset.sol`, are as follows: @@ -108,97 +110,85 @@ interface ICollateral is IAsset { ``` -## Some security considerations +## Types of Default + +Broadly speaking there are two ways a collateral can default: + +1. Fast: `refresh()` detects a clear problem with its defi protocol, and triggers in an immediate default. For instance, anytime the `refPerTok()` exchange rate falls between calls to `refresh()`, the collateral should immediately default. +2. Slow: `refresh()` detects a error condition that will _probably_ recover, but which should cause a default eventually. For instance, if the Collateral relies on USDT, and our price feed says that USDT trades at less than \$0.95 for (say) 24 hours, the Collateral should default. If a needed price feed is out-of-date or reverting for a similar period, the Collateral should default. + + In either of these cases, the collateral should first become `IFFY` and only move to `DISABLED` after the problem becomes sustained. In general, any pathway for default that cannot be assessed immediately should go through this delayed flow. + +## Security: Callbacks The protocol specifically does not allow the use of any assets that have a callback mechanism, such as ERC777 or native ETH. In order to support these assets, they must be wrapped in an ERC20 contract that does not have a callback mechanism. This is a security consideration to prevent reentrancy attacks. This recommendation extends to LP tokens that contain assets with callback mechanisms (Such as Curve raw ETH pools - CRV/ETH for example) as well as tokens/LPs that involve WETH with unwrapping built-in. ## Accounting Units and Exchange Rates -To create a Collateral plugin, you need to select its accounting units (`{tok}`, `{ref}`, `{target}`, and `{UoA}`), and implement views of the exchange rates: `refPerTok()` and `targetPerRef()`. +To create a Collateral plugin, you need to select its accounting units (`{tok}`, `{ref}`, and `{target}`), and implement views of the exchange rates: `refPerTok()` and `targetPerRef()`. Wherever `{UoA}` is used, you can assume this represents USD, the modern-day typical unit of account. -Typical accounting units in this sense are things like ETH, USD, USDC -- tokens, assets, currencies; anything that can be used as a measure of value. In general, a valid accounting unit is a linear combination of any number of assets; so (1 USDC + 0.5 USDP + 0.25 TUSD) is a valid unit, as is (say) (0.5 USD + 0.5 EUR), though such units will probably only arise in particularly tricky cases. Each Collateral plugin should describe in its documentation each of its four accounting units +Typical accounting units in this sense are things like ETH, USD, USDC -- tokens, assets, currencies; anything that can be used as a measure of value. In general, a valid accounting unit is a linear combination of any number of assets; so (1 USDC + 0.5 USDP + 0.25 TUSD) is a valid unit, as is (say) (0.5 USD + 0.5 EUR), though such units will probably only arise in particularly tricky cases. Each Collateral plugin should describe in its documentation each of its three accounting units. As a quick overview: -- The unit `{tok}` is just the concrete token being modeled. +- The unit `{tok}` is just the concrete token being modeled. If a wrapper needs to be involved, it is the wrapper. - The protocol measures growth as the increase of the value of `{tok}` against the value of `{ref}`, and treats that growth as revenue. - If two Collateral plugins have the same `{target}`, then when one defaults, the other one can serve as backup collateral. -- The unit `{UoA}` is a common accounting unit across all collateral in an RToken. +- The unit `{UoA}` is a common accounting unit across all assets, and always means USD (for now). ### Collateral unit `{tok}` -The collateral unit `{tok}` is just 1 of the ERC20 token that the Collateral plugin models. The protocol directly holds this unit of value. +The collateral unit `{tok}` is just the ERC20 token that the Collateral plugin models, or its wrapper, if a wrapper is involved. The protocol directly holds this unit of value. This is typically a token that is interesting to hold because it allows the accumulation of ever-increasing amounts of some other more-fundamental unit, called the reference unit. It's also possible for collateral to be non-appreciating, in which case it may still make sense to hold the collateral either because it allows the claiming of rewards over time, or simply because the protocol strongly requires stability (usually, short-term). -Note that a value denoted `{tok}` is a number of "whole tokens" with 18 decimals. So even though DAI has 18 decimals and USDC has 6 decimals, $1 in either token would be 1e18 when working with `uint192` values with the unit `{tok}`. For context on our approach for handling decimal-fixed-point, see [The Fix Library](solidity-style.md#The-Fix-Library). +Note that a value denoted `{tok}` is a number of "whole tokens" with 18 decimals. Even though DAI has 18 decimals and USDC has 6 decimals, $1 in either token would be 1e18 when working with `uint192` representations with the unit `{tok}`. For context on our approach for handling decimal-fixed-point, see [The Fix Library](solidity-style.md#The-Fix-Library). In-short, `uint192` is a special-cased uint size that always represents fractional values with 18 decimals. ### Reference unit `{ref}` -The _reference unit_, `{ref}`, is the measure of value that the protocol computes revenue against. When the exchange rate `refPerTok()` rises, the protocol keeps a constant amount of `{ref}` as backing, and sells the rest of the token it holds as revenue. - -There's room for flexibility and creativity in the choice of a Collateral's reference unit. The chief constraints are: +The _reference unit_, `{ref}`, is the measure of value that the protocol computes revenue against. When the exchange rate `refPerTok()` rises, the protocol keeps a constant amount of `{ref}` as backing, and considers any surplus balance of the token revenue. -- `refPerTok() {ref}` should always be a good market rate for 1 `{tok}` -- `refPerTok()` must be nondecreasing over time, at least on some sensible model of the collateral token's economics. If that model is violated, the Collateral plugin should immediately default. (i.e, permanently set `status()` to `DISABLED`) +There's room for flexibility and creativity in the choice of a Collateral's reference unit. The chief constraints is that `refPerTok()` must be nondecreasing over time, and as soon as this fails to be the case the `CollateralStatus` should become permanently `DISABLED`. -In many cases, the choice of reference unit is clear. +In many cases, the choice of reference unit is clear. For example: - The collateral token cUSDC (compound USDC) has a natural reference unit of USDC. cUSDC is permissionlessly redeemable in the Compound protocol for an ever-increasing amount of USDC. -- The collateral token USDT is its own natural reference unit. It's not natively redeemable for anything else on-chain, and we think of it as non-appreciating collateral. (Consider: what would it mean for USDT to "appreciate"?) +- The collateral token USDT is its own natural reference unit. It's not natively redeemable for anything else on-chain, and we think of it as non-appreciating collateral. The reference unit is not USD, because the USDT/USD exchange rate often has small fluctuations in both direction which would otherwise cause `refPerTok()` to decrease. -Often, the collateral token is directly redeemable for the reference unit in the token's protocol. (When this is the case, you can usually implement `refPerTok()` by looking up the redemption rate between the collateral token and its underlying token!) If you want to keep things simple, stick to "natural" collateral produced by protocols with nondecreasing exchange rates. +Often, the collateral token is directly redeemable for the reference unit in the token's protocol. (When this is the case, you can usually implement `refPerTok()` by looking up the redemption rate between the collateral token and its underlying token!). -However, the protocol never tries to handle reference-unit tokens itself, and in fact reference-unit tokens don't even need to exist. Thus, a Collateral can have a _synthetic_ reference unit for which there exists no corresponding underlying token. For some worked-out examples, read [Synthetic Unit Examples](#Synthetic_Unit_Example) below. +However, the protocol never tries to handle reference-unit tokens itself, and in fact the reference-unit doesn't even need to necessarily exist, it can simply be a measure. For example, AMM LP tokens would use their invariant measure as the reference unit, and their exchange between the LP token and the invariant measure would be the `refPerTok()` exchange rate (i.e. get_virtual_price() in Curve). ### Target unit `{target}` -The _target unit_, `{target}`, is the type of value that the Collateral is expected by users to represent over time. For instance, an RToken intended to be a USD stablecoin probably has a basket made of Collateral for which `{target} = USD`. When the protocol must reconfigure the basket, it will replace defaulting "prime" Collateral with other "backup" Collateral if and only if they have the same target unit. - -The target unit has to do with a concept called the Target Basket, and ultimately comes down to the reasons why this collateral might be chosen as backing in the first place. For instance, if you create an RToken in Register, the deployer selects a linear combination of target units such as: - -- 1 USD -- 0.5 USD + 0.55 EUR -- 0.5 USD + 0.35 EUR + 0.00001 BTC - -These Target Baskets have been selected to start with a market price of about \$1, assuming a slightly weak EUR and \$20k BTC. Over time, these RTokens would each have very different overall price trajectories. - -(Note: the Target Basket never manifests in the code directly. In the code, we have a slightly more specific concept called the Prime Basket. But the Target Basket is a coherent concept for someone thinking about the UX of an RToken. You can think of it like a simplified view of the Prime Basket.) +The _target unit_, `{target}`, is the type of value that the Collateral is expected by users to match over time. For instance, an RToken intended to be a USD stablecoin must necessarily have a basket of Collateral for which `{target} = USD`. When the protocol must reconfigure the basket, it will replace defaulting Collateral with other backup Collateral that share `USD` as their target unit. The target unit and reference unit must be even more tightly connected than the reference unit and collateral unit. The chief constraints on `{target}` are: -- `targetPerRef() {target}` should always be a reasonable market rate for 1 `{ref}`, ignoring short-term price fluxuations. -- `targetPerRef()` must be a _constant_. - -Moreover, `{target}` should be the simplest and most common unit that can satisfy those constraints. A major purpose of the Reserve protocol is to automatically move funds stored in a defaulting token into backup positions. Collateral A can have Collateral B as a backup token if and only if they have the same target unit. +- `targetPerRef()` must be _constant_ +- `targetPerRef()` should not diverge too much from the actual measured exchange rate on secondary markets. Divergence for periods of time is acceptable, but during these times the collateral should be marked `IFFY`. If the divergence is sustained long enough, the collateral should be permanently marked `DISABLED`. -Given those desired properties, after you've selected a collateral unit and reference unit, it's typically simple to choose a sensible target unit. For USDC the target unit would be USD; for EURT it would be the EUR; for WBTC it would be BTC. +For USDC the target unit would be USD; for EURT it would be the EUR; for WBTC it would be BTC. ### Unit of Account `{UoA}` -The Unit of Account `{UoA}` for a collateral plugin is simply a measure of value in which asset prices can be commonly denominated and compared. In principle, it's totally arbitrary, but all collateral plugins registered with an RToken must have the same unit of account. As of the current writing (October 2022), given the price information currently available on-chain, just use `USD` for the Unit of Account. +`{UoA} = USD` -Note, this doesn't disqualify collateral with USD as its target unit! It's fine for the target unit to be the unit of account. This doesn't disqualify collateral with a non-USD target unit either! It's fine for the target unit to be different from the unit of account. These two concepts are totally orthogonal. +The Unit of Account `{UoA}` for a collateral plugin is simply a measure of value in which asset prices can be commonly denominated and compared. In principle it's totally arbitrary, but all collateral plugins registered with an RToken must have the same unit of account. As of the current writing (September 2023), USD is the dominant common measure. We prefer to use `{UoA}` instead of USD in our code, because it's possible that in the future the dominant unit of account may change. -### Representing Fractional Values +Note, this doesn't disqualify collateral with USD as its target unit! It's fine for the target unit to be the unit of account. This doesn't disqualify collateral with a non-USD target unit either! It's fine for the target unit to be `BTC` and for the unit of account to be `USD`. -Wherever contract variables have these units, it's understood that even though they're handled as `uint192`s, they represent fractional values with 18 decimals. In particular, a `{tok}` value is a number of "whole tokens" with 18 decimals. So even though DAI has 18 decimals and USDC has 6 decimals, $1 in either token would be 1e18 when working in units of `{tok}`. +## Synthetic Units (Advanced) -For more about our approach for handling decimal-fixed-point, see our [docs on the Fix Library](solidity-style.md#The-Fix-Library). Ideally a user-defined type would be used but we found static analyses tools had trouble with that. - -## Synthetic Units - -Some collateral positions require a synthetic reference unit. Here are 3 ways one might do this (more are probably possible): +Some collateral positions require a synthetic reference unit. The two most common cases are: 1. [Defi Protocol Invariant](#defi-protocol-invariant) - Good for: bespoke LP tokens -2. [Demurrage Collateral](#demurrage-collateral) - Good for: tokens without obvious revenue mechanisms on their own -3. [Revenue Hiding](#revenue-hiding) + Good for: LP tokens +2. [Revenue Hiding](#revenue-hiding) Good for: tokens that _almost_ have a nondecreasing exchange rate but not quite - Update: Most of our collateral now have revenue hiding by default. See [AppreciatingFiatCollateral.sol](../contracts/plugins/AppreciatingFiatCollateral.sol) + Update: All of our appreciating collateral now have (a small amount of) revenue hiding by default, as an additional safety measure. See [AppreciatingFiatCollateral.sol](../contracts/plugins/assets/AppreciatingFiatCollateral.sol) -In general these approaches can be combined, though we don't recommend it! +These approaches can be combined. For example: [CurveStableCollateral.sol](../contracts/plugins/assets/curve/CurveStableCollateral.sol) ### Defi Protocol Invariant @@ -208,68 +198,23 @@ Consider the Uniswap V2 LP token, **UNI-V2**, for the USDC/USDT pair. (The follo A position's "natural" reference unit is whatever it's directly redeemable for. However, a Uniswap v2 LP token is not redeemable for any fixed, concrete unit. Rather, it's redeemable _pro rata_ for a share of the tokens in the liquidity pool, which can constantly change their proportion as trading occurs. -To demonstrate this difficulty, imagine we choose "1 USD" for the reference unit. We presume in this design that 1 USDC and 1 USDT are continuously redeemable for 1 USD each -- the Collateral can watch that assumption on price feeds and default if it fails, this is fine -- and we implement `refPerTok()` by computing the present redemption value of an LP token in USD. _This won't work_, because the redemption value of the LP token increases any time trading moves the pool's proportion of USDC to USDT tokens briefly away from the 1:1 point, and then decreases as trading brings the pool's proportion back to the 1:1 point. The protocol requires that `refPerTok()` never decreases, so this will cause immediate defaults. +To demonstrate this difficulty, imagine we choose "1 USD" for the reference unit. We presume in this design that 1 USDC and 1 USDT are continuously redeemable for 1 USD each and we implement `refPerTok()` by computing the present redemption value of an LP token in USD. _This won't work_, because the redemption value of the LP token increases any time trading moves the pool's proportion of USDC to USDT tokens briefly away from the 1:1 point and decreases when balances return to the 1:1 point. The protocol requires that `refPerTok()` never decreases, so this will cause defaults. Even with a large amount of revenue hiding, it may be possible for a griefer to flash loan enough USDC to intentionally swing the pool enough to trigger a default. -Instead, you might imagine that we choose "1 USDC + 1 USDT" as the reference unit. We compute `refPerTok()` at any moment by observing that we can redeem the `L` LP tokens in existence for `x` USDC and `y` USDT, and returning `min(x, y)/L`. _This also won't work_, because now `refPerTok()` will decrease any time the pool's proportion moves away from the 1:1 point, and it will increase whenever the proportion moves back. +Alternatively, you might imagine "0.5 USDC + 0.5 USDT" could be the reference unit. _This also won't work_, because now `refPerTok()` will decrease any time the pool's proportion moves away from the 1:1 point, and it will increase whenever the proportion moves back, as before. To make this Collateral position actually work, we have to account revenues against the pool's invariant. Assuming that there's a supply of `L` LP tokens for a pool with `x` USDC and `y` USDT, the strange-looking reference unit `sqrt(USDC * USDT)`, with corresponding `refPerTok() = sqrt(x * y)/L`, works exactly as desired. Without walking through the algebra, we can reason our way heuristically towards this design. The exchange rate `refPerTok()` should be a value that only ever increases. In UNI V2, that means it must not change when LP tokens are deposited or withdrawn; and it must not change due to trading, except insofar as it increases due to the protocol's fees. Deposit and withdrawal change all of `x`, `y`, and `L`, but in a lawful way: `x * y / (L * L)` is invariant even when the LP supply is changed due deposits or withdrawals. If there were zero fees, the same expression would be invariant during trading; with fees, `x * y` only increases, and so `x * y / (L * L)` only increases. However, this expression has bizarre units. However, this expression cannot possibly be a rate "per LP token", it's a rate per square of the LP token. Taking the square root gives us a rate per token of `sqrt(x * y) / L`. -[^comment]: tbh it's be a _good idea_ to walk through the algebra here, I'm just ... very busy right now! - After this choice after reference unit, we have two reasonable choices for target units. The simplest choice is to assert that the target unit is essentially unique to this particular instance of UNI v2 -- named by some horrible unique string like `UNIV2SQRTUSDTCUSDT` -- and that its redemption position cannot be traded, for certain, for any other backup position, so it cannot be backed up by a sensible basket. -This would be sensible for many UNI v2 pools, but someone holding value in a two-sided USD-fiatcoin pool probably intends to represent a USD position with those holdings, and so it'd be better for the Collateral plugin to have a target of USD. This is coherent so long as the Collateral plugin is setup to default under any of the following conditions: - -- According to a trusted oracle, USDC is far from \$1 for some time -- According a trusted oracle, USDT is far from \$1 for some time -- The UNI v2 pool is far from the 1:1 point for some time - -And even then, it would be somewhat dangerous for an RToken designer to use this LP token as a _backup_ Collateral position -- because whenever the pool's proportion is away from 1:1 at all, it'll take more than \$1 of collateral to buy an LP position that can reliably convert to \$1 later. - -### Demurrage Collateral - -If the collateral token does not have a reference unit it is nondecreasing against except for itself, a revenue stream can be created by composing a synthetic reference unit that refers to a falling quantity of the collateral token. This causes the reference unit to become inflationary with respect to the collateral unit, resulting in a monotonically increasing `refPerTok()` and allowing the protocol to recognize revenue. - -Plan: To ensure `refPerTok()` is nondecreasing, the reference unit is defined as a falling quantity of the collateral unit. As the reference unit "gets smaller", `refPerTok()` increases. This is viewed by the protocol as appreciation, allowing it to decrease how much `tok` is required per basket unit (`BU`). - -**Reference Unit** - -The equation below describes the relationship between the collateral unit and an inflationary reference unit. Over time there come to be more reference units per collateral token, allowing the protocol to identify revenue. - -``` -refPerTok(): (1 + demurrage_rate_per_second) ^ t - where t is seconds since 01/01/2020 00:00:00 GMT+0000 -``` - -The timestamp of 01/01/2020 00:00:00 GMT+0000 is chosen arbitrarily. It's not important what this value is, generally, but it's going to wind up being important that this anchor timestamp is the same _for all_ demurrage collateral, so we suggest just sticking with the provided timestamp. In unix time this is `1640995200`. - -(Note: In practice this equation will also have to be adjusted to account for the limited computation available on Ethereum. While the equation is expressed in terms of seconds, a larger granularity is likely necessary, such as hours or days. Exponentiation is expensive!) - -**Target Unit** - -A [constraint on the target unit](#target-unit-target) is that it should have a roughly constant exchange rate to the reference unit, modulo short-term price movements. In order to maintain this property, the target unit should be set to inflate at the same rate as the reference unit. This yields a trivial `targetPerRef()`. - -``` -targetPerRef(): 1 -``` - -The target unit must be named in a way that distinguishes it from the non-demurrage version of itself. We suggest the following naming scheme: - -`DMR{annual_demurrage_in_basis_points}{token_symbol}` or `DMR100wstETH` in this example. - -The `DMR` prefix is short for demurrage; the `annual_demurrage_in_basis_points` is a number such as 100 for 1% annually; the `token_symbol` is the symbol the collateral. - -Downside: Collateral can only be automatically substituted in the basket with collateral that share the same target unit. +This would be sensible for many UNI v2 pools, but someone holding value in a two-sided USD-fiatcoin pool probably intends to represent a USD position with those holdings, and so it'd be better for the Collateral plugin to have a target of USD. This is coherent so long as all tokens in the pool are pegged to USD. ### Revenue Hiding -An alternative to demurrage is to hide revenue from the protocol via a discounted `refPerTok()` function. `refPerTok()` should return X% less than the largest _actual_ refPerTok exchange rate that has been observed in the underlying Defi protocol. When the actual rate falls below this value, the collateral should be marked defaulted via the `refresh()` function. - -When implementing Revenue Hiding, the `price()/strictPrice()` functions should NOT hide revenue; they should use the current underlying exchange rate to calculate a best-effort estimate of what the collateral will trade at on secondary markets. A side-effect of this approach is that the RToken's price on markets becomes more variable. As such, it's best if the amount of hiding necessary is small. If the token will only rarely decrease in exchange rate---and only then a little---then revenue-hiding may be a good fit. +Revenue Hiding should be employed when the function underlying `refPerTok()` is not necessarily _strongly_ non-decreasing, or simply if there is uncertainty surrounding the guarantee. In general we recommend including a very small amount (1e-6) of revenue hiding for all appreciating collateral. This is already implemented in [AppreciatingFiatCollateral.sol](../contracts/plugins/assets/AppreciatingFiatCollateral.sol). -We already have an implementation of a Revenue Hiding contract at `contracts/plugins/assets/AppreciatingFiatCollateral.sol` that can be inherited from to create Revenue Hiding +When implementing Revenue Hiding, the `price/lotPrice()` functions should NOT hide revenue; they should use the current underlying exchange rate to calculate a best-effort estimate of what the collateral will trade at on secondary markets. A side-effect of this approach is that the RToken's price on markets becomes more variable. ## Important Properties for Collateral Plugins @@ -278,13 +223,13 @@ We already have an implementation of a Revenue Hiding contract at `contracts/plu Collateral plugins should be safe to reuse by many different Reserve Protocol instances. So: - Collateral plugins should neither require governance nor give special permissions to any particular accounts. -- Collateral plugins should not pull information from an RToken instance that they expect to use them directly. (There is already an RToken Asset that uses price information from the protocol directly; but it must not be extended for use as Collateral in its own basket!) +- Collateral plugins should not pull information from an RToken instance that they expect to use them directly. Check out [CurveStableRTokenMetapoolCollateral.sol](../contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol) for an example of a collateral plugin that allows one RToken instance to use another RToken instance as collateral, through an LP token. ### Token balances must be transferrable Collateral tokens must be tokens in the formal sense. That is: they must provide balances to holders, and these balances must be transferrable. -Some tokens may not be transferrable. Worse still, some positions in defi are not tokenized to begin with: take for example DSR-locked DAI or Convex's boosted staking positions. In these cases tokenization can be achieved by wrapping the position. In this kind of setup the wrapping contract issues tokens that correspond to pro-rata shares of the overall defi position, which it maintains under the hood in relation with the defi protocol. +Some positions may not be transferrable: take for example DSR-locked DAI or Convex's boosted staking positions. In these cases tokenization can be achieved by wrapping the position. In this kind of setup the wrapping contract issues tokens that correspond to pro-rata shares of the overall defi position, which it maintains under the hood in relation with the defi protocol. Here are some examples of what this looks like in Convex's case [here](https://github.com/convex-eth/platform/tree/main/contracts/contracts/wrappers). @@ -294,11 +239,9 @@ Some defi protocols yield returns by increasing the token balances of users, cal The Reserve Protocol cannot directly hold rebasing tokens. However, the protocol can indirectly hold a rebasing token, if it's wrapped by another token that does not itself rebase, but instead appreciates only through exchange-rate increases. Any rebasing token can be wrapped to be turned into an appreciating exchange-rate token, and vice versa. -To use a rebasing token as collateral backing, the rebasing ERC20 needs to be replaced with an ERC20 that is non-rebasing. This is _not_ a change to the collateral plugin contract itself. Instead, the collateral plugin designer needs to provide a wrapping ERC20 contract that RToken issuers or redeemers will have to deposit into or withdraw from. We expect to automate these transformations as zaps in the future, but at the time of this writing everything is still manual. +To use a rebasing token as collateral backing, the rebasing ERC20 needs to be replaced with an ERC20 that is non-rebasing. This is _not_ a change to the collateral plugin contract itself. Instead, the collateral plugin designer needs to provide a wrapping ERC20 contract that RToken issuers or redeemers will have to deposit into or withdraw from. -For an example of a token wrapper that performs this transformation, see [StaticATokenLM.sol](../contracts/plugins/aave/StaticATokenLM.sol). This is a standard wrapper to wrap Aave ATokens into StaticATokens. A thinned-down version of this contract makes a good starting point for developing other ERC20 wrappers -- but if the token is well-integrated in defi, a wrapping contract probably already exists. - -The same wrapper approach is easily used to tokenize positions in protocols that do not produce tokenized or transferrable positions. +There is a simple ERC20 wrapper that can be easily extended at [RewardableERC20Wrapper.sol](../contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol). You may add additional logic by extending `_afterDeposit()` or `_beforeWithdraw()`. ### `refresh()` should never revert @@ -316,7 +259,7 @@ Unless there's a good reason for a specific collateral to use a different mechan If `price()` returns 0 for the lower-bound price estimate `low`, the collateral should pass-through the [slow default](#types-of-default) process where it is first marked `IFFY` and eventually transitioned to `DISABLED` if the behavior is sustained. `status()` should NOT return `SOUND`. -If a collateral implementor extends [Fiat Collateral](../contracts/plugins/asset/FiatCollateral.sol), the logic inherited in the `refresh()` function already satisfies this property. +If a collateral implementor extends [Fiat Collateral](../contracts/plugins/assets/FiatCollateral.sol) or [AppreciatingFiatCollateral.sol](../contracts/plugins/assets/AppreciatingFiatCollateral.sol), the logic inherited in the `refresh()` function already satisfies this property. ### Collateral must default if `refPerTok()` falls. @@ -324,7 +267,7 @@ Notice that `refresh()` is the only non-view method on the ICollateral interface If `refresh()` is called twice, and `refPerTok()` just after the second call is lower than `refPerTok()` just after the first call, then `status()` must change to `CollateralStatus.DISABLED` immediately. This is true for any collateral plugin. For some collateral plugins it will be obvious that `refPerTok()` cannot decrease, in which case no checks are required. -If a collateral implementor extends [Fiat Collateral](../contracts/plugins/asset/FiatCollateral.sol), the logic inherited in the `refresh()` function already satisfies this property. +If a collateral implementor extends [Fiat Collateral](../contracts/plugins/assets/FiatCollateral.sol), the logic inherited in the `refresh()` function already satisfies this property. ### Defaulted Collateral must stay defaulted. @@ -332,7 +275,7 @@ If `status()` ever returns `CollateralStatus.DISABLED`, then it must always retu ### Token rewards should be claimable. -Protocol contracts that hold an asset for any significant amount of time must be able to call `claimRewards()` on the ERC20 itself (previously on the asset/collateral plugin via delegatecall). The erc20 should include whatever logic is necessary to claim rewards from all relevant defi protocols. These rewards are often emissions from other protocols, but may also be something like trading fees in the case of UNIV3 collateral. To take advantage of this: +Protocol contracts that hold an asset for any significant amount of time must be able to call `claimRewards()` _on the ERC20 itself_, if there are token rewards. The ERC20 should include whatever logic is necessary to claim rewards from all relevant defi protocols. These rewards are often emissions from other protocols, but may also be something like trading fees in the case of UNIV3 collateral. To take advantage of this: - `claimRewards()` must claim all rewards that may be earned by holding the asset ERC20 and send them to the holder, in the correct proportions based on amount of time held. - The `RewardsClaimed` event should be emitted for each token type claimed. @@ -349,7 +292,7 @@ The values returned by the following view methods should never change: ## Function-by-function walkthrough -Collateral implementors who extend from [Fiat Collateral](../contracts/plugins/asset/FiatCollateral.sol) can restrict their attention to overriding the following four functions: +Collateral implementors who extend from [Fiat Collateral](../contracts/plugins/assets/FiatCollateral.sol) or [AppreciatingFiatCollateral.sol](../contracts/plugins/assets/AppreciatingFiatCollateral.sol) can restrict their attention to overriding the following three functions: - `tryPrice()` (not on the ICollateral interface; used by `price()`/`refresh()`) - `refPerTok()` @@ -382,15 +325,6 @@ It's common for a Collateral plugin to reply on economic or technical assumption `status()` should trigger `DISABLED` when `refresh()` can tell that its assumptions are definitely being violated, and `status()` should trigger `IFFY` if it cannot tell that its assumptions _aren't_ being violated, such as if an oracle is reverting or has become stale. -#### Types of Default - -Broadly speaking there are two ways a collateral can default: - -1. Fast: `refresh()` detects a clear problem with its defi protocol, and triggers in an immediate default. For instance, anytime the `refPerTok()` exchange rate falls between calls to `refresh()`, the collateral should immediately default. -2. Slow: `refresh()` detects a error condition that will _probably_ recover, but which should cause a default eventually. For instance, if the Collateral relies on USDT, and our price feed says that USDT trades at less than \$0.95 for (say) 24 hours, the Collateral should default. If a needed price feed is out-of-date or reverting for a similar period, the Collateral should default. - - In either of these cases, the collateral should first become `IFFY` and only move to `DISABLED` after the problem becomes sustained. In general, any pathway for default that cannot be assessed immediately should go through this delayed flow. - ### status() `function status() external view returns (CollateralStatus)` @@ -407,7 +341,7 @@ enum CollateralStatus { #### Reasons to default -After a call to `refresh()`, it is expected the collateral is either `IFFY` or `DISABLED` if either `refPerTok()` or `targetPerRef()` might revert, of if `price()` would return a 0 value for `low`. +After a call to `refresh()`, it is expected the collateral is either `IFFY` or `DISABLED` if either `refPerTok()` or `targetPerRef()` might revert, or if `price()` would return a 0 value for `low`. The collateral should also be immediately set to `DISABLED` if `refPerTok()` has fallen. @@ -421,7 +355,7 @@ Lastly, once a collateral becomes `DISABLED`, it must remain `DISABLED`. Should never revert. -Should return a lower and upper estimate for the price of the token on secondary markets. +Should return the tightest possible lower and upper estimate for the price of the token on secondary markets. The difference between the upper and lower estimate should not exceed 5%, though this is not a hard-and-fast rule. When the difference (usually arising from an oracleError) is large, it can lead to [the price estimation of the RToken](../contracts/plugins/assets/RTokenAsset.sol) somewhat degrading. While this is not usually an issue it can come into play when one RToken is using another RToken as collateral either directly or indirectly through an LP token. If there is RSR overcollateralization then this issue is mitigated. @@ -433,6 +367,18 @@ Recommend decaying low estimate downwards and high estimate upwards over time. Should be gas-efficient. +The difference between the upper and lower estimate should not exceed ~5%, though this is not a hard-and-fast rule. When the difference (usually arising from an oracleError) is large, it can lead to [the price estimation of the RToken](../contracts/plugins/assets/RTokenAsset.sol) somewhat degrading. While this is not usually an issue it can come into play when one RToken is using another RToken as collateral either directly or indirectly through an LP token. If there is RSR overcollateralization then this issue is mitigated. + +### lotPrice() `{UoA/tok}` + +Should never revert. + +Lower estimate must be <= upper estimate. + +The low estimate should be nonzero while the asset is worth selling. + +Should be gas-efficient. + ### refPerTok() `{ref/tok}` Should never revert. @@ -443,7 +389,7 @@ Should be gas-efficient. ### targetPerRef() `{target/ref}` -Should never revert. Must return a constant value. +Should never revert. Must return a constant value. Almost always `FIX_ONE`, but can be different in principle. Should be gas-efficient. @@ -462,10 +408,8 @@ The target name is just a bytes32 serialization of the target unit string. Here For a collateral plugin that uses a novel target unit, get the targetName with `ethers.utils.formatBytes32String(unitName)`. -If implementing a demurrage-based collateral plugin, make sure your targetName follows the pattern laid out in [Demurrage Collateral](#demurrage-collateral). - ## Practical Advice from Previous Work -In most cases [Fiat Collateral](../contracts/plugins/asset/FiatCollateral.sol) can be extended, pretty easily, to support a new collateral type. This allows the collateral developer to limit their attention to the overriding of three functions: `tryPrice()`, `refPerTok()`, `targetPerRef()`. +In most cases [Fiat Collateral](../contracts/plugins/assets/FiatCollateral.sol) or [AppreciatingFiatCollateral.sol](../contracts/plugins/assets/AppreciatingFiatCollateral.sol) can be extended, pretty easily, to support a new collateral type. This allows the collateral developer to limit their attention to the overriding of three functions: `tryPrice()`, `refPerTok()`, `targetPerRef()`. -If you're quite stuck, you might also find it useful to read through our other Collateral plugins as models, found in our repository in `/contracts/plugins/assets`. +If you're quite stuck, you might also find it useful to read through our existing Collateral plugins, found at `/contracts/plugins/assets`. diff --git a/docs/deployed-addresses/1-ETH+.md b/docs/deployed-addresses/1-ETH+.md new file mode 100644 index 0000000000..cbb06461a1 --- /dev/null +++ b/docs/deployed-addresses/1-ETH+.md @@ -0,0 +1,24 @@ +# [ETH+ (ETHPlus)](https://etherscan.io/address/0xE72B141DF173b999AE7c1aDcbF60Cc9833Ce56a8) +## Component Addresses +| Contract | Address | Implementation | Version | +| --- | --- | --- | --- | +| RToken | [0xE72B141DF173b999AE7c1aDcbF60Cc9833Ce56a8](https://etherscan.io/address/0xE72B141DF173b999AE7c1aDcbF60Cc9833Ce56a8) | [0x5643d5ac6b79ae8467cf2f416da6d465d8e7d9c1](https://etherscan.io/address/0x5643d5ac6b79ae8467cf2f416da6d465d8e7d9c1#code) | 2.1.0 | +| Main | [0xb6A7d481719E97e142114e905E86a39a2Fa0dfD2](https://etherscan.io/address/0xb6A7d481719E97e142114e905E86a39a2Fa0dfD2) | [0x143c35bfe04720394ebd18abeca83ea9d8bede2f](https://etherscan.io/address/0x143c35bfe04720394ebd18abeca83ea9d8bede2f#code) | 2.0.0 | +| AssetRegistry | [0xf526f058858E4cD060cFDD775077999562b31bE0](https://etherscan.io/address/0xf526f058858E4cD060cFDD775077999562b31bE0) | [0x5a004f70b2450e909b4048050c585549ab8afeb8](https://etherscan.io/address/0x5a004f70b2450e909b4048050c585549ab8afeb8#code) | 2.0.0 | +| BackingManager | [0x608e1e01EF072c15E5Da7235ce793f4d24eCa67B](https://etherscan.io/address/0x608e1e01EF072c15E5Da7235ce793f4d24eCa67B) | [0xa0d4b6ad503e776457dbf4695d462ddf8621a1cc](https://etherscan.io/address/0xa0d4b6ad503e776457dbf4695d462ddf8621a1cc#code) | 2.0.0 | +| BasketHandler | [0x56f40A33e3a3fE2F1614bf82CBeb35987ac10194](https://etherscan.io/address/0x56f40A33e3a3fE2F1614bf82CBeb35987ac10194) | [0x5c13b3b6f40ad4bf7aa4793f844ba24e85482030](https://etherscan.io/address/0x5c13b3b6f40ad4bf7aa4793f844ba24e85482030#code) | 2.1.0 | +| Broker | [0x6ca42ce37e5ece334066C504ba37144b4f14D50a](https://etherscan.io/address/0x6ca42ce37e5ece334066C504ba37144b4f14D50a) | [0x89209a52d085d975b14555f3e828f43fb7eaf3b7](https://etherscan.io/address/0x89209a52d085d975b14555f3e828f43fb7eaf3b7#code) | 2.1.0 | +| RSRTrader | [0x6E20823cA50aA026b99789c8D468a01f8aA3581C](https://etherscan.io/address/0x6E20823cA50aA026b99789c8D468a01f8aA3581C) | [](https://etherscan.io/address/#code) | 2.0.0 | +| RTokenTrader | [0x977cb0e300a58978f597fc65ED5a2D2784D2DCF9](https://etherscan.io/address/0x977cb0e300a58978f597fc65ED5a2D2784D2DCF9) | [0xe5bd2249118b6a4b39be195951579dc9af05029a](https://etherscan.io/address/0xe5bd2249118b6a4b39be195951579dc9af05029a#code) | 2.0.0 | +| Distributor | [0x954B4770462e8894BcD2451543482F11DC160e1e](https://etherscan.io/address/0x954B4770462e8894BcD2451543482F11DC160e1e) | [](https://etherscan.io/address/#code) | 2.0.0 | +| Furnace | [0x9862efAB36F81524B24F787e07C97e2F5A6c206e](https://etherscan.io/address/0x9862efAB36F81524B24F787e07C97e2F5A6c206e) | [](https://etherscan.io/address/#code) | 2.0.0 | +| StRSR | [0xffa151Ad0A0e2e40F39f9e5E9F87cF9E45e819dd](https://etherscan.io/address/0xffa151Ad0A0e2e40F39f9e5E9F87cF9E45e819dd) | [0xfda8c62d86e426d5fb653b6c44a455bb657b693f](https://etherscan.io/address/0xfda8c62d86e426d5fb653b6c44a455bb657b693f#code) | 2.1.0 | + + +## Governance Addresses +| Contract | Address | Implementation | Version | +| --- | --- | --- | --- | +| Governor Alexios | [0x239cDcBE174B4728c870A24F77540dAB3dC5F981](https://etherscan.io/address/0x239cDcBE174B4728c870A24F77540dAB3dC5F981) | [](https://etherscan.io/address/#code) | 1 | +| Timelock | [0x5f4A10aE2fF68bE3cdA7d7FB432b10C6BFA6457B](https://etherscan.io/address/0x5f4A10aE2fF68bE3cdA7d7FB432b10C6BFA6457B) | [](https://etherscan.io/address/#code) | N/A | + + \ No newline at end of file diff --git a/docs/deployed-addresses/1-assets-2.1.0.md b/docs/deployed-addresses/1-assets-2.1.0.md new file mode 100644 index 0000000000..9547526591 --- /dev/null +++ b/docs/deployed-addresses/1-assets-2.1.0.md @@ -0,0 +1,44 @@ +# Assets (Mainnet 2.1.0) +## Assets +| Contract | Address | +| --- | --- | +| stkAAVE | [0x5cAF60bf01A5ecd436b2Cd0b68e4c04547eCb872](https://etherscan.io/address/0x5cAF60bf01A5ecd436b2Cd0b68e4c04547eCb872) | +| COMP | [0x159Af360D99b3dd6c4a47Cd08b730Ff7C9d113CC](https://etherscan.io/address/0x159Af360D99b3dd6c4a47Cd08b730Ff7C9d113CC) | +| CRV | [0x3752098adf2C9E1E17e48D9cE2Ea48961905064A](https://etherscan.io/address/0x3752098adf2C9E1E17e48D9cE2Ea48961905064A) | +| CVX | [0xbE301280e593d1665A2D54DA65687E92f46D5c44](https://etherscan.io/address/0xbE301280e593d1665A2D54DA65687E92f46D5c44) | + +## Collaterals +| Contract | Address | +| --- | --- | +| DAI | [0xB03A029FF70d7c4c53bb3C4288a87aCFea0Ee8FE](https://etherscan.io/address/0xB03A029FF70d7c4c53bb3C4288a87aCFea0Ee8FE) | +| USDC | [0x951d32B449D5D5cE53DA3a5C1E22b37ec0f2E387](https://etherscan.io/address/0x951d32B449D5D5cE53DA3a5C1E22b37ec0f2E387) | +| USDT | [0x7fc1c34782888a076d3c88c0cce27b75892ee85d](https://etherscan.io/address/0x7fc1c34782888a076d3c88c0cce27b75892ee85d) | +| USDP | [0xeD67e489E7aA622380288557FABfA6Be246dE776](https://etherscan.io/address/0xeD67e489E7aA622380288557FABfA6Be246dE776) | +| TUSD | [0x9cCc7B600F80ed6F3d997698e01301D9016F8656](https://etherscan.io/address/0x9cCc7B600F80ed6F3d997698e01301D9016F8656) | +| BUSD | [0x07cDEA861B2A231e249E220A553D9A38ba7383D6](https://etherscan.io/address/0x07cDEA861B2A231e249E220A553D9A38ba7383D6) | +| aDAI | [0x2cAF7BB8C9651377cc7DBd8dc297b58F67D8A816](https://etherscan.io/address/0x2cAF7BB8C9651377cc7DBd8dc297b58F67D8A816) | +| aUSDC | [0xE19ae8D1f3FFf987aaEaa65248BAB3A0d1FDC809](https://etherscan.io/address/0xE19ae8D1f3FFf987aaEaa65248BAB3A0d1FDC809) | +| aUSDT | [0x44AB1cB3C9f25A928E39A4eDE3CA08B52b4cdE24](https://etherscan.io/address/0x44AB1cB3C9f25A928E39A4eDE3CA08B52b4cdE24) | +| aBUSD | [0x002835840A6CB5dd3f73e78A21eF41db4C66948e](https://etherscan.io/address/0x002835840A6CB5dd3f73e78A21eF41db4C66948e) | +| aUSDP | [0x50f4991BE43a631f5BEDB5C39e45FF3E57Fa783e](https://etherscan.io/address/0x50f4991BE43a631f5BEDB5C39e45FF3E57Fa783e) | +| cDAI | [0xe11b8943b6C9abfc9D729306029f7401205bAa9B](https://etherscan.io/address/0xe11b8943b6C9abfc9D729306029f7401205bAa9B) | +| cUSDC | [0x7FC2df2B27220D9F23Fbd8C21b1f7b0CaEB6fE15](https://etherscan.io/address/0x7FC2df2B27220D9F23Fbd8C21b1f7b0CaEB6fE15) | +| cUSDT | [0x1F1941eE0B3CCb4Ff2135D31103C59F2E53C34B5](https://etherscan.io/address/0x1F1941eE0B3CCb4Ff2135D31103C59F2E53C34B5) | +| cUSDP | [0xD9438B058Ce83925E4AC0834744fC0b573A7AFbB](https://etherscan.io/address/0xD9438B058Ce83925E4AC0834744fC0b573A7AFbB) | +| cWBTC | [0xC3481edefE16599701940a71B7a488605803D4cB](https://etherscan.io/address/0xC3481edefE16599701940a71B7a488605803D4cB) | +| cETH | [0xA88304757c00D45b24eea13568bd346C4a49053C](https://etherscan.io/address/0xA88304757c00D45b24eea13568bd346C4a49053C) | +| WBTC | [0xe9c6bF8536e2Af014a54651F0dd6c74A18D13e70](https://etherscan.io/address/0xe9c6bF8536e2Af014a54651F0dd6c74A18D13e70) | +| WETH | [0xBd941FA60b6E2AcCa15afB8962f6B4795c848b8D](https://etherscan.io/address/0xBd941FA60b6E2AcCa15afB8962f6B4795c848b8D) | +| EURT | [0x14d5b63e8FfDDDB590C88d9A258461CbEfbB8d56](https://etherscan.io/address/0x14d5b63e8FfDDDB590C88d9A258461CbEfbB8d56) | +| wstETH | [0x3879C820c3cC4547Cb76F8dC842005946Cedb385](https://etherscan.io/address/0x3879C820c3cC4547Cb76F8dC842005946Cedb385) | +| rETH | [0xD2270A3E17DBeA5Cb491E0120441bFD0177Da913](https://etherscan.io/address/0xD2270A3E17DBeA5Cb491E0120441bFD0177Da913) | +| fUSDC | [0x1289a753e0BaE82CF7f87747f22Eaf8E4eb7C216](https://etherscan.io/address/0x1289a753e0BaE82CF7f87747f22Eaf8E4eb7C216) | +| fUSDT | [0x5F471bDE4950CdB00714A6dD033cA7f912a4f9Ee](https://etherscan.io/address/0x5F471bDE4950CdB00714A6dD033cA7f912a4f9Ee) | +| fDAI | [0xA4410B71033fFE8fA41c6096332Be58E3641326d](https://etherscan.io/address/0xA4410B71033fFE8fA41c6096332Be58E3641326d) | +| fFRAX | [0xcd46Ff27c0d6F088FB94896dcE8F17491BD84c75](https://etherscan.io/address/0xcd46Ff27c0d6F088FB94896dcE8F17491BD84c75) | +| cUSDCv3 | [0x615D92fAF203Faa9ea7a4D8cdDC49b2Ad0702a1f](https://etherscan.io/address/0x615D92fAF203Faa9ea7a4D8cdDC49b2Ad0702a1f) | +| cvx3Pool | [0xC34E547D66B5a57B370217aAe4F34b882a9933Dc](https://etherscan.io/address/0xC34E547D66B5a57B370217aAe4F34b882a9933Dc) | +| cvxTriCrypto | [0xb2EeD19C381b71d0f54327D61596312144f66fA7](https://etherscan.io/address/0xb2EeD19C381b71d0f54327D61596312144f66fA7) | +| cvxeUSDFRAXBP | [0x0D41E86D019cadaAA32a5a12A35d456711879770](https://etherscan.io/address/0x0D41E86D019cadaAA32a5a12A35d456711879770) | +| cvxMIM3Pool | [0x9866020B7A59022C2F017C6d358868cB11b86E2d](https://etherscan.io/address/0x9866020B7A59022C2F017C6d358868cB11b86E2d) | + \ No newline at end of file diff --git a/docs/deployed-addresses/1-components-2.1.0.md b/docs/deployed-addresses/1-components-2.1.0.md new file mode 100644 index 0000000000..4ead783227 --- /dev/null +++ b/docs/deployed-addresses/1-components-2.1.0.md @@ -0,0 +1,31 @@ +# Component Implementations (Mainnet 2.1.0) +## Component Addresses +| Contract | Address | Implementation | Version | +| --- | --- | --- | --- | +| AssetRegistry | [0x5a004F70b2450E909B4048050c585549Ab8afeB8](https://etherscan.io/address/0x5a004F70b2450E909B4048050c585549Ab8afeB8) | +| BackingManager | [0xa0D4b6aD503E776457dBF4695d462DdF8621A1CC](https://etherscan.io/address/0xa0D4b6aD503E776457dBF4695d462DdF8621A1CC) | +| BasketHandler | [0x5c13b3b6f40aD4bF7aa4793F844BA24E85482030](https://etherscan.io/address/0x5c13b3b6f40aD4bF7aa4793F844BA24E85482030) | +| Broker | [0x89209a52d085D975b14555F3e828F43fb7EaF3B7](https://etherscan.io/address/0x89209a52d085D975b14555F3e828F43fb7EaF3B7) | +| CvxMiningLib | [0xA6B8934a82874788043A75d50ca74a18732DC660](https://etherscan.io/address/0xA6B8934a82874788043A75d50ca74a18732DC660) | +| Deployer | [0x5c46b718Cd79F2BBA6869A3BeC13401b9a4B69bB](https://etherscan.io/address/0x5c46b718Cd79F2BBA6869A3BeC13401b9a4B69bB) | +| Distributor | [0xc78c5a84F30317B5F7D87170Ec21DC73Df38d569](https://etherscan.io/address/0xc78c5a84F30317B5F7D87170Ec21DC73Df38d569) | +| DutchTrade | [0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439](https://etherscan.io/address/0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439) | +| FacadeAct | [0x933c5DBdA80f03C102C560e9ed0c29812998fA78](https://etherscan.io/address/0x933c5DBdA80f03C102C560e9ed0c29812998fA78) | +| FacadeMonitor | [0xF3458200eDe2C5A592757dc0BA9A915e9CCA77C6](https://etherscan.io/address/0xF3458200eDe2C5A592757dc0BA9A915e9CCA77C6) | +| FacadeRead | [0xf535Cab96457558eE3eeAF1402fCA6441E832f08](https://etherscan.io/address/0xf535Cab96457558eE3eeAF1402fCA6441E832f08) | +| FacadeWrite | [0x1656D8aAd7Ee892582B9D5c2E9992d9f94ff3629](https://etherscan.io/address/0x1656D8aAd7Ee892582B9D5c2E9992d9f94ff3629) | +| FacadeWriteLib | [0xe33cEF9f56F0d8d2b683c6E1F6afcd1e43b77ea8](https://etherscan.io/address/0xe33cEF9f56F0d8d2b683c6E1F6afcd1e43b77ea8) | +| Furnace | [0x393002573ea4A3d74A80F3B1Af436a3ee3A30c96](https://etherscan.io/address/0x393002573ea4A3d74A80F3B1Af436a3ee3A30c96) | +| GNOSIS_EASY_AUCTION | [0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101](https://etherscan.io/address/0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101) | +| GnosisTrade | [0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439](https://etherscan.io/address/0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439) | +| Main | [0x143C35bFe04720394eBd18AbECa83eA9D8BEdE2F](https://etherscan.io/address/0x143C35bFe04720394eBd18AbECa83eA9D8BEdE2F) | +| RSR | [0x320623b8e4ff03373931769a31fc52a4e78b5d70](https://etherscan.io/address/0x320623b8e4ff03373931769a31fc52a4e78b5d70) | +| RSR_FEED | [0x759bBC1be8F90eE6457C44abc7d443842a976d02](https://etherscan.io/address/0x759bBC1be8F90eE6457C44abc7d443842a976d02) | +| RsrAsset | [0x9cd0F8387672fEaaf7C269b62c34C53590d7e948](https://etherscan.io/address/0x9cd0F8387672fEaaf7C269b62c34C53590d7e948) | +| RsrTrader | [0xE5bD2249118b6a4B39Be195951579dC9Af05029a](https://etherscan.io/address/0xE5bD2249118b6a4B39Be195951579dC9Af05029a) | +| RToken | [0x5643D5AC6b79ae8467Cf2F416da6D465d8e7D9C1](https://etherscan.io/address/0x5643D5AC6b79ae8467Cf2F416da6D465d8e7D9C1) | +| RTokenTrader | [0xE5bD2249118b6a4B39Be195951579dC9Af05029a](https://etherscan.io/address/0xE5bD2249118b6a4B39Be195951579dC9Af05029a) | +| StRSR | [0xfDa8C62d86E426D5fB653B6c44a455Bb657b693f](https://etherscan.io/address/0xfDa8C62d86E426D5fB653B6c44a455Bb657b693f) | +| Trade | [0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439](https://etherscan.io/address/0xAd4B0B11B041BB1342fEA16fc9c12Ef2a6443439) | +| TradingLib | [0x81b19Af39ab589D0Ca211DC3Dee4cfF7072eb478](https://etherscan.io/address/0x81b19Af39ab589D0Ca211DC3Dee4cfF7072eb478) | + \ No newline at end of file diff --git a/docs/deployed-addresses/1-eUSD.md b/docs/deployed-addresses/1-eUSD.md new file mode 100644 index 0000000000..d07209e433 --- /dev/null +++ b/docs/deployed-addresses/1-eUSD.md @@ -0,0 +1,24 @@ +# [eUSD (Electronic Dollar)](https://etherscan.io/address/0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F) +## Component Addresses +| Contract | Address | Implementation | Version | +| --- | --- | --- | --- | +| RToken | [0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F](https://etherscan.io/address/0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F) | [0x5643d5ac6b79ae8467cf2f416da6d465d8e7d9c1](https://etherscan.io/address/0x5643d5ac6b79ae8467cf2f416da6d465d8e7d9c1#code) | 2.1.0 | +| Main | [0x7697aE4dEf3C3Cd52493Ba3a6F57fc6d8c59108a](https://etherscan.io/address/0x7697aE4dEf3C3Cd52493Ba3a6F57fc6d8c59108a) | [0x143c35bfe04720394ebd18abeca83ea9d8bede2f](https://etherscan.io/address/0x143c35bfe04720394ebd18abeca83ea9d8bede2f#code) | 2.0.0 | +| AssetRegistry | [0x9B85aC04A09c8C813c37de9B3d563C2D3F936162](https://etherscan.io/address/0x9B85aC04A09c8C813c37de9B3d563C2D3F936162) | [0x5a004f70b2450e909b4048050c585549ab8afeb8](https://etherscan.io/address/0x5a004f70b2450e909b4048050c585549ab8afeb8#code) | 2.0.0 | +| BackingManager | [0xF014FEF41cCB703975827C8569a3f0940cFD80A4](https://etherscan.io/address/0xF014FEF41cCB703975827C8569a3f0940cFD80A4) | [0xa0d4b6ad503e776457dbf4695d462ddf8621a1cc](https://etherscan.io/address/0xa0d4b6ad503e776457dbf4695d462ddf8621a1cc#code) | 2.0.0 | +| BasketHandler | [0x6d309297ddDFeA104A6E89a132e2f05ce3828e07](https://etherscan.io/address/0x6d309297ddDFeA104A6E89a132e2f05ce3828e07) | [0x5c13b3b6f40ad4bf7aa4793f844ba24e85482030](https://etherscan.io/address/0x5c13b3b6f40ad4bf7aa4793f844ba24e85482030#code) | 2.1.0 | +| Broker | [0x90EB22A31b69C29C34162E0E9278cc0617aA2B50](https://etherscan.io/address/0x90EB22A31b69C29C34162E0E9278cc0617aA2B50) | [0x89209a52d085d975b14555f3e828f43fb7eaf3b7](https://etherscan.io/address/0x89209a52d085d975b14555f3e828f43fb7eaf3b7#code) | 2.1.0 | +| RSRTrader | [0xE04C26F68E0657d402FA95377aa7a2838D6cBA6f](https://etherscan.io/address/0xE04C26F68E0657d402FA95377aa7a2838D6cBA6f) | [0xe5bd2249118b6a4b39be195951579dc9af05029a](https://etherscan.io/address/0xe5bd2249118b6a4b39be195951579dc9af05029a#code) | 2.0.0 | +| RTokenTrader | [0x3d5EbB5399243412c7e895a7AA468c7cD4b1014A](https://etherscan.io/address/0x3d5EbB5399243412c7e895a7AA468c7cD4b1014A) | [0xe5bd2249118b6a4b39be195951579dc9af05029a](https://etherscan.io/address/0xe5bd2249118b6a4b39be195951579dc9af05029a#code) | 2.0.0 | +| Distributor | [0x8a77980f82A1d537600891D782BCd8bd41B85472](https://etherscan.io/address/0x8a77980f82A1d537600891D782BCd8bd41B85472) | [0xc78c5a84f30317b5f7d87170ec21dc73df38d569](https://etherscan.io/address/0xc78c5a84f30317b5f7d87170ec21dc73df38d569#code) | 2.0.0 | +| Furnace | [0x57084b3a6317bea01bA8f7c582eD033d9345c2B2](https://etherscan.io/address/0x57084b3a6317bea01bA8f7c582eD033d9345c2B2) | [0x393002573ea4a3d74a80f3b1af436a3ee3a30c96](https://etherscan.io/address/0x393002573ea4a3d74a80f3b1af436a3ee3a30c96#code) | 2.0.0 | +| StRSR | [0x18ba6e33ceb80f077DEb9260c9111e62f21aE7B8](https://etherscan.io/address/0x18ba6e33ceb80f077DEb9260c9111e62f21aE7B8) | [0xfda8c62d86e426d5fb653b6c44a455bb657b693f](https://etherscan.io/address/0xfda8c62d86e426d5fb653b6c44a455bb657b693f#code) | 2.1.0 | + + +## Governance Addresses +| Contract | Address | Implementation | Version | +| --- | --- | --- | --- | +| Governor Alexios | [0x7e880d8bD9c9612D6A9759F96aCD23df4A4650E6](https://etherscan.io/address/0x7e880d8bD9c9612D6A9759F96aCD23df4A4650E6) | [](https://etherscan.io/address/#code) | 1 | +| Timelock | [0xc8Ee187A5e5c9dC9b42414Ddf861FFc615446a2c](https://etherscan.io/address/0xc8Ee187A5e5c9dC9b42414Ddf861FFc615446a2c) | [](https://etherscan.io/address/#code) | N/A | + + \ No newline at end of file diff --git a/docs/deployed-addresses/1-hyUSD.md b/docs/deployed-addresses/1-hyUSD.md new file mode 100644 index 0000000000..7d23c9ae09 --- /dev/null +++ b/docs/deployed-addresses/1-hyUSD.md @@ -0,0 +1,24 @@ +# [hyUSD (High Yield USD)](https://etherscan.io/address/0xaCdf0DBA4B9839b96221a8487e9ca660a48212be) +## Component Addresses +| Contract | Address | Implementation | Version | +| --- | --- | --- | --- | +| RToken | [0xaCdf0DBA4B9839b96221a8487e9ca660a48212be](https://etherscan.io/address/0xaCdf0DBA4B9839b96221a8487e9ca660a48212be) | [0x5643d5ac6b79ae8467cf2f416da6d465d8e7d9c1](https://etherscan.io/address/0x5643d5ac6b79ae8467cf2f416da6d465d8e7d9c1#code) | 2.1.0 | +| Main | [0x2cabaa8010b3fbbDEeBe4a2D0fEffC2ed155bf37](https://etherscan.io/address/0x2cabaa8010b3fbbDEeBe4a2D0fEffC2ed155bf37) | [0x143c35bfe04720394ebd18abeca83ea9d8bede2f](https://etherscan.io/address/0x143c35bfe04720394ebd18abeca83ea9d8bede2f#code) | 2.0.0 | +| AssetRegistry | [0xaCacddeE9b900b7535B13Cd8662df130265b8c78](https://etherscan.io/address/0xaCacddeE9b900b7535B13Cd8662df130265b8c78) | [0x5a004f70b2450e909b4048050c585549ab8afeb8](https://etherscan.io/address/0x5a004f70b2450e909b4048050c585549ab8afeb8#code) | 2.0.0 | +| BackingManager | [0x61691c4181F876Dd7e19D6742B367B48AA280ed3](https://etherscan.io/address/0x61691c4181F876Dd7e19D6742B367B48AA280ed3) | [0xa0d4b6ad503e776457dbf4695d462ddf8621a1cc](https://etherscan.io/address/0xa0d4b6ad503e776457dbf4695d462ddf8621a1cc#code) | 2.0.0 | +| BasketHandler | [0x9119DB28432bd97aBF4c3D81B929849e0490c7A6](https://etherscan.io/address/0x9119DB28432bd97aBF4c3D81B929849e0490c7A6) | [0x5c13b3b6f40ad4bf7aa4793f844ba24e85482030](https://etherscan.io/address/0x5c13b3b6f40ad4bf7aa4793f844ba24e85482030#code) | 2.1.0 | +| Broker | [0x44344ca9014BE4bB622037224d107493586f35ed](https://etherscan.io/address/0x44344ca9014BE4bB622037224d107493586f35ed) | [0x89209a52d085d975b14555f3e828f43fb7eaf3b7](https://etherscan.io/address/0x89209a52d085d975b14555f3e828f43fb7eaf3b7#code) | 2.1.0 | +| RSRTrader | [0x0771301d56Eb734a5F61d275Da1b6c2459a00dc7](https://etherscan.io/address/0x0771301d56Eb734a5F61d275Da1b6c2459a00dc7) | [0xe5bd2249118b6a4b39be195951579dc9af05029a](https://etherscan.io/address/0xe5bd2249118b6a4b39be195951579dc9af05029a#code) | 2.0.0 | +| RTokenTrader | [0x4886f5549d3b25adCFaC68E40062c735faf81378](https://etherscan.io/address/0x4886f5549d3b25adCFaC68E40062c735faf81378) | [0xe5bd2249118b6a4b39be195951579dc9af05029a](https://etherscan.io/address/0xe5bd2249118b6a4b39be195951579dc9af05029a#code) | 2.0.0 | +| Distributor | [0x0297941cCB71f5595072C4fA34CE443b6C5b47A0](https://etherscan.io/address/0x0297941cCB71f5595072C4fA34CE443b6C5b47A0) | [0xc78c5a84f30317b5f7d87170ec21dc73df38d569](https://etherscan.io/address/0xc78c5a84f30317b5f7d87170ec21dc73df38d569#code) | 2.0.0 | +| Furnace | [0x43D806BB6cDfA1dde1D1754c5F2Ea28adC3bc0E8](https://etherscan.io/address/0x43D806BB6cDfA1dde1D1754c5F2Ea28adC3bc0E8) | [0x393002573ea4a3d74a80f3b1af436a3ee3a30c96](https://etherscan.io/address/0x393002573ea4a3d74a80f3b1af436a3ee3a30c96#code) | 2.0.0 | +| StRSR | [0x7Db3C57001c80644208fb8AA81bA1200C7B0731d](https://etherscan.io/address/0x7Db3C57001c80644208fb8AA81bA1200C7B0731d) | [0xfda8c62d86e426d5fb653b6c44a455bb657b693f](https://etherscan.io/address/0xfda8c62d86e426d5fb653b6c44a455bb657b693f#code) | 2.1.0 | + + +## Governance Addresses +| Contract | Address | Implementation | Version | +| --- | --- | --- | --- | +| Governor Alexios | [0x22d7937438b4bBf02f6cA55E3831ABB94Bd0b6f1](https://etherscan.io/address/0x22d7937438b4bBf02f6cA55E3831ABB94Bd0b6f1) | [](https://etherscan.io/address/#code) | 1 | +| Timelock | [0x624f9f076ED42ba3B37C3011dC5a1761C2209E1C](https://etherscan.io/address/0x624f9f076ED42ba3B37C3011dC5a1761C2209E1C) | [](https://etherscan.io/address/#code) | N/A | + + \ No newline at end of file diff --git a/docs/deployment-variables.md b/docs/deployment-variables.md index 30a80c383b..5671f02154 100644 --- a/docs/deployment-variables.md +++ b/docs/deployment-variables.md @@ -41,9 +41,9 @@ Dimension: `{1}` The `rewardRatio` is the fraction of the current reward amount that should be handed out per block. -Default value: `68764601000000` = a half life of 14 days. +Default value: `6876460100000` = a half life of 14 days. -Mainnet reasonable range: 1e11 to 1e13 +Mainnet reasonable range: 1e12 to 1e14 To calculate: `ln(2) / (60*60*24*desired_days_in_half_life/12)`, and then multiply by 1e18. diff --git a/docs/mev.md b/docs/mev.md index 7d32e64687..f0579c8c3b 100644 --- a/docs/mev.md +++ b/docs/mev.md @@ -51,6 +51,8 @@ To participate: 5. Wait until the desired block is reached (hopefully not in the first 40% of the auction) 6. Call `bid()`. If someone else completes the auction first, this will revert with the error message "bid already received". Approvals do not have to be revoked in the event that another MEV searcher wins the auction. (Though ideally the searcher includes the approval in the same tx they `bid()`) +For a sample price curve, see [docs/system-design.md](./system-design.md#sample-price-curve) + #### GnosisTrade `GnosisTrade.sol` implements a batch auction on top of Gnosis's [EasyAuction](https://github.com/gnosis/ido-contracts/blob/main/contracts/EasyAuction.sol) platform. In general a batch auction is designed to minimize MEV, and indeed that's why it was chosen in the first place. Both types of auctions (batch + dutch) can be opened at anytime, but the expectation is that dutch auctions will be preferred by MEV searchers because they are more likely to be profitable. diff --git a/docs/plugin-addresses.md b/docs/plugin-addresses.md index 4771abe3b6..15c2b973f5 100644 --- a/docs/plugin-addresses.md +++ b/docs/plugin-addresses.md @@ -4,45 +4,55 @@ Following are the addresses of non-collateral asset plugins. | Plugin | Feed | Underlying | | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| [COMP](https://etherscan.io/address/0x159Af360D99b3dd6c4a47Cd08b730Ff7C9d113CC) | [0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5](https://etherscan.io/address/0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5) | [0xc00e94Cb662C3520282E6f5717214004A7f26888](https://etherscan.io/address/0xc00e94Cb662C3520282E6f5717214004A7f26888) | -| [stkAAVE](https://etherscan.io/address/0x5cAF60bf01A5ecd436b2Cd0b68e4c04547eCb872) | [0x547a514d5e3769680Ce22B2361c10Ea13619e8a9](https://etherscan.io/address/0x547a514d5e3769680Ce22B2361c10Ea13619e8a9) | [0x4da27a545c0c5B758a6BA100e3a049001de870f5](https://etherscan.io/address/0x4da27a545c0c5B758a6BA100e3a049001de870f5) | -| [CRV](https://etherscan.io/address/0x3752098adf2C9E1E17e48D9cE2Ea48961905064A) | [0xCd627aA160A6fA45Eb793D19Ef54f5062F20f33f](https://etherscan.io/address/0xCd627aA160A6fA45Eb793D19Ef54f5062F20f33f) | [0xD533a949740bb3306d119CC777fa900bA034cd52](https://etherscan.io/address/0xD533a949740bb3306d119CC777fa900bA034cd52) | -| [CVX](https://etherscan.io/address/0xbE301280e593d1665A2D54DA65687E92f46D5c44) | [0xd962fC30A72A84cE50161031391756Bf2876Af5D](https://etherscan.io/address/0xd962fC30A72A84cE50161031391756Bf2876Af5D) | [0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B](https://etherscan.io/address/0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B) | +| [COMP](https://etherscan.io/address/0xCFA67f42A0fDe4F0Fb612ea5e66170B0465B84c1) | [0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5](https://etherscan.io/address/0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5) | [0xc00e94Cb662C3520282E6f5717214004A7f26888](https://etherscan.io/address/0xc00e94Cb662C3520282E6f5717214004A7f26888) | +| [stkAAVE](https://etherscan.io/address/0x6647c880Eb8F57948AF50aB45fca8FE86C154D24) | [0x547a514d5e3769680Ce22B2361c10Ea13619e8a9](https://etherscan.io/address/0x547a514d5e3769680Ce22B2361c10Ea13619e8a9) | [0x4da27a545c0c5B758a6BA100e3a049001de870f5](https://etherscan.io/address/0x4da27a545c0c5B758a6BA100e3a049001de870f5) | +| [CRV](https://etherscan.io/address/0x45B950AF443281c5F67c2c7A1d9bBc325ECb8eEA) | [0xCd627aA160A6fA45Eb793D19Ef54f5062F20f33f](https://etherscan.io/address/0xCd627aA160A6fA45Eb793D19Ef54f5062F20f33f) | [0xD533a949740bb3306d119CC777fa900bA034cd52](https://etherscan.io/address/0xD533a949740bb3306d119CC777fa900bA034cd52) | +| [CVX](https://etherscan.io/address/0x4024c00bBD0C420E719527D88781bc1543e63dd5) | [0xd962fC30A72A84cE50161031391756Bf2876Af5D](https://etherscan.io/address/0xd962fC30A72A84cE50161031391756Bf2876Af5D) | [0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B](https://etherscan.io/address/0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B) | ## Collateral Plugin Addresses Following are the addresses and configuration parameters of collateral plugins deployed to mainnet. -| Plugin | Tolerance | Delay (hrs) | Oracle(s) | Underlying | -| ---------------------------------------------------------------------------------------- | --------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| [DAI](https://etherscan.io/address/0xB03A029FF70d7c4c53bb3C4288a87aCFea0Ee8FE) | 1.25% | 24 | [0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9](https://etherscan.io/address/0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9) | [0x6B175474E89094C44Da98b954EedeAC495271d0F](https://etherscan.io/address/0x6B175474E89094C44Da98b954EedeAC495271d0F) | -| [USDC](https://etherscan.io/address/0x951d32B449D5D5cE53DA3a5C1E22b37ec0f2E387) | 1.25% | 24 | [0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) | [0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48](https://etherscan.io/address/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) | -| [USDT](https://etherscan.io/address/0x7fc1c34782888a076d3c88c0cce27b75892ee85d) | 1.25% | 24 | [0x3E7d1eAB13ad0104d2750B8863b489D65364e32D](https://etherscan.io/address/0x3E7d1eAB13ad0104d2750B8863b489D65364e32D) | [0xdAC17F958D2ee523a2206206994597C13D831ec7](https://etherscan.io/address/0xdAC17F958D2ee523a2206206994597C13D831ec7) | -| [USDP](https://etherscan.io/address/0xeD67e489E7aA622380288557FABfA6Be246dE776) | 2% | 24 | [0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3](https://etherscan.io/address/0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3) | [0x8E870D67F660D95d5be530380D0eC0bd388289E1](https://etherscan.io/address/0x8E870D67F660D95d5be530380D0eC0bd388289E1) | -| [TUSD](https://etherscan.io/address/0x9cCc7B600F80ed6F3d997698e01301D9016F8656) | 1.3% | 24 | [0xec746eCF986E2927Abd291a2A1716c940100f8Ba](https://etherscan.io/address/0xec746eCF986E2927Abd291a2A1716c940100f8Ba) | [0x0000000000085d4780B73119b644AE5ecd22b376](https://etherscan.io/address/0x0000000000085d4780B73119b644AE5ecd22b376) | -| [BUSD](https://etherscan.io/address/0x07cDEA861B2A231e249E220A553D9A38ba7383D6) | 1.5% | 24 | [0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A](https://etherscan.io/address/0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A) | [0x4Fabb145d64652a948d72533023f6E7A623C7C53](https://etherscan.io/address/0x4Fabb145d64652a948d72533023f6E7A623C7C53) | -| [aDAI](https://etherscan.io/address/0x2cAF7BB8C9651377cc7DBd8dc297b58F67D8A816) | 1.25% | 24 | [0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9](https://etherscan.io/address/0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9) | [0xF6147b4B44aE6240F7955803B2fD5E15c77bD7ea](https://etherscan.io/address/0xF6147b4B44aE6240F7955803B2fD5E15c77bD7ea) | -| [aUSDC](https://etherscan.io/address/0xE19ae8D1f3FFf987aaEaa65248BAB3A0d1FDC809) | 1.25% | 24 | [0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) | [0x60C384e226b120d93f3e0F4C502957b2B9C32B15](https://etherscan.io/address/0x60C384e226b120d93f3e0F4C502957b2B9C32B15) | -| [aUSDT](https://etherscan.io/address/0x44AB1cB3C9f25A928E39A4eDE3CA08B52b4cdE24) | 1.25% | 24 | [0x3E7d1eAB13ad0104d2750B8863b489D65364e32D](https://etherscan.io/address/0x3E7d1eAB13ad0104d2750B8863b489D65364e32D) | [0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9](https://etherscan.io/address/0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9) | -| [aBUSD](https://etherscan.io/address/0x002835840A6CB5dd3f73e78A21eF41db4C66948e) | 1.5% | 24 | [0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A](https://etherscan.io/address/0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A) | [0x83DAc0593BD7dE8fa7137D65Fb898B7b7FF6ede6](https://etherscan.io/address/0x83DAc0593BD7dE8fa7137D65Fb898B7b7FF6ede6) | -| [aUSDP](https://etherscan.io/address/0x50f4991BE43a631f5BEDB5C39e45FF3E57Fa783e) | 2% | 24 | [0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3](https://etherscan.io/address/0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3) | [0x0Ab24b246f80da96e4f826684218BdaA7E61F2a5](https://etherscan.io/address/0x0Ab24b246f80da96e4f826684218BdaA7E61F2a5) | -| [cDAI](https://etherscan.io/address/0xe11b8943b6C9abfc9D729306029f7401205bAa9B) | 1.25% | 24 | [0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9](https://etherscan.io/address/0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9) | [0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643](https://etherscan.io/address/0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643) | -| [cUSDC](https://etherscan.io/address/0x7FC2df2B27220D9F23Fbd8C21b1f7b0CaEB6fE15) | 1.25% | 24 | [0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) | [0x39AA39c021dfbaE8faC545936693aC917d5E7563](https://etherscan.io/address/0x39AA39c021dfbaE8faC545936693aC917d5E7563) | -| [cUSDT](https://etherscan.io/address/0x1F1941eE0B3CCb4Ff2135D31103C59F2E53C34B5) | 1.25% | 24 | [0x3E7d1eAB13ad0104d2750B8863b489D65364e32D](https://etherscan.io/address/0x3E7d1eAB13ad0104d2750B8863b489D65364e32D) | [0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9](https://etherscan.io/address/0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9) | -| [cUSDP](https://etherscan.io/address/0xD9438B058Ce83925E4AC0834744fC0b573A7AFbB) | 2% | 24 | [0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3](https://etherscan.io/address/0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3) | [0x041171993284df560249B57358F931D9eB7b925D](https://etherscan.io/address/0x041171993284df560249B57358F931D9eB7b925D) | -| [cWBTC](https://etherscan.io/address/0xC3481edefE16599701940a71B7a488605803D4cB) | 3.51% | 24 | [0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23](https://etherscan.io/address/0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23) | [0xccF4429DB6322D5C611ee964527D42E5d685DD6a](https://etherscan.io/address/0xccF4429DB6322D5C611ee964527D42E5d685DD6a) | -| [cETH](https://etherscan.io/address/0xA88304757c00D45b24eea13568bd346C4a49053C) | % | 0 | [0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419](https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419) | [0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5](https://etherscan.io/address/0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5) | -| [WBTC](https://etherscan.io/address/0xe9c6bF8536e2Af014a54651F0dd6c74A18D13e70) | 3.51% | 24 | [0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23](https://etherscan.io/address/0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23) | [0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599](https://etherscan.io/address/0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599) | -| [WETH](https://etherscan.io/address/0xBd941FA60b6E2AcCa15afB8962f6B4795c848b8D) | | 0 | [0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419](https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419) | [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2](https://etherscan.io/address/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) | -| [EURT](https://etherscan.io/address/0x14d5b63e8FfDDDB590C88d9A258461CbEfbB8d56) | 3% | 24 | [0x01D391A48f4F7339aC64CA2c83a07C22F95F587a](https://etherscan.io/address/0x01D391A48f4F7339aC64CA2c83a07C22F95F587a) | [0xC581b735A1688071A1746c968e0798D642EDE491](https://etherscan.io/address/0xC581b735A1688071A1746c968e0798D642EDE491) | -| [wstETH](https://etherscan.io/address/0x3879C820c3cC4547Cb76F8dC842005946Cedb385) | 15% | 24 | [0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8](https://etherscan.io/address/0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8) | [0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0](https://etherscan.io/address/0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0) | -| [rETH](https://etherscan.io/address/0xD2270A3E17DBeA5Cb491E0120441bFD0177Da913) | 15% | 24 | [0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419](https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419) | [0xae78736Cd615f374D3085123A210448E74Fc6393](https://etherscan.io/address/0xae78736Cd615f374D3085123A210448E74Fc6393) | -| [fUSDC](https://etherscan.io/address/0x1289a753e0BaE82CF7f87747f22Eaf8E4eb7C216) | 1.25% | 24 | [0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) | [0x465a5a630482f3abD6d3b84B39B29b07214d19e5](https://etherscan.io/address/0x465a5a630482f3abD6d3b84B39B29b07214d19e5) | -| [fUSDT](https://etherscan.io/address/0x5F471bDE4950CdB00714A6dD033cA7f912a4f9Ee) | 1.25% | 24 | [0x3E7d1eAB13ad0104d2750B8863b489D65364e32D](https://etherscan.io/address/0x3E7d1eAB13ad0104d2750B8863b489D65364e32D) | [0x81994b9607e06ab3d5cF3AffF9a67374f05F27d7](https://etherscan.io/address/0x81994b9607e06ab3d5cF3AffF9a67374f05F27d7) | -| [fDAI](https://etherscan.io/address/0xA4410B71033fFE8fA41c6096332Be58E3641326d) | 1.25% | 24 | [0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9](https://etherscan.io/address/0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9) | [0xe2bA8693cE7474900A045757fe0efCa900F6530b](https://etherscan.io/address/0xe2bA8693cE7474900A045757fe0efCa900F6530b) | -| [fFRAX](https://etherscan.io/address/0xcd46Ff27c0d6F088FB94896dcE8F17491BD84c75) | 2% | 24 | [0xB9E1E3A9feFf48998E45Fa90847ed4D467E8BcfD](https://etherscan.io/address/0xB9E1E3A9feFf48998E45Fa90847ed4D467E8BcfD) | [0x1C9A2d6b33B4826757273D47ebEe0e2DddcD978B](https://etherscan.io/address/0x1C9A2d6b33B4826757273D47ebEe0e2DddcD978B) | -| [cUSDCv3](https://etherscan.io/address/0x615D92fAF203Faa9ea7a4D8cdDC49b2Ad0702a1f) | 1.25% | 24 | [0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) | [0x45fd57EFd43f9Cf96859e38C15380A822C3c2352](https://etherscan.io/address/0x45fd57EFd43f9Cf96859e38C15380A822C3c2352) | -| [cvx3Pool](https://etherscan.io/address/0x14548a0aEcA46418cD9cFd08c6Bf8E02FbE53B5E) | 2% | 24 | [USDC: 0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) [DAI: 0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9](https://etherscan.io/address/0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9) [USDT: 0x3E7d1eAB13ad0104d2750B8863b489D65364e32D](https://etherscan.io/address/0x3E7d1eAB13ad0104d2750B8863b489D65364e32D) | [0x3d08EF64830137FBd426CBe3153a404104E4b103](https://etherscan.io/address/0x3d08EF64830137FBd426CBe3153a404104E4b103) | -| [cvxTriCrypto](https://etherscan.io/address/0xb2EeD19C381b71d0f54327D61596312144f66fA7) | 2% | 24 | [USDT: 0x3E7d1eAB13ad0104d2750B8863b489D65364e32D](https://etherscan.io/address/0x3E7d1eAB13ad0104d2750B8863b489D65364e32D) [ETH: 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419](https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419) [BTC: 0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23](https://etherscan.io/address/0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23) | [0xF68F5cde346729ADB14a89402605a26c5C8Bf028](https://etherscan.io/address/0xF68F5cde346729ADB14a89402605a26c5C8Bf028) | -| [cvxeUSDFRAXBP](https://etherscan.io/address/0x8DC1750B1fe69e940f570c021d658C14D8041834) | 2% | 72 | [eUSD: 0x6E3B6b31c910253fEf7314b4247823bf18d174d9](https://etherscan.io/address/0x6E3B6b31c910253fEf7314b4247823bf18d174d9)) [USDC: 0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) [fFRAX: 0xB9E1E3A9feFf48998E45Fa90847ed4D467E8BcfD](https://etherscan.io/address/0xB9E1E3A9feFf48998E45Fa90847ed4D467E8BcfD) | [0x83cD6Bd8591Ac6090Bd336C96e61062C103F0AD9](https://etherscan.io/address/0x83cD6Bd8591Ac6090Bd336C96e61062C103F0AD9) | -| [cvxMIM3Pool](https://etherscan.io/address/0xD5BE0AeC2b537481A4fE2EcF52422a24644e1EF3) | 6.25% | 24 | [MIM: 0x7A364e8770418566e3eb2001A96116E6138Eb32F](https://etherscan.io/address/0x7A364e8770418566e3eb2001A96116E6138Eb32F) [USDC: 0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) [DAI: 0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9](https://etherscan.io/address/0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9) [USDT: 0x3E7d1eAB13ad0104d2750B8863b489D65364e32D](https://etherscan.io/address/0x3E7d1eAB13ad0104d2750B8863b489D65364e32D) | [0x1B05624Bd47d0C69cFf2A4ae7Ef139A8166213ed](https://etherscan.io/address/0x1B05624Bd47d0C69cFf2A4ae7Ef139A8166213ed) | +| Plugin | Tolerance | Delay (hrs) | Oracle(s) | Underlying | +| ---------------------------------------------------------------------------------------- | --------- | ----------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| [DAI](https://etherscan.io/address/0xf7d1C6eE4C0D84C6B530D53A897daa1E9eB56833) | 1.25% | 24 | [0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9](https://etherscan.io/address/0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9) | [0x6B175474E89094C44Da98b954EedeAC495271d0F](https://etherscan.io/address/0x6B175474E89094C44Da98b954EedeAC495271d0F) | +| [USDC](https://etherscan.io/address/0xBE9D23040fe22E8Bd8A88BF5101061557355cA04) | 1.25% | 24 | [0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) | [0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48](https://etherscan.io/address/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) | +| [USDT](https://etherscan.io/address/0x58D7bF13D3572b08dE5d96373b8097d94B1325ad) | 1.25% | 24 | [0x3E7d1eAB13ad0104d2750B8863b489D65364e32D](https://etherscan.io/address/0x3E7d1eAB13ad0104d2750B8863b489D65364e32D) | [0xdAC17F958D2ee523a2206206994597C13D831ec7](https://etherscan.io/address/0xdAC17F958D2ee523a2206206994597C13D831ec7) | +| [USDP](https://etherscan.io/address/0x2f98bA77a8ca1c630255c4517b1b3878f6e60C89) | 2.0% | 24 | [0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3](https://etherscan.io/address/0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3) | [0x8E870D67F660D95d5be530380D0eC0bd388289E1](https://etherscan.io/address/0x8E870D67F660D95d5be530380D0eC0bd388289E1) | +| [TUSD](https://etherscan.io/address/0x7F9999B2C9D310a5f48dfD070eb5129e1e8565E2) | 1.3% | 24 | [0xec746eCF986E2927Abd291a2A1716c940100f8Ba](https://etherscan.io/address/0xec746eCF986E2927Abd291a2A1716c940100f8Ba) | [0x0000000000085d4780B73119b644AE5ecd22b376](https://etherscan.io/address/0x0000000000085d4780B73119b644AE5ecd22b376) | +| [BUSD](https://etherscan.io/address/0xCBcd605088D5A5Da9ceEb3618bc01BFB87387423) | 1.5% | 24 | [0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A](https://etherscan.io/address/0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A) | [0x4Fabb145d64652a948d72533023f6E7A623C7C53](https://etherscan.io/address/0x4Fabb145d64652a948d72533023f6E7A623C7C53) | +| [aDAI](https://etherscan.io/address/0x256b89658bD831CC40283F42e85B1fa8973Db0c9) | 1.25% | 24 | [0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9](https://etherscan.io/address/0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9) | [0xafd16aFdE22D42038223A6FfDF00ee49c8fDa985](https://etherscan.io/address/0xafd16aFdE22D42038223A6FfDF00ee49c8fDa985) | +| [aUSDC](https://etherscan.io/address/0x7cd9ca6401f743b38b3b16ea314bbab8e9c1ac51) | 1.25% | 24 | [0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) | [0x60C384e226b120d93f3e0F4C502957b2B9C32B15](https://etherscan.io/address/0x60C384e226b120d93f3e0F4C502957b2B9C32B15) | +| [aUSDT](https://etherscan.io/address/0xe39188ddd4eb27d1d25f5f58cc6a5fd9228eedef) | 1.25% | 24 | [0x3E7d1eAB13ad0104d2750B8863b489D65364e32D](https://etherscan.io/address/0x3E7d1eAB13ad0104d2750B8863b489D65364e32D) | [0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9](https://etherscan.io/address/0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9) | +| [aBUSD](https://etherscan.io/address/0xeB1A036E83aD95f0a28d0c8E2F20bf7f1B299F05) | 1.5% | 24 | [0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A](https://etherscan.io/address/0x833D8Eb16D306ed1FbB5D7A2E019e106B960965A) | [0xe639d53Aa860757D7fe9cD4ebF9C8b92b8DedE7D](https://etherscan.io/address/0xe639d53Aa860757D7fe9cD4ebF9C8b92b8DedE7D) | +| [aUSDP](https://etherscan.io/address/0x0d61Ce1801A460eB683b5ed1b6C7965d31b769Fd) | 2.0% | 24 | [0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3](https://etherscan.io/address/0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3) | [0x80A574cC2B369dc496af6655f57a16a4f180BfAF](https://etherscan.io/address/0x80A574cC2B369dc496af6655f57a16a4f180BfAF) | +| [cDAI](https://etherscan.io/address/0x440A634DdcFb890BCF8b0Bf07Ef2AaBB37dd5F8C) | 1.25% | 24 | [0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9](https://etherscan.io/address/0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9) | [0x3043be171e846c33D5f06864Cc045d9Fc799aF52](https://etherscan.io/address/0x3043be171e846c33D5f06864Cc045d9Fc799aF52) | +| [cUSDC](https://etherscan.io/address/0x50a9d529EA175CdE72525Eaa809f5C3c47dAA1bB) | 1.25% | 24 | [0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) | [0xf579F9885f1AEa0d3F8bE0F18AfED28c92a43022](https://etherscan.io/address/0xf579F9885f1AEa0d3F8bE0F18AfED28c92a43022) | +| [cUSDT](https://etherscan.io/address/0x5757fF814da66a2B4f9D11d48570d742e246CfD9) | 1.25% | 24 | [0x3E7d1eAB13ad0104d2750B8863b489D65364e32D](https://etherscan.io/address/0x3E7d1eAB13ad0104d2750B8863b489D65364e32D) | [0x4Be33630F92661afD646081BC29079A38b879aA0](https://etherscan.io/address/0x4Be33630F92661afD646081BC29079A38b879aA0) | +| [cUSDP](https://etherscan.io/address/0x99bD63BF7e2a69822cD73A82d42cF4b5501e5E50) | 2.0% | 24 | [0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3](https://etherscan.io/address/0x09023c0DA49Aaf8fc3fA3ADF34C6A7016D38D5e3) | [0xF69c995129CC16d0F577C303091a400cC1879fFa](https://etherscan.io/address/0xF69c995129CC16d0F577C303091a400cC1879fFa) | +| [cWBTC](https://etherscan.io/address/0x688c95461d611Ecfc423A8c87caCE163C6B40384) | 3.51% | 24 | [0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23](https://etherscan.io/address/0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23) | [0xF2A309bc36A504c772B416a4950d5d0021219745](https://etherscan.io/address/0xF2A309bc36A504c772B416a4950d5d0021219745) | +| [cETH](https://etherscan.io/address/0x357d4dB0c2179886334cC33B8528048F7E1D3Fe3) | 0.0% | 0 | [0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419](https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419) | [0xbF6E8F64547Bdec55bc3FBb0664722465FCC2F0F](https://etherscan.io/address/0xbF6E8F64547Bdec55bc3FBb0664722465FCC2F0F) | +| [WBTC](https://etherscan.io/address/0x87A959e0377C68A50b08a91ae5ab3aFA7F41ACA4) | 3.51% | 24 | [0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23](https://etherscan.io/address/0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23) | [0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599](https://etherscan.io/address/0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599) | +| [WETH](https://etherscan.io/address/0x6B87142C7e6cA80aa3E6ead0351673C45c8990e3) | 0.0% | 0 | [0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419](https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419) | [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2](https://etherscan.io/address/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) | +| [EURT](https://etherscan.io/address/0xEBD07CE38e2f46031c982136012472A4D24AE070) | 3.0% | 24 | [0x01D391A48f4F7339aC64CA2c83a07C22F95F587a](https://etherscan.io/address/0x01D391A48f4F7339aC64CA2c83a07C22F95F587a) | [0xC581b735A1688071A1746c968e0798D642EDE491](https://etherscan.io/address/0xC581b735A1688071A1746c968e0798D642EDE491) | +| [wstETH](https://etherscan.io/address/0x29F2EB4A0D3dC211BB488E9aBe12740cafBCc49C) | 2.5% | 24 | [0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8](https://etherscan.io/address/0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8) | [0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0](https://etherscan.io/address/0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0) | +| [rETH](https://etherscan.io/address/0x1103851D1FCDD3f88096fbed812c8FF01949cF9d) | 4.51% | 24 | [0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419](https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419) | [0xae78736Cd615f374D3085123A210448E74Fc6393](https://etherscan.io/address/0xae78736Cd615f374D3085123A210448E74Fc6393) | +| [fUSDC](https://etherscan.io/address/0x1FFA5955D64Ee32cB1BF7104167b81bb085b0c8d) | 1.25% | 24 | [0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) | [0x6D05CB2CB647B58189FA16f81784C05B4bcd4fe9](https://etherscan.io/address/0x6D05CB2CB647B58189FA16f81784C05B4bcd4fe9) | +| [fUSDT](https://etherscan.io/address/0xF73EB45d83AC86f8a6F75a6252ca1a59a9A3aED3) | 1.25% | 24 | [0x3E7d1eAB13ad0104d2750B8863b489D65364e32D](https://etherscan.io/address/0x3E7d1eAB13ad0104d2750B8863b489D65364e32D) | [0x2837f952c1FD773B3Ce02631A90f95E4b9ce2cF7](https://etherscan.io/address/0x2837f952c1FD773B3Ce02631A90f95E4b9ce2cF7) | +| [fDAI](https://etherscan.io/address/0xE1fcCf8e23713Ed0497ED1a0E6Ae2b19ED443eCd) | 1.25% | 24 | [0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9](https://etherscan.io/address/0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9) | [0x714341800AD1913B5FCCBFd5d136553Ad1C314d6](https://etherscan.io/address/0x714341800AD1913B5FCCBFd5d136553Ad1C314d6) | +| [fFRAX](https://etherscan.io/address/0x8b06c065b4b44B310442d4ee98777BF7a1EBC6E3) | 2.0% | 24 | [0xB9E1E3A9feFf48998E45Fa90847ed4D467E8BcfD](https://etherscan.io/address/0xB9E1E3A9feFf48998E45Fa90847ed4D467E8BcfD) | [0x55590a1Bf90fbf7352A46c4af652A231AA5CbF13](https://etherscan.io/address/0x55590a1Bf90fbf7352A46c4af652A231AA5CbF13) | +| [cUSDCv3](https://etherscan.io/address/0x85b256e9051B781A0BC0A987857AD6166C94040a) | 1.25% | 24 | [0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) | [0x7e1e077b289c0153b5ceAD9F264d66215341c9Ab](https://etherscan.io/address/0x7e1e077b289c0153b5ceAD9F264d66215341c9Ab) | +| [cvx3Pool](https://etherscan.io/address/0x62C394620f674e85768a7618a6C202baE7fB8Dd1) | 2.0% | 24 | [0x0000000000000000000000000000000000000001](https://etherscan.io/address/0x0000000000000000000000000000000000000001) | [0xaBd7E7a5C846eD497681a590feBED99e7157B6a3](https://etherscan.io/address/0xaBd7E7a5C846eD497681a590feBED99e7157B6a3) | +| [cvxeUSDFRAXBP](https://etherscan.io/address/0x890FAa00C16EAD6AA76F18A1A7fe9C40838F9122) | 2.0% | 72 | [0x0000000000000000000000000000000000000001](https://etherscan.io/address/0x0000000000000000000000000000000000000001) | [0x3BECE5EC596331033726E5C6C188c313Ff4E3fE5](https://etherscan.io/address/0x3BECE5EC596331033726E5C6C188c313Ff4E3fE5) | +| [cvxMIM3Pool](https://etherscan.io/address/0xCBE084C44e7A2223F76362Dcc4EbDacA5Fb1cbA7) | 6.25% | 24 | [0x7A364e8770418566e3eb2001A96116E6138Eb32F](https://etherscan.io/address/0x7A364e8770418566e3eb2001A96116E6138Eb32F) | [0x9FF9c353136e86EFe02ADD177E7c9769f8a5A77F](https://etherscan.io/address/0x9FF9c353136e86EFe02ADD177E7c9769f8a5A77F) | +| [crv3Pool](https://etherscan.io/address/0x8Af118a89c5023Bb2B03C70f70c8B396aE71963D) | 2.0% | 24 | [0x0000000000000000000000000000000000000001](https://etherscan.io/address/0x0000000000000000000000000000000000000001) | [0xC9c37FC53682207844B058026024853A9C0b8c7B](https://etherscan.io/address/0xC9c37FC53682207844B058026024853A9C0b8c7B) | +| [crveUSDFRAXBP](https://etherscan.io/address/0xC87CDFFD680D57BF50De4C364BF4277B8A90098E) | 2.0% | 72 | [0x0000000000000000000000000000000000000001](https://etherscan.io/address/0x0000000000000000000000000000000000000001) | [0x27F672aAf061cb0b2640a4DFCCBd799cD1a7309A](https://etherscan.io/address/0x27F672aAf061cb0b2640a4DFCCBd799cD1a7309A) | +| [crvMIM3Pool](https://etherscan.io/address/0x14c443d8BdbE9A65F3a23FA4e199d8741D5B38Fa) | 6.25% | 24 | [0x7A364e8770418566e3eb2001A96116E6138Eb32F](https://etherscan.io/address/0x7A364e8770418566e3eb2001A96116E6138Eb32F) | [0xe8461dB45A7430AA7aB40346E68821284980FdFD](https://etherscan.io/address/0xe8461dB45A7430AA7aB40346E68821284980FdFD) | +| [sDAI](https://etherscan.io/address/0xde0e2f0c9792617d3908d92a024caa846354cea2) | 1.25% | 24 | [0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9](https://etherscan.io/address/0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9) | [0x83F20F44975D03b1b09e64809B757c47f942BEeA](https://etherscan.io/address/0x83F20F44975D03b1b09e64809B757c47f942BEeA) | +| [cbETH](https://etherscan.io/address/0x3962695aCce0Efce11cFf997890f3D1D7467ec40) | 4.51% | 24 | [0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419](https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419) | [0xBe9895146f7AF43049ca1c1AE358B0541Ea49704](https://etherscan.io/address/0xBe9895146f7AF43049ca1c1AE358B0541Ea49704) | +| [maUSDT](https://etherscan.io/address/0xd000a79bd2a07eb6d2e02ecad73437de40e52d69) | 1.25% | 24 | [0x3E7d1eAB13ad0104d2750B8863b489D65364e32D](https://etherscan.io/address/0x3E7d1eAB13ad0104d2750B8863b489D65364e32D) | [0xaA91d24c2F7DBb6487f61869cD8cd8aFd5c5Cab2](https://etherscan.io/address/0xaA91d24c2F7DBb6487f61869cD8cd8aFd5c5Cab2) | +| [maUSDC](https://etherscan.io/address/0x2304E98cD1E2F0fd3b4E30A1Bc6E9594dE2ea9b7) | 1.25% | 24 | [0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6](https://etherscan.io/address/0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) | [0x7f7B77e49d5b30445f222764a794AFE14af062eB](https://etherscan.io/address/0x7f7B77e49d5b30445f222764a794AFE14af062eB) | +| [maDAI](https://etherscan.io/address/0x9d38BFF9Af50738DF92a54Ceab2a2C2322BB1FAB) | 1.25% | 24 | [0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9](https://etherscan.io/address/0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9) | [0xE2b16e14dB6216e33082D5A8Be1Ef01DF7511bBb](https://etherscan.io/address/0xE2b16e14dB6216e33082D5A8Be1Ef01DF7511bBb) | +| [maWBTC](https://etherscan.io/address/0x49A44d50d3B1E098DAC9402c4aF8D0C0E499F250) | 3.51% | 24 | [0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c](https://etherscan.io/address/0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c) | [0xe0E1d3c6f09DA01399e84699722B11308607BBfC](https://etherscan.io/address/0xe0E1d3c6f09DA01399e84699722B11308607BBfC) | +| [maWETH](https://etherscan.io/address/0x878b995bDD2D9900BEE896Bd78ADd877672e1637) | 0.0% | 24 | [0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419](https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419) | [0x291ed25eB61fcc074156eE79c5Da87e5DA94198F](https://etherscan.io/address/0x291ed25eB61fcc074156eE79c5Da87e5DA94198F) | +| [maStETH](https://etherscan.io/address/0x33E840e5711549358f6d4D11F9Ab2896B36E9822) | 2.0025% | 24 | [0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419](https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419) | [0x97F9d5ed17A0C99B279887caD5254d15fb1B619B](https://etherscan.io/address/0x97F9d5ed17A0C99B279887caD5254d15fb1B619B) | diff --git a/docs/solidity-style.md b/docs/solidity-style.md index ad3d1a5f2e..90b386b82e 100644 --- a/docs/solidity-style.md +++ b/docs/solidity-style.md @@ -303,7 +303,7 @@ The **recommended** process to perform an upgrade is the following: - Ensure metadata of the existing/deployed implementations is created for the required network. This is located in a folder names `.openzeppelin`, which should be persisted in `git` for Production networks. This can be done for prior versions using the `upgrades/force-import.ts` task in our repository. This task is limited to be run only on Mainnet. -- Create the new implementation version of the contract. This should follow all the recommendations from the article linked above, to make sure the implementation is "Upgrade Safe". At anytime you can check for compatibility by running the `upgrades/validate-upgrade.ts` task in our repo, in a Mainnet fork. This task would compare the current code vs. a previously deployed implementation and validate if it is "upgrade safe". Make sure the MAINNET_BLOCK is set up appropiately. +- Create the new implementation version of the contract. This should follow all the recommendations from the article linked above, to make sure the implementation is "Upgrade Safe". At anytime you can check for compatibility by running the `upgrades/validate-upgrade.ts` task in our repo, in a Mainnet fork. This task would compare the current code vs. a previously deployed implementation and validate if it is "upgrade safe". Make sure the FORK_BLOCK is set up appropiately. - To deploy to Mainnet the new version, make sure you use the script provided in `scripts/deployment/phase1-common/2_deploy_implementations.ts`. If you are upgrading a previous version you need to specify the `LAST_VERSION_DEPLOYED` value at the top of the script. For new, clean deployments just leave that empty. This script will perform all validations on the new code, deploy the new implementation contracts, and register the deployment in the network file. It relies on the `deployImplementation` (for new deployments) or `prepareUpgrade` functions of the OZ Plugin. diff --git a/docs/system-design.md b/docs/system-design.md index 76c746a2db..a2116c359b 100644 --- a/docs/system-design.md +++ b/docs/system-design.md @@ -164,8 +164,176 @@ The Dutch auction occurs in two phases: Geometric/Exponential Phase (first 40% of auction): The price starts at about 1000x the best plausible price and decays down to the best plausible price following a geometric/exponential series. The price decreases by the same percentage each time. This phase is primarily defensive, and it's not expected to receive a bid; it merely protects against manipulated prices. -Linear Phase (last 60% of auction): During this phase, the price decreases linearly from the best plausible price to the worst plausible price. The worst price is further discounted based on maxTradeSlippage, which considers how far from minTradeVolume to maxTradeVolume the trade lies. No further discount is applied at maxTradeVolume. +Linear Phase (last 60% of auction): During this phase, the price decreases linearly from the best plausible price to the worst plausible price. The `dutchAuctionLength` can be configured to be any value. The suggested default is 30 minutes for a blockchain with a 12-second blocktime. At this ratio of blocktime to auction length, there is a 10.87% price drop per block during the geometric/exponential period and a 0.05% drop during the linear period. The duration of the auction can be adjusted, which will impact the size of the price decreases per block. -The "best plausible price" is equal to the exchange rate at the high price of the sell token and the low price of the buy token. The "worst-case price" is equal to the exchange rate at the low price of the sell token and the high price of the sell token, plus an additional discount ranging from 0 to `maxTradeSlippage()`. At minimum auction size the full `maxTradeSlippage()` is applied, while at max auction size no further discount is applied. +The "best plausible price" is equal to the exchange rate at the high price of the sell token and the low price of the buy token. The "worst-case price" is equal to the exchange rate at the low price of the sell token and the high price of the sell token, plus an additional discount equal to `maxTradeSlippage`. + +#### Trade violation fallback + +Dutch auctions become disabled for an asset being traded if a trade clears in the geometric phase. The rationale is that a trade that clears in this range (multiples above the plausible price) only does so because either 1) the auctioned asset's price was manipulated downwards, or 2) the bidding asset was manipulated upwards, such that the protocol accepts an unfavorable trade. All subsequent trades for that particular trading pair will be forced to use the batch auctions as a result. Dutch auctions for disabled assets must be manually re-enabled by governance. + +Take for example the scenario of an RToken basket change requiring a trade of 5M USDC for 5M USDT, where the `maxTradeSize` is $1M (therefore requiring at least 5 auctions). If the system's price inputs for USDC was manipulated to read a price of $0.001/USDC, settling the auction in the geometric phase at any multiple less than 1000x will yield a profit for the trader, at a cost to the RToken system. Accordingly, Dutch auctions become disabled for the subsequent trades to swap USDC to USDT. + +Dutch auctions for other assets that have not cleared in the geometric zone will remain enabled. + +#### Sample price curve + +This price curve is for two assets with 1% oracleError, and with a 1% maxTradeSlippage, during a 30-minute auction. The token has 6 decimals and the "even price" occurs at 100,000,000. The phase changes between different portions of the auction are shown with `============` dividers. + +``` +BigNumber { value: "102020210210" } +BigNumber { value: "82140223099" } +BigNumber { value: "66134114376" } +BigNumber { value: "53247007608" } +BigNumber { value: "42871124018" } +BigNumber { value: "34517153077" } +BigNumber { value: "27791029333" } +BigNumber { value: "22375579749" } +BigNumber { value: "18015402132" } +BigNumber { value: "14504862785" } +BigNumber { value: "11678398454" } +BigNumber { value: "9402708076" } +BigNumber { value: "7570466062" } +BigNumber { value: "6095260636" } +BigNumber { value: "4907518495" } +BigNumber { value: "3951227569" } +BigNumber { value: "3181278625" } +BigNumber { value: "2561364414" } +BigNumber { value: "2062248686" } +BigNumber { value: "1660392258" } +BigNumber { value: "1336842869" } +BigNumber { value: "1076341357" } +BigNumber { value: "866602010" } +BigNumber { value: "697733148" } +BigNumber { value: "561770617" } +BigNumber { value: "452302636" } +BigNumber { value: "364165486" } +BigNumber { value: "293203025" } +BigNumber { value: "236068538" } +BigNumber { value: "190067462" } +BigNumber { value: "153030304" } +============ +BigNumber { value: "151670034" } +BigNumber { value: "150309765" } +BigNumber { value: "148949495" } +BigNumber { value: "147589226" } +BigNumber { value: "146228957" } +BigNumber { value: "144868687" } +BigNumber { value: "143508418" } +BigNumber { value: "142148149" } +BigNumber { value: "140787879" } +BigNumber { value: "139427610" } +BigNumber { value: "138067341" } +BigNumber { value: "136707071" } +BigNumber { value: "135346802" } +BigNumber { value: "133986532" } +BigNumber { value: "132626263" } +BigNumber { value: "131265994" } +BigNumber { value: "129905724" } +BigNumber { value: "128545455" } +BigNumber { value: "127185186" } +BigNumber { value: "125824916" } +BigNumber { value: "124464647" } +BigNumber { value: "123104378" } +BigNumber { value: "121744108" } +BigNumber { value: "120383839" } +BigNumber { value: "119023570" } +BigNumber { value: "117663300" } +BigNumber { value: "116303031" } +BigNumber { value: "114942761" } +BigNumber { value: "113582492" } +BigNumber { value: "112222223" } +BigNumber { value: "110861953" } +BigNumber { value: "109501684" } +BigNumber { value: "108141415" } +BigNumber { value: "106781145" } +BigNumber { value: "105420876" } +BigNumber { value: "104060607" } +BigNumber { value: "102700337" } +============ +BigNumber { value: "101986999" } +BigNumber { value: "101920591" } +BigNumber { value: "101854183" } +BigNumber { value: "101787775" } +BigNumber { value: "101721367" } +BigNumber { value: "101654959" } +BigNumber { value: "101588551" } +BigNumber { value: "101522143" } +BigNumber { value: "101455735" } +BigNumber { value: "101389327" } +BigNumber { value: "101322919" } +BigNumber { value: "101256511" } +BigNumber { value: "101190103" } +BigNumber { value: "101123695" } +BigNumber { value: "101057287" } +BigNumber { value: "100990879" } +BigNumber { value: "100924471" } +BigNumber { value: "100858063" } +BigNumber { value: "100791655" } +BigNumber { value: "100725247" } +BigNumber { value: "100658839" } +BigNumber { value: "100592431" } +BigNumber { value: "100526023" } +BigNumber { value: "100459615" } +BigNumber { value: "100393207" } +BigNumber { value: "100326799" } +BigNumber { value: "100260391" } +BigNumber { value: "100193983" } +BigNumber { value: "100127575" } +BigNumber { value: "100061167" } +BigNumber { value: "99994759" } +BigNumber { value: "99928351" } +BigNumber { value: "99861943" } +BigNumber { value: "99795535" } +BigNumber { value: "99729127" } +BigNumber { value: "99662719" } +BigNumber { value: "99596311" } +BigNumber { value: "99529903" } +BigNumber { value: "99463496" } +BigNumber { value: "99397088" } +BigNumber { value: "99330680" } +BigNumber { value: "99264272" } +BigNumber { value: "99197864" } +BigNumber { value: "99131456" } +BigNumber { value: "99065048" } +BigNumber { value: "98998640" } +BigNumber { value: "98932232" } +BigNumber { value: "98865824" } +BigNumber { value: "98799416" } +BigNumber { value: "98733008" } +BigNumber { value: "98666600" } +BigNumber { value: "98600192" } +BigNumber { value: "98533784" } +BigNumber { value: "98467376" } +BigNumber { value: "98400968" } +BigNumber { value: "98334560" } +BigNumber { value: "98268152" } +BigNumber { value: "98201744" } +BigNumber { value: "98135336" } +BigNumber { value: "98068928" } +BigNumber { value: "98002520" } +BigNumber { value: "97936112" } +BigNumber { value: "97869704" } +BigNumber { value: "97803296" } +BigNumber { value: "97736888" } +BigNumber { value: "97670480" } +BigNumber { value: "97604072" } +BigNumber { value: "97537664" } +BigNumber { value: "97471256" } +BigNumber { value: "97404848" } +BigNumber { value: "97338440" } +BigNumber { value: "97272032" } +BigNumber { value: "97205624" } +BigNumber { value: "97139216" } +BigNumber { value: "97072808" } +============ +BigNumber { value: "97039604" } +BigNumber { value: "97039604" } +BigNumber { value: "97039604" } +BigNumber { value: "97039604" } +BigNumber { value: "97039604" } +BigNumber { value: "97039604" } +BigNumber { value: "97039604" } +``` diff --git a/hardhat.config.ts b/hardhat.config.ts index e9364399e1..7b540748d4 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -11,6 +11,7 @@ import 'solidity-coverage' import * as tenderly from '@tenderly/hardhat-tenderly' import { useEnv } from '#/utils/env' +import { forkRpcs, Network } from '#/utils/fork' import { HardhatUserConfig } from 'hardhat/types' import forkBlockNumber from '#/test/integration/fork-block-numbers' @@ -23,6 +24,7 @@ const MAINNET_RPC_URL = useEnv(['MAINNET_RPC_URL', 'ALCHEMY_MAINNET_RPC_URL']) const TENDERLY_RPC_URL = useEnv('TENDERLY_RPC_URL') const GOERLI_RPC_URL = useEnv('GOERLI_RPC_URL') const BASE_GOERLI_RPC_URL = useEnv('BASE_GOERLI_RPC_URL') +const BASE_RPC_URL = useEnv('BASE_RPC_URL') const MNEMONIC = useEnv('MNEMONIC') ?? 'test test test test test test test test test test test junk' const TIMEOUT = useEnv('SLOW') ? 6_000_000 : 600_000 @@ -36,8 +38,8 @@ const config: HardhatUserConfig = { // network for tests/in-process stuff forking: useEnv('FORK') ? { - url: MAINNET_RPC_URL, - blockNumber: Number(useEnv('MAINNET_BLOCK', forkBlockNumber['default'].toString())), + url: forkRpcs[(useEnv('FORK_NETWORK') ?? 'mainnet') as Network], + blockNumber: Number(useEnv(`FORK_BLOCK`, forkBlockNumber['default'].toString())), } : undefined, gas: 0x1ffffffff, @@ -69,6 +71,13 @@ const config: HardhatUserConfig = { mnemonic: MNEMONIC, }, }, + base: { + chainId: 8453, + url: BASE_RPC_URL, + accounts: { + mnemonic: MNEMONIC, + }, + }, mainnet: { chainId: 1, url: MAINNET_RPC_URL, @@ -76,7 +85,7 @@ const config: HardhatUserConfig = { mnemonic: MNEMONIC, }, // gasPrice: 30_000_000_000, - gasMultiplier: 1.05, // 5% buffer; seen failures on RToken deployment and asset refreshes otherwise + gasMultiplier: 2, // 100% buffer; seen failures on RToken deployment and asset refreshes otherwise }, tenderly: { chainId: 3, @@ -85,7 +94,7 @@ const config: HardhatUserConfig = { mnemonic: MNEMONIC, }, // gasPrice: 10_000_000_000, - gasMultiplier: 1.05, // 5% buffer; seen failures on RToken deployment and asset refreshes otherwise + gasMultiplier: 2, // 100% buffer; seen failures on RToken deployment and asset refreshes otherwise }, }, solidity: { @@ -133,6 +142,14 @@ const config: HardhatUserConfig = { etherscan: { apiKey: useEnv('ETHERSCAN_API_KEY'), customChains: [ + { + network: 'base', + chainId: 8453, + urls: { + apiURL: 'https://api.basescan.org/api', + browserURL: 'https://basescan.org', + }, + }, { network: 'base-goerli', chainId: 84531, diff --git a/package.json b/package.json index 1d6413e41f..c4c37617a1 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,8 @@ }, "packageManager": "yarn@3.3.1", "dependencies": { + "@aave/core-v3": "^1.18.0", + "@aave/periphery-v3": "^2.5.0", "@nomicfoundation/hardhat-toolbox": "^2.0.1", "@types/isomorphic-fetch": "^0.0.36", "isomorphic-fetch": "^3.0.0" diff --git a/scripts/addresses/84531-tmp-assets-collateral.json b/scripts/addresses/84531-tmp-assets-collateral.json index 6e8bbc2523..c7e0351b15 100644 --- a/scripts/addresses/84531-tmp-assets-collateral.json +++ b/scripts/addresses/84531-tmp-assets-collateral.json @@ -1,9 +1,9 @@ { "assets": {}, "collateral": { - "DAI": "0x89B2eF0dd1422F482617eE8B01E57ef5f778E612", - "USDC": "0x0908A3193D14064f5831cbAFc47703f001313Ff6", - "USDT": "0xB5e44CbbC77D23e4C973a27Db5AE59AcE4c46a87" + "DAI": "0xdD740A7C787B0f3500977c9e14BB9a91057e38e7", + "USDC": "0x10D7A1ED1c431Ced12888fe90acEFD898eFaf2ba", + "USDT": "0x1DdB7dfdC5D26FE1f2aD02d9972f12481346Ae9b" }, "erc20s": { "DAI": "0xDA2eA2f60545555e268124E51EA27bc97DE78E9c", diff --git a/scripts/addresses/84531-tmp-deployments.json b/scripts/addresses/84531-tmp-deployments.json index 71e44eaf32..72ffc5d6f9 100644 --- a/scripts/addresses/84531-tmp-deployments.json +++ b/scripts/addresses/84531-tmp-deployments.json @@ -4,32 +4,32 @@ "RSR_FEED": "0xbEfB78358eAaaCAa083C2dff5D2Ed6e7e32b2d3A", "GNOSIS_EASY_AUCTION": "0xcdf32E323e69090eCA17adDeF058A6A921c3e75A" }, - "tradingLib": "0x8d68d450a33ea275edE80Efc82D8cd208DAe4402", - "cvxMiningLib": "0xF64A5C1329Ad224B0E50C4640f4bBd677a5cb391", - "facadeRead": "0xe1aa15DA8b993c6312BAeD91E0b470AE405F91BF", - "facadeAct": "0x3d6D679c863858E89e35c925F937F5814ca687F3", - "facadeWriteLib": "0x29e9740275D26fdeDBb0ABA8129C74c15c393027", - "basketLib": "0x25Aa9878a97948f9908DB4325dc20e5635023Ee2", - "facadeWrite": "0x0903048fD4E948c60451B41A48B35E0bafc0967F", - "deployer": "0xf1B06c2305445E34CF0147466352249724c2EAC1", - "rsrAsset": "0x23b57479327f9BccE6A1F6Be65F3dAa3C9Db797B", + "tradingLib": "0x662608C6dDb54C426899126bEEF96011fB700a3A", + "cvxMiningLib": "0xf584f06759d0E32f74C5B18C9Dcbd03503936518", + "facadeRead": "0x6490D66B17A1E9a460Ab54131165C8F921aCcDeB", + "facadeAct": "0x5fE248625aC2AB0e17A115fef288f17AF1952402", + "facadeWriteLib": "0x42D0fA25d6d5bff01aC050c0F5aB0B2C9D01b4a3", + "basketLib": "0xE90457dd23C27CD4955bFc056744e1C9D790f771", + "facadeWrite": "0xc87800FC32dd93b0584bb696326ED6a11Ef5221b", + "deployer": "0xE77c43F499524FF354D2aFFbE815729613d8F856", + "rsrAsset": "0x5EBE8927e5495e0A7731888C81AF463cD63602fb", "implementations": { - "main": "0x4E01677488384B851EeAa09C8b8F6Dd0b16d7E9B", + "main": "0x7B04De7DCa80e7C1ed5E0F41B2fD3C1C3588436C", "trading": { - "gnosisTrade": "0xDfCc89cf76aC93D113A21Da8fbfA63365b1E3DC7", - "dutchTrade": "0x9c387fc258061bd3E02c851F36aE227DB03a396C" + "gnosisTrade": "0xbC0033679AEf41Fb9FeB553Fdf55a8Bb2fC5B29e", + "dutchTrade": "0x451C1702d95877A51a816489315B8Cb1C6c0367a" }, "components": { - "assetRegistry": "0xD4e1D5b1311C992b2735710D46A10284Bcd7D39F", - "backingManager": "0x63e12c3b2DBCaeF1835Bb99Ac1Fdb0Ebe1bE69bE", - "basketHandler": "0x25E92785C1AC01B397224E0534f3D626868A1Cbf", - "broker": "0x12c3BB1B0da85fDaE0137aE8fDe901F7D0e106ba", - "distributor": "0xd31de64957b79435bfc702044590ac417e02c19B", - "furnace": "0x45D7dFE976cdF80962d863A66918346a457b87Bd", - "rsrTrader": "0xAeA6BD7b231C0eC7f35C2bdf47A76053D09dbD09", - "rTokenTrader": "0xAeA6BD7b231C0eC7f35C2bdf47A76053D09dbD09", - "rToken": "0xA42850A760151bb3ACF17E7f8643EB4d864bF7a6", - "stRSR": "0x53321f03A7cce52413515DFD0527e0163ec69A46" + "assetRegistry": "0xB333C42A9075d725c927EBDa95C325E49A07DA46", + "backingManager": "0x7d45524B9C3D7d77940FDda4a59be05f2EFc6c7a", + "basketHandler": "0x7FC3aDB139f8C473E705E82c91d23C18f4De968E", + "broker": "0xDdcba8fafDb0854Ac1368C686C0c90Eaa2992A98", + "distributor": "0x4D1658ee58ddf467c23bF31D55E02AB5186e8031", + "furnace": "0x308447562442Cc43978f8274fA722C9C14BafF8b", + "rsrTrader": "0x6272D1d531bD844d52b5244505fd0EE59841E167", + "rTokenTrader": "0x6272D1d531bD844d52b5244505fd0EE59841E167", + "rToken": "0x876057B2aeFC3e5CFEB712EcE7A07E4Cc5F1fe3A", + "stRSR": "0x973A81A6D21dC02Ab2FaF1e9FF71771E8426c568" } } } diff --git a/scripts/addresses/base-3.0.0/8453-tmp-assets-collateral.json b/scripts/addresses/base-3.0.0/8453-tmp-assets-collateral.json new file mode 100644 index 0000000000..ec42edd113 --- /dev/null +++ b/scripts/addresses/base-3.0.0/8453-tmp-assets-collateral.json @@ -0,0 +1,22 @@ +{ + "assets": { + "COMP": "0x277FD5f51fE53a9B3707a0383bF930B149C74ABf" + }, + "collateral": { + "DAI": "0x5EBE8927e5495e0A7731888C81AF463cD63602fb", + "WETH": "0x42D0fA25d6d5bff01aC050c0F5aB0B2C9D01b4a3", + "USDbC": "0x6490D66B17A1E9a460Ab54131165C8F921aCcDeB", + "cbETH": "0x5fE248625aC2AB0e17A115fef288f17AF1952402", + "cUSDbCv3": "0xa372EC846131FBf9AE8b589efa3D041D9a94dF41", + "aBasUSDbC": "0x1DdB7dfdC5D26FE1f2aD02d9972f12481346Ae9b" + }, + "erc20s": { + "COMP": "0x9e1028F5F1D5eDE59748FFceE5532509976840E0", + "DAI": "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", + "WETH": "0x4200000000000000000000000000000000000006", + "USDbC": "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA", + "cbETH": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", + "cUSDbCv3": "0xbC0033679AEf41Fb9FeB553Fdf55a8Bb2fC5B29e", + "aBasUSDbC": "0x308447562442Cc43978f8274fA722C9C14BafF8b" + } +} diff --git a/scripts/addresses/base-3.0.0/8453-tmp-deployments.json b/scripts/addresses/base-3.0.0/8453-tmp-deployments.json new file mode 100644 index 0000000000..1cc9767524 --- /dev/null +++ b/scripts/addresses/base-3.0.0/8453-tmp-deployments.json @@ -0,0 +1,35 @@ +{ + "prerequisites": { + "RSR": "0xaB36452DbAC151bE02b16Ca17d8919826072f64a", + "RSR_FEED": "0xAa98aE504658766Dfe11F31c5D95a0bdcABDe0b1", + "GNOSIS_EASY_AUCTION": "0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02" + }, + "tradingLib": "0x4E01677488384B851EeAa09C8b8F6Dd0b16d7E9B", + "cvxMiningLib": "", + "facadeRead": "0xe1aa15DA8b993c6312BAeD91E0b470AE405F91BF", + "facadeAct": "0x3d6D679c863858E89e35c925F937F5814ca687F3", + "facadeWriteLib": "0x29e9740275D26fdeDBb0ABA8129C74c15c393027", + "basketLib": "0x199E12d58B36deE2D2B3dD2b91aD7bb25c787a71", + "facadeWrite": "0x0903048fD4E948c60451B41A48B35E0bafc0967F", + "deployer": "0xf1B06c2305445E34CF0147466352249724c2EAC1", + "rsrAsset": "0x23b57479327f9BccE6A1F6Be65F3dAa3C9Db797B", + "implementations": { + "main": "0x1D6d0B74E7A701aE5C2E11967b242E9861275143", + "trading": { + "gnosisTrade": "0xD4e1D5b1311C992b2735710D46A10284Bcd7D39F", + "dutchTrade": "0xDfCc89cf76aC93D113A21Da8fbfA63365b1E3DC7" + }, + "components": { + "assetRegistry": "0x9c387fc258061bd3E02c851F36aE227DB03a396C", + "backingManager": "0x63e12c3b2DBCaeF1835Bb99Ac1Fdb0Ebe1bE69bE", + "basketHandler": "0x25E92785C1AC01B397224E0534f3D626868A1Cbf", + "broker": "0x12c3BB1B0da85fDaE0137aE8fDe901F7D0e106ba", + "distributor": "0xd31de64957b79435bfc702044590ac417e02c19B", + "furnace": "0x45D7dFE976cdF80962d863A66918346a457b87Bd", + "rsrTrader": "0xAeA6BD7b231C0eC7f35C2bdf47A76053D09dbD09", + "rTokenTrader": "0xAeA6BD7b231C0eC7f35C2bdf47A76053D09dbD09", + "rToken": "0xA42850A760151bb3ACF17E7f8643EB4d864bF7a6", + "stRSR": "0x53321f03A7cce52413515DFD0527e0163ec69A46" + } + } +} diff --git a/scripts/addresses/mainnet-3.0.0/1-tmp-assets-collateral.json b/scripts/addresses/mainnet-3.0.0/1-tmp-assets-collateral.json new file mode 100644 index 0000000000..7cb86310ed --- /dev/null +++ b/scripts/addresses/mainnet-3.0.0/1-tmp-assets-collateral.json @@ -0,0 +1,100 @@ +{ + "assets": { + "stkAAVE": "0x6647c880Eb8F57948AF50aB45fca8FE86C154D24", + "COMP": "0xCFA67f42A0fDe4F0Fb612ea5e66170B0465B84c1", + "CRV": "0x45B950AF443281c5F67c2c7A1d9bBc325ECb8eEA", + "CVX": "0x4024c00bBD0C420E719527D88781bc1543e63dd5" + }, + "collateral": { + "DAI": "0xf7d1C6eE4C0D84C6B530D53A897daa1E9eB56833", + "USDC": "0xBE9D23040fe22E8Bd8A88BF5101061557355cA04", + "USDT": "0x58D7bF13D3572b08dE5d96373b8097d94B1325ad", + "USDP": "0x2f98bA77a8ca1c630255c4517b1b3878f6e60C89", + "TUSD": "0x7F9999B2C9D310a5f48dfD070eb5129e1e8565E2", + "BUSD": "0xCBcd605088D5A5Da9ceEb3618bc01BFB87387423", + "aDAI": "0x256b89658bD831CC40283F42e85B1fa8973Db0c9", + "aUSDC": "0x7cd9ca6401f743b38b3b16ea314bbab8e9c1ac51", + "aUSDT": "0xe39188ddd4eb27d1d25f5f58cc6a5fd9228eedef", + "aBUSD": "0xeB1A036E83aD95f0a28d0c8E2F20bf7f1B299F05", + "aUSDP": "0x0d61Ce1801A460eB683b5ed1b6C7965d31b769Fd", + "cDAI": "0x440A634DdcFb890BCF8b0Bf07Ef2AaBB37dd5F8C", + "cUSDC": "0x50a9d529EA175CdE72525Eaa809f5C3c47dAA1bB", + "cUSDT": "0x5757fF814da66a2B4f9D11d48570d742e246CfD9", + "cUSDP": "0x99bD63BF7e2a69822cD73A82d42cF4b5501e5E50", + "cWBTC": "0x688c95461d611Ecfc423A8c87caCE163C6B40384", + "cETH": "0x357d4dB0c2179886334cC33B8528048F7E1D3Fe3", + "WBTC": "0x87A959e0377C68A50b08a91ae5ab3aFA7F41ACA4", + "WETH": "0x6B87142C7e6cA80aa3E6ead0351673C45c8990e3", + "EURT": "0xEBD07CE38e2f46031c982136012472A4D24AE070", + "wstETH": "0x29F2EB4A0D3dC211BB488E9aBe12740cafBCc49C", + "rETH": "0x1103851D1FCDD3f88096fbed812c8FF01949cF9d", + "fUSDC": "0x3C0a9143063Fc306F7D3cBB923ff4879d70Cf1EA", + "fUSDT": "0xbe6Fb2b2908D85179e34ee0D996e32fa2BF4410A", + "fDAI": "0x33C1665Eb1b3673213Daa5f068ae1026fC8D5875", + "fFRAX": "0xaAeF84f6FfDE4D0390E14DA9c527d1a1ABf28B92", + "cUSDCv3": "0x85b256e9051B781A0BC0A987857AD6166C94040a", + "cvx3Pool": "0x62C394620f674e85768a7618a6C202baE7fB8Dd1", + "cvxeUSDFRAXBP": "0x890FAa00C16EAD6AA76F18A1A7fe9C40838F9122", + "cvxMIM3Pool": "0xCBE084C44e7A2223F76362Dcc4EbDacA5Fb1cbA7", + "crv3Pool": "0x8Af118a89c5023Bb2B03C70f70c8B396aE71963D", + "crveUSDFRAXBP": "0xC87CDFFD680D57BF50De4C364BF4277B8A90098E", + "crvMIM3Pool": "0x14c443d8BdbE9A65F3a23FA4e199d8741D5B38Fa", + "sDAI": "0xde0e2f0c9792617d3908d92a024caa846354cea2", + "cbETH": "0x3962695aCce0Efce11cFf997890f3D1D7467ec40", + "maUSDT": "0xd000a79bd2a07eb6d2e02ecad73437de40e52d69", + "maUSDC": "0x2304E98cD1E2F0fd3b4E30A1Bc6E9594dE2ea9b7", + "maDAI": "0x9d38BFF9Af50738DF92a54Ceab2a2C2322BB1FAB", + "maWBTC": "0x49A44d50d3B1E098DAC9402c4aF8D0C0E499F250", + "maWETH": "0x878b995bDD2D9900BEE896Bd78ADd877672e1637", + "maStETH": "0x33E840e5711549358f6d4D11F9Ab2896B36E9822", + "aEthUSDC": "0x12c3BB1B0da85fDaE0137aE8fDe901F7D0e106ba" + }, + "erc20s": { + "stkAAVE": "0x4da27a545c0c5B758a6BA100e3a049001de870f5", + "COMP": "0xc00e94Cb662C3520282E6f5717214004A7f26888", + "CRV": "0xD533a949740bb3306d119CC777fa900bA034cd52", + "CVX": "0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B", + "DAI": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "USDC": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "USDT": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "USDP": "0x8E870D67F660D95d5be530380D0eC0bd388289E1", + "TUSD": "0x0000000000085d4780B73119b644AE5ecd22b376", + "BUSD": "0x4Fabb145d64652a948d72533023f6E7A623C7C53", + "aDAI": "0xafd16aFdE22D42038223A6FfDF00ee49c8fDa985", + "aUSDC": "0x60C384e226b120d93f3e0F4C502957b2B9C32B15", + "aUSDT": "0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9", + "aBUSD": "0xe639d53Aa860757D7fe9cD4ebF9C8b92b8DedE7D", + "aUSDP": "0x80A574cC2B369dc496af6655f57a16a4f180BfAF", + "cDAI": "0x3043be171e846c33D5f06864Cc045d9Fc799aF52", + "cUSDC": "0xf579F9885f1AEa0d3F8bE0F18AfED28c92a43022", + "cUSDT": "0x4Be33630F92661afD646081BC29079A38b879aA0", + "cUSDP": "0xF69c995129CC16d0F577C303091a400cC1879fFa", + "cWBTC": "0xF2A309bc36A504c772B416a4950d5d0021219745", + "cETH": "0xbF6E8F64547Bdec55bc3FBb0664722465FCC2F0F", + "WBTC": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "EURT": "0xC581b735A1688071A1746c968e0798D642EDE491", + "wstETH": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + "rETH": "0xae78736Cd615f374D3085123A210448E74Fc6393", + "fUSDC": "0x465a5a630482f3abD6d3b84B39B29b07214d19e5", + "fUSDT": "0x81994b9607e06ab3d5cF3AffF9a67374f05F27d7", + "fDAI": "0xe2bA8693cE7474900A045757fe0efCa900F6530b", + "fFRAX": "0x1C9A2d6b33B4826757273D47ebEe0e2DddcD978B", + "cUSDCv3": "0x7e1e077b289c0153b5ceAD9F264d66215341c9Ab", + "cvx3Pool": "0xaBd7E7a5C846eD497681a590feBED99e7157B6a3", + "cvxeUSDFRAXBP": "0x3BECE5EC596331033726E5C6C188c313Ff4E3fE5", + "cvxMIM3Pool": "0x9FF9c353136e86EFe02ADD177E7c9769f8a5A77F", + "crv3Pool": "0xC9c37FC53682207844B058026024853A9C0b8c7B", + "crveUSDFRAXBP": "0x27F672aAf061cb0b2640a4DFCCBd799cD1a7309A", + "crvMIM3Pool": "0xe8461dB45A7430AA7aB40346E68821284980FdFD", + "sDAI": "0x83f20f44975d03b1b09e64809b757c47f942beea", + "cbETH": "0xBe9895146f7AF43049ca1c1AE358B0541Ea49704", + "maUSDT": "0xaA91d24c2F7DBb6487f61869cD8cd8aFd5c5Cab2", + "maUSDC": "0x7f7b77e49d5b30445f222764a794afe14af062eb", + "maDAI": "0xE2b16e14dB6216e33082D5A8Be1Ef01DF7511bBb", + "maWBTC": "0xe0E1d3c6f09DA01399e84699722B11308607BBfC", + "maWETH": "0x291ed25eB61fcc074156eE79c5Da87e5DA94198F", + "maStETH": "0x97F9d5ed17A0C99B279887caD5254d15fb1B619B", + "aEthUSDC": "0x63e12c3b2DBCaeF1835Bb99Ac1Fdb0Ebe1bE69bE" + } +} \ No newline at end of file diff --git a/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json b/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json index 48f2121bd0..aa505c83ff 100644 --- a/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json +++ b/scripts/addresses/mainnet-3.0.0/1-tmp-deployments.json @@ -4,32 +4,32 @@ "RSR_FEED": "0x759bBC1be8F90eE6457C44abc7d443842a976d02", "GNOSIS_EASY_AUCTION": "0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101" }, - "tradingLib": "", - "cvxMiningLib": "", - "facadeRead": "0xcb71dDa3BdB1208B8bf18554Aab8CCF9bDe3e53D", - "facadeAct": "0x98f292e6Bb4722664fEffb81448cCFB5B7211469", - "facadeWriteLib": "", - "basketLib": "", - "facadeWrite": "", - "deployer": "", - "rsrAsset": "0x9cd0F8387672fEaaf7C269b62c34C53590d7e948", + "tradingLib": "0xB81a1fa9A497953CEC7f370CACFA5cc364871A73", + "cvxMiningLib": "0xeA4ecB9519Bae14bf343ddde0406C2D6108c1472", + "facadeRead": "0x81b9Ae0740CcA7cDc5211b2737de735FBC4BeB3C", + "facadeAct": "0x801fF27bacc7C00fBef17FC901504c79D59E845C", + "facadeWriteLib": "0x0776Ad71Ae99D759354B3f06fe17454b94837B0D", + "basketLib": "0xA87e9DAe6E9EA5B2Be858686CC6c21B953BfE0B8", + "facadeWrite": "0x41edAFFB50CA1c2FEC86C629F845b8490ced8A2c", + "deployer": "0x15480f5B5ED98A94e1d36b52Dd20e9a35453A38e", + "rsrAsset": "0x7edD40933DfdA0ecEe1ad3E61a5044962284e1A6", "implementations": { - "main": "", + "main": "0xF5366f67FF66A3CefcB18809a762D5b5931FebF8", "trading": { - "gnosisTrade": "", - "dutchTrade": "" + "gnosisTrade": "0xe416Db92A1B27c4e28D5560C1EEC03f7c582F630", + "dutchTrade": "0x2387C22727ACb91519b80A15AEf393ad40dFdb2F" }, "components": { - "assetRegistry": "", - "backingManager": "", - "basketHandler": "", - "broker": "", - "distributor": "", - "furnace": "", - "rsrTrader": "", - "rTokenTrader": "", - "rToken": "", - "stRSR": "" + "assetRegistry": "0x773cf50adCF1730964D4A9b664BaEd4b9FFC2450", + "backingManager": "0x0A388FC05AA017b31fb084e43e7aEaFdBc043080", + "basketHandler": "0x5ccca36CbB66a4E4033B08b4F6D7bAc96bA55cDc", + "broker": "0x9A5F8A9bB91a868b7501139eEdB20dC129D28F04", + "distributor": "0x0e8439a17bA5cBb2D9823c03a02566B9dd5d96Ac", + "furnace": "0x99580Fc649c02347eBc7750524CAAe5cAcf9d34c", + "rsrTrader": "0x1cCa3FBB11C4b734183f997679d52DeFA74b613A", + "rTokenTrader": "0x1cCa3FBB11C4b734183f997679d52DeFA74b613A", + "rToken": "0xb6f01Aa21defA4a4DE33Bed16BcC06cfd23b6A6F", + "stRSR": "0xC98eaFc9F249D90e3E35E729e3679DD75A899c10" } } } diff --git a/scripts/collateral-params.ts b/scripts/collateral-params.ts index 45ebbe86da..847e84ec9d 100644 --- a/scripts/collateral-params.ts +++ b/scripts/collateral-params.ts @@ -10,12 +10,12 @@ import { // This prints an MD table of all the collateral plugin parameters // Usage: npx hardhat run --network mainnet scripts/collateral-params.ts async function main() { - const header = ['Plugin', 'Tolerance', 'Delay (hrs)', 'Oracle(s)', 'Underlying'] + const header = ['Plugin', 'Peg Tolerance', 'Delay (hrs)', 'Oracle(s)', 'Underlying'] const body: string[][] = [] const chainId = await getChainId(hre) // Get deployed collateral - const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId, 'mainnet-2.1.0') + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId, 'mainnet-3.0.0') const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) const { collateral: collaterals } = assetCollDeployments @@ -34,7 +34,7 @@ async function main() { const underlyingMd = getEtherscanMd(underlyingAddr) const clFeedMd = getEtherscanMd(chainlinkFeed) - const NEEDS_ATTENTION = ['[cvxMIM3'] // first 8 chars only + const NEEDS_ATTENTION = ['[cvxMIM3', '[crvMIM3'] // first 8 chars only body.push([ collateralMd, diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 39739b1c5c..12e104aeed 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -1,7 +1,7 @@ /* eslint-disable no-process-exit */ import hre from 'hardhat' import { getChainId } from '../common/blockchain-utils' -import { networkConfig } from '../common/configuration' +import { baseL2Chains, networkConfig } from '../common/configuration' import { sh } from './deployment/utils' async function main() { @@ -25,7 +25,8 @@ async function main() { // Part 1/3 of the *overall* deployment process: Deploy all contracts // See `confirm.ts` for part 2 - const scripts = [ + // Phase 1- Implementations + let scripts = [ 'phase1-common/0_setup_deployments.ts', 'phase1-common/1_deploy_libraries.ts', 'phase1-common/2_deploy_implementations.ts', @@ -34,35 +35,55 @@ async function main() { 'phase1-common/5_deploy_deployer.ts', 'phase1-common/6_deploy_facadeWrite.ts', 'phase1-common/7_deploy_facadeAct.ts', - // ============================================= - 'phase2-assets/0_setup_deployments.ts', - 'phase2-assets/1_deploy_assets.ts', - 'phase2-assets/assets/deploy_crv.ts', - 'phase2-assets/assets/deploy_cvx.ts', - 'phase2-assets/2_deploy_collateral.ts', - 'phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts', - 'phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts', - 'phase2-assets/collaterals/deploy_flux_finance_collateral.ts', - 'phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts', - 'phase2-assets/collaterals/deploy_convex_stable_plugin.ts', - // 'phase2-assets/collaterals/deploy_convex_volatile_plugin.ts', // tricrypto on hold - 'phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts', - 'phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts', - 'phase2-assets/collaterals/deploy_curve_stable_plugin.ts', - // 'phase2-assets/collaterals/deploy_curve_volatile_plugin.ts', // tricrypto on hold - 'phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts', - 'phase2-assets/collaterals/deploy_curve_stable_metapool_plugin.ts', - 'phase2-assets/collaterals/deploy_dsr_sdai.ts', - 'phase2-assets/collaterals/deploy_cbeth_collateral.ts', - // =============================================== - // These phase3 scripts will not deploy functional RTokens or Governance. They deploy bricked - // versions that are used for verification only. Further deployment is left up to the Register. - // 'phase3-rtoken/0_setup_deployments.ts', - // 'phase3-rtoken/1_deploy_rtoken.ts', - // 'phase3-rtoken/2_deploy_governance.ts', - // We can uncomment this section whenever we update governance, which will be rarely ] + // ============================================= + + // Phase 2 - Assets/Collateral + if (!baseL2Chains.includes(hre.network.name)) { + scripts.push( + 'phase2-assets/0_setup_deployments.ts', + 'phase2-assets/1_deploy_assets.ts', + 'phase2-assets/assets/deploy_crv.ts', + 'phase2-assets/assets/deploy_cvx.ts', + 'phase2-assets/2_deploy_collateral.ts', + 'phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts', + 'phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts', + 'phase2-assets/collaterals/deploy_flux_finance_collateral.ts', + 'phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts', + 'phase2-assets/collaterals/deploy_convex_stable_plugin.ts', + 'phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts', + 'phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts', + 'phase2-assets/collaterals/deploy_curve_stable_plugin.ts', + 'phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts', + 'phase2-assets/collaterals/deploy_curve_stable_metapool_plugin.ts', + 'phase2-assets/collaterals/deploy_dsr_sdai.ts', + 'phase2-assets/collaterals/deploy_cbeth_collateral.ts', + 'phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts', + 'phase2-assets/collaterals/deploy_aave_v3_usdc.ts' + ) + } else if (chainId == '8453' || chainId == '84531') { + // Base L2 chains + scripts.push( + 'phase2-assets/0_setup_deployments.ts', + 'phase2-assets/1_deploy_assets.ts', + 'phase2-assets/2_deploy_collateral.ts', + 'phase2-assets/collaterals/deploy_cbeth_collateral.ts', + 'phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts', + 'phase2-assets/collaterals/deploy_aave_v3_usdbc.ts' + ) + } + + // =============================================== + + // Phase 3 - RTokens + // These phase3 scripts will not deploy functional RTokens or Governance. They deploy bricked + // versions that are used for verification only. Further deployment is left up to the Register. + // 'phase3-rtoken/0_setup_deployments.ts', + // 'phase3-rtoken/1_deploy_rtoken.ts', + // 'phase3-rtoken/2_deploy_governance.ts', + // We can uncomment and prepare this section whenever we update governance, which will be rarely + for (const script of scripts) { console.log('\n===========================================\n', script, '') await sh(`hardhat run scripts/deployment/${script}`) diff --git a/scripts/deployment/phase1-common/1_deploy_libraries.ts b/scripts/deployment/phase1-common/1_deploy_libraries.ts index 764676c327..35fc34e373 100644 --- a/scripts/deployment/phase1-common/1_deploy_libraries.ts +++ b/scripts/deployment/phase1-common/1_deploy_libraries.ts @@ -1,7 +1,7 @@ import fs from 'fs' import hre, { ethers } from 'hardhat' import { getChainId } from '../../../common/blockchain-utils' -import { networkConfig } from '../../../common/configuration' +import { baseL2Chains, networkConfig } from '../../../common/configuration' import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../common' import { validatePrerequisites } from '../utils' import { BasketLibP1, CvxMining, RecollateralizationLibP1 } from '../../../typechain' @@ -47,17 +47,19 @@ async function main() { fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) // Deploy CvxMining external library - const CvxMiningFactory = await ethers.getContractFactory('CvxMining') - cvxMiningLib = await CvxMiningFactory.connect(burner).deploy() - await cvxMiningLib.deployed() - deployments.cvxMiningLib = cvxMiningLib.address + if (!baseL2Chains.includes(hre.network.name)) { + const CvxMiningFactory = await ethers.getContractFactory('CvxMining') + cvxMiningLib = await CvxMiningFactory.connect(burner).deploy() + await cvxMiningLib.deployed() + deployments.cvxMiningLib = cvxMiningLib.address - fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) + fs.writeFileSync(deploymentFilename, JSON.stringify(deployments, null, 2)) + } console.log(`Deployed to ${hre.network.name} (${chainId}): TradingLib: ${tradingLib.address} BasketLib: ${basketLib.address} - CvxMiningLib: ${cvxMiningLib.address} + CvxMiningLib: ${cvxMiningLib ? cvxMiningLib.address : 'N/A'} Deployment file: ${deploymentFilename}`) } diff --git a/scripts/deployment/phase2-assets/1_deploy_assets.ts b/scripts/deployment/phase2-assets/1_deploy_assets.ts index 67d0c52b76..cab49a5515 100644 --- a/scripts/deployment/phase2-assets/1_deploy_assets.ts +++ b/scripts/deployment/phase2-assets/1_deploy_assets.ts @@ -1,7 +1,7 @@ import fs from 'fs' import hre, { ethers } from 'hardhat' import { getChainId } from '../../../common/blockchain-utils' -import { networkConfig } from '../../../common/configuration' +import { baseL2Chains, networkConfig } from '../../../common/configuration' import { fp } from '../../../common/numbers' import { getDeploymentFile, @@ -37,19 +37,21 @@ async function main() { const deployedAssets: string[] = [] /******** Deploy StkAAVE Asset **************************/ - const { asset: stkAAVEAsset } = await hre.run('deploy-asset', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.AAVE, - oracleError: fp('0.01').toString(), // 1% - tokenAddress: networkConfig[chainId].tokens.stkAAVE, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr - }) - await (await ethers.getContractAt('Asset', stkAAVEAsset)).refresh() + if (!baseL2Chains.includes(hre.network.name)) { + const { asset: stkAAVEAsset } = await hre.run('deploy-asset', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.AAVE, + oracleError: fp('0.01').toString(), // 1% + tokenAddress: networkConfig[chainId].tokens.stkAAVE, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + }) + await (await ethers.getContractAt('Asset', stkAAVEAsset)).refresh() - assetCollDeployments.assets.stkAAVE = stkAAVEAsset - assetCollDeployments.erc20s.stkAAVE = networkConfig[chainId].tokens.stkAAVE - deployedAssets.push(stkAAVEAsset.toString()) + assetCollDeployments.assets.stkAAVE = stkAAVEAsset + assetCollDeployments.erc20s.stkAAVE = networkConfig[chainId].tokens.stkAAVE + deployedAssets.push(stkAAVEAsset.toString()) + } /******** Deploy Comp Asset **************************/ const { asset: compAsset } = await hre.run('deploy-asset', { diff --git a/scripts/deployment/phase2-assets/2_deploy_collateral.ts b/scripts/deployment/phase2-assets/2_deploy_collateral.ts index 475dd1d5c2..b232dc5ed1 100644 --- a/scripts/deployment/phase2-assets/2_deploy_collateral.ts +++ b/scripts/deployment/phase2-assets/2_deploy_collateral.ts @@ -2,7 +2,7 @@ import fs from 'fs' import hre, { ethers } from 'hardhat' import { expect } from 'chai' import { getChainId } from '../../../common/blockchain-utils' -import { networkConfig } from '../../../common/configuration' +import { baseL2Chains, networkConfig } from '../../../common/configuration' import { bn, fp } from '../../../common/numbers' import { CollateralStatus } from '../../../common/constants' import { @@ -40,655 +40,733 @@ async function main() { // Get Oracle Lib address if previously deployed (can override with arbitrary address) const deployedCollateral: string[] = [] + let collateral: ICollateral + /******** Deploy Fiat Collateral - DAI **************************/ - const { collateral: daiCollateral } = await hre.run('deploy-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.DAI, - oracleError: fp('0.0025').toString(), // 0.25% - tokenAddress: networkConfig[chainId].tokens.DAI, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - }) - let collateral = await ethers.getContractAt('ICollateral', daiCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - assetCollDeployments.collateral.DAI = daiCollateral - assetCollDeployments.erc20s.DAI = networkConfig[chainId].tokens.DAI - deployedCollateral.push(daiCollateral.toString()) + const daiOracleTimeout = baseL2Chains.includes(hre.network.name) ? 86400 : 3600 // 24 hr (Base) or 1 hour + const daiOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% + + if (networkConfig[chainId].tokens.DAI && networkConfig[chainId].chainlinkFeeds.DAI) { + const { collateral: daiCollateral } = await hre.run('deploy-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.DAI, + oracleError: daiOracleError.toString(), + tokenAddress: networkConfig[chainId].tokens.DAI, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, daiOracleTimeout).toString(), + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(daiOracleError).toString(), + delayUntilDefault: bn('86400').toString(), // 24h + }) + collateral = await ethers.getContractAt('ICollateral', daiCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.DAI = daiCollateral + assetCollDeployments.erc20s.DAI = networkConfig[chainId].tokens.DAI + deployedCollateral.push(daiCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + } + + const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% /******** Deploy Fiat Collateral - USDC **************************/ - const { collateral: usdcCollateral } = await hre.run('deploy-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, - oracleError: fp('0.0025').toString(), // 0.25% - tokenAddress: networkConfig[chainId].tokens.USDC, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - }) - collateral = await ethers.getContractAt('ICollateral', usdcCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - assetCollDeployments.collateral.USDC = usdcCollateral - assetCollDeployments.erc20s.USDC = networkConfig[chainId].tokens.USDC - deployedCollateral.push(usdcCollateral.toString()) + if (networkConfig[chainId].tokens.USDC && networkConfig[chainId].chainlinkFeeds.USDC) { + const { collateral: usdcCollateral } = await hre.run('deploy-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, + oracleError: usdcOracleError.toString(), + tokenAddress: networkConfig[chainId].tokens.USDC, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(usdcOracleError).toString(), + delayUntilDefault: bn('86400').toString(), // 24h + }) + collateral = await ethers.getContractAt('ICollateral', usdcCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.USDC = usdcCollateral + assetCollDeployments.erc20s.USDC = networkConfig[chainId].tokens.USDC + deployedCollateral.push(usdcCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + } /******** Deploy Fiat Collateral - USDT **************************/ - const { collateral: usdtCollateral } = await hre.run('deploy-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.USDT, - oracleError: fp('0.0025').toString(), // 0.25% - tokenAddress: networkConfig[chainId].tokens.USDT, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - }) - collateral = await ethers.getContractAt('ICollateral', usdtCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - assetCollDeployments.collateral.USDT = usdtCollateral - assetCollDeployments.erc20s.USDT = networkConfig[chainId].tokens.USDT - deployedCollateral.push(usdtCollateral.toString()) + const usdtOracleTimeout = 86400 // 24 hr + const usdtOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% + + if (networkConfig[chainId].tokens.USDT && networkConfig[chainId].chainlinkFeeds.USDT) { + const { collateral: usdtCollateral } = await hre.run('deploy-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.USDT, + oracleError: usdtOracleError.toString(), + tokenAddress: networkConfig[chainId].tokens.USDT, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, usdtOracleTimeout).toString(), // 24 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(usdtOracleError).toString(), + delayUntilDefault: bn('86400').toString(), // 24h + }) + collateral = await ethers.getContractAt('ICollateral', usdtCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.USDT = usdtCollateral + assetCollDeployments.erc20s.USDT = networkConfig[chainId].tokens.USDT + deployedCollateral.push(usdtCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + } /******** Deploy Fiat Collateral - USDP **************************/ - const { collateral: usdpCollateral } = await hre.run('deploy-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.USDP, - oracleError: fp('0.01').toString(), // 1% - tokenAddress: networkConfig[chainId].tokens.USDP, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.02').toString(), // 2% - delayUntilDefault: bn('86400').toString(), // 24h - }) - collateral = await ethers.getContractAt('ICollateral', usdpCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - assetCollDeployments.collateral.USDP = usdpCollateral - assetCollDeployments.erc20s.USDP = networkConfig[chainId].tokens.USDP - deployedCollateral.push(usdpCollateral.toString()) - /******** Deploy Fiat Collateral - TUSD **************************/ - const { collateral: tusdCollateral } = await hre.run('deploy-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.TUSD, - oracleError: fp('0.003').toString(), // 0.3% - tokenAddress: networkConfig[chainId].tokens.TUSD, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.013').toString(), // 1.3% - delayUntilDefault: bn('86400').toString(), // 24h - }) - collateral = await ethers.getContractAt('ICollateral', tusdCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.TUSD = tusdCollateral - assetCollDeployments.erc20s.TUSD = networkConfig[chainId].tokens.TUSD - deployedCollateral.push(tusdCollateral.toString()) + if (networkConfig[chainId].tokens.USDP && networkConfig[chainId].chainlinkFeeds.USDP) { + const { collateral: usdpCollateral } = await hre.run('deploy-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.USDP, + oracleError: fp('0.01').toString(), // 1% + tokenAddress: networkConfig[chainId].tokens.USDP, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.02').toString(), // 2% + delayUntilDefault: bn('86400').toString(), // 24h + }) + collateral = await ethers.getContractAt('ICollateral', usdpCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.USDP = usdpCollateral + assetCollDeployments.erc20s.USDP = networkConfig[chainId].tokens.USDP + deployedCollateral.push(usdpCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + } + /******** Deploy Fiat Collateral - TUSD **************************/ + if (networkConfig[chainId].tokens.TUSD && networkConfig[chainId].chainlinkFeeds.TUSD) { + const { collateral: tusdCollateral } = await hre.run('deploy-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.TUSD, + oracleError: fp('0.003').toString(), // 0.3% + tokenAddress: networkConfig[chainId].tokens.TUSD, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.013').toString(), // 1.3% + delayUntilDefault: bn('86400').toString(), // 24h + }) + collateral = await ethers.getContractAt('ICollateral', tusdCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.TUSD = tusdCollateral + assetCollDeployments.erc20s.TUSD = networkConfig[chainId].tokens.TUSD + deployedCollateral.push(tusdCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + } /******** Deploy Fiat Collateral - BUSD **************************/ - const { collateral: busdCollateral } = await hre.run('deploy-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.BUSD, - oracleError: fp('0.005').toString(), // 0.5% - tokenAddress: networkConfig[chainId].tokens.BUSD, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.015').toString(), // 1.5% - delayUntilDefault: bn('86400').toString(), // 24h - }) - collateral = await ethers.getContractAt('ICollateral', busdCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - assetCollDeployments.collateral.BUSD = busdCollateral - assetCollDeployments.erc20s.BUSD = networkConfig[chainId].tokens.BUSD - deployedCollateral.push(busdCollateral.toString()) - - /******** Deploy AToken Fiat Collateral - aDAI **************************/ - - // Get AToken to retrieve name and symbol - let aToken: ATokenMock = ( - await ethers.getContractAt('ATokenMock', networkConfig[chainId].tokens.aDAI as string) - ) - - // Wrap in StaticAToken - const StaticATokenFactory = await ethers.getContractFactory('StaticATokenLM') - const adaiStaticToken: StaticATokenLM = ( - await StaticATokenFactory.connect(burner).deploy( - networkConfig[chainId].AAVE_LENDING_POOL as string, - aToken.address, - 'Static ' + (await aToken.name()), - 's' + (await aToken.symbol()) + if (networkConfig[chainId].tokens.BUSD && networkConfig[chainId].chainlinkFeeds.BUSD) { + const { collateral: busdCollateral } = await hre.run('deploy-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.BUSD, + oracleError: fp('0.005').toString(), // 0.5% + tokenAddress: networkConfig[chainId].tokens.BUSD, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.015').toString(), // 1.5% + delayUntilDefault: bn('86400').toString(), // 24h + }) + collateral = await ethers.getContractAt('ICollateral', busdCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.BUSD = busdCollateral + assetCollDeployments.erc20s.BUSD = networkConfig[chainId].tokens.BUSD + deployedCollateral.push(busdCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + } + + /******** Base L2 - Deploy Fiat Collateral - USDbC **************************/ + if (networkConfig[chainId].tokens.USDbC && networkConfig[chainId].chainlinkFeeds.USDC) { + const { collateral: usdcCollateral } = await hre.run('deploy-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, + oracleError: usdcOracleError.toString(), + tokenAddress: networkConfig[chainId].tokens.USDbC, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(usdcOracleError).toString(), // 1.3% + delayUntilDefault: bn('86400').toString(), // 24h + }) + collateral = await ethers.getContractAt('ICollateral', usdcCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.USDbC = usdcCollateral + assetCollDeployments.erc20s.USDbC = networkConfig[chainId].tokens.USDbC + deployedCollateral.push(usdcCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + } + + /*** AAVE V2 not available in Base L2s */ + if (!baseL2Chains.includes(hre.network.name)) { + /******** Deploy AToken Fiat Collateral - aDAI **************************/ + + // Get AToken to retrieve name and symbol + let aToken: ATokenMock = ( + await ethers.getContractAt('ATokenMock', networkConfig[chainId].tokens.aDAI as string) + ) + + // Wrap in StaticAToken + const StaticATokenFactory = await ethers.getContractFactory('StaticATokenLM') + const adaiStaticToken: StaticATokenLM = ( + await StaticATokenFactory.connect(burner).deploy( + networkConfig[chainId].AAVE_LENDING_POOL as string, + aToken.address, + 'Static ' + (await aToken.name()), + 's' + (await aToken.symbol()) + ) ) - ) - await adaiStaticToken.deployed() - console.log( - `Deployed StaticAToken for aDAI on ${hre.network.name} (${chainId}): ${adaiStaticToken.address} ` - ) - - const { collateral: aDaiCollateral } = await hre.run('deploy-atoken-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.DAI, - oracleError: fp('0.0025').toString(), // 0.25% - staticAToken: adaiStaticToken.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', aDaiCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - assetCollDeployments.collateral.aDAI = aDaiCollateral - assetCollDeployments.erc20s.aDAI = adaiStaticToken.address - deployedCollateral.push(aDaiCollateral.toString()) - - /******** Deploy AToken Fiat Collateral - aUSDC **************************/ - - // Get AToken to retrieve name and symbol - aToken = ( - await ethers.getContractAt('ATokenMock', networkConfig[chainId].tokens.aUSDC as string) - ) - - // Wrap in StaticAToken - const ausdcStaticToken: StaticATokenLM = ( - await StaticATokenFactory.connect(burner).deploy( - networkConfig[chainId].AAVE_LENDING_POOL as string, - aToken.address, - 'Static ' + (await aToken.name()), - 's' + (await aToken.symbol()) + await adaiStaticToken.deployed() + console.log( + `Deployed StaticAToken for aDAI on ${hre.network.name} (${chainId}): ${adaiStaticToken.address} ` ) - ) - await ausdcStaticToken.deployed() - - console.log( - `Deployed StaticAToken for aUSDC on ${hre.network.name} (${chainId}): ${ausdcStaticToken.address} ` - ) - - const { collateral: aUsdcCollateral } = await hre.run('deploy-atoken-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, - oracleError: fp('0.0025').toString(), // 0.25% - staticAToken: ausdcStaticToken.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', aUsdcCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.aUSDC = aUsdcCollateral - assetCollDeployments.erc20s.aUSDC = ausdcStaticToken.address - deployedCollateral.push(aUsdcCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - /******** Deploy AToken Fiat Collateral - aUSDT **************************/ - - // Get AToken to retrieve name and symbol - aToken = ( - await ethers.getContractAt('ATokenMock', networkConfig[chainId].tokens.aUSDT as string) - ) - - // Wrap in StaticAToken - const ausdtStaticToken: StaticATokenLM = ( - await StaticATokenFactory.connect(burner).deploy( - networkConfig[chainId].AAVE_LENDING_POOL as string, - aToken.address, - 'Static ' + (await aToken.name()), - 's' + (await aToken.symbol()) + + const { collateral: aDaiCollateral } = await hre.run('deploy-atoken-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.DAI, + oracleError: fp('0.0025').toString(), // 0.25% + staticAToken: adaiStaticToken.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.0125').toString(), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', aDaiCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.aDAI = aDaiCollateral + assetCollDeployments.erc20s.aDAI = adaiStaticToken.address + deployedCollateral.push(aDaiCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + /******** Deploy AToken Fiat Collateral - aUSDC **************************/ + + // Get AToken to retrieve name and symbol + aToken = ( + await ethers.getContractAt('ATokenMock', networkConfig[chainId].tokens.aUSDC as string) ) - ) - await ausdtStaticToken.deployed() - - console.log( - `Deployed StaticAToken for aUSDT on ${hre.network.name} (${chainId}): ${ausdtStaticToken.address} ` - ) - - const { collateral: aUsdtCollateral } = await hre.run('deploy-atoken-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.USDT, - oracleError: fp('0.0025').toString(), // 0.25% - staticAToken: ausdtStaticToken.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', aUsdtCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.aUSDT = aUsdtCollateral - assetCollDeployments.erc20s.aUSDT = ausdtStaticToken.address - deployedCollateral.push(aUsdtCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - /******** Deploy AToken Fiat Collateral - aBUSD **************************/ - - // Get AToken to retrieve name and symbol - aToken = ( - await ethers.getContractAt('ATokenMock', networkConfig[chainId].tokens.aBUSD as string) - ) - - const abusdStaticToken: StaticATokenLM = ( - await StaticATokenFactory.connect(burner).deploy( - networkConfig[chainId].AAVE_LENDING_POOL as string, - aToken.address, - 'Static ' + (await aToken.name()), - 's' + (await aToken.symbol()) + + // Wrap in StaticAToken + const ausdcStaticToken: StaticATokenLM = ( + await StaticATokenFactory.connect(burner).deploy( + networkConfig[chainId].AAVE_LENDING_POOL as string, + aToken.address, + 'Static ' + (await aToken.name()), + 's' + (await aToken.symbol()) + ) ) - ) - await abusdStaticToken.deployed() - - console.log( - `Deployed StaticAToken for aBUSD on ${hre.network.name} (${chainId}): ${abusdStaticToken.address} ` - ) - - const { collateral: aBusdCollateral } = await hre.run('deploy-atoken-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.BUSD, - oracleError: fp('0.005').toString(), // 0.5% - staticAToken: abusdStaticToken.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.015').toString(), // 1.5% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', aBusdCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.aBUSD = aBusdCollateral - assetCollDeployments.erc20s.aBUSD = abusdStaticToken.address - deployedCollateral.push(aBusdCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - /******** Deploy AToken Fiat Collateral - aUSDP **************************/ - - // Get AToken to retrieve name and symbol - aToken = ( - await ethers.getContractAt('ATokenMock', networkConfig[chainId].tokens.aUSDP as string) - ) - - // Wrap in StaticAToken - const ausdpStaticToken: StaticATokenLM = ( - await StaticATokenFactory.connect(burner).deploy( - networkConfig[chainId].AAVE_LENDING_POOL as string, - aToken.address, - 'Static ' + (await aToken.name()), - 's' + (await aToken.symbol()) + await ausdcStaticToken.deployed() + + console.log( + `Deployed StaticAToken for aUSDC on ${hre.network.name} (${chainId}): ${ausdcStaticToken.address} ` ) - ) - await ausdpStaticToken.deployed() - - console.log( - `Deployed StaticAToken for aUSDP on ${hre.network.name} (${chainId}): ${ausdpStaticToken.address} ` - ) - - const { collateral: aUsdpCollateral } = await hre.run('deploy-atoken-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.USDP, - oracleError: fp('0.01').toString(), // 1% - staticAToken: ausdpStaticToken.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.02').toString(), // 2% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', aUsdpCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.aUSDP = aUsdpCollateral - assetCollDeployments.erc20s.aUSDP = ausdpStaticToken.address - deployedCollateral.push(aUsdpCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - /******** Deploy CToken Fiat Collateral - cDAI **************************/ - const CTokenFactory = await ethers.getContractFactory('CTokenWrapper') - const cDai = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cDAI!) - - const cDaiVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cDAI!, - `${await cDai.name()} Vault`, - `${await cDai.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cDaiVault.deployed() - - console.log(`Deployed Vault for cDAI on ${hre.network.name} (${chainId}): ${cDaiVault.address} `) - - const { collateral: cDaiCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.DAI, - oracleError: fp('0.0025').toString(), // 0.25% - cToken: cDaiVault.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', cDaiCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.cDAI = cDaiCollateral - assetCollDeployments.erc20s.cDAI = cDaiVault.address - deployedCollateral.push(cDaiCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - /******** Deploy CToken Fiat Collateral - cUSDC **************************/ - const cUsdc = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDC!) - - const cUsdcVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cUSDC!, - `${await cUsdc.name()} Vault`, - `${await cUsdc.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cUsdcVault.deployed() - - console.log( - `Deployed Vault for cUSDC on ${hre.network.name} (${chainId}): ${cUsdcVault.address} ` - ) - - const { collateral: cUsdcCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, - oracleError: fp('0.0025').toString(), // 0.25% - cToken: cUsdcVault.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', cUsdcCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.cUSDC = cUsdcCollateral - assetCollDeployments.erc20s.cUSDC = cUsdcVault.address - deployedCollateral.push(cUsdcCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - /******** Deploy CToken Fiat Collateral - cUSDT **************************/ - const cUsdt = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDT!) - - const cUsdtVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cUSDT!, - `${await cUsdt.name()} Vault`, - `${await cUsdt.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cUsdtVault.deployed() - - console.log( - `Deployed Vault for cUSDT on ${hre.network.name} (${chainId}): ${cUsdtVault.address} ` - ) - - const { collateral: cUsdtCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.USDT, - oracleError: fp('0.0025').toString(), // 0.25% - cToken: cUsdtVault.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1.25% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', cUsdtCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.cUSDT = cUsdtCollateral - assetCollDeployments.erc20s.cUSDT = cUsdtVault.address - deployedCollateral.push(cUsdtCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - /******** Deploy CToken Fiat Collateral - cUSDP **************************/ - const cUsdp = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDP!) - - const cUsdpVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cUSDP!, - `${await cUsdp.name()} Vault`, - `${await cUsdp.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cUsdpVault.deployed() - - console.log( - `Deployed Vault for cUSDP on ${hre.network.name} (${chainId}): ${cUsdpVault.address} ` - ) - - const { collateral: cUsdpCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.USDP, - oracleError: fp('0.01').toString(), // 1% - cToken: cUsdpVault.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.02').toString(), // 2% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', cUsdpCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.cUSDP = cUsdpCollateral - assetCollDeployments.erc20s.cUSDP = cUsdpVault.address - deployedCollateral.push(cUsdpCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - /******** Deploy CToken Non-Fiat Collateral - cWBTC **************************/ - const cWBTC = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cWBTC!) - - const cWBTCVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cWBTC!, - `${await cWBTC.name()} Vault`, - `${await cWBTC.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cWBTCVault.deployed() - - console.log( - `Deployed Vault for cWBTC on ${hre.network.name} (${chainId}): ${cWBTCVault.address} ` - ) + + const { collateral: aUsdcCollateral } = await hre.run('deploy-atoken-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, + oracleError: fp('0.0025').toString(), // 0.25% + staticAToken: ausdcStaticToken.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.0125').toString(), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', aUsdcCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.aUSDC = aUsdcCollateral + assetCollDeployments.erc20s.aUSDC = ausdcStaticToken.address + deployedCollateral.push(aUsdcCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + /******** Deploy AToken Fiat Collateral - aUSDT **************************/ + + // Get AToken to retrieve name and symbol + aToken = ( + await ethers.getContractAt('ATokenMock', networkConfig[chainId].tokens.aUSDT as string) + ) + + // Wrap in StaticAToken + const ausdtStaticToken: StaticATokenLM = ( + await StaticATokenFactory.connect(burner).deploy( + networkConfig[chainId].AAVE_LENDING_POOL as string, + aToken.address, + 'Static ' + (await aToken.name()), + 's' + (await aToken.symbol()) + ) + ) + await ausdtStaticToken.deployed() + + console.log( + `Deployed StaticAToken for aUSDT on ${hre.network.name} (${chainId}): ${ausdtStaticToken.address} ` + ) + + const { collateral: aUsdtCollateral } = await hre.run('deploy-atoken-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.USDT, + oracleError: fp('0.0025').toString(), // 0.25% + staticAToken: ausdtStaticToken.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.0125').toString(), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', aUsdtCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.aUSDT = aUsdtCollateral + assetCollDeployments.erc20s.aUSDT = ausdtStaticToken.address + deployedCollateral.push(aUsdtCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + /******** Deploy AToken Fiat Collateral - aBUSD **************************/ + + // Get AToken to retrieve name and symbol + aToken = ( + await ethers.getContractAt('ATokenMock', networkConfig[chainId].tokens.aBUSD as string) + ) + + const abusdStaticToken: StaticATokenLM = ( + await StaticATokenFactory.connect(burner).deploy( + networkConfig[chainId].AAVE_LENDING_POOL as string, + aToken.address, + 'Static ' + (await aToken.name()), + 's' + (await aToken.symbol()) + ) + ) + await abusdStaticToken.deployed() + + console.log( + `Deployed StaticAToken for aBUSD on ${hre.network.name} (${chainId}): ${abusdStaticToken.address} ` + ) + + const { collateral: aBusdCollateral } = await hre.run('deploy-atoken-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.BUSD, + oracleError: fp('0.005').toString(), // 0.5% + staticAToken: abusdStaticToken.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.015').toString(), // 1.5% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', aBusdCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.aBUSD = aBusdCollateral + assetCollDeployments.erc20s.aBUSD = abusdStaticToken.address + deployedCollateral.push(aBusdCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + /******** Deploy AToken Fiat Collateral - aUSDP **************************/ + + // Get AToken to retrieve name and symbol + aToken = ( + await ethers.getContractAt('ATokenMock', networkConfig[chainId].tokens.aUSDP as string) + ) + + // Wrap in StaticAToken + const ausdpStaticToken: StaticATokenLM = ( + await StaticATokenFactory.connect(burner).deploy( + networkConfig[chainId].AAVE_LENDING_POOL as string, + aToken.address, + 'Static ' + (await aToken.name()), + 's' + (await aToken.symbol()) + ) + ) + await ausdpStaticToken.deployed() + + console.log( + `Deployed StaticAToken for aUSDP on ${hre.network.name} (${chainId}): ${ausdpStaticToken.address} ` + ) + + const { collateral: aUsdpCollateral } = await hre.run('deploy-atoken-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.USDP, + oracleError: fp('0.01').toString(), // 1% + staticAToken: ausdpStaticToken.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.02').toString(), // 2% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', aUsdpCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.aUSDP = aUsdpCollateral + assetCollDeployments.erc20s.aUSDP = ausdpStaticToken.address + deployedCollateral.push(aUsdpCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + } const wbtcOracleError = fp('0.02') // 2% const btcOracleError = fp('0.005') // 0.5% const combinedBTCWBTCError = combinedError(wbtcOracleError, btcOracleError) - const { collateral: cWBTCCollateral } = await hre.run('deploy-ctoken-nonfiat-collateral', { - priceTimeout: priceTimeout.toString(), - referenceUnitFeed: networkConfig[chainId].chainlinkFeeds.WBTC, - targetUnitFeed: networkConfig[chainId].chainlinkFeeds.BTC, - combinedOracleError: combinedBTCWBTCError.toString(), - cToken: cWBTCVault.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetUnitOracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr - targetName: hre.ethers.utils.formatBytes32String('BTC'), - defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% - delayUntilDefault: bn('86400').toString(), // 24h - revenueHiding: revenueHiding.toString(), - }) - collateral = await ethers.getContractAt('ICollateral', cWBTCCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.cWBTC = cWBTCCollateral - assetCollDeployments.erc20s.cWBTC = cWBTCVault.address - deployedCollateral.push(cWBTCCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - /******** Deploy CToken Self-Referential Collateral - cETH **************************/ - const cETH = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cETH!) - - const cETHVault = await CTokenFactory.deploy( - networkConfig[chainId].tokens.cETH!, - `${await cETH.name()} Vault`, - `${await cETH.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await cETHVault.deployed() - - console.log(`Deployed Vault for cETH on ${hre.network.name} (${chainId}): ${cETHVault.address} `) - - const { collateral: cETHCollateral } = await hre.run('deploy-ctoken-selfreferential-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.ETH, - oracleError: fp('0.005').toString(), // 0.5% - cToken: cETHVault.address, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr - targetName: hre.ethers.utils.formatBytes32String('ETH'), - revenueHiding: revenueHiding.toString(), - referenceERC20Decimals: '18', - }) - collateral = await ethers.getContractAt('ICollateral', cETHCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.cETH = cETHCollateral - assetCollDeployments.erc20s.cETH = cETHVault.address - deployedCollateral.push(cETHCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + /*** Compound V2 not available in Base L2s */ + if (!baseL2Chains.includes(hre.network.name)) { + /******** Deploy CToken Fiat Collateral - cDAI **************************/ + const CTokenFactory = await ethers.getContractFactory('CTokenWrapper') + const cDai = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cDAI!) + + const cDaiVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cDAI!, + `${await cDai.name()} Vault`, + `${await cDai.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) - /******** Deploy Non-Fiat Collateral - wBTC **************************/ - const { collateral: wBTCCollateral } = await hre.run('deploy-nonfiat-collateral', { - priceTimeout: priceTimeout.toString(), - referenceUnitFeed: networkConfig[chainId].chainlinkFeeds.WBTC, - targetUnitFeed: networkConfig[chainId].chainlinkFeeds.BTC, - combinedOracleError: combinedBTCWBTCError.toString(), - tokenAddress: networkConfig[chainId].tokens.WBTC, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetUnitOracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr - targetName: ethers.utils.formatBytes32String('BTC'), - defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% - delayUntilDefault: bn('86400').toString(), // 24h - }) - collateral = await ethers.getContractAt('ICollateral', wBTCCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.WBTC = wBTCCollateral - assetCollDeployments.erc20s.WBTC = networkConfig[chainId].tokens.WBTC - deployedCollateral.push(wBTCCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + await cDaiVault.deployed() + + console.log( + `Deployed Vault for cDAI on ${hre.network.name} (${chainId}): ${cDaiVault.address} ` + ) + + const { collateral: cDaiCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.DAI, + oracleError: fp('0.0025').toString(), // 0.25% + cToken: cDaiVault.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.0125').toString(), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', cDaiCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.cDAI = cDaiCollateral + assetCollDeployments.erc20s.cDAI = cDaiVault.address + deployedCollateral.push(cDaiCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + /******** Deploy CToken Fiat Collateral - cUSDC **************************/ + const cUsdc = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDC!) + + const cUsdcVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cUSDC!, + `${await cUsdc.name()} Vault`, + `${await cUsdc.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) + + await cUsdcVault.deployed() + + console.log( + `Deployed Vault for cUSDC on ${hre.network.name} (${chainId}): ${cUsdcVault.address} ` + ) + + const { collateral: cUsdcCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, + oracleError: fp('0.0025').toString(), // 0.25% + cToken: cUsdcVault.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.0125').toString(), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', cUsdcCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.cUSDC = cUsdcCollateral + assetCollDeployments.erc20s.cUSDC = cUsdcVault.address + deployedCollateral.push(cUsdcCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + /******** Deploy CToken Fiat Collateral - cUSDT **************************/ + const cUsdt = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDT!) + + const cUsdtVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cUSDT!, + `${await cUsdt.name()} Vault`, + `${await cUsdt.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) + + await cUsdtVault.deployed() + + console.log( + `Deployed Vault for cUSDT on ${hre.network.name} (${chainId}): ${cUsdtVault.address} ` + ) + + const { collateral: cUsdtCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.USDT, + oracleError: fp('0.0025').toString(), // 0.25% + cToken: cUsdtVault.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.0125').toString(), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', cUsdtCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.cUSDT = cUsdtCollateral + assetCollDeployments.erc20s.cUSDT = cUsdtVault.address + deployedCollateral.push(cUsdtCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + /******** Deploy CToken Fiat Collateral - cUSDP **************************/ + const cUsdp = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDP!) + + const cUsdpVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cUSDP!, + `${await cUsdp.name()} Vault`, + `${await cUsdp.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) + + await cUsdpVault.deployed() + + console.log( + `Deployed Vault for cUSDP on ${hre.network.name} (${chainId}): ${cUsdpVault.address} ` + ) + const { collateral: cUsdpCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.USDP, + oracleError: fp('0.01').toString(), // 1% + cToken: cUsdpVault.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.02').toString(), // 2% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', cUsdpCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.cUSDP = cUsdpCollateral + assetCollDeployments.erc20s.cUSDP = cUsdpVault.address + deployedCollateral.push(cUsdpCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + /******** Deploy CToken Non-Fiat Collateral - cWBTC **************************/ + const cWBTC = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cWBTC!) + + const cWBTCVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cWBTC!, + `${await cWBTC.name()} Vault`, + `${await cWBTC.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) + + await cWBTCVault.deployed() + + console.log( + `Deployed Vault for cWBTC on ${hre.network.name} (${chainId}): ${cWBTCVault.address} ` + ) + + const { collateral: cWBTCCollateral } = await hre.run('deploy-ctoken-nonfiat-collateral', { + priceTimeout: priceTimeout.toString(), + referenceUnitFeed: networkConfig[chainId].chainlinkFeeds.WBTC, + targetUnitFeed: networkConfig[chainId].chainlinkFeeds.BTC, + combinedOracleError: combinedBTCWBTCError.toString(), + cToken: cWBTCVault.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + targetUnitOracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: hre.ethers.utils.formatBytes32String('BTC'), + defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', cWBTCCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.cWBTC = cWBTCCollateral + assetCollDeployments.erc20s.cWBTC = cWBTCVault.address + deployedCollateral.push(cWBTCCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + /******** Deploy CToken Self-Referential Collateral - cETH **************************/ + const cETH = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cETH!) + + const cETHVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cETH!, + `${await cETH.name()} Vault`, + `${await cETH.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) + + await cETHVault.deployed() + + console.log( + `Deployed Vault for cETH on ${hre.network.name} (${chainId}): ${cETHVault.address} ` + ) + + const { collateral: cETHCollateral } = await hre.run( + 'deploy-ctoken-selfreferential-collateral', + { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.ETH, + oracleError: fp('0.005').toString(), // 0.5% + cToken: cETHVault.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: hre.ethers.utils.formatBytes32String('ETH'), + revenueHiding: revenueHiding.toString(), + referenceERC20Decimals: '18', + } + ) + collateral = await ethers.getContractAt('ICollateral', cETHCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.cETH = cETHCollateral + assetCollDeployments.erc20s.cETH = cETHVault.address + deployedCollateral.push(cETHCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + } + + /******** Deploy Non-Fiat Collateral - wBTC **************************/ + if ( + networkConfig[chainId].tokens.WBTC && + networkConfig[chainId].chainlinkFeeds.BTC && + networkConfig[chainId].chainlinkFeeds.WBTC + ) { + const { collateral: wBTCCollateral } = await hre.run('deploy-nonfiat-collateral', { + priceTimeout: priceTimeout.toString(), + referenceUnitFeed: networkConfig[chainId].chainlinkFeeds.WBTC, + targetUnitFeed: networkConfig[chainId].chainlinkFeeds.BTC, + combinedOracleError: combinedBTCWBTCError.toString(), + tokenAddress: networkConfig[chainId].tokens.WBTC, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + targetUnitOracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: ethers.utils.formatBytes32String('BTC'), + defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% + delayUntilDefault: bn('86400').toString(), // 24h + }) + collateral = await ethers.getContractAt('ICollateral', wBTCCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.WBTC = wBTCCollateral + assetCollDeployments.erc20s.WBTC = networkConfig[chainId].tokens.WBTC + deployedCollateral.push(wBTCCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + } /******** Deploy Self Referential Collateral - wETH **************************/ - const { collateral: wETHCollateral } = await hre.run('deploy-selfreferential-collateral', { - priceTimeout: priceTimeout.toString(), - priceFeed: networkConfig[chainId].chainlinkFeeds.ETH, - oracleError: fp('0.005').toString(), // 0.5% - tokenAddress: networkConfig[chainId].tokens.WETH, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr - targetName: hre.ethers.utils.formatBytes32String('ETH'), - }) - collateral = await ethers.getContractAt('ICollateral', wETHCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.WETH = wETHCollateral - assetCollDeployments.erc20s.WETH = networkConfig[chainId].tokens.WETH - deployedCollateral.push(wETHCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + if (networkConfig[chainId].tokens.WETH && networkConfig[chainId].chainlinkFeeds.ETH) { + const ethOracleTimeout = baseL2Chains.includes(hre.network.name) ? 1200 : 3600 // 20 min (Base) or 1 hr + const ethOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.0015') : fp('0.005') // 0.15% (Base) or 0.5% + + const { collateral: wETHCollateral } = await hre.run('deploy-selfreferential-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.ETH, + oracleError: ethOracleError.toString(), + tokenAddress: networkConfig[chainId].tokens.WETH, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, ethOracleTimeout).toString(), + targetName: hre.ethers.utils.formatBytes32String('ETH'), + }) + collateral = await ethers.getContractAt('ICollateral', wETHCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.WETH = wETHCollateral + assetCollDeployments.erc20s.WETH = networkConfig[chainId].tokens.WETH + deployedCollateral.push(wETHCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + } /******** Deploy EUR Fiat Collateral - EURT **************************/ const eurtError = fp('0.02') // 2% - const { collateral: eurtCollateral } = await hre.run('deploy-eurfiat-collateral', { - priceTimeout: priceTimeout.toString(), - referenceUnitFeed: networkConfig[chainId].chainlinkFeeds.EURT, - targetUnitFeed: networkConfig[chainId].chainlinkFeeds.EUR, - oracleError: eurtError.toString(), // 2% - tokenAddress: networkConfig[chainId].tokens.EURT, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetUnitOracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetName: ethers.utils.formatBytes32String('EUR'), - defaultThreshold: fp('0.03').toString(), // 3% - delayUntilDefault: bn('86400').toString(), // 24h - }) - collateral = await ethers.getContractAt('ICollateral', eurtCollateral) - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - assetCollDeployments.collateral.EURT = eurtCollateral - assetCollDeployments.erc20s.EURT = networkConfig[chainId].tokens.EURT - deployedCollateral.push(eurtCollateral.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + if ( + networkConfig[chainId].tokens.EURT && + networkConfig[chainId].chainlinkFeeds.EUR && + networkConfig[chainId].chainlinkFeeds.EURT + ) { + const { collateral: eurtCollateral } = await hre.run('deploy-eurfiat-collateral', { + priceTimeout: priceTimeout.toString(), + referenceUnitFeed: networkConfig[chainId].chainlinkFeeds.EURT, + targetUnitFeed: networkConfig[chainId].chainlinkFeeds.EUR, + oracleError: eurtError.toString(), // 2% + tokenAddress: networkConfig[chainId].tokens.EURT, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + targetUnitOracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + targetName: ethers.utils.formatBytes32String('EUR'), + defaultThreshold: fp('0.03').toString(), // 3% + delayUntilDefault: bn('86400').toString(), // 24h + }) + collateral = await ethers.getContractAt('ICollateral', eurtCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.EURT = eurtCollateral + assetCollDeployments.erc20s.EURT = networkConfig[chainId].tokens.EURT + deployedCollateral.push(eurtCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + } console.log(`Deployed collateral to ${hre.network.name} (${chainId}) New deployments: ${deployedCollateral} diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdbc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdbc.ts new file mode 100644 index 0000000000..e7fbc98512 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdbc.ts @@ -0,0 +1,110 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { baseL2Chains, networkConfig } from '../../../../common/configuration' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { bn, fp } from '#/common/numbers' +import { AaveV3FiatCollateral } from '../../../../typechain' +import { priceTimeout, revenueHiding, oracleTimeout } from '../../utils' + +// This file specifically deploys Aave V3 USDC collateral + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Only exists on Base L2 + if (!baseL2Chains.includes(hre.network.name)) { + throw new Error(`Invalid network ${hre.network.name} - only available on Base`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Aave V3 USDbC wrapper **************************/ + + const StaticATokenFactory = await hre.ethers.getContractFactory('StaticATokenV3LM') + const erc20 = await StaticATokenFactory.deploy( + networkConfig[chainId].AAVE_V3_POOL!, + networkConfig[chainId].AAVE_V3_INCENTIVES_CONTROLLER! + ) + await erc20.deployed() + await ( + await erc20.initialize( + networkConfig[chainId].tokens.aBasUSDbC!, + 'Static Aave Base USDbC', + 'saBasUSDbC' + ) + ).wait() + + console.log( + `Deployed wrapper for Aave V3 USDbC on ${hre.network.name} (${chainId}): ${erc20.address} ` + ) + + /******** Deploy Aave V3 USDbC collateral plugin **************************/ + + const CollateralFactory = await ethers.getContractFactory('AaveV3FiatCollateral') + const collateral = await CollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, + oracleError: fp('0.003'), // 3% + erc20: erc20.address, + maxTradeVolume: fp('1e6'), + oracleTimeout: oracleTimeout(chainId, bn('86400')), // 24 hr + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.013'), + delayUntilDefault: bn('86400'), + }, + revenueHiding + ) + + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed Aave V3 USDbC collateral to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.aBasUSDbC = collateral.address + assetCollDeployments.erc20s.aBasUSDbC = erc20.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdc.ts new file mode 100644 index 0000000000..2d56f9d3f9 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdc.ts @@ -0,0 +1,111 @@ +import fs from 'fs' +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { baseL2Chains, networkConfig } from '../../../../common/configuration' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { bn, fp } from '#/common/numbers' +import { AaveV3FiatCollateral } from '../../../../typechain' +import { priceTimeout, revenueHiding, oracleTimeout } from '../../utils' + +// This file specifically deploys Aave V3 USDC collateral + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Only exists on Mainnet + if (baseL2Chains.includes(hre.network.name)) { + throw new Error(`Invalid network ${hre.network.name} - only available on Mainnet`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy Aave V3 USDC wrapper **************************/ + + const StaticATokenFactory = await hre.ethers.getContractFactory('StaticATokenV3LM') + const erc20 = await StaticATokenFactory.deploy( + networkConfig[chainId].AAVE_V3_POOL!, + networkConfig[chainId].AAVE_V3_INCENTIVES_CONTROLLER! + ) + await erc20.deployed() + await ( + await erc20.initialize( + networkConfig[chainId].tokens.aEthUSDC!, + 'Static Aave Ethereum USDC', + 'saEthUSDC' + ) + ).wait() + + console.log( + `Deployed wrapper for Aave V3 USDC on ${hre.network.name} (${chainId}): ${erc20.address} ` + ) + + /******** Deploy Aave V3 USDC collateral plugin **************************/ + const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% + + const CollateralFactory = await ethers.getContractFactory('AaveV3FiatCollateral') + const collateral = await CollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, + oracleError: usdcOracleError, + erc20: erc20.address, + maxTradeVolume: fp('1e6'), + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout), + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(usdcOracleError), + delayUntilDefault: bn('86400'), + }, + revenueHiding + ) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed Aave V3 USDC collateral to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.aEthUSDC = collateral.address + assetCollDeployments.erc20s.aEthUSDC = erc20.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_cbeth_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_cbeth_collateral.ts index a3a562d7f8..eab6850157 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_cbeth_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_cbeth_collateral.ts @@ -1,7 +1,7 @@ import fs from 'fs' import hre from 'hardhat' import { getChainId } from '../../../../common/blockchain-utils' -import { networkConfig } from '../../../../common/configuration' +import { baseL2Chains, networkConfig } from '../../../../common/configuration' import { bn, fp } from '../../../../common/numbers' import { expect } from 'chai' import { CollateralStatus } from '../../../../common/constants' @@ -13,7 +13,12 @@ import { fileExists, } from '../../common' import { priceTimeout, oracleTimeout, combinedError } from '../../utils' -import { CBEthCollateral__factory } from '../../../../typechain' +import { + CBEthCollateral, + CBEthCollateralL2, + CBEthCollateralL2__factory, + CBEthCollateral__factory, +} from '../../../../typechain' async function main() { // ==== Read Configuration ==== @@ -41,29 +46,66 @@ async function main() { /******** Deploy Coinbase ETH Collateral - CBETH **************************/ - const CBETHCollateralFactory: CBEthCollateral__factory = (await hre.ethers.getContractFactory( - 'CBEthCollateral' - )) as CBEthCollateral__factory - - const collateral = await CBETHCollateralFactory.connect(deployer).deploy( - { - priceTimeout: priceTimeout.toString(), - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, - oracleError: combinedError(fp('0.005'), fp('0.02')).toString(), // 0.5% & 2%, - erc20: networkConfig[chainId].tokens.cbETH!, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, - targetName: hre.ethers.utils.formatBytes32String('ETH'), - defaultThreshold: fp('0.15').toString(), // 15% - delayUntilDefault: bn('86400').toString(), // 24h - }, - fp('1e-4').toString(), // revenueHiding = 0.01% - networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed - oracleTimeout(chainId, '86400').toString() // refPerTokChainlinkTimeout - ) - await collateral.deployed() - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + let collateral: CBEthCollateral | CBEthCollateralL2 + + if (!baseL2Chains.includes(hre.network.name)) { + const CBETHCollateralFactory: CBEthCollateral__factory = (await hre.ethers.getContractFactory( + 'CBEthCollateral' + )) as CBEthCollateral__factory + + const oracleError = combinedError(fp('0.005'), fp('0.02')) // 0.5% & 2% + + collateral = await CBETHCollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, + oracleError: oracleError.toString(), // 0.5% & 2%, + erc20: networkConfig[chainId].tokens.cbETH!, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, + targetName: hre.ethers.utils.formatBytes32String('ETH'), + defaultThreshold: fp('0.02').add(oracleError).toString(), // ~4.5% + delayUntilDefault: bn('86400').toString(), // 24h + }, + fp('1e-4').toString(), // revenueHiding = 0.01% + networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed + oracleTimeout(chainId, '86400').toString() // refPerTokChainlinkTimeout + ) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + } else if (chainId == '8453' || chainId == '84531') { + // Base L2 chains + const CBETHCollateralFactory: CBEthCollateralL2__factory = (await hre.ethers.getContractFactory( + 'CBEthCollateralL2' + )) as CBEthCollateralL2__factory + + const oracleError = combinedError(fp('0.0015'), fp('0.005')) // 0.15% & 0.5% + + collateral = await CBETHCollateralFactory.connect(deployer).deploy( + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, + oracleError: oracleError.toString(), // 0.15% & 0.5%, + erc20: networkConfig[chainId].tokens.cbETH!, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '1200').toString(), // 20 min + targetName: hre.ethers.utils.formatBytes32String('ETH'), + defaultThreshold: fp('0.02').add(oracleError).toString(), // ~2.5% + delayUntilDefault: bn('86400').toString(), // 24h + }, + fp('1e-4').toString(), // revenueHiding = 0.01% + networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed + oracleTimeout(chainId, '86400').toString(), // refPerTokChainlinkTimeout + networkConfig[chainId].chainlinkFeeds.cbETHETHexr!, // exchangeRateChainlinkFeed + oracleTimeout(chainId, '86400').toString() // exchangeRateChainlinkTimeout + ) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + } else { + throw new Error(`Unsupported chainId: ${chainId}`) + } console.log(`Deployed Coinbase cbETH to ${hre.network.name} (${chainId}): ${collateral.address}`) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts new file mode 100644 index 0000000000..3f4755ff72 --- /dev/null +++ b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts @@ -0,0 +1,104 @@ +import fs from 'fs' +import hre from 'hardhat' +import { getChainId } from '../../../../common/blockchain-utils' +import { baseL2Chains, networkConfig } from '../../../../common/configuration' +import { bn, fp } from '../../../../common/numbers' +import { expect } from 'chai' +import { CollateralStatus } from '../../../../common/constants' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, + getDeploymentFilename, + fileExists, +} from '../../common' +import { priceTimeout, oracleTimeout, revenueHiding } from '../../utils' +import { CTokenV3Collateral } from '../../../../typechain' +import { ContractFactory } from 'ethers' + +async function main() { + // ==== Read Configuration ==== + const [deployer] = await hre.ethers.getSigners() + + const chainId = await getChainId(hre) + + console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) + with burner account: ${deployer.address}`) + + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + // Only exists on Base L2 + if (!baseL2Chains.includes(hre.network.name)) { + throw new Error(`Invalid network ${hre.network.name} - only available on Base`) + } + + // Get phase1 deployment + const phase1File = getDeploymentFilename(chainId) + if (!fileExists(phase1File)) { + throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) + } + // Check previous step completed + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) + + const deployedCollateral: string[] = [] + + /******** Deploy CompoundV3 USDC - cUSDbCv3 **************************/ + + const WrapperFactory: ContractFactory = await hre.ethers.getContractFactory('CusdcV3Wrapper') + const erc20 = await WrapperFactory.deploy( + networkConfig[chainId].tokens.cUSDbCv3, + networkConfig[chainId].COMET_REWARDS, + networkConfig[chainId].tokens.COMP + ) + await erc20.deployed() + + console.log( + `Deployed wrapper for cUSDbCv3 on ${hre.network.name} (${chainId}): ${erc20.address} ` + ) + + const CTokenV3Factory: ContractFactory = await hre.ethers.getContractFactory('CTokenV3Collateral') + + const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleError = fp('0.003') // 0.3% (Base) + + const collateral = await CTokenV3Factory.connect(deployer).deploy( + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC, + oracleError: usdcOracleError.toString(), + erc20: erc20.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24h hr, + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(usdcOracleError).toString(), // 1% + 0.3% + delayUntilDefault: bn('86400').toString(), // 24h + }, + revenueHiding.toString(), + bn('10000e6').toString() // $10k + ) + await collateral.deployed() + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + console.log( + `Deployed CompoundV3 USDbC to ${hre.network.name} (${chainId}): ${collateral.address}` + ) + + assetCollDeployments.collateral.cUSDbCv3 = collateral.address + assetCollDeployments.erc20s.cUSDbCv3 = erc20.address + deployedCollateral.push(collateral.address.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + console.log(`Deployed collateral to ${hre.network.name} (${chainId}) + New deployments: ${deployedCollateral} + Deployment file: ${assetCollDeploymentFilename}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts index a6d0bd5e86..873b8fbcc0 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts @@ -1,7 +1,7 @@ import fs from 'fs' import hre from 'hardhat' import { getChainId } from '../../../../common/blockchain-utils' -import { networkConfig } from '../../../../common/configuration' +import { baseL2Chains, networkConfig } from '../../../../common/configuration' import { bn, fp } from '../../../../common/numbers' import { expect } from 'chai' import { CollateralStatus } from '../../../../common/constants' @@ -29,6 +29,11 @@ async function main() { throw new Error(`Missing network configuration for ${hre.network.name}`) } + // Only exists on Mainnet + if (baseL2Chains.includes(hre.network.name)) { + throw new Error(`Invalid network ${hre.network.name} - only available on Mainnet`) + } + // Get phase1 deployment const phase1File = getDeploymentFilename(chainId) if (!fileExists(phase1File)) { @@ -40,13 +45,12 @@ async function main() { const deployedCollateral: string[] = [] - /******** Deploy CompoundV3 USDC - cUSDCv3 **************************/ + /******** Deploy CompoundV3 USDC - cUSDCv3 **************************/ const WrapperFactory: ContractFactory = await hre.ethers.getContractFactory('CusdcV3Wrapper') - const erc20 = await WrapperFactory.deploy( networkConfig[chainId].tokens.cUSDCv3, - '0x1B0e765F6224C21223AeA2af16c1C46E38885a40', + networkConfig[chainId].COMET_REWARDS, networkConfig[chainId].tokens.COMP ) await erc20.deployed() @@ -55,16 +59,19 @@ async function main() { const CTokenV3Factory: ContractFactory = await hre.ethers.getContractFactory('CTokenV3Collateral') + const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% + const collateral = await CTokenV3Factory.connect(deployer).deploy( { priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC, - oracleError: fp('0.0025').toString(), // 0.25%, + oracleError: usdcOracleError.toString(), erc20: erc20.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24h hr, + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24h hr, targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1% + 0.25% + defaultThreshold: fp('0.01').add(usdcOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h }, revenueHiding.toString(), diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts index 3bc89cb18f..b2d6462d6c 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts @@ -110,13 +110,14 @@ async function main() { } ) await collateral.deployed() - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) console.log( `Deployed Curve Stable Collateral to ${hre.network.name} (${chainId}): ${collateral.address}` ) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + assetCollDeployments.collateral.cvx3Pool = collateral.address assetCollDeployments.erc20s.crv3Pool = w3Pool.address deployedCollateral.push(collateral.address.toString()) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_dsr_sdai.ts b/scripts/deployment/phase2-assets/collaterals/deploy_dsr_sdai.ts index c3fa6bbe40..26ab8341ac 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_dsr_sdai.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_dsr_sdai.ts @@ -63,12 +63,12 @@ async function main() { POT ) await collateral.deployed() - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) console.log( `Deployed DSR-wrapping sDAI to ${hre.network.name} (${chainId}): ${collateral.address}` ) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.sDAI = collateral.address assetCollDeployments.erc20s.sDAI = networkConfig[chainId].tokens.sDAI diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts index 0aeb08cefa..af7258ec4c 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts @@ -41,27 +41,13 @@ async function main() { const deployedCollateral: string[] = [] /******** Deploy FToken Fiat Collateral - fUSDC **************************/ - const FTokenFactory = await ethers.getContractFactory('CTokenWrapper') const fUsdc = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.fUSDC!) - const fUsdcVault = await FTokenFactory.deploy( - networkConfig[chainId].tokens.fUSDC!, - `${await fUsdc.name()} Vault`, - `${await fUsdc.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await fUsdcVault.deployed() - - console.log( - `Deployed Vault for fUSDC on ${hre.network.name} (${chainId}): ${fUsdcVault.address} ` - ) - const { collateral: fUsdcCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, oracleError: fp('0.0025').toString(), // 0.25% - cToken: fUsdcVault.address, + cToken: fUsdc.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), @@ -74,7 +60,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.fUSDC = fUsdcCollateral - assetCollDeployments.erc20s.fUSDC = fUsdcVault.address + assetCollDeployments.erc20s.fUSDC = fUsdc.address deployedCollateral.push(fUsdcCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -82,24 +68,11 @@ async function main() { /******** Deploy FToken Fiat Collateral - fUSDT **************************/ const fUsdt = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.fUSDT!) - const fUsdtVault = await FTokenFactory.deploy( - networkConfig[chainId].tokens.fUSDT!, - `${await fUsdt.name()} Vault`, - `${await fUsdt.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await fUsdtVault.deployed() - - console.log( - `Deployed Vault for fUSDT on ${hre.network.name} (${chainId}): ${fUsdtVault.address} ` - ) - const { collateral: fUsdtCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.USDT, oracleError: fp('0.0025').toString(), // 0.25% - cToken: fUsdtVault.address, + cToken: fUsdt.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), @@ -112,7 +85,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.fUSDT = fUsdtCollateral - assetCollDeployments.erc20s.fUSDT = fUsdtVault.address + assetCollDeployments.erc20s.fUSDT = fUsdt.address deployedCollateral.push(fUsdtCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -120,22 +93,11 @@ async function main() { /******** Deploy FToken Fiat Collateral - fDAI **************************/ const fDai = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.fDAI!) - const fDaiVault = await FTokenFactory.deploy( - networkConfig[chainId].tokens.fDAI!, - `${await fDai.name()} Vault`, - `${await fDai.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await fDaiVault.deployed() - - console.log(`Deployed Vault for fDAI on ${hre.network.name} (${chainId}): ${fDaiVault.address} `) - const { collateral: fDaiCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.DAI, oracleError: fp('0.0025').toString(), // 0.25% - cToken: fDaiVault.address, + cToken: fDai.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), @@ -148,7 +110,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.fDAI = fDaiCollateral - assetCollDeployments.erc20s.fDAI = fDaiVault.address + assetCollDeployments.erc20s.fDAI = fDai.address deployedCollateral.push(fDaiCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) @@ -156,24 +118,11 @@ async function main() { /******** Deploy FToken Fiat Collateral - fFRAX **************************/ const fFrax = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.fFRAX!) - const fFraxVault = await FTokenFactory.deploy( - networkConfig[chainId].tokens.fFRAX!, - `${await fFrax.name()} Vault`, - `${await fFrax.symbol()}-VAULT`, - networkConfig[chainId].COMPTROLLER! - ) - - await fFraxVault.deployed() - - console.log( - `Deployed Vault for fFRAX on ${hre.network.name} (${chainId}): ${fFraxVault.address} ` - ) - const { collateral: fFRAXCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { priceTimeout: priceTimeout.toString(), priceFeed: networkConfig[chainId].chainlinkFeeds.FRAX, oracleError: fp('0.01').toString(), // 1% - cToken: fFraxVault.address, + cToken: fFrax.address, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), @@ -186,7 +135,7 @@ async function main() { expect(await collateral.status()).to.equal(CollateralStatus.SOUND) assetCollDeployments.collateral.fFRAX = fFRAXCollateral - assetCollDeployments.erc20s.fFRAX = fFraxVault.address + assetCollDeployments.erc20s.fFRAX = fFrax.address deployedCollateral.push(fFRAXCollateral.toString()) fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts index 7381cf92e8..bc6a8b160a 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts @@ -81,7 +81,7 @@ async function main() { maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, targetName: hre.ethers.utils.formatBytes32String('ETH'), - defaultThreshold: fp('0.15').toString(), // 15% + defaultThreshold: fp('0.025').toString(), // 2.5% = 2% + 0.5% stethEth feed oracleError delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4').toString(), // revenueHiding = 0.01% diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts index 429eac9944..19962dd88f 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts @@ -143,6 +143,8 @@ async function main() { ) assetCollDeployments.collateral.maUSDT = collateral.address deployedCollateral.push(collateral.address.toString()) + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + await (await collateral.refresh()).wait() } { const collateral = await FiatCollateralFactory.connect(deployer).deploy( @@ -155,6 +157,8 @@ async function main() { ) assetCollDeployments.collateral.maUSDC = collateral.address deployedCollateral.push(collateral.address.toString()) + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + await (await collateral.refresh()).wait() } { const collateral = await FiatCollateralFactory.connect(deployer).deploy( @@ -167,6 +171,8 @@ async function main() { ) assetCollDeployments.collateral.maDAI = collateral.address deployedCollateral.push(collateral.address.toString()) + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + await (await collateral.refresh()).wait() } { @@ -182,15 +188,17 @@ async function main() { targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: fp('0.01').add(combinedBTCWBTCError), // ~3.5% delayUntilDefault: bn('86400'), // 24h - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.WBTC!, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.BTC!, // {UoA/target} erc20: maWBTC.address, }, revenueHiding, - networkConfig[chainId].chainlinkFeeds.wBTCBTC!, + networkConfig[chainId].chainlinkFeeds.WBTC!, // {target/ref} oracleTimeout(chainId, '86400').toString() // 1 hr ) assetCollDeployments.collateral.maWBTC = collateral.address deployedCollateral.push(collateral.address.toString()) + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + await (await collateral.refresh()).wait() } { @@ -201,15 +209,17 @@ async function main() { maxTradeVolume: fp('1e6'), // $1m, oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr targetName: ethers.utils.formatBytes32String('ETH'), - defaultThreshold: fp('0.05'), // 5% + defaultThreshold: fp('0'), // 0% -- no soft default for self-referential collateral delayUntilDefault: bn('86400'), // 24h chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, - erc20: maWBTC.address, + erc20: maWETH.address, }, revenueHiding ) assetCollDeployments.collateral.maWETH = collateral.address deployedCollateral.push(collateral.address.toString()) + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + await (await collateral.refresh()).wait() } { @@ -230,19 +240,19 @@ async function main() { targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.01').add(combinedOracleErrors), // ~1.5% delayUntilDefault: bn('86400'), // 24h - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, // {UoA/target} erc20: maStETH.address, }, revenueHiding, networkConfig[chainId].chainlinkFeeds.stETHETH!, // {target/ref} oracleTimeout(chainId, '86400').toString() // 1 hr ) - assetCollDeployments.collateral.maWBTC = collateral.address + assetCollDeployments.collateral.maStETH = collateral.address deployedCollateral.push(collateral.address.toString()) + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + await (await collateral.refresh()).wait() } - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - console.log(`Deployed collateral to ${hre.network.name} (${chainId}) New deployments: ${deployedCollateral} Deployment file: ${assetCollDeploymentFilename}`) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts index 386985881f..6d39259061 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts @@ -61,16 +61,18 @@ async function main() { 'RethCollateral' ) + const oracleError = combinedError(fp('0.005'), fp('0.02')) // 0.5% & 2% + const collateral = await RethCollateralFactory.connect(deployer).deploy( { priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH, - oracleError: combinedError(fp('0.005'), fp('0.02')).toString(), // 0.5% & 2%, + oracleError: oracleError.toString(), // 0.5% & 2% erc20: networkConfig[chainId].tokens.rETH, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, targetName: hre.ethers.utils.formatBytes32String('ETH'), - defaultThreshold: fp('0.15').toString(), // 15% + defaultThreshold: fp('0.02').add(oracleError).toString(), // ~4.5% delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4').toString(), // revenueHiding = 0.01% diff --git a/scripts/deployment/utils.ts b/scripts/deployment/utils.ts index a76d0f4e6a..84dad2be5e 100644 --- a/scripts/deployment/utils.ts +++ b/scripts/deployment/utils.ts @@ -17,7 +17,7 @@ export const longOracleTimeout = bn('4294967296') // Returns the base plus 1 minute export const oracleTimeout = (chainId: string, base: BigNumberish) => { - return chainId == '1' ? bn('60').add(base) : longOracleTimeout + return chainId == '1' || chainId == '8453' ? bn('60').add(base) : longOracleTimeout } export const combinedError = (x: BigNumber, y: BigNumber): BigNumber => { diff --git a/scripts/verification/6_verify_collateral.ts b/scripts/verification/6_verify_collateral.ts index a5377340a2..9da0d9791d 100644 --- a/scripts/verification/6_verify_collateral.ts +++ b/scripts/verification/6_verify_collateral.ts @@ -1,7 +1,7 @@ import hre, { ethers } from 'hardhat' import { getChainId } from '../../common/blockchain-utils' -import { developmentChains, networkConfig } from '../../common/configuration' +import { baseL2Chains, developmentChains, networkConfig } from '../../common/configuration' import { fp, bn } from '../../common/numbers' import { getDeploymentFile, @@ -34,6 +34,9 @@ async function main() { deployments = getDeploymentFile(assetCollDeploymentFilename) /******** Verify Fiat Collateral - DAI **************************/ + const daiOracleTimeout = baseL2Chains.includes(hre.network.name) ? 86400 : 3600 // 24 hr (Base) or 1 hour + const daiOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% + await verifyContract( chainId, deployments.collateral.DAI, @@ -41,17 +44,43 @@ async function main() { { priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI, - oracleError: fp('0.0025').toString(), // 0.25% + oracleError: daiOracleError.toString(), erc20: networkConfig[chainId].tokens.DAI, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: oracleTimeout(chainId, daiOracleTimeout).toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1.25% + defaultThreshold: fp('0.01').add(daiOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h }, ], 'contracts/plugins/assets/FiatCollateral.sol:FiatCollateral' ) + + /******** Verify Fiat Collateral - USDbC **************************/ + const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% + + if (baseL2Chains.includes(hre.network.name)) { + await verifyContract( + chainId, + deployments.collateral.USDbC, + [ + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC, + oracleError: usdcOracleError.toString(), + erc20: networkConfig[chainId].tokens.USDbC, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(usdcOracleError).toString(), + delayUntilDefault: bn('86400').toString(), // 24h + }, + ], + 'contracts/plugins/assets/FiatCollateral.sol:FiatCollateral' + ) + } + /******** Verify StaticATokenLM - aDAI **************************/ // Get AToken to retrieve name and symbol const aToken: ATokenMock = ( @@ -200,7 +229,11 @@ async function main() { ], 'contracts/plugins/assets/NonFiatCollateral.sol:NonFiatCollateral' ) + /********************** Verify SelfReferentialCollateral - WETH ****************************************/ + const ethOracleTimeout = baseL2Chains.includes(hre.network.name) ? 1200 : 3600 // 20 min (Base) or 1 hr + const ethOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.0015') : fp('0.005') // 0.15% (Base) or 0.5% + await verifyContract( chainId, deployments.collateral.WETH, @@ -208,10 +241,10 @@ async function main() { { priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH, - oracleError: fp('0.005').toString(), // 0.5% + oracleError: ethOracleError.toString(), // 0.5% erc20: networkConfig[chainId].tokens.WETH, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), + oracleTimeout: oracleTimeout(chainId, ethOracleTimeout).toString(), targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: '0', delayUntilDefault: '0', diff --git a/scripts/verification/collateral-plugins/verify_aave_v3_usdbc.ts b/scripts/verification/collateral-plugins/verify_aave_v3_usdbc.ts new file mode 100644 index 0000000000..edb092d1af --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_aave_v3_usdbc.ts @@ -0,0 +1,71 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { baseL2Chains, developmentChains, networkConfig } from '../../../common/configuration' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { fp, bn } from '../../../common/numbers' +import { priceTimeout, oracleTimeout, verifyContract, revenueHiding } from '../../deployment/utils' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + // Only exists on Base L2 + if (!baseL2Chains.includes(hre.network.name)) { + throw new Error(`Invalid network ${hre.network.name} - only available on Base`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + /******** Verify Wrapper **************************/ + const erc20 = await ethers.getContractAt( + 'StaticATokenV3LM', + deployments.erc20s.aBasUSDbC as string + ) + + await verifyContract( + chainId, + deployments.erc20s.aBasUSDbC, + [await erc20.POOL(), await erc20.INCENTIVES_CONTROLLER()], + 'contracts/plugins/assets/aave-v3/vendor/StaticATokenV3LM.sol:StaticATokenV3LM' + ) + + /******** Verify Aave V3 USDbC plugin **************************/ + await verifyContract( + chainId, + deployments.collateral.aBasUSDbC, + [ + { + erc20: erc20.address, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, + oracleError: fp('0.003').toString(), // 3% + oracleTimeout: oracleTimeout(chainId, bn('86400')).toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), + defaultThreshold: fp('0.013').toString(), + delayUntilDefault: bn('86400').toString(), + }, + revenueHiding.toString(), + ], + 'contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol:AaveV3FiatCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_aave_v3_usdc.ts b/scripts/verification/collateral-plugins/verify_aave_v3_usdc.ts new file mode 100644 index 0000000000..1486c37cab --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_aave_v3_usdc.ts @@ -0,0 +1,69 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { baseL2Chains, developmentChains, networkConfig } from '../../../common/configuration' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { fp, bn } from '../../../common/numbers' +import { priceTimeout, oracleTimeout, verifyContract, revenueHiding } from '../../deployment/utils' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + /******** Verify Wrapper **************************/ + const erc20 = await ethers.getContractAt( + 'StaticATokenV3LM', + deployments.erc20s.aEthUSDC as string + ) + + await verifyContract( + chainId, + deployments.erc20s.aEthUSDC, + [await erc20.POOL(), await erc20.INCENTIVES_CONTROLLER()], + 'contracts/plugins/assets/aave-v3/vendor/StaticATokenV3LM.sol:StaticATokenV3LM' + ) + + /******** Verify Aave V3 USDC plugin **************************/ + const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% + + await verifyContract( + chainId, + deployments.collateral.aEthUSDC, + [ + { + erc20: erc20.address, + targetName: ethers.utils.formatBytes32String('USD'), + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, + oracleError: usdcOracleError.toString(), + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24 hr + maxTradeVolume: fp('1e6').toString(), + defaultThreshold: fp('0.01').add(usdcOracleError).toString(), + delayUntilDefault: bn('86400').toString(), + }, + revenueHiding.toString(), + ], + 'contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol:AaveV3FiatCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_cbeth.ts b/scripts/verification/collateral-plugins/verify_cbeth.ts index abe6322e57..4e58ad88d5 100644 --- a/scripts/verification/collateral-plugins/verify_cbeth.ts +++ b/scripts/verification/collateral-plugins/verify_cbeth.ts @@ -1,6 +1,6 @@ import hre from 'hardhat' import { getChainId } from '../../../common/blockchain-utils' -import { developmentChains, networkConfig } from '../../../common/configuration' +import { baseL2Chains, developmentChains, networkConfig } from '../../../common/configuration' import { fp, bn } from '../../../common/numbers' import { getDeploymentFile, @@ -26,27 +26,57 @@ async function main() { deployments = getDeploymentFile(assetCollDeploymentFilename) /******** Verify Coinbase staked ETH - CBETH **************************/ - await verifyContract( - chainId, - deployments.collateral.cbETH, - [ - { - priceTimeout: priceTimeout.toString(), - chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH, - oracleError: combinedError(fp('0.005'), fp('0.02')).toString(), // 0.5% & 2%, - erc20: networkConfig[chainId].tokens.cbETH!, - maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, - targetName: hre.ethers.utils.formatBytes32String('ETH'), - defaultThreshold: fp('0.15').toString(), // 15% - delayUntilDefault: bn('86400').toString(), // 24h - }, - fp('1e-4'), // revenueHiding = 0.01% - networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed - oracleTimeout(chainId, '86400').toString(), // refPerTokChainlinkTimeout - ], - 'contracts/plugins/assets/cbeth/CBEthCollateral.sol:CBEthCollateral' - ) + + if (!baseL2Chains.includes(hre.network.name)) { + const oracleError = combinedError(fp('0.005'), fp('0.02')) // 0.5% & 2% + + await verifyContract( + chainId, + deployments.collateral.cbETH, + [ + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH, + oracleError: oracleError.toString(), // 0.5% & 2% + erc20: networkConfig[chainId].tokens.cbETH!, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + targetName: hre.ethers.utils.formatBytes32String('ETH'), + defaultThreshold: fp('0.02').add(oracleError).toString(), // ~4.5% + delayUntilDefault: bn('86400').toString(), // 24h + }, + fp('1e-4'), // revenueHiding = 0.01% + networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed + oracleTimeout(chainId, '86400').toString(), // refPerTokChainlinkTimeout + ], + 'contracts/plugins/assets/cbeth/CBETHCollateral.sol:CBEthCollateral' + ) + } else if (chainId == '8453' || chainId == '84531') { + const oracleError = combinedError(fp('0.0015'), fp('0.005')) // 0.15% & 0.5% + await verifyContract( + chainId, + deployments.collateral.cbETH, + [ + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH, + oracleError: oracleError.toString(), // 0.15% & 0.5%, + erc20: networkConfig[chainId].tokens.cbETH!, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '1200').toString(), // 20 min + targetName: hre.ethers.utils.formatBytes32String('ETH'), + defaultThreshold: fp('0.02').add(oracleError).toString(), // ~2.5% + delayUntilDefault: bn('86400').toString(), // 24h + }, + fp('1e-4'), // revenueHiding = 0.01% + networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed + oracleTimeout(chainId, '86400').toString(), // refPerTokChainlinkTimeout + networkConfig[chainId].chainlinkFeeds.cbETHETHexr!, // exchangeRateChainlinkFeed + oracleTimeout(chainId, '86400').toString(), // exchangeRateChainlinkTimeout + ], + 'contracts/plugins/assets/cbeth/CBETHCollateralL2.sol:CBEthCollateralL2' + ) + } } main().catch((error) => { diff --git a/scripts/verification/collateral-plugins/verify_convex_stable.ts b/scripts/verification/collateral-plugins/verify_convex_stable.ts index b26f111b39..3c22ae2557 100644 --- a/scripts/verification/collateral-plugins/verify_convex_stable.ts +++ b/scripts/verification/collateral-plugins/verify_convex_stable.ts @@ -61,7 +61,7 @@ async function main() { chainId, await w3PoolCollateral.erc20(), [], - 'contracts/plugins/assets/convex/vendor/ConvexStakingWrapper.sol:ConvexStakingWrapper', + 'contracts/plugins/assets/curve/cvx/vendor/ConvexStakingWrapper.sol:ConvexStakingWrapper', { CvxMining: coreDeployments.cvxMiningLib } ) @@ -71,7 +71,7 @@ async function main() { chainId, coreDeployments.cvxMiningLib, [], - 'contracts/plugins/assets/convex/vendor/CvxMining.sol:CvxMining' + 'contracts/plugins/assets/curve/cvx/vendor/CvxMining.sol:CvxMining' ) /******** Verify 3Pool plugin **************************/ @@ -85,7 +85,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -105,7 +105,7 @@ async function main() { lpToken: THREE_POOL_TOKEN, }, ], - 'contracts/plugins/assets/convex/CurveStableCollateral.sol:CurveStableCollateral' + 'contracts/plugins/assets/curve/CurveStableCollateral.sol:CurveStableCollateral' ) } diff --git a/scripts/verification/collateral-plugins/verify_convex_stable_metapool.ts b/scripts/verification/collateral-plugins/verify_convex_stable_metapool.ts index 417ff521c6..440f8854b2 100644 --- a/scripts/verification/collateral-plugins/verify_convex_stable_metapool.ts +++ b/scripts/verification/collateral-plugins/verify_convex_stable_metapool.ts @@ -86,7 +86,7 @@ async function main() { MIM_THREE_POOL, MIM_DEFAULT_THRESHOLD, ], - 'contracts/plugins/assets/convex/CurveStableMetapoolCollateral.sol:CurveStableMetapoolCollateral' + 'contracts/plugins/assets/curve/CurveStableMetapoolCollateral.sol:CurveStableMetapoolCollateral' ) } diff --git a/scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts b/scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts index 00d7ae5b37..771f6bc767 100644 --- a/scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts +++ b/scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts @@ -59,7 +59,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, @@ -80,7 +80,7 @@ async function main() { eUSD_FRAX_BP, DEFAULT_THRESHOLD, // 2% ], - 'contracts/plugins/assets/convex/CurveStableRTokenMetapoolCollateral.sol:CurveStableRTokenMetapoolCollateral' + 'contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol:CurveStableRTokenMetapoolCollateral' ) } diff --git a/scripts/verification/collateral-plugins/verify_curve_stable.ts b/scripts/verification/collateral-plugins/verify_curve_stable.ts index e43317350d..ce1120f618 100644 --- a/scripts/verification/collateral-plugins/verify_curve_stable.ts +++ b/scripts/verification/collateral-plugins/verify_curve_stable.ts @@ -72,7 +72,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, diff --git a/scripts/verification/collateral-plugins/verify_curve_stable_rtoken_metapool.ts b/scripts/verification/collateral-plugins/verify_curve_stable_rtoken_metapool.ts index 45101d93fd..a48df02d1f 100644 --- a/scripts/verification/collateral-plugins/verify_curve_stable_rtoken_metapool.ts +++ b/scripts/verification/collateral-plugins/verify_curve_stable_rtoken_metapool.ts @@ -59,7 +59,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero + oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, diff --git a/scripts/verification/collateral-plugins/verify_cusdbcv3.ts b/scripts/verification/collateral-plugins/verify_cusdbcv3.ts new file mode 100644 index 0000000000..c3a6cb314e --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_cusdbcv3.ts @@ -0,0 +1,81 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { baseL2Chains, developmentChains, networkConfig } from '../../../common/configuration' +import { fp, bn } from '../../../common/numbers' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { priceTimeout, oracleTimeout, verifyContract, revenueHiding } from '../../deployment/utils' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + // Only exists on Base L2 + if (!baseL2Chains.includes(hre.network.name)) { + throw new Error(`Invalid network ${hre.network.name} - only available on Base`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + const collateral = await ethers.getContractAt( + 'CTokenV3Collateral', + deployments.collateral.cUSDbCv3 as string + ) + + /******** Verify Wrapper token - wcUSDCv3 **************************/ + + await verifyContract( + chainId, + await collateral.erc20(), + [ + networkConfig[chainId].tokens.cUSDbCv3, + networkConfig[chainId].COMET_REWARDS, + networkConfig[chainId].tokens.COMP, + ], + 'contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol:CusdcV3Wrapper' + ) + + /******** Verify Collateral - wcUSDbCv3 **************************/ + + const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleError = fp('0.003') // 0.3% (Base) + + await verifyContract( + chainId, + deployments.collateral.cUSDbCv3, + [ + { + priceTimeout: priceTimeout.toString(), + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC, + oracleError: usdcOracleError.toString(), + erc20: await collateral.erc20(), + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24h hr, + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.01').add(usdcOracleError).toString(), // 1% + 0.3% + delayUntilDefault: bn('86400').toString(), // 24h + }, + revenueHiding, + bn('10000e6'), // $10k + ], + 'contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol:CTokenV3Collateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_cusdcv3.ts b/scripts/verification/collateral-plugins/verify_cusdcv3.ts index 6632edbac4..62c1389289 100644 --- a/scripts/verification/collateral-plugins/verify_cusdcv3.ts +++ b/scripts/verification/collateral-plugins/verify_cusdcv3.ts @@ -1,6 +1,6 @@ import hre, { ethers } from 'hardhat' import { getChainId } from '../../../common/blockchain-utils' -import { developmentChains, networkConfig } from '../../../common/configuration' +import { baseL2Chains, developmentChains, networkConfig } from '../../../common/configuration' import { fp, bn } from '../../../common/numbers' import { getDeploymentFile, @@ -37,7 +37,7 @@ async function main() { await collateral.erc20(), [ networkConfig[chainId].tokens.cUSDCv3, - '0x1B0e765F6224C21223AeA2af16c1C46E38885a40', + networkConfig[chainId].COMET_REWARDS, networkConfig[chainId].tokens.COMP, ], 'contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol:CusdcV3Wrapper' @@ -45,6 +45,9 @@ async function main() { /******** Verify Collateral - wcUSDCv3 **************************/ + const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% + await verifyContract( chainId, deployments.collateral.cUSDCv3, @@ -52,12 +55,12 @@ async function main() { { priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC, - oracleError: fp('0.0025').toString(), // 0.25%, + oracleError: usdcOracleError.toString(), erc20: await collateral.erc20(), maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24h hr, + oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24h hr, targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), // 1% + 0.25% + defaultThreshold: fp('0.01').add(usdcOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h }, revenueHiding, diff --git a/scripts/verification/collateral-plugins/verify_morpho.ts b/scripts/verification/collateral-plugins/verify_morpho.ts new file mode 100644 index 0000000000..ba7658f5af --- /dev/null +++ b/scripts/verification/collateral-plugins/verify_morpho.ts @@ -0,0 +1,140 @@ +import hre, { ethers } from 'hardhat' +import { getChainId } from '../../../common/blockchain-utils' +import { developmentChains, networkConfig } from '../../../common/configuration' +import { fp, bn } from '../../../common/numbers' +import { + getDeploymentFile, + getAssetCollDeploymentFilename, + IAssetCollDeployments, +} from '../../deployment/common' +import { + combinedError, + priceTimeout, + oracleTimeout, + verifyContract, + revenueHiding, +} from '../../deployment/utils' + +let deployments: IAssetCollDeployments + +async function main() { + // ********** Read config ********** + const chainId = await getChainId(hre) + if (!networkConfig[chainId]) { + throw new Error(`Missing network configuration for ${hre.network.name}`) + } + + if (developmentChains.includes(hre.network.name)) { + throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) + } + + const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) + deployments = getDeploymentFile(assetCollDeploymentFilename) + + /******** MorphoAaveV2TokenisedDeposit **************************/ + + await verifyContract( + chainId, + deployments.erc20s.maUSDT, + [ + { + morphoController: networkConfig[chainId].MORPHO_AAVE_CONTROLLER!, + morphoLens: networkConfig[chainId].MORPHO_AAVE_LENS!, + rewardsDistributor: networkConfig[chainId].MORPHO_REWARDS_DISTRIBUTOR!, + underlyingERC20: networkConfig[chainId].tokens.USDT!, + poolToken: networkConfig[chainId].tokens.aUSDT!, + rewardToken: networkConfig[chainId].tokens.MORPHO!, + }, + ], + 'contracts/plugins/assets/morpho-aave/MorphoAaveV2TokenisedDeposit.sol:MorphoAaveV2TokenisedDeposit' + ) + + /******** MorphoFiatCollateral **************************/ + + const maUSDT = await ethers.getContractAt( + 'MorphoFiatCollateral', + deployments.collateral.maUSDT as string + ) + + await verifyContract( + chainId, + maUSDT.address, + [ + { + priceTimeout: priceTimeout.toString(), + oracleError: fp('0.0025').toString(), // 0.25% + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 1 hr + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.0025').add(fp('0.01')).toString(), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDT!, + erc20: await maUSDT.erc20(), + }, + revenueHiding, + ], + 'contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol:MorphoFiatCollateral' + ) + + /******** MorphoNonFiatCollateral **************************/ + + const maWBTC = await ethers.getContractAt( + 'MorphoNonFiatCollateral', + deployments.collateral.maWBTC as string + ) + const combinedBTCWBTCError = combinedError(fp('0.02'), fp('0.005')) + + await verifyContract( + chainId, + maWBTC.address, + [ + { + priceTimeout: priceTimeout.toString(), + oracleError: combinedBTCWBTCError.toString(), // 0.25% + maxTradeVolume: fp('1e6'), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + targetName: ethers.utils.formatBytes32String('BTC'), + defaultThreshold: fp('0.01').add(combinedBTCWBTCError), // ~3.5% + delayUntilDefault: bn('86400'), // 24h + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.BTC!, + erc20: await maWBTC.erc20(), + }, + revenueHiding, + networkConfig[chainId].chainlinkFeeds.WBTC!, + oracleTimeout(chainId, '86400').toString(), // 1 hr + ], + 'contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol:MorphoNonFiatCollateral' + ) + + /******** MorphoSelfReferentialCollateral **************************/ + + const maWETH = await ethers.getContractAt( + 'MorphoSelfReferentialCollateral', + deployments.collateral.maWETH as string + ) + + await verifyContract( + chainId, + maWETH.address, + [ + { + priceTimeout: priceTimeout, + oracleError: fp('0.005'), + maxTradeVolume: fp('1e6'), // $1m, + oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + targetName: ethers.utils.formatBytes32String('ETH'), + defaultThreshold: fp('0'), // 0% -- no soft default for self-referential collateral + delayUntilDefault: bn('86400'), // 24h + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH!, + erc20: await maWETH.erc20(), + }, + revenueHiding, + ], + 'contracts/plugins/assets/morpho-aave/MorphoSelfReferentialCollateral.sol:MorphoSelfReferentialCollateral' + ) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/verification/collateral-plugins/verify_reth.ts b/scripts/verification/collateral-plugins/verify_reth.ts index 5dca337e0b..324a081859 100644 --- a/scripts/verification/collateral-plugins/verify_reth.ts +++ b/scripts/verification/collateral-plugins/verify_reth.ts @@ -26,6 +26,7 @@ async function main() { deployments = getDeploymentFile(assetCollDeploymentFilename) /******** Verify Rocket-Pool ETH - rETH **************************/ + const oracleError = combinedError(fp('0.005'), fp('0.02')) // 0.5% & 2% await verifyContract( chainId, deployments.collateral.rETH, @@ -33,12 +34,12 @@ async function main() { { priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH, - oracleError: combinedError(fp('0.005'), fp('0.02')).toString(), // 0.5% & 2%, + oracleError: oracleError.toString(), // 0.5% & 2%, erc20: networkConfig[chainId].tokens.rETH, maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, targetName: hre.ethers.utils.formatBytes32String('ETH'), - defaultThreshold: fp('0.15').toString(), // 15% + defaultThreshold: fp('0.02').add(oracleError).toString(), // ~4.5% delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4'), // revenueHiding = 0.01% diff --git a/scripts/verification/collateral-plugins/verify_wsteth.ts b/scripts/verification/collateral-plugins/verify_wsteth.ts index 5ddc2291cf..c0b73b2fb0 100644 --- a/scripts/verification/collateral-plugins/verify_wsteth.ts +++ b/scripts/verification/collateral-plugins/verify_wsteth.ts @@ -40,7 +40,7 @@ async function main() { maxTradeVolume: fp('1e6').toString(), // $1m, oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, targetName: hre.ethers.utils.formatBytes32String('ETH'), - defaultThreshold: fp('0.15').toString(), // 15% + defaultThreshold: fp('0.025').toString(), // 2.5% = 2% + 0.5% stethETH feed oracleError delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4'), // revenueHiding = 0.01% diff --git a/scripts/verify_etherscan.ts b/scripts/verify_etherscan.ts index 0769abafa8..6014dfcfb3 100644 --- a/scripts/verify_etherscan.ts +++ b/scripts/verify_etherscan.ts @@ -1,7 +1,7 @@ /* eslint-disable no-process-exit */ import hre from 'hardhat' import { getChainId } from '../common/blockchain-utils' -import { developmentChains, networkConfig } from '../common/configuration' +import { baseL2Chains, developmentChains, networkConfig } from '../common/configuration' import { sh } from './deployment/utils' import { getDeploymentFile, @@ -35,6 +35,7 @@ async function main() { // This process is intelligent enough that it can be run on all verify scripts, // even if some portions have already been verified + // Phase 1- Common const scripts = [ '0_verify_libraries.ts', '1_verify_implementations.ts', @@ -42,23 +43,39 @@ async function main() { '3_verify_deployer.ts', '4_verify_facade.ts', '5_verify_facadeWrite.ts', - '6_verify.ts', - '7_verify_rToken.ts', - '8_verify_governance.ts', - 'collateral-plugins/verify_convex_stable.ts', - 'collateral-plugins/verify_convex_stable_metapool.ts', - 'collateral-plugins/verify_convex_volatile.ts', - 'collateral-plugins/verify_convex_stable_rtoken_metapool.ts', - 'collateral-plugins/verify_curve_stable.ts', - 'collateral-plugins/verify_curve_stable_metapool.ts', - 'collateral-plugins/verify_curve_volatile.ts', - 'collateral-plugins/verify_curve_stable_rtoken_metapool.ts', - 'collateral-plugins/verify_cusdcv3.ts', - 'collateral-plugins/verify_reth.ts', - 'collateral-plugins/verify_wsteth.ts', - 'collateral-plugins/verify_cbeth.ts', + '6_verify_collateral.ts', ] + // Phase 2 - Individual Plugins + if (!baseL2Chains.includes(hre.network.name)) { + scripts.push( + 'collateral-plugins/verify_convex_stable.ts', + 'collateral-plugins/verify_convex_stable_metapool.ts', + 'collateral-plugins/verify_convex_stable_rtoken_metapool.ts', + 'collateral-plugins/verify_curve_stable.ts', + 'collateral-plugins/verify_curve_stable_metapool.ts', + 'collateral-plugins/verify_curve_stable_rtoken_metapool.ts', + 'collateral-plugins/verify_cusdcv3.ts', + 'collateral-plugins/verify_reth.ts', + 'collateral-plugins/verify_wsteth.ts', + 'collateral-plugins/verify_cbeth.ts', + 'collateral-plugins/verify_sdai.ts', + 'collateral-plugins/verify_morpho.ts', + 'collateral-plugins/verify_aave_v3_usdc.ts' + ) + } else if (chainId == '8453' || chainId == '84531') { + // Base L2 chains + scripts.push( + 'collateral-plugins/verify_cbeth.ts', + 'collateral-plugins/verify_cusdbcv3.ts', + 'collateral-plugins/verify_aave_v3_usdbc' + ) + } + + // Phase 3 - RTokens and Governance + // '7_verify_rToken.ts', + // '8_verify_governance.ts', + for (const script of scripts) { console.log('\n===========================================\n', script, '') await sh(`hardhat run scripts/verification/${script}`) diff --git a/tasks/deployment/get-addresses.ts b/tasks/deployment/get-addresses.ts new file mode 100644 index 0000000000..c7423cb198 --- /dev/null +++ b/tasks/deployment/get-addresses.ts @@ -0,0 +1,269 @@ +import { getChainId } from '../../common/blockchain-utils' +import { task, types } from 'hardhat/config' +import fs from 'fs' +import { + IAssetCollDeployments, + getAssetCollDeploymentFilename, + getDeploymentFile, + getDeploymentFilename, +} from '#/scripts/deployment/common' +import { ITokens } from '#/common/configuration' +import { MainP1 } from '@typechain/MainP1' +import { Contract } from 'ethers' + +task('get-addys', 'Compile the deployed addresses of an RToken deployment') + .addOptionalParam('rtoken', 'The address of the RToken', undefined, types.string) + .addOptionalParam('gov', 'The address of the RToken Governance', undefined, types.string) + .addOptionalParam('ver', 'The target version', undefined, types.string) + .setAction(async (params, hre) => { + /* + Helper functions + */ + const capitalize = (s: string) => s && s[0].toUpperCase() + s.slice(1) + + const etherscanUrl = 'https://etherscan.io/address/' + + const getVersion = async (c: Contract) => { + try { + return await c.version() + } catch (e) { + return 'N/A' + } + } + + const createRTokenTableRow = async (name: string, address: string) => { + const url = `https://api.etherscan.io/api?module=contract&action=getsourcecode&address=${address}&apikey=${process.env.ETHERSCAN_API_KEY}` + const response = await fetch(url) + const data = await response.json() + const implementation = data.result[0].Implementation + const component = await hre.ethers.getContractAt('ComponentP1', address) + return `| ${name} | [${address}](${etherscanUrl}${address}) | [${implementation}](${etherscanUrl}${implementation}#code) | ${await getVersion( + component + )} |` + } + + const createAssetTableRow = async (name: string, address: string) => { + return `| ${name} | [${address}](${etherscanUrl}${address}) |` + } + + const createTableRows = async ( + components: { name: string; address: string }[], + isRToken: boolean + ) => { + const rows = [] + for (const component of components) { + isRToken + ? rows.push(await createRTokenTableRow(component.name, component.address)) + : rows.push(await createAssetTableRow(component.name, component.address)) + } + return rows.join('\n') + } + + const createRTokenMarkdown = async ( + name: string, + address: string, + rows: string, + govRows: string | undefined + ) => { + return `# [${name}](${etherscanUrl}${address}) +## Component Addresses +| Contract | Address | Implementation | Version | +| --- | --- | --- | --- | +${rows} + +${ + govRows && + ` +## Governance Addresses +| Contract | Address | Implementation | Version | +| --- | --- | --- | --- | +${govRows} +` +} + ` + } + + const createAssetMarkdown = async (name: string, assets: string, collaterals: string) => { + return `# ${name} +## Assets +| Contract | Address | +| --- | --- | +${assets} + +## Collaterals +| Contract | Address | +| --- | --- | +${collaterals} + ` + } + + const getRTokenFileName = async (rtoken: string) => { + const chainId = await getChainId(hre) + const rToken = await hre.ethers.getContractAt('IRToken', rtoken) + const rTokenSymbol = await rToken.symbol() + return `${outputDir}${chainId}-${rTokenSymbol}.md` + } + + const getAssetFileName = async (version: string) => { + const chainId = await getChainId(hre) + return `${outputDir}${chainId}-assets-${version}.md` + } + + const getComponentFileName = async (version: string) => { + const chainId = await getChainId(hre) + return `${outputDir}${chainId}-components-${version}.md` + } + + const getActiveRoleHolders = async (main: MainP1, role: string) => { + // get active owners + // + const grantedFilter = main.filters.RoleGranted(role) + const revokedFilter = main.filters.RoleRevoked(role) + + // get granted owners + const ownersGranted = await main.queryFilter(grantedFilter) + let owners = ownersGranted.map((event) => { + return event.args![1] + }) + interface OwnerCount { + [key: string]: number + } + + // count granted owners + let ownerCount: OwnerCount = {} + owners.forEach((owner: string) => { + ownerCount[owner] = (ownerCount[owner] || 0) + 1 + }) + + // reduce counts by revoked owners + const ownersRevoked = await main.queryFilter(revokedFilter) + ownersRevoked.forEach((event) => { + const owner = event.args![1] + ownerCount[owner] = (ownerCount[owner] || 0) - 1 + }) + return Object.keys(ownerCount).filter((owner) => ownerCount[owner] > 0) + } + + /* + Compile target addresses and create markdown files + */ + + const outputDir = 'docs/deployed-addresses/' + + if (params.rtoken && params.gov) { + // if rtoken address is provided, print component addresses + + const rToken = await hre.ethers.getContractAt('IRToken', params.rtoken) + const mainAddress = await rToken.main() + const main = await hre.ethers.getContractAt('MainP1', mainAddress) + const backingManagerAddress = await main.backingManager() + const basketHandlerAddress = await main.basketHandler() + const brokerAddress = await main.broker() + const rsrTraderAddress = await main.rsrTrader() + const rTokenTraderAddress = await main.rTokenTrader() + const furnaceAddress = await main.furnace() + const assetRegistryAddress = await main.assetRegistry() + const distributorAddress = await main.distributor() + const stRSRAddress = await main.stRSR() + + const components = [ + { name: 'RToken', address: params.rtoken }, + { name: 'Main', address: mainAddress }, + { name: 'AssetRegistry', address: assetRegistryAddress }, + { name: 'BackingManager', address: backingManagerAddress }, + { name: 'BasketHandler', address: basketHandlerAddress }, + { name: 'Broker', address: brokerAddress }, + { name: 'RSRTrader', address: rsrTraderAddress }, + { name: 'RTokenTrader', address: rTokenTraderAddress }, + { name: 'Distributor', address: distributorAddress }, + { name: 'Furnace', address: furnaceAddress }, + { name: 'StRSR', address: stRSRAddress }, + ] + + const governance = await hre.ethers.getContractAt('Governance', params.gov) + const timelock = await governance.timelock() + + // confirm timelock is in fact owner of main + const isOwner = await main.hasRole(await main.OWNER_ROLE(), timelock) + if (!isOwner) { + throw new Error('Wrong governance address (Timelock is not owner of Main)') + } + + const govComponents = [ + { name: 'Governor Alexios', address: params.gov }, + { name: 'Timelock', address: timelock }, + ] + + const rTokenName = await rToken.name() + const rTokenSymbol = await rToken.symbol() + + const rows = await createTableRows(components, true) + const govRows = await createTableRows(govComponents, true) + const markdown = await createRTokenMarkdown( + `${rTokenSymbol} (${rTokenName})`, + params.rtoken, + rows, + govRows + ) + fs.writeFileSync(await getRTokenFileName(params.rtoken), markdown) + } else if (params.ver) { + // if version is provided, print implementation addresses + const version = `${hre.network.name}-${params.ver}` + const collateralDepl = getDeploymentFile( + getAssetCollDeploymentFilename(await getChainId(hre), version) + ) as IAssetCollDeployments + + const collaterals = Object.keys(collateralDepl.collateral).map((coll) => { + const key = coll as keyof ITokens + return { name: coll, address: collateralDepl.collateral[key]! } + }) + const collateralRows = await createTableRows(collaterals, false) + + const assets = Object.keys(collateralDepl.assets).map((ass) => { + const key = ass as keyof ITokens + return { name: ass, address: collateralDepl.assets[key]! } + }) + const assetRows = await createTableRows(assets, false) + + const assetMarkdown = await createAssetMarkdown( + `Assets (${capitalize(hre.network.name)} ${params.ver})`, + assetRows, + collateralRows + ) + fs.writeFileSync(await getAssetFileName(params.ver), assetMarkdown) + + const componentDepl = getDeploymentFile(getDeploymentFilename(await getChainId(hre), version)) + const recursiveDestructure = ( + obj: string | { [key: string]: string }, + key: string + ): Array<{ name: string; address: string }> | { name: string; address: string } => { + if (typeof obj === 'string') { + return { name: capitalize(key), address: obj } + } else { + return Object.keys(obj) + .map((k) => { + return recursiveDestructure(obj[k], k) + }) + .flat() + } + } + + let components = recursiveDestructure(componentDepl as {}, '') as Array<{ + name: string + address: string + }> + components = components.sort((a, b) => a.name.localeCompare(b.name)) + const componentMarkdown = await createRTokenMarkdown( + `Component Implementations (${capitalize(hre.network.name)} ${params.ver})`, + params.version, + await createTableRows(components, false), + undefined + ) + fs.writeFileSync(await getComponentFileName(params.ver), componentMarkdown) + } else { + // if neither rtoken address nor version number is provided, throw error + throw new Error( + 'must provide either RToken address (--rtoken) and RToken governance (--gov), or Version (--ver)' + ) + } + }) diff --git a/tasks/index.ts b/tasks/index.ts index 74d51bf9b8..4f167da7a5 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -19,6 +19,7 @@ import './deployment/create-deployer-registry' import './deployment/empty-wallet' import './deployment/cancel-tx' import './deployment/sign-msg' +import './deployment/get-addresses' import './upgrades/force-import' import './upgrades/validate-upgrade' import './testing/mint-tokens' diff --git a/tasks/testing/upgrade-checker-utils/constants.ts b/tasks/testing/upgrade-checker-utils/constants.ts index 518a48adc3..602c245d47 100644 --- a/tasks/testing/upgrade-checker-utils/constants.ts +++ b/tasks/testing/upgrade-checker-utils/constants.ts @@ -8,16 +8,14 @@ export const whales: { [key: string]: string } = { [networkConfig['1'].tokens.aUSDT!.toLowerCase()]: '0x0B6B712B0f3998961Cd3109341b00c905b16124A', ['0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9'.toLowerCase()]: '0x5754284f345afc66a98fbB0a0Afe71e0F007B949', // saUSDT - // TODO: Replace with real address - ['0x840748F7Fd3EA956E5f4c88001da5CC1ABCBc038'.toLowerCase()]: + ['0x4Be33630F92661afD646081BC29079A38b879aA0'.toLowerCase()]: '0x5754284f345afc66a98fbB0a0Afe71e0F007B949', // cUSDTVault [networkConfig['1'].tokens.aUSDC!.toLowerCase()]: '0x777777c9898D384F785Ee44Acfe945efDFf5f3E0', [networkConfig['1'].tokens.cUSDC!.toLowerCase()]: '0x97D868b5C2937355Bf89C5E5463d52016240fE86', ['0x60C384e226b120d93f3e0F4C502957b2B9C32B15'.toLowerCase()]: '0x51eDF02152EBfb338e03E30d65C15fBf06cc9ECC', // saUSDC - // TODO: Replace with real address - ['0xf201fFeA8447AB3d43c98Da3349e0749813C9009'.toLowerCase()]: + ['0xf579F9885f1AEa0d3F8bE0F18AfED28c92a43022'.toLowerCase()]: '0x51eDF02152EBfb338e03E30d65C15fBf06cc9ECC', // cUSDCVault [networkConfig['1'].tokens.RSR!.toLowerCase()]: '0x6bab6EB87Aa5a1e4A8310C73bDAAA8A5dAAd81C1', diff --git a/tasks/testing/upgrade-checker-utils/logs.ts b/tasks/testing/upgrade-checker-utils/logs.ts index 756c7594d5..a601fdf2b3 100644 --- a/tasks/testing/upgrade-checker-utils/logs.ts +++ b/tasks/testing/upgrade-checker-utils/logs.ts @@ -44,8 +44,8 @@ const tokens: { [key: string]: string } = { [networkConfig['1'].tokens.DAI!.toLowerCase()]: 'DAI', ['0x60C384e226b120d93f3e0F4C502957b2B9C32B15'.toLowerCase()]: 'saUSDC', ['0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9'.toLowerCase()]: 'saUSDT', - ['0xf201fFeA8447AB3d43c98Da3349e0749813C9009'.toLowerCase()]: 'cUSDCVault', // TODO: Replace with real address - ['0x840748F7Fd3EA956E5f4c88001da5CC1ABCBc038'.toLowerCase()]: 'cUSDTVault', // TODO: Replace with real address + ['0xf579F9885f1AEa0d3F8bE0F18AfED28c92a43022'.toLowerCase()]: 'cUSDCVault', + ['0x4Be33630F92661afD646081BC29079A38b879aA0'.toLowerCase()]: 'cUSDTVault', } export const logToken = (tokenAddress: string) => { diff --git a/tasks/testing/upgrade-checker-utils/trades.ts b/tasks/testing/upgrade-checker-utils/trades.ts index d45a377e54..564e91b037 100644 --- a/tasks/testing/upgrade-checker-utils/trades.ts +++ b/tasks/testing/upgrade-checker-utils/trades.ts @@ -178,9 +178,8 @@ export const getTokens = async ( case '0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9': // saUSDT await getStaticAToken(hre, tokenAddress, amount, recipient) break - // TODO: Replace with real addresses - case '0xf201fFeA8447AB3d43c98Da3349e0749813C9009': // cUSDCVault - case '0x840748F7Fd3EA956E5f4c88001da5CC1ABCBc038': // cUSDTVault + case '0xf579F9885f1AEa0d3F8bE0F18AfED28c92a43022': // cUSDCVault + case '0x4Be33630F92661afD646081BC29079A38b879aA0': // cUSDTVault await getCTokenVault(hre, tokenAddress, amount, recipient) break default: diff --git a/tasks/testing/upgrade-checker-utils/upgrades/3_0_0.ts b/tasks/testing/upgrade-checker-utils/upgrades/3_0_0.ts index 49d5fc4753..82e7892e64 100644 --- a/tasks/testing/upgrade-checker-utils/upgrades/3_0_0.ts +++ b/tasks/testing/upgrade-checker-utils/upgrades/3_0_0.ts @@ -2,33 +2,14 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types' import { expect } from 'chai' import { ProposalBuilder, buildProposal } from '../governance' import { Proposal } from '#/utils/subgraph' -import { IImplementations, networkConfig } from '#/common/configuration' +import { networkConfig } from '#/common/configuration' import { bn, fp, toBNDecimals } from '#/common/numbers' import { CollateralStatus, TradeKind, ZERO_ADDRESS } from '#/common/constants' import { pushOraclesForward, setOraclePrice } from '../oracles' import { whileImpersonating } from '#/utils/impersonation' import { whales } from '../constants' import { getTokens, runDutchTrade } from '../trades' -import { - AssetRegistryP1, - BackingManagerP1, - BasketHandlerP1, - BasketLibP1, - BrokerP1, - CTokenFiatCollateral, - DistributorP1, - EURFiatCollateral, - FurnaceP1, - MockV3Aggregator, - GnosisTrade, - IERC20Metadata, - DutchTrade, - RevenueTraderP1, - RTokenP1, - StRSRP1Votes, - MainP1, - RecollateralizationLibP1, -} from '../../../../typechain' +import { EURFiatCollateral, MockV3Aggregator } from '../../../../typechain' import { advanceTime, advanceToTimestamp, @@ -47,6 +28,8 @@ export default async ( const main = await hre.ethers.getContractAt('IMain', await rToken.main()) const governor = await hre.ethers.getContractAt('Governance', governorAddress) const timelockAddress = await governor.timelock() + const timelock = await hre.ethers.getContractAt('TimelockController', timelockAddress) + const assetRegistry = await hre.ethers.getContractAt( 'AssetRegistryP1', await main.assetRegistry() @@ -155,15 +138,17 @@ export default async ( console.log(`successfully tested issuance and trading pause`) /* - New setters for enabling auctions + New getters/setters for auctions */ - // Auction setters + // Auction getters/setters await whileImpersonating(hre, timelockAddress, async (tl) => { await broker.connect(tl).enableBatchTrade() await broker.connect(tl).enableDutchTrade(rsr.address) }) + expect(await broker.batchTradeDisabled()).to.equal(false) + expect(await broker.dutchTradeDisabled(rsr.address)).to.equal(false) - console.log(`successfully tested new auction setters`) + console.log(`successfully tested new auction getters/setters`) /* Dust Auctions @@ -328,51 +313,27 @@ export default async ( }) console.log(`successfully tested withrawalLeak`) - // we pushed the chain forward, so we need to keep the rToken SOUND - await pushOraclesForward(hre, rTokenAddress) - /* - RToken Asset + Governance changes */ - console.log(`swapping RTokenAsset...`) - - const rTokenAsset = await hre.ethers.getContractAt( - 'TestIAsset', - await assetRegistry.toAsset(rToken.address) - ) - const maxTradeVolumePrev = await rTokenAsset.maxTradeVolume() - - const newRTokenAsset = await ( - await hre.ethers.getContractFactory('RTokenAsset') - ).deploy(rToken.address, maxTradeVolumePrev) - - // Swap RToken Asset - await whileImpersonating(hre, timelockAddress, async (tl) => { - await assetRegistry.connect(tl).swapRegistered(newRTokenAsset.address) - }) - await assetRegistry.refresh() + console.log(`testing governance...`) - // Check interface behaves properly - expect(await newRTokenAsset.isCollateral()).to.equal(false) - expect(await newRTokenAsset.erc20()).to.equal(rToken.address) - expect(await rToken.decimals()).to.equal(18) - expect(await newRTokenAsset.version()).to.equal('3.0.0') - expect(await newRTokenAsset.maxTradeVolume()).to.equal(maxTradeVolumePrev) + const EXECUTOR_ROLE = await timelock.EXECUTOR_ROLE() + expect(await timelock.hasRole(EXECUTOR_ROLE, governor.address)).to.equal(true) + expect(await timelock.hasRole(EXECUTOR_ROLE, ZERO_ADDRESS)).to.equal(false) - const [lowPricePrev, highPricePrev] = await rTokenAsset.price() - const [lowPrice, highPrice] = await newRTokenAsset.price() - expect(lowPrice).to.equal(lowPricePrev) - expect(highPrice).to.equal(highPricePrev) + console.log(`successfully tested governance`) - await expect(rTokenAsset.claimRewards()).to.not.emit(rTokenAsset, 'RewardsClaimed') - console.log(`successfully tested RTokenAsset`) + // we pushed the chain forward, so we need to keep the rToken SOUND + await pushOraclesForward(hre, rTokenAddress) console.log('\n3.0.0 check succeeded!') } export const proposal_3_0_0: ProposalBuilder = async ( hre: HardhatRuntimeEnvironment, - rTokenAddress: string + rTokenAddress: string, + governorAddress: string ): Promise => { const rToken = await hre.ethers.getContractAt('RTokenP1', rTokenAddress) const main = await hre.ethers.getContractAt('MainP1', await rToken.main()) @@ -395,225 +356,109 @@ export const proposal_3_0_0: ProposalBuilder = async ( const rTokenTrader = await hre.ethers.getContractAt('RevenueTraderP1', await main.rTokenTrader()) const stRSR = await hre.ethers.getContractAt('StRSRP1Votes', await main.stRSR()) - // TODO: Uncomment and replace with deployed addresses once they are available - /* - const mainImplAddr = '0x...' - const batchTradeImplAddr = '0x...' - const dutchTradeImplAddr = '0x...' - const assetRegImplAddr = '0x...' - const bckMgrImplAddr = '0x...' - const bsktHdlImplAddr = '0x...' - const brokerImplAddr = '0x...' - const distImplAddr = '0x...' - const furnaceImplAddr = '0x...' - const rsrTraderImplAddr = '0x...' - const rTokenTraderImplAddr = '0x...' - const rTokenImplAddr = '0x...' - const stRSRImplAddr = '0x...' - */ - - // TODO: Remove code once addresses are available - const implementations: IImplementations = await deployNewImplementations(hre) - const mainImplAddr = implementations.main - const batchTradeImplAddr = implementations.trading.gnosisTrade - const dutchTradeImplAddr = implementations.trading.dutchTrade - const assetRegImplAddr = implementations.components.assetRegistry - const bckMgrImplAddr = implementations.components.backingManager - const bsktHdlImplAddr = implementations.components.basketHandler - const brokerImplAddr = implementations.components.broker - const distImplAddr = implementations.components.distributor - const furnaceImplAddr = implementations.components.furnace - const rsrTraderImplAddr = implementations.components.rsrTrader - const rTokenTraderImplAddr = implementations.components.rTokenTrader - const rTokenImplAddr = implementations.components.rToken - const stRSRImplAddr = implementations.components.stRSR - - // TODO: Uncomment and replace with deployed addresses once they are available - /* - const cUSDCVaultAddr = '0x...' - const cUSDCNewCollateralAddr = '0x...' - const cUSDTVaultAddr = '0x...' - const cUSDTNewCollateralAddr = '0x...' - */ - const saUSDCCollateralAddr = '0x60C384e226b120d93f3e0F4C502957b2B9C32B15' - const saUSDTCollateralAddr = '0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9' - - // TODO: Remove code once addresses are available - // CUSDC Vault and collateral - const [cUSDCVaultAddr, cUSDCVaultCollateralAddr] = await makeCTokenVaultCollateral( - hre, - networkConfig['1'].tokens.cUSDC!, - await assetRegistry.toColl(networkConfig['1'].tokens.cUSDC!), - networkConfig['1'].COMPTROLLER! - ) - const [cUSDTVaultAddr, cUSDTVaultCollateralAddr] = await makeCTokenVaultCollateral( - hre, - networkConfig['1'].tokens.cUSDT!, - await assetRegistry.toColl(networkConfig['1'].tokens.cUSDT!), - networkConfig['1'].COMPTROLLER! - ) - - // Step 1 - Update implementations and config + const governor = await hre.ethers.getContractAt('Governance', governorAddress) + const timelock = await hre.ethers.getContractAt('TimelockController', await governor.timelock()) + + const mainImplAddr = '0xF5366f67FF66A3CefcB18809a762D5b5931FebF8' + const batchTradeImplAddr = '0xe416Db92A1B27c4e28D5560C1EEC03f7c582F630' + const dutchTradeImplAddr = '0x2387C22727ACb91519b80A15AEf393ad40dFdb2F' + const assetRegImplAddr = '0x773cf50adCF1730964D4A9b664BaEd4b9FFC2450' + const bckMgrImplAddr = '0x0A388FC05AA017b31fb084e43e7aEaFdBc043080' + const bsktHdlImplAddr = '0x5ccca36CbB66a4E4033B08b4F6D7bAc96bA55cDc' + const brokerImplAddr = '0x9A5F8A9bB91a868b7501139eEdB20dC129D28F04' + const distImplAddr = '0x0e8439a17bA5cBb2D9823c03a02566B9dd5d96Ac' + const furnaceImplAddr = '0x99580Fc649c02347eBc7750524CAAe5cAcf9d34c' + const rsrTraderImplAddr = '0x1cCa3FBB11C4b734183f997679d52DeFA74b613A' + const rTokenTraderImplAddr = '0x1cCa3FBB11C4b734183f997679d52DeFA74b613A' + const rTokenImplAddr = '0xb6f01Aa21defA4a4DE33Bed16BcC06cfd23b6A6F' + const stRSRImplAddr = '0xC98eaFc9F249D90e3E35E729e3679DD75A899c10' + + const cUSDCVaultAddr = '0xf579F9885f1AEa0d3F8bE0F18AfED28c92a43022' + const cUSDCVaultCollateralAddr = '0x50a9d529EA175CdE72525Eaa809f5C3c47dAA1bB' + const cUSDTVaultAddr = '0x4Be33630F92661afD646081BC29079A38b879aA0' + const cUSDTVaultCollateralAddr = '0x5757fF814da66a2B4f9D11d48570d742e246CfD9' + const saUSDCAddr = '0x60C384e226b120d93f3e0F4C502957b2B9C32B15' + const aUSDCCollateralAddr = '0x7CD9CA6401f743b38B3B16eA314BbaB8e9c1aC51' + const saUSDTAddr = '0x21fe646D1Ed0733336F2D4d9b2FE67790a6099D9' + const aUSDTCollateralAddr = '0xE39188Ddd4eb27d1D25f5f58cC6A5fD9228EEdeF' + + const RSRAssetAddr = '0x7edD40933DfdA0ecEe1ad3E61a5044962284e1A6' + const TUSDCollateralAddr = '0x7F9999B2C9D310a5f48dfD070eb5129e1e8565E2' + const USDPCollateralAddr = '0x2f98bA77a8ca1c630255c4517b1b3878f6e60C89' + const DAICollateralAddr = '0xf7d1C6eE4C0D84C6B530D53A897daa1E9eB56833' + const USDTCollateralAddr = '0x58D7bF13D3572b08dE5d96373b8097d94B1325ad' + const USDCCollateralAddr = '0xBE9D23040fe22E8Bd8A88BF5101061557355cA04' + const COMPAssetAddr = '0xCFA67f42A0fDe4F0Fb612ea5e66170B0465B84c1' + const stkAAVEAssetAddr = '0x6647c880Eb8F57948AF50aB45fca8FE86C154D24' + const RTokenAssetAddr = '0x70C34352a73b76322cEc6bB965B9fd1a95C77A61' + + // Step 1 - Update implementations const txs = [ - await main.populateTransaction.upgradeTo(mainImplAddr), await assetRegistry.populateTransaction.upgradeTo(assetRegImplAddr), await backingManager.populateTransaction.upgradeTo(bckMgrImplAddr), await basketHandler.populateTransaction.upgradeTo(bsktHdlImplAddr), await broker.populateTransaction.upgradeTo(brokerImplAddr), await distributor.populateTransaction.upgradeTo(distImplAddr), await furnace.populateTransaction.upgradeTo(furnaceImplAddr), + await main.populateTransaction.upgradeTo(mainImplAddr), await rsrTrader.populateTransaction.upgradeTo(rsrTraderImplAddr), await rTokenTrader.populateTransaction.upgradeTo(rTokenTraderImplAddr), await rToken.populateTransaction.upgradeTo(rTokenImplAddr), await stRSR.populateTransaction.upgradeTo(stRSRImplAddr), - await broker.populateTransaction.setBatchTradeImplementation(batchTradeImplAddr), - await broker.populateTransaction.setDutchTradeImplementation(dutchTradeImplAddr), + ] + + // Step 2 - Cache components + txs.push( await backingManager.populateTransaction.cacheComponents(), - await rsrTrader.populateTransaction.cacheComponents(), - await rTokenTrader.populateTransaction.cacheComponents(), await distributor.populateTransaction.cacheComponents(), - await basketHandler.populateTransaction.setWarmupPeriod(900), - await stRSR.populateTransaction.setWithdrawalLeak(bn('5e16')), - await broker.populateTransaction.setDutchAuctionLength(1800), - ] + await rsrTrader.populateTransaction.cacheComponents(), + await rTokenTrader.populateTransaction.cacheComponents() + ) - // Step 2 - Basket change + // Step 3 - Register and swap assets txs.push( await assetRegistry.populateTransaction.register(cUSDCVaultCollateralAddr), await assetRegistry.populateTransaction.register(cUSDTVaultCollateralAddr), + await assetRegistry.populateTransaction.swapRegistered(aUSDCCollateralAddr), + await assetRegistry.populateTransaction.swapRegistered(aUSDTCollateralAddr), + await assetRegistry.populateTransaction.swapRegistered(RSRAssetAddr), + await assetRegistry.populateTransaction.swapRegistered(TUSDCollateralAddr), + await assetRegistry.populateTransaction.swapRegistered(USDPCollateralAddr), + await assetRegistry.populateTransaction.swapRegistered(DAICollateralAddr), + await assetRegistry.populateTransaction.swapRegistered(USDTCollateralAddr), + await assetRegistry.populateTransaction.swapRegistered(USDCCollateralAddr), + await assetRegistry.populateTransaction.swapRegistered(COMPAssetAddr), + await assetRegistry.populateTransaction.swapRegistered(stkAAVEAssetAddr), + await assetRegistry.populateTransaction.swapRegistered(RTokenAssetAddr) + ) + + // Step 4 - Basket change + txs.push( await basketHandler.populateTransaction.setPrimeBasket( - [saUSDCCollateralAddr, cUSDCVaultAddr, saUSDTCollateralAddr, cUSDTVaultAddr], + [cUSDCVaultAddr, cUSDTVaultAddr, saUSDCAddr, saUSDTAddr], [fp('0.25'), fp('0.25'), fp('0.25'), fp('0.25')] ), await basketHandler.populateTransaction.refreshBasket() ) - const description = - 'Upgrade implementations, set trade plugins, components, config values, and update basket' - - return buildProposal(txs, description) -} - -// TODO: Remove once final addresses exist on Mainnet -const deployNewImplementations = async ( - hre: HardhatRuntimeEnvironment -): Promise => { - // Deploy new implementations - const MainImplFactory = await hre.ethers.getContractFactory('MainP1') - const mainImpl: MainP1 = await MainImplFactory.deploy() - - // Deploy TradingLib external library - const TradingLibFactory = await hre.ethers.getContractFactory('RecollateralizationLibP1') - const tradingLib: RecollateralizationLibP1 = ( - await TradingLibFactory.deploy() - ) - - // Deploy BasketLib external library - const BasketLibFactory = await hre.ethers.getContractFactory('BasketLibP1') - const basketLib: BasketLibP1 = await BasketLibFactory.deploy() - - const AssetRegImplFactory = await hre.ethers.getContractFactory('AssetRegistryP1') - const assetRegImpl: AssetRegistryP1 = await AssetRegImplFactory.deploy() - - const BackingMgrImplFactory = await hre.ethers.getContractFactory('BackingManagerP1', { - libraries: { - RecollateralizationLibP1: tradingLib.address, - }, - }) - const backingMgrImpl: BackingManagerP1 = await BackingMgrImplFactory.deploy() - - const BskHandlerImplFactory = await hre.ethers.getContractFactory('BasketHandlerP1', { - libraries: { BasketLibP1: basketLib.address }, - }) - const bskHndlrImpl: BasketHandlerP1 = await BskHandlerImplFactory.deploy() - - const DistribImplFactory = await hre.ethers.getContractFactory('DistributorP1') - const distribImpl: DistributorP1 = await DistribImplFactory.deploy() - - const RevTraderImplFactory = await hre.ethers.getContractFactory('RevenueTraderP1') - const revTraderImpl: RevenueTraderP1 = await RevTraderImplFactory.deploy() - - const FurnaceImplFactory = await hre.ethers.getContractFactory('FurnaceP1') - const furnaceImpl: FurnaceP1 = await FurnaceImplFactory.deploy() - - const GnosisTradeImplFactory = await hre.ethers.getContractFactory('GnosisTrade') - const gnosisTrade: GnosisTrade = await GnosisTradeImplFactory.deploy() - - const DutchTradeImplFactory = await hre.ethers.getContractFactory('DutchTrade') - const dutchTrade: DutchTrade = await DutchTradeImplFactory.deploy() - - const BrokerImplFactory = await hre.ethers.getContractFactory('BrokerP1') - const brokerImpl: BrokerP1 = await BrokerImplFactory.deploy() - - const RTokenImplFactory = await hre.ethers.getContractFactory('RTokenP1') - const rTokenImpl: RTokenP1 = await RTokenImplFactory.deploy() - - const StRSRImplFactory = await hre.ethers.getContractFactory('StRSRP1Votes') - const stRSRImpl: StRSRP1Votes = await StRSRImplFactory.deploy() - - return { - main: mainImpl.address, - trading: { gnosisTrade: gnosisTrade.address, dutchTrade: dutchTrade.address }, - components: { - assetRegistry: assetRegImpl.address, - backingManager: backingMgrImpl.address, - basketHandler: bskHndlrImpl.address, - broker: brokerImpl.address, - distributor: distribImpl.address, - furnace: furnaceImpl.address, - rsrTrader: revTraderImpl.address, - rTokenTrader: revTraderImpl.address, - rToken: rTokenImpl.address, - stRSR: stRSRImpl.address, - }, - } -} - -// TODO: Remove once final addresses exist on Mainnet -const makeCTokenVaultCollateral = async ( - hre: HardhatRuntimeEnvironment, - tokenAddress: string, - collAddress: string, - comptrollerAddr: string -): Promise<[string, string]> => { - const CTokenWrapperFactory = await hre.ethers.getContractFactory('CTokenWrapper') - const CTokenCollateralFactory = await hre.ethers.getContractFactory('CTokenFiatCollateral') - - const erc20: IERC20Metadata = ( - await hre.ethers.getContractAt('CTokenMock', tokenAddress) - ) - - const currentColl: CTokenFiatCollateral = ( - await hre.ethers.getContractAt('CTokenFiatCollateral', collAddress) - ) - - const vault = await CTokenWrapperFactory.deploy( - erc20.address, - `${await erc20.name()} Vault`, - `${await erc20.symbol()}-VAULT`, - comptrollerAddr + // Step 5 - Governance + const EXECUTOR_ROLE = await timelock.EXECUTOR_ROLE() + txs.push( + await timelock.populateTransaction.grantRole(EXECUTOR_ROLE, governor.address), + await timelock.populateTransaction.revokeRole(EXECUTOR_ROLE, ZERO_ADDRESS) ) - await vault.deployed() - - const coll = await CTokenCollateralFactory.deploy( - { - priceTimeout: await currentColl.priceTimeout(), - chainlinkFeed: await currentColl.chainlinkFeed(), - oracleError: await currentColl.oracleError(), - erc20: vault.address, - maxTradeVolume: await currentColl.maxTradeVolume(), - oracleTimeout: await currentColl.oracleTimeout(), - targetName: hre.ethers.utils.formatBytes32String('USD'), - defaultThreshold: fp('0.0125').toString(), - delayUntilDefault: await currentColl.delayUntilDefault(), - }, - fp('1e-6') + // Step 6 - Initializations + txs.push( + await basketHandler.populateTransaction.setWarmupPeriod(900), + await stRSR.populateTransaction.setWithdrawalLeak(bn('5e16')), + await broker.populateTransaction.setBatchTradeImplementation(batchTradeImplAddr), + await broker.populateTransaction.setDutchTradeImplementation(dutchTradeImplAddr), + await broker.populateTransaction.setDutchAuctionLength(1800) ) - await coll.deployed() - - await (await coll.refresh()).wait() + const description = + 'Upgrade implementations, assets, set trade plugins, config values, and update basket' - return [vault.address, coll.address] + return buildProposal(txs, description) } diff --git a/tasks/testing/upgrade-checker.ts b/tasks/testing/upgrade-checker.ts index 9ee7df67a7..0cc0f5436d 100644 --- a/tasks/testing/upgrade-checker.ts +++ b/tasks/testing/upgrade-checker.ts @@ -47,7 +47,7 @@ task('upgrade-checker', 'Mints all the tokens to an address') .addParam('governor', 'the address of the OWNER of the RToken being upgraded') .addOptionalParam('proposalid', 'the ID of the governance proposal', undefined) .setAction(async (params, hre) => { - await resetFork(hre, Number(useEnv('MAINNET_BLOCK'))) + await resetFork(hre, Number(useEnv('FORK_BLOCK'))) const [tester] = await hre.ethers.getSigners() const chainId = await getChainId(hre) @@ -118,8 +118,8 @@ task('upgrade-checker', 'Mints all the tokens to an address') const usdcAddress = networkConfig['1'].tokens.USDC! const cUsdtAddress = networkConfig['1'].tokens.cUSDT! const cUsdcAddress = networkConfig['1'].tokens.cUSDC! - const cUsdtVaultAddress = '0x840748F7Fd3EA956E5f4c88001da5CC1ABCBc038'.toLowerCase() - const cUsdcVaultAddress = '0xf201fFeA8447AB3d43c98Da3349e0749813C9009'.toLowerCase() + const cUsdtVaultAddress = '0x4Be33630F92661afD646081BC29079A38b879aA0'.toLowerCase() + const cUsdcVaultAddress = '0xf579F9885f1AEa0d3F8bE0F18AfED28c92a43022'.toLowerCase() /* diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 46c5d69280..90a34a56e2 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -1178,12 +1178,18 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { expect(await trade.startBlock()).to.equal((await getLatestBlockNumber()) + 1) const tradeLen = (await trade.endBlock()).sub(await trade.startBlock()) expect(await trade.endTime()).to.equal( - tradeLen.mul(12).add(await getLatestBlockTimestamp()) + tradeLen + .add(1) + .mul(12) + .add(await getLatestBlockTimestamp()) ) expect(await trade.bestPrice()).to.equal( divCeil(prices.sellHigh.mul(fp('1')), prices.buyLow) ) - expect(await trade.worstPrice()).to.equal(prices.sellLow.mul(fp('1')).div(prices.buyHigh)) + const worstPrice = prices.sellLow + .mul(fp('1').sub(await backingManager.maxTradeSlippage())) + .div(prices.buyHigh) + expect(await trade.worstPrice()).to.equal(worstPrice) expect(await trade.canSettle()).to.equal(false) // Attempt to initialize again diff --git a/test/Deployer.test.ts b/test/Deployer.test.ts index e46f5adfca..39a2ecf8de 100644 --- a/test/Deployer.test.ts +++ b/test/Deployer.test.ts @@ -373,7 +373,9 @@ describe(`DeployerP${IMPLEMENTATION} contract #fast`, () => { rToken.address, bn('1e27') ) - await deployer.deployRTokenAsset(rToken.address, bn('1e27')) // fp('1e9') + await expect(deployer.deployRTokenAsset(rToken.address, bn('1e27'))) + .to.emit(deployer, 'RTokenAssetCreated') + .withArgs(rToken.address, newRTokenAssetAddr) // fp('1e9') const newRTokenAsset = await ethers.getContractAt('RTokenAsset', newRTokenAssetAddr) expect(await newRTokenAsset.maxTradeVolume()).to.equal(bn('1e27')) // fp('1e9') }) diff --git a/test/FacadeWrite.test.ts b/test/FacadeWrite.test.ts index 2618e777da..97210ce749 100644 --- a/test/FacadeWrite.test.ts +++ b/test/FacadeWrite.test.ts @@ -688,6 +688,9 @@ describe('FacadeWrite contract', () => { timelock = ( await ethers.getContractAt('TimelockController', timelockAddr) ) + expect(await timelock.hasRole(await timelock.EXECUTOR_ROLE(), governor.address)).to.equal( + true + ) }) it('Should setup owner, freezer and pauser correctly', async () => { @@ -773,6 +776,9 @@ describe('FacadeWrite contract', () => { timelock = ( await ethers.getContractAt('TimelockController', timelockAddr) ) + expect(await timelock.hasRole(await timelock.EXECUTOR_ROLE(), governor.address)).to.equal( + true + ) }) it('Should setup owner, freezer and pauser correctly', async () => { diff --git a/test/Governance.test.ts b/test/Governance.test.ts index 0247d8040f..83dffb413a 100644 --- a/test/Governance.test.ts +++ b/test/Governance.test.ts @@ -104,8 +104,8 @@ describeP1(`Governance - P${IMPLEMENTATION}`, () => { // Setup Governor as only proposer await timelock.grantRole(proposerRole, governor.address) - // Setup anyone as executor - await timelock.grantRole(executorRole, ZERO_ADDRESS) + // Setup Governor as only executor + await timelock.grantRole(executorRole, governor.address) // Setup guardian as canceller await timelock.grantRole(cancellerRole, guardian.address) @@ -504,6 +504,23 @@ describeP1(`Governance - P${IMPLEMENTATION}`, () => { await advanceTime(MIN_DELAY + 1) await advanceBlocks(1) + // Regression test -- Should fail to execute from random EOA + await expect( + timelock + .connect(addr3) + .executeBatch( + [backingManager.address], + [0], + [encodedFunctionCall], + '0x0000000000000000000000000000000000000000000000000000000000000000', + proposalDescHash + ) + ).to.be.revertedWith( + 'AccessControl: account ' + + addr3.address.toLowerCase() + + ' is missing role 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63' // executor role + ) + // Execute await governor .connect(addr1) diff --git a/test/Main.test.ts b/test/Main.test.ts index 7556200088..2871945e37 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -2904,6 +2904,35 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { expect(highPrice).to.equal(MAX_UINT192) }) + it('Should distinguish between price/lotPrice', async () => { + // Set basket with single collateral + await basketHandler.connect(owner).setPrimeBasket([token0.address], [fp('1')]) + await basketHandler.refreshBasket() + + await collateral0.refresh() + const [low, high] = await collateral0.price() + await setOraclePrice(collateral0.address, MAX_UINT256.div(2)) // oracle error + + // lotPrice() should begin at 100% + let [lowPrice, highPrice] = await basketHandler.price() + let [lotLowPrice, lotHighPrice] = await basketHandler.lotPrice() + expect(lowPrice).to.equal(0) + expect(highPrice).to.equal(MAX_UINT192) + expect(lotLowPrice).to.be.eq(low) + expect(lotHighPrice).to.be.eq(high) + + // Advance time past 100% period -- lotPrice() should begin to fall + await advanceTime(await collateral0.oracleTimeout()) + ;[lowPrice, highPrice] = await basketHandler.price() + ;[lotLowPrice, lotHighPrice] = await basketHandler.lotPrice() + expect(lowPrice).to.equal(0) + expect(highPrice).to.equal(MAX_UINT192) + expect(lotLowPrice).to.be.closeTo(low, low.div(bn('1e5'))) // small decay expected + expect(lotLowPrice).to.be.lt(low) + expect(lotHighPrice).to.be.closeTo(high, high.div(bn('1e5'))) // small decay expected + expect(lotHighPrice).to.be.lt(high) + }) + it('Should disable basket on asset deregistration + return quantities correctly', async () => { // Check values expect(await facadeTest.wholeBasketsHeldBy(rToken.address, addr1.address)).to.equal( diff --git a/test/RTokenExtremes.test.ts b/test/RTokenExtremes.test.ts index f55c9f619e..229960812c 100644 --- a/test/RTokenExtremes.test.ts +++ b/test/RTokenExtremes.test.ts @@ -153,6 +153,8 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { await rToken.connect(owner).setIssuanceThrottleParams(issuanceThrottleParams) await rToken.connect(owner).setRedemptionThrottleParams(redemptionThrottleParams) + // Recharge throttle + await advanceTime(3600) await advanceTime(await basketHandler.warmupPeriod()) // ==== Issue the "initial" rtoken supply to owner diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 6fc7a8ec71..3dbabd518a 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -1050,6 +1050,8 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { minBuyAmtRToken.div(bn('1e15')) ) }) + it('Should be able to start a dust auction BATCH_AUCTION, if enabled', async () => { + const minTrade = bn('1e18') it('Should be able to start a dust auction BATCH_AUCTION, if enabled', async () => { const minTrade = bn('1e18') diff --git a/test/ZTradingExtremes.test.ts b/test/ZTradingExtremes.test.ts index 77ca05227d..de63aea75b 100644 --- a/test/ZTradingExtremes.test.ts +++ b/test/ZTradingExtremes.test.ts @@ -342,6 +342,8 @@ describeExtreme(`Trading Extreme Values (${SLOW ? 'slow mode' : 'fast mode'})`, const noThrottle = { amtRate: MAX_THROTTLE_AMT_RATE, pctRate: 0 } await rToken.setIssuanceThrottleParams(noThrottle) await rToken.setRedemptionThrottleParams(noThrottle) + // Recharge throttle + await advanceTime(3600) await rToken.connect(addr1).issue(rTokenSupply) expect(await rToken.balanceOf(addr1.address)).to.equal(rTokenSupply) diff --git a/test/__snapshots__/Broker.test.ts.snap b/test/__snapshots__/Broker.test.ts.snap index a4980f89e4..fc823d852b 100644 --- a/test/__snapshots__/Broker.test.ts.snap +++ b/test/__snapshots__/Broker.test.ts.snap @@ -1,12 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `259526`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `251984`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `374517`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `361087`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `376632`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `363202`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `378770`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `365340`; exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63333`; diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index ce8f9fa1c2..848354f559 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8314897`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8393668`; -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464235`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464253`; exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Without governance 1`] = `114895`; diff --git a/test/__snapshots__/Main.test.ts.snap b/test/__snapshots__/Main.test.ts.snap index 160af5632c..06ba9d68c7 100644 --- a/test/__snapshots__/Main.test.ts.snap +++ b/test/__snapshots__/Main.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `357566`; +exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `357705`; exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `195889`; exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `195889`; -exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `167023`; +exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `167045`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80510`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80532`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `70022`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `70044`; diff --git a/test/__snapshots__/RToken.test.ts.snap b/test/__snapshots__/RToken.test.ts.snap index a893a4a2dc..f50430c9b4 100644 --- a/test/__snapshots__/RToken.test.ts.snap +++ b/test/__snapshots__/RToken.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `787379`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `787453`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `614383`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `614457`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `589091`; +exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `589230`; exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56658`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index c03809a471..9e0d532f8e 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1375510`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1384418`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1511188`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1510705`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `747257`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `747331`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1664434`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1680908`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1606646`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1613640`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1695120`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1702037`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `202908`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index f4c0f2c10c..81fa8bb746 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -12,16 +12,16 @@ exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229377`; exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212277`; -exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1030919`; +exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1008567`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `773978`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `773918`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1180719`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1181227`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `311446`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `266512`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `739778`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `739718`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `242306`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index c2e974d064..dbc65bb91d 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -2,7 +2,7 @@ exports[`StRSRP1 contract Gas Reporting Stake 1`] = `139717`; -exports[`StRSRP1 contract Gas Reporting Stake 2`] = `134917`; +exports[`StRSRP1 contract Gas Reporting Stake 2`] = `151759`; exports[`StRSRP1 contract Gas Reporting Transfer 1`] = `63409`; @@ -14,6 +14,6 @@ exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `222609`; exports[`StRSRP1 contract Gas Reporting Unstake 2`] = `139758`; -exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `571937`; +exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `572011`; -exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `525941`; +exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `526015`; diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index a10b4ce63d..55c2eaa66f 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -336,6 +336,12 @@ describe('Assets contracts #fast', () => { config.minTradeVolume.mul((await assetRegistry.erc20s()).length) ) expect(await rTokenAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) + + // Should have lot price, equal to price when feed works OK + const [lowPrice, highPrice] = await rTokenAsset.price() + const [lotLow, lotHigh] = await rTokenAsset.lotPrice() + expect(lotLow).to.equal(lowPrice) + expect(lotHigh).to.equal(highPrice) }) it('Should calculate trade min correctly', async () => { diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index d62e3f7c53..a4da2cd3e1 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -1660,6 +1660,38 @@ describe('Collateral contracts', () => { expect(newHigh).to.equal(currHigh) }) + it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { + const currRate = await cNonFiatTokenVault.exchangeRateStored() + const [currLow, currHigh] = await cTokenNonFiatCollateral.price() + + expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.SOUND) + await expectPrice(cTokenNonFiatCollateral.address, fp('400'), ORACLE_ERROR, true) + + // Make cToken revert on exchangeRateCurrent() + const cTokenErc20Mock = ( + await ethers.getContractAt('CTokenMock', await cNonFiatTokenVault.underlying()) + ) + await cTokenErc20Mock.setRevertExchangeRate(true) + + // Refresh - should not revert - Sets DISABLED + await expect(cTokenNonFiatCollateral.refresh()) + .to.emit(cTokenNonFiatCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) + + expect(await cTokenNonFiatCollateral.status()).to.equal(CollateralStatus.DISABLED) + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) + expect(await cTokenNonFiatCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + + // Exchange rate stored is still accessible + expect(await cNonFiatTokenVault.exchangeRateStored()).to.equal(currRate) + + // Price remains the same + await expectPrice(cTokenNonFiatCollateral.address, fp('400'), ORACLE_ERROR, true) + const [newLow, newHigh] = await cTokenNonFiatCollateral.price() + expect(newLow).to.equal(currLow) + expect(newHigh).to.equal(currHigh) + }) + it('Reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { const invalidChainlinkFeed: InvalidMockV3Aggregator = ( await InvalidMockV3AggregatorFactory.deploy(8, bn('1e8')) @@ -2081,6 +2113,38 @@ describe('Collateral contracts', () => { expect(newHigh).to.equal(currHigh) }) + it('Enters DISABLED state when exchangeRateCurrent() reverts', async () => { + const currRate = await cSelfRefToken.exchangeRateStored() + const [currLow, currHigh] = await cTokenSelfReferentialCollateral.price() + + expect(await cTokenSelfReferentialCollateral.status()).to.equal(CollateralStatus.SOUND) + await expectPrice(cTokenSelfReferentialCollateral.address, fp('0.02'), ORACLE_ERROR, true) + + // Make cToken revert on exchangeRateCurrent() + const cTokenErc20Mock = ( + await ethers.getContractAt('CTokenMock', await cSelfRefToken.underlying()) + ) + await cTokenErc20Mock.setRevertExchangeRate(true) + + // Refresh - should not revert - Sets DISABLED + await expect(cTokenSelfReferentialCollateral.refresh()) + .to.emit(cTokenSelfReferentialCollateral, 'CollateralStatusChanged') + .withArgs(CollateralStatus.SOUND, CollateralStatus.DISABLED) + + expect(await cTokenSelfReferentialCollateral.status()).to.equal(CollateralStatus.DISABLED) + const expectedDefaultTimestamp: BigNumber = bn(await getLatestBlockTimestamp()) + expect(await cTokenSelfReferentialCollateral.whenDefault()).to.equal(expectedDefaultTimestamp) + + // Exchange rate stored is still accessible + expect(await cSelfRefToken.exchangeRateStored()).to.equal(currRate) + + // Price remains the same + await expectPrice(cTokenSelfReferentialCollateral.address, fp('0.02'), ORACLE_ERROR, true) + const [newLow, newHigh] = await cTokenSelfReferentialCollateral.price() + expect(newLow).to.equal(currLow) + expect(newHigh).to.equal(currHigh) + }) + it('Reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { const invalidChainlinkFeed: InvalidMockV3Aggregator = ( await InvalidMockV3AggregatorFactory.deploy(8, bn('1e8')) diff --git a/test/plugins/__snapshots__/Asset.test.ts.snap b/test/plugins/__snapshots__/Asset.test.ts.snap index 2889fd482e..bc2103db48 100644 --- a/test/plugins/__snapshots__/Asset.test.ts.snap +++ b/test/plugins/__snapshots__/Asset.test.ts.snap @@ -8,6 +8,6 @@ exports[`Assets contracts #fast Gas Reporting refresh() refresh() after oracle t exports[`Assets contracts #fast Gas Reporting refresh() refresh() after oracle timeout 2`] = `38605`; -exports[`Assets contracts #fast Gas Reporting refresh() refresh() during SOUND 1`] = `51027`; +exports[`Assets contracts #fast Gas Reporting refresh() refresh() during SOUND 1`] = `51050`; -exports[`Assets contracts #fast Gas Reporting refresh() refresh() during SOUND 2`] = `51027`; +exports[`Assets contracts #fast Gas Reporting refresh() refresh() during SOUND 2`] = `51050`; diff --git a/test/plugins/__snapshots__/Collateral.test.ts.snap b/test/plugins/__snapshots__/Collateral.test.ts.snap index 399bd37b97..83c6bf2eb6 100644 --- a/test/plugins/__snapshots__/Collateral.test.ts.snap +++ b/test/plugins/__snapshots__/Collateral.test.ts.snap @@ -1,23 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral contracts Gas Reporting refresh() after full price timeout 1`] = `46228`; +exports[`Collateral contracts Gas Reporting refresh() after hard default 1`] = `71881`; -exports[`Collateral contracts Gas Reporting refresh() after hard default 1`] = `71836`; +exports[`Collateral contracts Gas Reporting refresh() after hard default 2`] = `75163`; -exports[`Collateral contracts Gas Reporting refresh() after hard default 2`] = `75118`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 1`] = `61571`; -exports[`Collateral contracts Gas Reporting refresh() after oracle timeout 1`] = `46228`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 2`] = `54303`; -exports[`Collateral contracts Gas Reporting refresh() during + after soft default 1`] = `61548`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 3`] = `54021`; -exports[`Collateral contracts Gas Reporting refresh() during + after soft default 2`] = `54280`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 4`] = `53962`; -exports[`Collateral contracts Gas Reporting refresh() during + after soft default 3`] = `53998`; +exports[`Collateral contracts Gas Reporting refresh() during + after soft default 5`] = `54021`; -exports[`Collateral contracts Gas Reporting refresh() during + after soft default 4`] = `53939`; +exports[`Collateral contracts Gas Reporting refresh() during SOUND 1`] = `54021`; -exports[`Collateral contracts Gas Reporting refresh() during + after soft default 5`] = `53998`; - -exports[`Collateral contracts Gas Reporting refresh() during SOUND 1`] = `53998`; - -exports[`Collateral contracts Gas Reporting refresh() during SOUND 2`] = `53998`; +exports[`Collateral contracts Gas Reporting refresh() during SOUND 2`] = `54021`; diff --git a/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts b/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts new file mode 100644 index 0000000000..cb7876809f --- /dev/null +++ b/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts @@ -0,0 +1,219 @@ +import collateralTests from '../collateralTests' +import { CollateralFixtureContext, MintCollateralFunc } from '../pluginTestTypes' +import { ethers } from 'hardhat' +import { BigNumberish, BigNumber } from 'ethers' +import { + TestICollateral, + AaveV3FiatCollateral__factory, + IERC20Metadata, + MockStaticATokenV3LM, +} from '@typechain/index' +import { bn, fp } from '#/common/numbers' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { noop } from 'lodash' +import { PRICE_TIMEOUT } from '#/test/fixtures' +import { networkConfig } from '#/common/configuration' +import { getResetFork } from '../helpers' +import { whileImpersonating } from '#/test/utils/impersonation' +import { AAVE_V3_USDC_POOL, AAVE_V3_INCENTIVES_CONTROLLER } from './constants' + +interface AaveV3FiatCollateralFixtureContext extends CollateralFixtureContext { + staticWrapper: MockStaticATokenV3LM + baseToken: IERC20Metadata +} + +/* + Define deployment functions +*/ + +type CollateralParams = Parameters[0] & { + revenueHiding?: BigNumberish +} + +// This defines options for the Aave V3 USDC Market +export const defaultCollateralOpts: CollateralParams = { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: networkConfig[1].chainlinkFeeds.USDC!, + oracleError: fp('0.0025'), + erc20: '', // to be set + maxTradeVolume: fp('1e6'), + oracleTimeout: bn('86400'), + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.0125'), + delayUntilDefault: bn('86400'), +} + +export const deployCollateral = async (opts: Partial = {}) => { + const combinedOpts = { ...defaultCollateralOpts, ...opts } + const CollateralFactory = await ethers.getContractFactory('AaveV3FiatCollateral') + + if (!combinedOpts.erc20 || combinedOpts.erc20 === '') { + const V3LMFactory = await ethers.getContractFactory('MockStaticATokenV3LM') + const staticWrapper = await V3LMFactory.deploy(AAVE_V3_USDC_POOL, AAVE_V3_INCENTIVES_CONTROLLER) + await staticWrapper.deployed() + await staticWrapper.initialize( + networkConfig[1].tokens.aEthUSDC!, + 'Static Aave Ethereum USDC', + 'saEthUSDC' + ) + + combinedOpts.erc20 = staticWrapper.address + } + + const collateral = await CollateralFactory.deploy( + combinedOpts, + opts.revenueHiding ?? fp('0'), // change this to test with revenueHiding + { + gasLimit: 30000000, + } + ) + await collateral.deployed() + + // sometimes we are trying to test a negative test case and we want this to fail silently + // fortunately this syntax fails silently because our tools are terrible + await expect(collateral.refresh()) + + // our tools really suck don't they + return collateral as unknown as TestICollateral +} + +type Fixture = () => Promise + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts = {} +): Fixture => { + const collateralOpts = { ...defaultCollateralOpts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') + const chainlinkFeed = await MockV3AggregatorFactory.deploy(8, bn('1e8')) + + const collateral = await deployCollateral({ + ...collateralOpts, + chainlinkFeed: chainlinkFeed.address, + }) + + const staticWrapper = await ethers.getContractAt( + 'MockStaticATokenV3LM', + await collateral.erc20() + ) + + return { + collateral, + staticWrapper, + chainlinkFeed, + tok: await ethers.getContractAt('IERC20Metadata', await collateral.erc20()), + baseToken: await ethers.getContractAt('IERC20Metadata', await staticWrapper.asset()), + } + } + + return makeCollateralFixtureContext +} + +/* + Define helper functions +*/ + +const mintCollateralTo: MintCollateralFunc = async ( + ctx: AaveV3FiatCollateralFixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + const requiredCollat = await ctx.staticWrapper.previewMint(amount) + + // USDC Richie Rich + await whileImpersonating('0x0A59649758aa4d66E25f08Dd01271e891fe52199', async (signer) => { + await ctx.baseToken + .connect(signer) + .approve(ctx.staticWrapper.address, ethers.constants.MaxUint256) + await ctx.staticWrapper + .connect(signer) + ['deposit(uint256,address,uint16,bool)'](requiredCollat, recipient, 0, true) + }) +} + +const modifyRefPerTok = async (ctx: AaveV3FiatCollateralFixtureContext, changeFactor = 100) => { + const staticWrapper = ctx.staticWrapper + const currentRate = await staticWrapper.rate() + + await staticWrapper.mockSetCustomRate(currentRate.mul(changeFactor).div(100)) +} + +const reduceRefPerTok = async ( + ctx: AaveV3FiatCollateralFixtureContext, + pctDecrease: BigNumberish +) => { + await modifyRefPerTok(ctx, 100 - Number(pctDecrease.toString())) +} + +const increaseRefPerTok = async ( + ctx: AaveV3FiatCollateralFixtureContext, + pctIncrease: BigNumberish +) => { + await modifyRefPerTok(ctx, 100 + Number(pctIncrease.toString())) +} + +const getExpectedPrice = async (ctx: AaveV3FiatCollateralFixtureContext): Promise => { + const initRefPerTok = await ctx.collateral.refPerTok() + const decimals = await ctx.chainlinkFeed.decimals() + + const initData = await ctx.chainlinkFeed.latestRoundData() + return initData.answer + .mul(bn(10).pow(18 - decimals)) + .mul(initRefPerTok) + .div(fp('1')) +} + +const reduceTargetPerRef = async ( + ctx: AaveV3FiatCollateralFixtureContext, + pctDecrease: BigNumberish +) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.sub(lastRound.answer.mul(pctDecrease).div(100)) + + await ctx.chainlinkFeed.updateAnswer(nextAnswer) +} + +const increaseTargetPerRef = async ( + ctx: AaveV3FiatCollateralFixtureContext, + pctIncrease: BigNumberish +) => { + const lastRound = await ctx.chainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(pctIncrease).div(100)) + + await ctx.chainlinkFeed.updateAnswer(nextAnswer) +} + +/* + Run the test suite +*/ + +export const stableOpts = { + deployCollateral, + collateralSpecificConstructorTests: noop, + collateralSpecificStatusTests: noop, + beforeEachRewardsTest: noop, + makeCollateralFixtureContext, + mintCollateralTo, + reduceRefPerTok, + increaseRefPerTok, + resetFork: getResetFork(18000000), + collateralName: 'Aave V3 Fiat Collateral (USDC)', + reduceTargetPerRef, + increaseTargetPerRef, + 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, + itChecksRefPerTokDefault: it, + itHasRevenueHiding: it, + itIsPricedByPeg: true, + chainlinkDefaultAnswer: 1e8, + itChecksPriceChanges: it, + getExpectedPrice, + toleranceDivisor: bn('1e9'), // 1e15 adjusted for ((x + 1)/x) timestamp precision +} + +collateralTests(stableOpts) diff --git a/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap new file mode 100644 index 0000000000..607d7bd001 --- /dev/null +++ b/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69299`; + +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after full price timeout 2`] = `67631`; + +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after hard default 1`] = `72125`; + +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after hard default 2`] = `64443`; + +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `69299`; + +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `67631`; + +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after soft default 1`] = `67290`; + +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after soft default 2`] = `67290`; + +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during SOUND 1`] = `87706`; + +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during SOUND 2`] = `87706`; + +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during soft default 1`] = `89730`; + +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during soft default 2`] = `87988`; diff --git a/test/plugins/individual-collateral/aave-v3/constants.ts b/test/plugins/individual-collateral/aave-v3/constants.ts new file mode 100644 index 0000000000..37e6c70f75 --- /dev/null +++ b/test/plugins/individual-collateral/aave-v3/constants.ts @@ -0,0 +1,2 @@ +export const AAVE_V3_USDC_POOL = '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2' +export const AAVE_V3_INCENTIVES_CONTROLLER = '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb' diff --git a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap index 9b578e0fa7..79711c5fe3 100644 --- a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `74354`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `74365`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `72686`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `72697`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `72915`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `72960`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `65233`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `65278`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `74354`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `74365`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `72686`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `72697`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `91124`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `91095`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `91050`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `91095`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `92188`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `92307`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92262`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92233`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `127259`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `127304`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `91465`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `91510`; diff --git a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap index aeb5818f7b..37480626fe 100644 --- a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `60326`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `60337`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55857`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55868`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `99345`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `99413`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `91662`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `91730`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `60326`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `60337`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55857`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55868`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `55516`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `55527`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `55516`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `55527`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `91589`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `91657`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `91589`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `91657`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `99140`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `99208`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `91871`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `91939`; diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts index 47ca9b8da1..2dc585a5b8 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts @@ -20,13 +20,13 @@ import { ethers } from 'hardhat' import { expect } from 'chai' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { MockV3Aggregator } from '@typechain/MockV3Aggregator' -import { CBEth, ERC20Mock, MockV3Aggregator__factory } from '@typechain/index' +import { ICBEth, ERC20Mock, MockV3Aggregator__factory } from '@typechain/index' import { mintCBETH, resetFork } from './helpers' import { whileImpersonating } from '#/utils/impersonation' import hre from 'hardhat' interface CbEthCollateralFixtureContext extends CollateralFixtureContext { - cbETH: CBEth + cbETH: ICBEth targetPerTokChainlinkFeed: MockV3Aggregator } @@ -97,7 +97,7 @@ const makeCollateralFixtureContext = ( collateralOpts.targetPerTokChainlinkFeed = targetPerTokChainlinkFeed.address collateralOpts.targetPerTokChainlinkTimeout = ORACLE_TIMEOUT - const cbETH = (await ethers.getContractAt('CBEth', CB_ETH)) as unknown as CBEth + const cbETH = (await ethers.getContractAt('ICBEth', CB_ETH)) as unknown as ICBEth const collateral = await deployCollateral(collateralOpts) return { diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts new file mode 100644 index 0000000000..489f89d3df --- /dev/null +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts @@ -0,0 +1,287 @@ +import collateralTests from '../collateralTests' +import { CollateralFixtureContext, CollateralOpts, MintCollateralFunc } from '../pluginTestTypes' +import { + CBETH_ETH_PRICE_FEED_BASE, + DEFAULT_THRESHOLD, + DELAY_UNTIL_DEFAULT, + MAX_TRADE_VOL, + ORACLE_ERROR, + ORACLE_TIMEOUT, + PRICE_TIMEOUT, + CBETH_ETH_EXCHANGE_RATE_FEED_BASE, + FORK_BLOCK_BASE, + CB_ETH_BASE, + ETH_USD_PRICE_FEED_BASE, +} from './constants' +import { BigNumber, BigNumberish, ContractFactory } from 'ethers' +import { bn, fp } from '#/common/numbers' +import { TestICollateral } from '@typechain/TestICollateral' +import { ethers } from 'hardhat' +import { expect } from 'chai' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { MockV3Aggregator } from '@typechain/MockV3Aggregator' +import { ICBEth, CBEthCollateralL2, ERC20Mock, MockV3Aggregator__factory } from '@typechain/index' +import { mintCBETHBase } from './helpers' +import { pushOracleForward } from '../../../utils/oracles' +import { getResetFork } from '../helpers' + +interface CbEthCollateralL2FixtureContext extends CollateralFixtureContext { + cbETH: ICBEth + targetPerTokChainlinkFeed: MockV3Aggregator + exchangeRateChainlinkFeed: MockV3Aggregator +} + +interface CbEthCollateralL2Opts extends CollateralOpts { + targetPerTokChainlinkFeed?: string + targetPerTokChainlinkTimeout?: BigNumberish + exchangeRateChainlinkFeed?: string + exchangeRateChainlinkTimeout?: BigNumberish +} + +export const deployCollateral = async ( + opts: CbEthCollateralL2Opts = {} +): Promise => { + opts = { ...defaultCBEthCollateralL2Opts, ...opts } + + const CBETHCollateralFactory: ContractFactory = await ethers.getContractFactory( + 'CBEthCollateralL2' + ) + + const collateral = await CBETHCollateralFactory.deploy( + { + erc20: opts.erc20, + targetName: opts.targetName, + priceTimeout: opts.priceTimeout, + chainlinkFeed: opts.chainlinkFeed, + oracleError: opts.oracleError, + oracleTimeout: opts.oracleTimeout, + maxTradeVolume: opts.maxTradeVolume, + defaultThreshold: opts.defaultThreshold, + delayUntilDefault: opts.delayUntilDefault, + }, + opts.revenueHiding, + opts.targetPerTokChainlinkFeed ?? CBETH_ETH_PRICE_FEED_BASE, + opts.targetPerTokChainlinkTimeout ?? ORACLE_TIMEOUT, + opts.exchangeRateChainlinkFeed ?? CBETH_ETH_EXCHANGE_RATE_FEED_BASE, + opts.exchangeRateChainlinkTimeout ?? ORACLE_TIMEOUT, + { gasLimit: 2000000000 } + ) + await collateral.deployed() + + await pushOracleForward(opts.chainlinkFeed!) + await pushOracleForward(opts.targetPerTokChainlinkFeed ?? CBETH_ETH_PRICE_FEED_BASE) + await pushOracleForward(opts.exchangeRateChainlinkFeed ?? CBETH_ETH_EXCHANGE_RATE_FEED_BASE) + + await expect(collateral.refresh()) + + return collateral +} + +const chainlinkDefaultAnswer = bn('1600e8') +const targetPerTokChainlinkDefaultAnswer = bn('1e18') +const exchangeRateChainlinkFeedDefaultAnswer = bn('1e18') + +type Fixture = () => Promise + +const makeCollateralFixtureContext = ( + alice: SignerWithAddress, + opts: CbEthCollateralL2Opts = {} +): Fixture => { + const collateralOpts = { ...defaultCBEthCollateralL2Opts, ...opts } + + const makeCollateralFixtureContext = async () => { + const MockV3AggregatorFactory = ( + await ethers.getContractFactory('MockV3Aggregator') + ) + + const chainlinkFeed = ( + await MockV3AggregatorFactory.deploy(8, chainlinkDefaultAnswer) + ) + collateralOpts.chainlinkFeed = chainlinkFeed.address + + const targetPerTokChainlinkFeed = ( + await MockV3AggregatorFactory.deploy(18, targetPerTokChainlinkDefaultAnswer) + ) + collateralOpts.targetPerTokChainlinkFeed = targetPerTokChainlinkFeed.address + collateralOpts.targetPerTokChainlinkTimeout = ORACLE_TIMEOUT + + const exchangeRateChainlinkFeed = ( + await MockV3AggregatorFactory.deploy(18, exchangeRateChainlinkFeedDefaultAnswer) + ) + collateralOpts.exchangeRateChainlinkFeed = exchangeRateChainlinkFeed.address + collateralOpts.exchangeRateChainlinkTimeout = ORACLE_TIMEOUT + + const cbETH = (await ethers.getContractAt('ICBEth', CB_ETH_BASE)) as unknown as ICBEth + const collateral = await deployCollateral(collateralOpts) + + return { + alice, + collateral, + chainlinkFeed, + targetPerTokChainlinkFeed, + exchangeRateChainlinkFeed, + cbETH, + tok: cbETH as unknown as ERC20Mock, + } + } + + return makeCollateralFixtureContext +} +/* + Define helper functions +*/ + +const mintCollateralTo: MintCollateralFunc = async ( + ctx: CbEthCollateralL2FixtureContext, + amount: BigNumberish, + user: SignerWithAddress, + recipient: string +) => { + await mintCBETHBase(amount, recipient) +} + +const changeTargetPerRef = async ( + ctx: CbEthCollateralL2FixtureContext, + percentChange: BigNumber +) => { + const lastRound = await ctx.targetPerTokChainlinkFeed.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) + await ctx.targetPerTokChainlinkFeed.updateAnswer(nextAnswer) +} + +const reduceTargetPerRef = async ( + ctx: CbEthCollateralL2FixtureContext, + pctDecrease: BigNumberish +) => { + await changeTargetPerRef(ctx, bn(pctDecrease).mul(-1)) +} + +const increaseTargetPerRef = async ( + ctx: CbEthCollateralL2FixtureContext, + pctDecrease: BigNumberish +) => { + await changeTargetPerRef(ctx, bn(pctDecrease)) +} + +const changeRefPerTok = async (ctx: CbEthCollateralL2FixtureContext, percentChange: BigNumber) => { + const collateral = ctx.collateral as unknown as CBEthCollateralL2 + const exchangeRateOracle = await ethers.getContractAt( + 'MockV3Aggregator', + await collateral.exchangeRateChainlinkFeed() + ) + const lastRound = await exchangeRateOracle.latestRoundData() + const nextAnswer = lastRound.answer.add(lastRound.answer.mul(percentChange).div(100)) + await exchangeRateOracle.updateAnswer(nextAnswer) + + const targetPerTokOracle = await ethers.getContractAt( + 'MockV3Aggregator', + await collateral.targetPerTokChainlinkFeed() + ) + const lastRoundtpt = await targetPerTokOracle.latestRoundData() + const nextAnswertpt = lastRoundtpt.answer.add(lastRoundtpt.answer.mul(percentChange).div(100)) + await targetPerTokOracle.updateAnswer(nextAnswertpt) +} + +const reduceRefPerTok = async (ctx: CbEthCollateralL2FixtureContext, pctDecrease: BigNumberish) => { + await changeRefPerTok(ctx, bn(pctDecrease).mul(-1)) +} + +const increaseRefPerTok = async ( + ctx: CbEthCollateralL2FixtureContext, + pctIncrease: BigNumberish +) => { + await changeRefPerTok(ctx, bn(pctIncrease)) +} +const getExpectedPrice = async (ctx: CbEthCollateralL2FixtureContext): Promise => { + const clData = await ctx.chainlinkFeed.latestRoundData() + const clDecimals = await ctx.chainlinkFeed.decimals() + + const clRptData = await ctx.targetPerTokChainlinkFeed.latestRoundData() + const clRptDecimals = await ctx.targetPerTokChainlinkFeed.decimals() + + return clData.answer + .mul(bn(10).pow(18 - clDecimals)) + .mul(clRptData.answer.mul(bn(10).pow(18 - clRptDecimals))) + .div(fp('1')) +} + +/* + Define collateral-specific tests +*/ + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificConstructorTests = () => { + it('does not allow missing targetPerTok chainlink feed', async () => { + await expect( + deployCollateral({ targetPerTokChainlinkFeed: ethers.constants.AddressZero }) + ).to.be.revertedWith('missing targetPerTok feed') + }) + + it('does not allow targetPerTok oracle timeout at 0', async () => { + await expect(deployCollateral({ targetPerTokChainlinkTimeout: 0 })).to.be.revertedWith( + 'targetPerTokChainlinkTimeout zero' + ) + }) + + it('does not allow missing exchangeRate chainlink feed', async () => { + await expect( + deployCollateral({ exchangeRateChainlinkFeed: ethers.constants.AddressZero }) + ).to.be.revertedWith('missing exchangeRate feed') + }) + + it('does not allow exchangeRate oracle timeout at 0', async () => { + await expect(deployCollateral({ exchangeRateChainlinkTimeout: 0 })).to.be.revertedWith( + 'exchangeRateChainlinkTimeout zero' + ) + }) +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const collateralSpecificStatusTests = () => {} +// eslint-disable-next-line @typescript-eslint/no-empty-function +const beforeEachRewardsTest = async () => {} + +export const defaultCBEthCollateralL2Opts: CollateralOpts = { + erc20: CB_ETH_BASE, + targetName: ethers.utils.formatBytes32String('ETH'), + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: ETH_USD_PRICE_FEED_BASE, + oracleTimeout: ORACLE_TIMEOUT, + oracleError: ORACLE_ERROR, + maxTradeVolume: MAX_TRADE_VOL, + defaultThreshold: DEFAULT_THRESHOLD, + delayUntilDefault: DELAY_UNTIL_DEFAULT, + revenueHiding: fp('0'), +} + +export const resetFork = getResetFork(FORK_BLOCK_BASE) + +/* + Run the test suite +*/ + +const opts = { + deployCollateral, + collateralSpecificConstructorTests, + collateralSpecificStatusTests, + beforeEachRewardsTest, + makeCollateralFixtureContext, + mintCollateralTo, + reduceTargetPerRef, + increaseTargetPerRef, + reduceRefPerTok, + increaseRefPerTok, + getExpectedPrice, + itClaimsRewards: it.skip, + itChecksTargetPerRefDefault: it, + itChecksRefPerTokDefault: it, + itChecksPriceChanges: it, + itHasRevenueHiding: it, + resetFork, + collateralName: 'CBEthCollateralL2', + chainlinkDefaultAnswer, + itIsPricedByPeg: true, + targetNetwork: 'base', +} + +collateralTests(opts) diff --git a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap index 73e13d7e26..bf09f1f35e 100644 --- a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59813`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59824`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55344`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55355`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `98249`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `98317`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `90566`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `90634`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59813`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59824`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55344`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55355`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `55003`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `55014`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `55003`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `55014`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `90563`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `90631`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `90563`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `90631`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `98114`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `98182`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `90845`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `90913`; diff --git a/test/plugins/individual-collateral/cbeth/constants.ts b/test/plugins/individual-collateral/cbeth/constants.ts index 887a8db617..19324775f7 100644 --- a/test/plugins/individual-collateral/cbeth/constants.ts +++ b/test/plugins/individual-collateral/cbeth/constants.ts @@ -9,6 +9,14 @@ export const WETH = networkConfig['31337'].tokens.WETH as string export const CB_ETH_MINTER = '0xd0F73E06E7b88c8e1da291bB744c4eEBAf9Af59f' export const CB_ETH_ORACLE = '0x9b37180d847B27ADC13C2277299045C1237Ae281' +export const ETH_USD_PRICE_FEED_BASE = networkConfig['8453'].chainlinkFeeds.ETH as string +export const CBETH_ETH_PRICE_FEED_BASE = networkConfig['8453'].chainlinkFeeds.cbETH as string +export const CB_ETH_BASE = networkConfig['8453'].tokens.cbETH as string +export const WETH_BASE = networkConfig['8453'].tokens.WETH as string +export const CBETH_ETH_EXCHANGE_RATE_FEED_BASE = networkConfig['8453'].chainlinkFeeds + .cbETHETHexr as string +export const CB_ETH_MINTER_BASE = '0x4200000000000000000000000000000000000010' + export const PRICE_TIMEOUT = bn('604800') // 1 week export const ORACLE_TIMEOUT = bn(86400) // 24 hours in seconds export const ORACLE_ERROR = fp('0.005') @@ -17,3 +25,4 @@ export const DELAY_UNTIL_DEFAULT = bn(86400) export const MAX_TRADE_VOL = bn(1000) export const FORK_BLOCK = 17479312 +export const FORK_BLOCK_BASE = 4446300 diff --git a/test/plugins/individual-collateral/cbeth/helpers.ts b/test/plugins/individual-collateral/cbeth/helpers.ts index 0b90ef4285..0658af4971 100644 --- a/test/plugins/individual-collateral/cbeth/helpers.ts +++ b/test/plugins/individual-collateral/cbeth/helpers.ts @@ -1,17 +1,25 @@ import { ethers } from 'hardhat' -import { CBEth } from '../../../../typechain' +import { ICBEth } from '../../../../typechain' import { BigNumberish } from 'ethers' -import { CB_ETH_MINTER, CB_ETH, FORK_BLOCK } from './constants' +import { CB_ETH, CB_ETH_BASE, CB_ETH_MINTER, CB_ETH_MINTER_BASE, FORK_BLOCK } from './constants' import { getResetFork } from '../helpers' import { whileImpersonating } from '#/utils/impersonation' import hre from 'hardhat' export const resetFork = getResetFork(FORK_BLOCK) export const mintCBETH = async (amount: BigNumberish, recipient: string) => { - const cbETH: CBEth = await ethers.getContractAt('CBEth', CB_ETH) + const cbETH: ICBEth = await ethers.getContractAt('ICBEth', CB_ETH) await whileImpersonating(hre, CB_ETH_MINTER, async (minter) => { await cbETH.connect(minter).configureMinter(CB_ETH_MINTER, amount) await cbETH.connect(minter).mint(recipient, amount) }) } + +export const mintCBETHBase = async (amount: BigNumberish, recipient: string) => { + const cbETH: ICBEth = await ethers.getContractAt('ICBEth', CB_ETH_BASE) + + await whileImpersonating(hre, CB_ETH_MINTER_BASE, async (minter) => { + await cbETH.connect(minter).mint(recipient, amount) + }) +} diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 7be0cd4520..c174f8f143 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -34,7 +34,10 @@ import { import snapshotGasCost from '../../utils/snapshotGasCost' import { IMPLEMENTATION, Implementation } from '../../fixtures' -const describeFork = useEnv('FORK') ? describe : describe.skip +// const describeFork = useEnv('FORK') ? describe : describe.skip +const getDescribeFork = (targetNetwork = 'mainnet') => { + return useEnv('FORK') && useEnv('FORK_NETWORK') === targetNetwork ? describe : describe.skip +} const describeGas = IMPLEMENTATION == Implementation.P1 && useEnv('REPORT_GAS') ? describe.only : describe.skip @@ -63,9 +66,11 @@ export default function fn( resetFork, collateralName, chainlinkDefaultAnswer, + toleranceDivisor, + targetNetwork, } = fixtures - describeFork(`Collateral: ${collateralName}`, () => { + getDescribeFork(targetNetwork)(`Collateral: ${collateralName}`, () => { before(resetFork) describe('constructor validation', () => { @@ -191,7 +196,7 @@ export default function fn( itChecksPriceChanges('prices change as USD feed price changes', async () => { const oracleError = await collateral.oracleError() const expectedPrice = await getExpectedPrice(ctx) - await expectPrice(collateral.address, expectedPrice, oracleError, true) + await expectPrice(collateral.address, expectedPrice, oracleError, true, toleranceDivisor) // Update values in Oracles increase by 10-20% const newPrice = BigNumber.from(chainlinkDefaultAnswer).mul(11).div(10) @@ -202,7 +207,13 @@ export default function fn( await collateral.refresh() const newExpectedPrice = await getExpectedPrice(ctx) expect(newExpectedPrice).to.be.gt(expectedPrice) - await expectPrice(collateral.address, newExpectedPrice, oracleError, true) + await expectPrice( + collateral.address, + newExpectedPrice, + oracleError, + true, + toleranceDivisor + ) }) // all our collateral that have targetPerRef feeds use them only for soft default checks @@ -211,7 +222,13 @@ export default function fn( async () => { const oracleError = await collateral.oracleError() const expectedPrice = await getExpectedPrice(ctx) - await expectPrice(collateral.address, expectedPrice, oracleError, true) + await expectPrice( + collateral.address, + expectedPrice, + oracleError, + true, + toleranceDivisor + ) // Get refPerTok initial values const initialRefPerTok = await collateral.refPerTok() @@ -223,13 +240,19 @@ export default function fn( if (itIsPricedByPeg) { // Check new prices -- increase expected const newPrice = await getExpectedPrice(ctx) - await expectPrice(collateral.address, newPrice, oracleError, true) + await expectPrice(collateral.address, newPrice, oracleError, true, toleranceDivisor) const [newLow, newHigh] = await collateral.price() expect(oldLow).to.be.lt(newLow) expect(oldHigh).to.be.lt(newHigh) } else { // Check new prices -- no increase expected - await expectPrice(collateral.address, expectedPrice, oracleError, true) + await expectPrice( + collateral.address, + expectedPrice, + oracleError, + true, + toleranceDivisor + ) const [newLow, newHigh] = await collateral.price() expect(oldLow).to.equal(newLow) expect(oldHigh).to.equal(newHigh) @@ -249,7 +272,7 @@ export default function fn( const [initLow, initHigh] = await collateral.price() const expectedPrice = await getExpectedPrice(ctx) - await expectPrice(collateral.address, expectedPrice, oracleError, true) + await expectPrice(collateral.address, expectedPrice, oracleError, true, toleranceDivisor) // need to deposit in order to get an exchange rate const amount = bn('200').mul(bn(10).pow(await ctx.tok.decimals())) @@ -362,6 +385,8 @@ export default function fn( await advanceTime(await collateral.oracleTimeout()) + await advanceTime(await collateral.oracleTimeout()) + // Should be roughly half, after half of priceTimeout const priceTimeout = await collateral.priceTimeout() await advanceTime(priceTimeout / 2) diff --git a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap index b30c8694f3..ed6522b5ce 100644 --- a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `119336`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `119347`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `117668`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `117679`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `76197`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `76242`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `68515`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `68560`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `119336`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `119347`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `117668`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `117679`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `138736`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `138781`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `138662`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `138707`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `139800`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `139845`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `139800`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `139845`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `174945`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `174990`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `139003`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `139048`; diff --git a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap index fe068917ae..be30eecd44 100644 --- a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `109052`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `109063`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `104315`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `104326`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `134426`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `134471`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `126743`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `126788`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `109052`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `109063`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `104315`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `104326`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `107042`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `107053`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `103974`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `103985`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `126740`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `126785`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `126740`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `126785`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `134291`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `134336`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `127022`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `127067`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap index 3d8979d19d..adac76df06 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `80257`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `52705`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `75789`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48237`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `252516`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `251539`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `247634`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `246657`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `80257`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `52705`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `75789`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48237`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `75448`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `47896`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `75448`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `47896`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `247631`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `246654`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `247631`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `246654`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `255209`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `254250`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `247941`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `246982`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap index 26d549a418..74b5d0e51d 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `105255`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69638`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `100861`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `65170`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `227702`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `226883`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `222820`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `222001`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `102235`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `101429`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `97767`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `96961`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `97426`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `96620`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `97426`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `96620`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `222817`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `221998`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `222817`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `221998`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `210256`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `209488`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `202988`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `202220`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap index 9fda0fffcd..d725ca2610 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61884`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61895`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57416`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57427`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `199422`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `200033`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `194540`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `195151`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61884`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61895`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57416`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57427`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57075`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57086`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57075`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57086`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `194537`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `195148`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `194537`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `195148`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `181728`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `182161`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `174460`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `174893`; diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts index ba3e7b7180..09fde97a48 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableTestSuite.test.ts @@ -9,6 +9,7 @@ import { ethers } from 'hardhat' import { ContractFactory, BigNumberish } from 'ethers' import { ERC20Mock, + IERC20, MockV3Aggregator, MockV3Aggregator__factory, TestICollateral, @@ -42,6 +43,7 @@ import { CRV, THREE_POOL_HOLDER, } from '../constants' +import { whileImpersonating } from '#/test/utils/impersonation' type Fixture = () => Promise @@ -130,7 +132,7 @@ export const deployMaxTokensCollateral = async ( const maxTokenCollOpts = { ...defaultCvxStableCollateralOpts, ...{ - nTokens: bn('4'), + nTokens: 4, erc20: fix.wrapper.address, curvePool: fix.curvePool.address, lpToken: SUSD_POOL_TOKEN, @@ -244,7 +246,6 @@ const mintCollateralTo: MintCurveCollateralFunc = Define collateral-specific tests */ -// eslint-disable-next-line @typescript-eslint/no-empty-function const collateralSpecificConstructorTests = () => { describe('Handles constructor with 4 tokens (max allowed) - sUSD', () => { let collateral: TestICollateral @@ -359,7 +360,6 @@ const collateralSpecificConstructorTests = () => { }) } -// eslint-disable-next-line @typescript-eslint/no-empty-function const collateralSpecificStatusTests = () => { it('handles properly multiple price feeds', async () => { const MockV3AggregatorFactory = await ethers.getContractFactory('MockV3Aggregator') @@ -413,6 +413,53 @@ const collateralSpecificStatusTests = () => { const finalRefPerTok = await multiFeedCollateral.refPerTok() expect(finalRefPerTok).to.equal(initialRefPerTok) }) + + it('handles shutdown correctly', async () => { + const fix = await makeW3PoolStable() + const [, alice, bob] = await ethers.getSigners() + const amount = fp('100') + const rewardPerBlock = bn('83197823300') + + const lpToken = ( + await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', + await fix.wrapper.curveToken() + ) + ) + const CRV = ( + await ethers.getContractAt( + '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', + '0xD533a949740bb3306d119CC777fa900bA034cd52' + ) + ) + await whileImpersonating(THREE_POOL_HOLDER, async (signer) => { + await lpToken.connect(signer).transfer(alice.address, amount.mul(2)) + }) + + await lpToken.connect(alice).approve(fix.wrapper.address, ethers.constants.MaxUint256) + await fix.wrapper.connect(alice).deposit(amount, alice.address) + + // let's shutdown! + await fix.wrapper.shutdown() + + const prevBalance = await CRV.balanceOf(alice.address) + await fix.wrapper.connect(alice).claimRewards() + expect(await CRV.balanceOf(alice.address)).to.be.eq(prevBalance.add(rewardPerBlock)) + + const prevBalanceBob = await CRV.balanceOf(bob.address) + + // transfer to bob + await fix.wrapper + .connect(alice) + .transfer(bob.address, await fix.wrapper.balanceOf(alice.address)) + + await fix.wrapper.connect(bob).claimRewards() + expect(await CRV.balanceOf(bob.address)).to.be.eq(prevBalanceBob.add(rewardPerBlock)) + + await expect(fix.wrapper.connect(alice).deposit(amount, alice.address)).to.be.reverted + await expect(fix.wrapper.connect(bob).withdraw(await fix.wrapper.balanceOf(bob.address))).to.not + .be.reverted + }) } /* diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap index 6ccf76eb1a..699536b70f 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `80257`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `52705`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `75789`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48237`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `252516`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `251539`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `247634`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `246657`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `80257`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `52705`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `75789`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48237`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `75448`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `47896`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `75448`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `47896`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `247631`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `246654`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `247631`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `246654`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `255209`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `254250`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `247941`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `246982`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap index dcc8149e3b..34a9b7f027 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `105329`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69638`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `100861`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `65170`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `227702`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `226883`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `222820`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `222001`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `102235`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `101429`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `97767`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `96961`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `97426`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `96620`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `97426`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `96620`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `222817`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `221998`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `222817`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `221998`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `210256`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `209488`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `202988`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `202220`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap index f28ce6cb13..8b01856f7e 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61884`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61895`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57416`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57427`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `199422`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `200033`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `194540`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `195151`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61884`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61895`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57416`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57427`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57075`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57086`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57075`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57086`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `194537`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `195148`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `194537`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `195148`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `181728`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `182161`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `174460`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `174893`; diff --git a/test/plugins/individual-collateral/curve/cvx/helpers.ts b/test/plugins/individual-collateral/curve/cvx/helpers.ts index 686a2d8f8f..77081254f6 100644 --- a/test/plugins/individual-collateral/curve/cvx/helpers.ts +++ b/test/plugins/individual-collateral/curve/cvx/helpers.ts @@ -11,7 +11,6 @@ import { ERC20Mock, ICurvePool, MockV3Aggregator, - RewardableERC4626Vault, } from '../../../../../typechain' import { getResetFork } from '../../helpers' import { @@ -88,7 +87,7 @@ export const makeW3PoolStable = async (): Promise => usdc, usdt, curvePool, - wrapper: wrapper as unknown as RewardableERC4626Vault, + wrapper: wrapper as unknown as ConvexStakingWrapper, } } @@ -136,7 +135,7 @@ export const makeWSUSDPoolStable = async (): Promise { - const cvxWrapper = ctx.wrapper as ConvexStakingWrapper + const cvxWrapper = ctx.wrapper const lpToken = await ethers.getContractAt( '@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', await cvxWrapper.curveToken() diff --git a/test/plugins/individual-collateral/curve/pluginTestTypes.ts b/test/plugins/individual-collateral/curve/pluginTestTypes.ts index 8714a502fd..b4502bf73a 100644 --- a/test/plugins/individual-collateral/curve/pluginTestTypes.ts +++ b/test/plugins/individual-collateral/curve/pluginTestTypes.ts @@ -1,7 +1,6 @@ import { BigNumberish } from 'ethers' import { ConvexStakingWrapper, - CurveGaugeWrapper, CurvePoolMock, ERC20Mock, MockV3Aggregator, @@ -15,7 +14,7 @@ type Fixture = () => Promise export interface CurveBase { curvePool: CurvePoolMock - wrapper: CurveGaugeWrapper | ConvexStakingWrapper + wrapper: ConvexStakingWrapper } // The basic fixture context used in the Curve collateral plugin tests diff --git a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap index d4baa3fc3f..178c2fa1e9 100644 --- a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `116743`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `116754`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `108431`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `108442`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `131238`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `131283`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `123278`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `123323`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `116562`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `116573`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `108431`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `108442`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `111417`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `111428`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `108090`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `108101`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `123275`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `123320`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `123275`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `123320`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `131177`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `131148`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `123631`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `123602`; diff --git a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts index f3f15a53cf..51ab3b2c47 100644 --- a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts @@ -4,7 +4,7 @@ import { CollateralFixtureContext, CollateralOpts, MintCollateralFunc } from '.. import { ethers } from 'hardhat' import { ContractFactory, BigNumberish } from 'ethers' import { - CTokenWrapper, + ICToken, MockV3Aggregator, MockV3Aggregator__factory, TestICollateral, @@ -12,7 +12,6 @@ import { import { pushOracleForward } from '../../../utils/oracles' import { networkConfig } from '../../../../common/configuration' import { bn, fp } from '../../../../common/numbers' -import { ZERO_ADDRESS } from '../../../../common/constants' import { expect } from 'chai' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { @@ -173,10 +172,7 @@ all.forEach((curr: FTokenEnumeration) => { collateralOpts.chainlinkFeed = chainlinkFeed.address const collateral = await deployCollateral(collateralOpts) - const erc20 = await ethers.getContractAt( - 'CTokenWrapper', - (await collateral.erc20()) as string - ) // the fToken + const erc20 = await ethers.getContractAt('ICToken', (await collateral.erc20()) as string) // the fToken return { alice, @@ -218,22 +214,18 @@ all.forEach((curr: FTokenEnumeration) => { } const increaseRefPerTok = async (ctx: CollateralFixtureContext, pctIncrease: BigNumberish) => { - const tok = ctx.tok as CTokenWrapper - const fToken = await ethers.getContractAt('ICToken', await tok.underlying()) - const totalSupply = await fToken.totalSupply() + const totalSupply = await ctx.tok.totalSupply() await setStorageAt( - fToken.address, + ctx.tok.address, 13, // interesting, the storage slot is 13 for fTokens and 14 for cTokens totalSupply.sub(totalSupply.mul(pctIncrease).div(100)) ) // expand supply by pctDecrease, since it's denominator of exchange rate calculation } const reduceRefPerTok = async (ctx: CollateralFixtureContext, pctDecrease: BigNumberish) => { - const tok = ctx.tok as CTokenWrapper - const fToken = await ethers.getContractAt('ICToken', await tok.underlying()) - const totalSupply = await fToken.totalSupply() + const totalSupply = await ctx.tok.totalSupply() await setStorageAt( - fToken.address, + ctx.tok.address, 13, // interesting, the storage slot is 13 for fTokens and 14 for cTokens totalSupply.add(totalSupply.mul(pctDecrease).div(100)) ) // expand supply by pctDecrease, since it's denominator of exchange rate calculation diff --git a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap index 556dc87aef..35d0ae91c9 100644 --- a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap @@ -1,97 +1,97 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117336`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117347`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `115668`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `115679`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `140936`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `140981`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139122`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139167`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117336`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117347`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115668`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115679`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `115327`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `115338`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `115327`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `115338`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139119`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139164`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139119`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139164`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141069`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141114`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139475`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139520`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117528`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117539`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `115860`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `115871`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `141192`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `141237`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139378`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139423`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117528`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117539`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115860`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115871`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `115519`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `115530`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `115519`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `115530`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139375`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139420`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139375`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139420`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141325`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141370`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139657`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139702`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `125818`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `125829`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `124150`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `124161`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `149904`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `149949`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `148160`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `148205`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `125818`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `125829`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `124150`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `124161`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `123809`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `123820`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `123809`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `123820`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `148157`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `148202`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `148157`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `148202`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `150037`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `150082`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `148439`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `148484`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `120466`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `120477`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `118798`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `118809`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `144338`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `144383`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `142524`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `142569`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `120466`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `120477`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `118798`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `118809`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `118457`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `118468`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `118457`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `118468`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `142451`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `142496`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `142521`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `142566`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `144401`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `144446`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `142803`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `142848`; diff --git a/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap index f277b7eed1..04d291e85c 100644 --- a/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `58984`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `58995`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `54247`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `54258`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59684`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59695`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58015`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58026`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `73786`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `73831`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `73786`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `73831`; diff --git a/test/plugins/individual-collateral/helpers.ts b/test/plugins/individual-collateral/helpers.ts index 341d5ee385..721209d081 100644 --- a/test/plugins/individual-collateral/helpers.ts +++ b/test/plugins/individual-collateral/helpers.ts @@ -1,5 +1,6 @@ import hre from 'hardhat' import { useEnv } from '#/utils/env' +import { forkRpcs, Network } from '#/utils/fork' export const getResetFork = (forkBlock: number) => { return async () => { @@ -11,7 +12,7 @@ export const getResetFork = (forkBlock: number) => { params: [ { forking: { - jsonRpcUrl: useEnv('MAINNET_RPC_URL'), + jsonRpcUrl: forkRpcs[(useEnv('FORK_NETWORK') as Network) ?? 'mainnet'], blockNumber: forkBlock, }, }, diff --git a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap index 99ff840eac..09f0a2c8ad 100644 --- a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `88033`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `88044`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `83564`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `83575`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `132830`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `132898`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `125147`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `125215`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `88033`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `88044`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `83564`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `83575`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `83223`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `83234`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `83223`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `83234`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `125144`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `125212`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `125144`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `125212`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `129895`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `129963`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `125426`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `125494`; diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts index 36dbf620c2..5d5a91ee51 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts @@ -236,6 +236,7 @@ const makeAaveNonFiatCollateralTestSuite = ( itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, + itIsPricedByPeg: true, resetFork: getResetFork(FORK_BLOCK), collateralName, chainlinkDefaultAnswer: defaultCollateralOpts.defaultPrice!, @@ -252,8 +253,8 @@ makeAaveNonFiatCollateralTestSuite('MorphoAAVEV2NonFiatCollateral - WBTC', { underlyingToken: configToUse.tokens.WBTC!, poolToken: configToUse.tokens.aWBTC!, priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: configToUse.chainlinkFeeds.WBTC!, - targetPrRefFeed: configToUse.chainlinkFeeds.wBTCBTC!, + chainlinkFeed: configToUse.chainlinkFeeds.BTC!, + targetPrRefFeed: configToUse.chainlinkFeeds.WBTC!, oracleTimeout: ORACLE_TIMEOUT, oracleError: ORACLE_ERROR, maxTradeVolume: fp('1e6'), diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap index 4fdabac216..97e0228cdb 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap @@ -1,73 +1,73 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134211`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134222`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129742`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129753`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 1`] = `179789`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 1`] = `179834`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 2`] = `172106`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 2`] = `172151`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134211`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134222`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129742`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129753`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 1`] = `129401`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 1`] = `129412`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 2`] = `129401`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 2`] = `129412`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `172103`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `172148`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 2`] = `172103`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 2`] = `172148`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 1`] = `179654`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 1`] = `179699`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 2`] = `172385`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 2`] = `172430`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134414`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134425`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129945`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129956`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `180195`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `180240`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `172512`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `172557`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134414`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134425`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129945`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129956`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `129604`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `129615`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `129604`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `129615`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `172509`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `172554`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `172509`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `172554`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `180060`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `180105`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `172791`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `172836`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133567`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133578`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129098`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129109`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 1`] = `178501`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 1`] = `178546`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 2`] = `170818`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 2`] = `170863`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `133567`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `133578`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129098`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129109`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 1`] = `128757`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 1`] = `128768`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 2`] = `128757`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 2`] = `128768`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `170815`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `170860`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 2`] = `170815`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 2`] = `170860`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 1`] = `178366`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 1`] = `178411`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 2`] = `171097`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 2`] = `171142`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap index a96dc4d339..2c8af6504a 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap @@ -1,49 +1,49 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133634`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133645`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129165`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129176`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 1`] = `199032`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 1`] = `199843`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 2`] = `191349`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 2`] = `192160`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `182849`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `182889`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `178380`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `178420`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 1`] = `178039`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 1`] = `178079`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 2`] = `178039`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 2`] = `178079`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `191346`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `192157`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 2`] = `191346`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 2`] = `192157`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 1`] = `196097`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 1`] = `199708`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 2`] = `191628`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 2`] = `192439`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `167266`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `167277`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `162797`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `162808`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 1`] = `238296`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 1`] = `239107`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 2`] = `230613`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 2`] = `231424`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `222113`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `222153`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `217644`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `217684`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 1`] = `217303`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 1`] = `217343`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 2`] = `217303`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 2`] = `217343`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `230610`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `231421`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `230610`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `231421`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 1`] = `235361`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 1`] = `238972`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 2`] = `230892`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 2`] = `231703`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap index 115445294b..8bc1fa5575 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `201552`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `201563`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `197083`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `197094`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 1`] = `217740`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 1`] = `217785`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 2`] = `210057`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 2`] = `210102`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `201552`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `201563`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `197083`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `197094`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `210054`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `210099`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `210054`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `210099`; diff --git a/test/plugins/individual-collateral/pluginTestTypes.ts b/test/plugins/individual-collateral/pluginTestTypes.ts index 445a237284..11b73fbaa1 100644 --- a/test/plugins/individual-collateral/pluginTestTypes.ts +++ b/test/plugins/individual-collateral/pluginTestTypes.ts @@ -111,6 +111,12 @@ export interface CollateralTestSuiteFixtures // the default answer that will come from the chainlink feed after deployment chainlinkDefaultAnswer: BigNumberish + + // the default tolerance divisor that will be used in expectPrice checks + toleranceDivisor?: BigNumber + + // the target network to run the collaterals tests on (only runs if forking this network) + targetNetwork?: string } export enum CollateralStatus { diff --git a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts index 3673cdefcb..e39347b7d9 100644 --- a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts @@ -148,56 +148,6 @@ const makeCollateralFixtureContext = ( return makeCollateralFixtureContext } -// const deployCollateralCometMockContext = async ( -// opts: CometCollateralOpts = {} -// ): Promise => { -// const collateralOpts = { ...defaultCometCollateralOpts, ...opts } - -// const MockV3AggregatorFactory = ( -// await ethers.getContractFactory('MockV3Aggregator') -// ) -// const chainlinkFeed = await MockV3AggregatorFactory.deploy(6, bn('1e6')) -// collateralOpts.chainlinkFeed = chainlinkFeed.address - -// const CometFactory = await ethers.getContractFactory('CometMock') -// const cusdcV3 = await CometFactory.deploy(bn('5e15'), bn('1e15'), CUSDC_V3) - -// const CusdcV3WrapperFactory = ( -// await ethers.getContractFactory('CusdcV3Wrapper') -// ) -// const wcusdcV3 = ( -// await CusdcV3WrapperFactory.deploy(cusdcV3.address, REWARDS, COMP) -// ) -// const CusdcV3WrapperMockFactory = ( -// await ethers.getContractFactory('CusdcV3WrapperMock') -// ) -// const wcusdcV3Mock = await (( -// await CusdcV3WrapperMockFactory.deploy(wcusdcV3.address) -// )) - -// const realMock = (await ethers.getContractAt( -// 'ICusdcV3WrapperMock', -// wcusdcV3Mock.address -// )) as ICusdcV3WrapperMock -// collateralOpts.erc20 = wcusdcV3.address -// collateralOpts.erc20 = realMock.address -// const usdc = await ethers.getContractAt('ERC20Mock', USDC) -// const collateral = await deployCollateral(collateralOpts) - -// const rewardToken = await ethers.getContractAt('ERC20Mock', COMP) - -// return { -// collateral, -// chainlinkFeed, -// cusdcV3, -// wcusdcV3: wcusdcV3Mock, -// wcusdcV3Mock, -// usdc, -// tok: wcusdcV3, -// rewardToken, -// } -// } - /* Define helper functions */ diff --git a/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap index bd6260705b..4c876c0ecb 100644 --- a/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `70876`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `70887`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `66407`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `66418`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 1`] = `113875`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 1`] = `113943`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 2`] = `106192`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 2`] = `106260`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `70876`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `70887`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `66407`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `66418`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after soft default 1`] = `66066`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after soft default 1`] = `66077`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after soft default 2`] = `66066`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after soft default 2`] = `66077`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `106189`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `106257`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `106189`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `106257`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during soft default 1`] = `113740`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during soft default 1`] = `113808`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during soft default 2`] = `106471`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during soft default 2`] = `106539`; diff --git a/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap b/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap index e6a60268e6..3eb6942200 100644 --- a/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap @@ -4,9 +4,9 @@ exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting re exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `50795`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `68976`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `68999`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `66362`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `66385`; exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `55263`; @@ -16,21 +16,21 @@ exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting re exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `50454`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `66237`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `66260`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `66237`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `66260`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `73787`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `73810`; -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `66519`; +exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `66542`; exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `55263`; exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `50795`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `68976`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `68999`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `66362`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `66385`; exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `55263`; @@ -40,10 +40,10 @@ exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting r exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `50454`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `66237`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `66260`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `66237`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `66260`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `73787`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `73810`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `66519`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `66542`; diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index 39422b06f6..89a9ca08e6 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12087808`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12092382`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9839121`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9830758`; exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2281990`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13612503`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13617164`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20688918`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20897690`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10989496`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10991870`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8723721`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8713158`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `6559535`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `6561504`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `14445209`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `15130053`; diff --git a/test/utils/oracles.ts b/test/utils/oracles.ts index 440acf6d3d..2444878fe4 100644 --- a/test/utils/oracles.ts +++ b/test/utils/oracles.ts @@ -31,7 +31,7 @@ export const expectPrice = async ( const expectedHigh = avgPrice.add(delta) if (near) { - const tolerance = avgPrice.div(overrideToleranceDiv || toleranceDivisor) + const tolerance = avgPrice.div(overrideToleranceDiv ?? toleranceDivisor) expect(lowPrice).to.be.closeTo(expectedLow, tolerance) expect(highPrice).to.be.closeTo(expectedHigh, tolerance) } else { diff --git a/test/utils/trades.ts b/test/utils/trades.ts index 7c7e75d394..b952deabfe 100644 --- a/test/utils/trades.ts +++ b/test/utils/trades.ts @@ -81,7 +81,6 @@ export const dutchBuyAmount = async ( assetInAddr: string, assetOutAddr: string, outAmount: BigNumber, - minTradeVolume: BigNumber, maxTradeSlippage: BigNumber ): Promise => { const assetIn = await ethers.getContractAt('IAsset', assetInAddr) @@ -93,18 +92,7 @@ export const dutchBuyAmount = async ( let maxTradeVolume = await assetOut.maxTradeVolume() if (inMaxTradeVolume.lt(maxTradeVolume)) maxTradeVolume = inMaxTradeVolume - const auctionVolume = outAmount.mul(sellHigh).div(fp('1')) - const slippage1e18 = maxTradeSlippage.mul( - fp('1').sub( - auctionVolume.sub(minTradeVolume).mul(fp('1')).div(maxTradeVolume.sub(minTradeVolume)) - ) - ) - - // Adjust for rounding - const leftover = slippage1e18.mod(fp('1')) - const slippage = slippage1e18.div(fp('1')).add(leftover.gte(fp('0.5')) ? 1 : 0) - - const worstPrice = sellLow.mul(fp('1').sub(slippage)).div(buyHigh) + const worstPrice = sellLow.mul(fp('1').sub(maxTradeSlippage)).div(buyHigh) const bestPrice = divCeil(sellHigh.mul(fp('1')), buyLow) const highPrice = divCeil(sellHigh.mul(fp('1.5')), buyLow) diff --git a/tools/docgen/custom.css b/tools/docgen/custom.css new file mode 100644 index 0000000000..cf3177ffac --- /dev/null +++ b/tools/docgen/custom.css @@ -0,0 +1,116 @@ +:root { + font-size: 62.5%; + --rp-blue: rgb(1, 81, 175); + --rp-offwhite: #F9EDDD; + --rp-black: #000000; + --rp-cream: #F9EDDD; + --white: #ffffff; +} + +html { + font-family: "Arial"; + color: var(--rp-black); + background-color: var(--white); + text-size-adjust: none; + -webkit-text-size-adjust: none; +} + +body { + margin: 0; + font-size: 2rem; + line-height: 1.6; + overflow-x: hidden; + background-color: var(--white); +} + +/* Typography */ +h1 a, h2 a, h3 a, body h4, body header { + color: var(--rp-blue) !important; +} + +.menu-title { + color: var(--rp-black); + opacity: 70%; + font-size: larger; + font-family: "Open Sans"; +} + +.chapter li a.active { + font-weight: bold; + color: var(--rp-cream); +} + +/* Table Styles */ +table thead { + background-color: var(--rp-blue) !important; +} + +table tbody tr { + background-color: var(--rp-cream) !important; +} + +tr th { + color: var(--rp-offwhite); +} + +/* Blockquote Style */ +blockquote { + margin: 20px 0; + padding: 0 20px; + color: var(--rp-black); + background-color: var(--rp-cream); + border-top: 0.1em solid var(--rp-blue); + border-bottom: 0.1em solid var(--rp-blue); +} + +/* Code Style */ +code.hljs { + background-color: var(--rp-offwhite); + color: var(--rp-blue) !important; +} + +/* Form Styles */ +textarea { + background-color: var(--white); + color: var(--rp-black); +} + +#searchbar { + background-color: var(--rp-cream); +} + +#searchbar:focus { + box-shadow: 0 0 2px var(--rp-blue); +} + +#searchbar:focus-visible { + outline: var(--rp-blue) solid 0.5px; + outline-offset: 0px; +} + +/* Navigation Styles */ +#menu-bar { + background-color: var(--rp-offwhite); + border: none; +} + +.nav-chapters:hover, .mobile-nav-chapters { + background-color: var(--rp-offwhite) !important; +} + +.chapter-item { + font-size: larger; +} + +.sidebar-scrollbox, .fa-copy:hover { + background-color: var(--rp-blue) !important; +} + +/* Icon Styles */ +.fa-copy { + background-color: var(--rp-cream) !important; +} + +i.fa-paint-brush { + display: none !important; +} \ No newline at end of file diff --git a/tools/docgen/foundry.toml b/tools/docgen/foundry.toml new file mode 100644 index 0000000000..91664f1de0 --- /dev/null +++ b/tools/docgen/foundry.toml @@ -0,0 +1,7 @@ +[profile.default] +src="tools/docgen" + +[doc] +out = "tools/docgen" +book = "tools/docgen" +ignore = ["**/node_modules/**/*"] \ No newline at end of file diff --git a/utils/env.ts b/utils/env.ts index 9f07c74e1b..0944189851 100644 --- a/utils/env.ts +++ b/utils/env.ts @@ -14,7 +14,6 @@ type IEnvVars = | 'PROTO_IMPL' | 'ETHERSCAN_API_KEY' | 'NO_OPT' - | 'MAINNET_BLOCK' | 'ONLY_FAST' | 'JOBS' | 'EXTREME' @@ -22,6 +21,9 @@ type IEnvVars = | 'TENDERLY_RPC_URL' | 'SKIP_PROMPT' | 'BASE_GOERLI_RPC_URL' + | 'BASE_RPC_URL' + | 'FORK_NETWORK' + | 'FORK_BLOCK' export function useEnv(key: IEnvVars | IEnvVars[], _default = ''): string { if (typeof key === 'string') { diff --git a/utils/fork.ts b/utils/fork.ts new file mode 100644 index 0000000000..6749b5054d --- /dev/null +++ b/utils/fork.ts @@ -0,0 +1,12 @@ +import { useEnv } from './env' + +const MAINNET_RPC_URL = useEnv(['MAINNET_RPC_URL', 'ALCHEMY_MAINNET_RPC_URL']) +const TENDERLY_RPC_URL = useEnv('TENDERLY_RPC_URL') +const GOERLI_RPC_URL = useEnv('GOERLI_RPC_URL') +const BASE_GOERLI_RPC_URL = useEnv('BASE_GOERLI_RPC_URL') +const BASE_RPC_URL = useEnv('BASE_RPC_URL') +export type Network = 'mainnet' | 'base' +export const forkRpcs = { + mainnet: MAINNET_RPC_URL, + base: BASE_RPC_URL, +} diff --git a/yarn.lock b/yarn.lock index cd38842265..7b7e7a4873 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,22 @@ __metadata: languageName: node linkType: hard +"@aave/core-v3@npm:1.19.0, @aave/core-v3@npm:^1.18.0": + version: 1.19.0 + resolution: "@aave/core-v3@npm:1.19.0" + checksum: 42470788f81e85945c4722d6578add6d4ba0070faabb322e1d2db038c2bf637177feefe86af9bb4b9691d39e4027c76845ad836bd06bc16b221cb16e2e768ffe + languageName: node + linkType: hard + +"@aave/periphery-v3@npm:^2.5.0": + version: 2.5.0 + resolution: "@aave/periphery-v3@npm:2.5.0" + dependencies: + "@aave/core-v3": 1.19.0 + checksum: 4af7a07181097e5fdbda9e1012b8e81c868e447b2050418cd051edcac324db6d0033200bcb5e4d6031bc818613ccf9f2e758fb9e97c94a283d1fbec51ba8404e + languageName: node + linkType: hard + "@aave/protocol-v2@npm:^1.0.1": version: 1.0.1 resolution: "@aave/protocol-v2@npm:1.0.1" @@ -8819,6 +8835,8 @@ __metadata: version: 0.0.0-use.local resolution: "reserve-protocol@workspace:." dependencies: + "@aave/core-v3": ^1.18.0 + "@aave/periphery-v3": ^2.5.0 "@aave/protocol-v2": ^1.0.1 "@chainlink/contracts": ^0.5.1 "@ethersproject/providers": ^5.7.2 From 04e4e5a84eb70a721a38bcbd5e6f9e433b98ff19 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 2 Oct 2023 19:38:15 -0400 Subject: [PATCH 435/462] cleanup --- CHANGELOG.md | 116 ++---------------------------------------- docs/collateral.md | 13 +---- test/Revenues.test.ts | 4 -- 3 files changed, 6 insertions(+), 127 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fedf2f6832..156df3212c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,5 @@ # Changelog -<<<<<<< HEAD # 3.1.0 - Unreleased ### Upgrade Steps -- Required @@ -49,30 +48,6 @@ _All_ asset plugins (and their corresponding ERC20s) must be upgraded. The only Call the following functions, once it is desired to turn on the new features: -======= -# 3.0.0 - -### Upgrade Steps - -#### Required Steps - -Update _all_ component contracts, including Main. - -Call the following functions: - -- `BackingManager.cacheComponents()` -- `RevenueTrader.cacheComponents()` (for both rsrTrader and rTokenTrader) -- `Distributor.cacheComponents()` - -_All_ asset plugins (and their corresponding ERC20s) must be upgraded. The only exception is the `StaticATokenLM` ERC20s from Aave V2. These can be left the same, however their assets should upgraded. - -- Note: Make sure to use `Deployer.deployRTokenAsset()` to create new `RTokenAsset` instances. This asset should be swapped too. - -#### Optional Steps - -Call the following functions, once it is desired to turn on the new features: - ->>>>>>> master - `BasketHandler.setWarmupPeriod()` - `StRSR.setWithdrawalLeak()` - `Broker.setDutchAuctionLength()` @@ -83,9 +58,7 @@ It is acceptable to leave these function calls out of the initial upgrade tx and ### Core Protocol Contracts - `AssetRegistry` [+1 slot] - Summary: StRSR contract need to know when refresh() was last called - - # Add last refresh timestamp tracking and expose via `lastRefresh()` getter - Summary: Other component contracts need to know when refresh() was last called + Summary: Other component contracts need to know when refresh() was last called - Add `lastRefresh()` timestamp getter - Add `size()` getter for number of registered assets - Require asset is SOUND on registration @@ -204,8 +177,6 @@ It is acceptable to leave these function calls out of the initial upgrade tx and - Add `UnstakingCancelled()` event - Allow payout of (already acquired) RSR rewards while frozen - Add ability for governance to `resetStakes()` when stake rate falls outside (1e12, 1e24) -<<<<<<< HEAD - <<<<<<< HEAD - `StRSRVotes` [+0 slots] - Add `stakeAndDelegate(uint256 rsrAmount, address delegate)` function to encourage people to receive voting weight upon staking @@ -227,33 +198,10 @@ Remove `FacadeMonitor` - now redundant with `nextRecollateralizationAuction()` a - `FacadeRead` Summary: Add new data summary views frontends may be interested in -======= - -- `StRSRVotes` [+0 slots] - - Add `stakeAndDelegate(uint256 rsrAmount, address delegate)` function to encourage people to receive voting weight upon staking - -### Facades - -Remove `FacadeMonitor` - now redundant with `nextRecollateralizationAuction()` and `revenueOverview()` - -- `FacadeAct` - Summary: Remove unused `getActCalldata()` and add way to run revenue auctions - - - Remove `getActCalldata(..)` - - Remove `canRunRecollateralizationAuctions(..)` - - Remove `runRevenueAuctions(..)` - - Add `revenueOverview(IRevenueTrader) returns ( IERC20[] memory erc20s, bool[] memory canStart, uint256[] memory surpluses, uint256[] memory minTradeAmounts)` - - Add `nextRecollateralizationAuction(..) returns (bool canStart, IERC20 sell, IERC20 buy, uint256 sellAmount)` - - Modify all functions to work on both 3.0.0 and 2.1.0 RTokens - -- `FacadeRead` - Summary: Add new data summary views frontends may be interested in - ->>>>>>> master - - Remove `basketNonce` from `redeem(.., uint48 basketNonce)` - - Add `redeemCustom(.., uint48[] memory basketNonces, uint192[] memory portions)` callstatic to simulate multi-basket redemptions - - Remove `traderBalances(..)` - - Add `balancesAcrossAllTraders(IBackingManager) returns (IERC20[] memory erc20s, uint256[] memory balances, uint256[] memory balancesNeededByBackingManager)` +- Remove `basketNonce` from `redeem(.., uint48 basketNonce)` +- Add `redeemCustom(.., uint48[] memory basketNonces, uint192[] memory portions)` callstatic to simulate multi-basket redemptions +- Remove `traderBalances(..)` +- Add `balancesAcrossAllTraders(IBackingManager) returns (IERC20[] memory erc20s, uint256[] memory balances, uint256[] memory balancesNeededByBackingManager)` - `FacadeWrite` Summary: More expressive and fine-grained control over the set of pausers and freezers @@ -317,7 +265,6 @@ Duration: 30 min (default) - Modify `backingOverview() to handle unpriced cases` - `FacadeAct` - Add `runRevenueAuctions()` -<<<<<<< HEAD ### Plugins @@ -406,65 +353,12 @@ Duration: 30 min (default) - Bugfix: Adjust `Curve*Collateral` and `RTokenAsset` to treat FIX_MAX correctly as +inf - Bugfix: Continue updating cached price after collateral default (impacts all appreciating collateral) -# 2.1.0 - -### Core protocol contracts - -- `BasketHandler` - - Bugfix for `getPrimeBasket()` view - - Minor change to `_price()` rounding - - Minor natspec improvement to `refreshBasket()` -- `Broker` - - Fix `GnosisTrade` trade implemention to treat defensive rounding by EasyAuction correctly - - Add `setGnosis()` and `setTradeImplementation()` governance functions -- `RToken` - - Minor gas optimization added to `redeemTo` to use saved `assetRegistry` variable -- `StRSR` - - Expose RSR variables via `getDraftRSR()`, `getStakeRSR()`, and `getTotalDrafts()` views - -### Facades - -- `FacadeRead` - - Extend `issue()` to return the estimated USD value of deposits as `depositsUoA` - - Add `traderBalances()` - - Add `auctionsSettleable()` - - Add `nextRecollateralizationAuction()` - - Modify `backingOverview() to handle unpriced cases` -- `FacadeAct` - - Add `runRevenueAuctions()` - -======= - ->>>>>>> master -### Plugins - -#### Assets and Collateral - -Across all collateral, `tryPrice()` was updated to exclude revenueHiding considerations - -- Deploy CRV + CVX plugins -- Add `AnkrStakedEthCollateral` + tests + deployment/verification scripts for ankrETH -- Add FluxFinance collateral tests + deployment/verification scripts for fUSDC, fUSDT, fDAI, and fFRAX -- Add CompoundV3 `CTokenV3Collateral` + tests + deployment/verification scripts for cUSDCV3 -- Add Convex `CvxStableCollateral` + tests + deployment/verification scripts for 3Pool -- Add Convex `CvxVolatileCollateral` + tests + deployment/verification scripts for Tricrypto -- Add Convex `CvxStableMetapoolCollateral` + tests + deployment/verification scripts for MIM/3Pool -- Add Convex `CvxStableRTokenMetapoolCollateral` + tests + deployment/verification scripts for eUSD/fraxBP -- Add Frax `SFraxEthCollateral` + tests + deployment/verification scripts for sfrxETH -- Add Lido `LidoStakedEthCollateral` + tests + deployment/verification scripts for wstETH -- Add RocketPool `RethCollateral` + tests + deployment/verification scripts for rETH - ### Testing - Add generic collateral testing suite at `test/plugins/individual-collateral/collateralTests.ts` - Add EasyAuction regression test for Broker false positive (observed during USDC de-peg) - Add EasyAuction extreme tests -<<<<<<< HEAD -> > > > > > > master - -======= ->>>>>>> master ### Documentation - Add `docs/plugin-addresses.md` as well as accompanying script for generation at `scripts/collateral-params.ts` diff --git a/docs/collateral.md b/docs/collateral.md index 146fb630db..baae8c4389 100644 --- a/docs/collateral.md +++ b/docs/collateral.md @@ -147,16 +147,10 @@ Note that a value denoted `{tok}` is a number of "whole tokens" with 18 decimals ### Reference unit `{ref}` The _reference unit_, `{ref}`, is the measure of value that the protocol computes revenue against. When the exchange rate `refPerTok()` rises, the protocol keeps a constant amount of `{ref}` as backing, and considers any surplus balance of the token revenue. -<<<<<<< HEAD There's room for flexibility and creativity in the choice of a Collateral's reference unit. The chief constraints is that `refPerTok()` must be nondecreasing over time, and as soon as this fails to be the case the `CollateralStatus` should become permanently `DISABLED`. -======= - -There's room for flexibility and creativity in the choice of a Collateral's reference unit. The chief constraints is that `refPerTok()` must be nondecreasing over time, and as soon as this fails to be the case the `CollateralStatus` should become permanently `DISABLED`. - -> > > > > > > master -> > > > > > > In many cases, the choice of reference unit is clear. For example: +In many cases, the choice of reference unit is clear. For example: - The collateral token cUSDC (compound USDC) has a natural reference unit of USDC. cUSDC is permissionlessly redeemable in the Compound protocol for an ever-increasing amount of USDC. - The collateral token USDT is its own natural reference unit. It's not natively redeemable for anything else on-chain, and we think of it as non-appreciating collateral. The reference unit is not USD, because the USDT/USD exchange rate often has small fluctuations in both direction which would otherwise cause `refPerTok()` to decrease. @@ -253,12 +247,7 @@ There is a simple ERC20 wrapper that can be easily extended at [RewardableERC20W Because it’s called at the beginning of many transactions, `refresh()` should never revert. If `refresh()` encounters a critical error, it should change the Collateral contract’s state so that `status()` becomes `DISABLED`. -<<<<<<< HEAD To prevent `refresh()` from reverting due to overflow or other numeric errors, the base collateral plugin [Fiat Collateral](../contracts/plugins/asset/FiatCollateral.sol) has a `tryPrice()` function that encapsulates both the oracle lookup as well as any subsequent math required. This function is always executed via a try-catch in `price()`/`refresh()`. Extenders of this contract should not have to override any of these three functions, just `tryPrice()`. -======= -To prevent `refresh()` from reverting due to overflow or other numeric errors, the base collateral plugin [Fiat Collateral](../contracts/plugins/assets/FiatCollateral.sol) has a `tryPrice()` function that encapsulates both the oracle lookup as well as any subsequent math required. This function is always executed via a try-catch in `price()`/`lotPrice()`/`refresh()`. Extenders of this contract should not have to override any of these three functions, just `tryPrice()`. - -> > > > > > > master ### The `IFFY` status should be temporary. diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 3dbabd518a..8c85df62d4 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -1050,8 +1050,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { minBuyAmtRToken.div(bn('1e15')) ) }) - it('Should be able to start a dust auction BATCH_AUCTION, if enabled', async () => { - const minTrade = bn('1e18') it('Should be able to start a dust auction BATCH_AUCTION, if enabled', async () => { const minTrade = bn('1e18') @@ -3026,7 +3024,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { rTokenAsset.address, collateral0.address, issueAmount, - config.minTradeVolume, config.maxTradeSlippage ) expect(actual).to.be.closeTo(expected, expected.div(bn('1e15'))) @@ -3086,7 +3083,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { rTokenAsset.address, collateral0.address, issueAmount, - config.minTradeVolume, config.maxTradeSlippage ) expect(await rTokenTrader.tradesOpen()).to.equal(0) From 4106b1a9f5f1d74425319ebd61bc22fe1c52cad1 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 2 Oct 2023 19:59:18 -0400 Subject: [PATCH 436/462] more cleanup --- contracts/p0/mixins/TradingLib.sol | 4 +--- docs/collateral.md | 2 +- test/plugins/Asset.test.ts | 8 +------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index fd9ea5aa19..e43c2a800d 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -304,9 +304,7 @@ library TradingLibP0 { } (uint192 low, uint192 high) = asset.price(); // {UoA/tok} - // price() is better than lotPrice() here: it's important to not underestimate how - // much value could be in a token that is unpriced by using a decaying high lotPrice. - // price() will return [0, FIX_MAX] in this case, which is preferable. + // low decays down; high decays up // Skip over dust-balance assets not in the basket // Intentionally include value of IFFY/DISABLED collateral diff --git a/docs/collateral.md b/docs/collateral.md index baae8c4389..c5289dd1f5 100644 --- a/docs/collateral.md +++ b/docs/collateral.md @@ -214,7 +214,7 @@ This would be sensible for many UNI v2 pools, but someone holding value in a two Revenue Hiding should be employed when the function underlying `refPerTok()` is not necessarily _strongly_ non-decreasing, or simply if there is uncertainty surrounding the guarantee. In general we recommend including a very small amount (1e-6) of revenue hiding for all appreciating collateral. This is already implemented in [AppreciatingFiatCollateral.sol](../contracts/plugins/assets/AppreciatingFiatCollateral.sol). -When implementing Revenue Hiding, the `price/lotPrice()` functions should NOT hide revenue; they should use the current underlying exchange rate to calculate a best-effort estimate of what the collateral will trade at on secondary markets. A side-effect of this approach is that the RToken's price on markets becomes more variable. +When implementing Revenue Hiding, the `price` function should NOT hide revenue; they should use the current underlying exchange rate to calculate a best-effort estimate of what the collateral will trade at on secondary markets. A side-effect of this approach is that the RToken's price on markets becomes more variable. ## Important Properties for Collateral Plugins diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index 55c2eaa66f..c0a49a79a5 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -324,7 +324,7 @@ describe('Assets contracts #fast', () => { ) }) - it('Should not revert during RToken price() if supply is zero', async () => { + it('Should not revert RToken price if supply is zero', async () => { // Redeem RToken to make price function revert // Note: To get RToken price to 0, a full basket refresh needs to occur (covered in RToken tests) await rToken.connect(wallet).redeem(amt) @@ -336,12 +336,6 @@ describe('Assets contracts #fast', () => { config.minTradeVolume.mul((await assetRegistry.erc20s()).length) ) expect(await rTokenAsset.maxTradeVolume()).to.equal(config.rTokenMaxTradeVolume) - - // Should have lot price, equal to price when feed works OK - const [lowPrice, highPrice] = await rTokenAsset.price() - const [lotLow, lotHigh] = await rTokenAsset.lotPrice() - expect(lotLow).to.equal(lowPrice) - expect(lotHigh).to.equal(highPrice) }) it('Should calculate trade min correctly', async () => { From 7262b6c1768b89f56985da7dfd12eb56eda6b106 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 2 Oct 2023 20:08:35 -0400 Subject: [PATCH 437/462] more cleanup --- test/plugins/individual-collateral/collateralTests.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index ac77ffea8c..8af1117c26 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -385,10 +385,6 @@ export default function fn( await advanceTime(await collateral.oracleTimeout()) - await advanceTime(await collateral.oracleTimeout()) - - await advanceTime(await collateral.oracleTimeout()) - // Should be roughly half, after half of priceTimeout const priceTimeout = await collateral.priceTimeout() await advanceTime(priceTimeout / 2) From 7de788551557885e60bc6aa5bb0a362c3b74a7bb Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 2 Oct 2023 20:58:13 -0400 Subject: [PATCH 438/462] fix broken tests --- .../aave-v3/AaveV3FiatCollateral.test.ts | 4 ++++ .../flux-finance/FTokenFiatCollateral.test.ts | 1 - test/plugins/individual-collateral/flux-finance/helpers.ts | 6 ++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts b/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts index cb7876809f..8566f70d76 100644 --- a/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts @@ -17,6 +17,7 @@ import { networkConfig } from '#/common/configuration' import { getResetFork } from '../helpers' import { whileImpersonating } from '#/test/utils/impersonation' import { AAVE_V3_USDC_POOL, AAVE_V3_INCENTIVES_CONTROLLER } from './constants' +import { pushOracleForward } from '../../../utils/oracles' interface AaveV3FiatCollateralFixtureContext extends CollateralFixtureContext { staticWrapper: MockStaticATokenV3LM @@ -70,6 +71,9 @@ export const deployCollateral = async (opts: Partial = {}) => ) await collateral.deployed() + // Push forward chainlink feed + await pushOracleForward(combinedOpts.chainlinkFeed!) + // sometimes we are trying to test a negative test case and we want this to fail silently // fortunately this syntax fails silently because our tools are terrible await expect(collateral.refresh()) diff --git a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts index 1f1d2ca6ad..d38beecde3 100644 --- a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts @@ -12,7 +12,6 @@ import { import { pushOracleForward } from '../../../utils/oracles' import { networkConfig } from '../../../../common/configuration' import { bn, fp } from '../../../../common/numbers' -import { ZERO_ADDRESS } from '../../../../common/constants' import { expect } from 'chai' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { diff --git a/test/plugins/individual-collateral/flux-finance/helpers.ts b/test/plugins/individual-collateral/flux-finance/helpers.ts index 74cb8d9614..7f1838aea8 100644 --- a/test/plugins/individual-collateral/flux-finance/helpers.ts +++ b/test/plugins/individual-collateral/flux-finance/helpers.ts @@ -1,4 +1,4 @@ -import { CTokenWrapper, ICToken, IERC20Metadata } from '../../../../typechain' +import { ICToken, IERC20Metadata } from '../../../../typechain' import { whileImpersonating } from '../../../utils/impersonation' import { BigNumberish } from 'ethers' import { getResetFork } from '../helpers' @@ -8,7 +8,6 @@ export const mintFToken = async ( underlying: IERC20Metadata, holderUnderlying: string, fToken: ICToken, - fTokenVault: CTokenWrapper, amount: BigNumberish, recipient: string ) => { @@ -16,8 +15,7 @@ export const mintFToken = async ( const balUnderlying = await underlying.balanceOf(signer.address) await underlying.connect(signer).approve(fToken.address, balUnderlying) await fToken.connect(signer).mint(balUnderlying) - await fToken.connect(signer).approve(fTokenVault.address, amount) - await fTokenVault.connect(signer).deposit(amount, recipient) + await fToken.connect(signer).transfer(recipient, amount) }) } From 14a46aed071baf6a52e15b25a668b6a3a91c4bc1 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 2 Oct 2023 20:58:16 -0400 Subject: [PATCH 439/462] gas snapshots --- test/__snapshots__/Broker.test.ts.snap | 6 +- test/__snapshots__/FacadeWrite.test.ts.snap | 2 +- test/__snapshots__/Main.test.ts.snap | 8 +- test/__snapshots__/RToken.test.ts.snap | 6 +- .../Recollateralization.test.ts.snap | 12 +-- test/__snapshots__/Revenues.test.ts.snap | 8 +- test/__snapshots__/ZZStRSR.test.ts.snap | 10 +- .../__snapshots__/Collateral.test.ts.snap | 8 +- .../AaveV3FiatCollateral.test.ts.snap | 24 ++--- .../ATokenFiatCollateral.test.ts.snap | 24 ++--- .../AnkrEthCollateralTestSuite.test.ts.snap | 24 ++--- .../CBETHCollateral.test.ts.snap | 24 ++--- .../CTokenFiatCollateral.test.ts.snap | 24 ++--- .../__snapshots__/CometTestSuite.test.ts.snap | 24 ++--- .../CrvStableMetapoolSuite.test.ts.snap | 24 ++--- ...StableRTokenMetapoolTestSuite.test.ts.snap | 24 ++--- .../CrvStableTestSuite.test.ts.snap | 24 ++--- .../CvxStableMetapoolSuite.test.ts.snap | 24 ++--- ...StableRTokenMetapoolTestSuite.test.ts.snap | 24 ++--- .../CvxStableTestSuite.test.ts.snap | 24 ++--- .../SDaiCollateralTestSuite.test.ts.snap | 24 ++--- .../FTokenFiatCollateral.test.ts.snap | 96 +++++++++---------- .../SFrxEthTestSuite.test.ts.snap | 12 +-- .../LidoStakedEthTestSuite.test.ts.snap | 24 ++--- .../MorphoAAVEFiatCollateral.test.ts.snap | 72 +++++++------- .../MorphoAAVENonFiatCollateral.test.ts.snap | 48 +++++----- ...AAVESelfReferentialCollateral.test.ts.snap | 16 ++-- .../RethCollateralTestSuite.test.ts.snap | 24 ++--- .../__snapshots__/MaxBasketSize.test.ts.snap | 16 ++-- 29 files changed, 342 insertions(+), 338 deletions(-) diff --git a/test/__snapshots__/Broker.test.ts.snap b/test/__snapshots__/Broker.test.ts.snap index fc823d852b..635ba04aab 100644 --- a/test/__snapshots__/Broker.test.ts.snap +++ b/test/__snapshots__/Broker.test.ts.snap @@ -2,11 +2,11 @@ exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Initialize Trade 1`] = `251984`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `361087`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 1`] = `366975`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `363202`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 2`] = `369090`; -exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `365340`; +exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `371228`; exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63333`; diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index 848354f559..4b4c7894ff 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8393668`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8497592`; exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464253`; diff --git a/test/__snapshots__/Main.test.ts.snap b/test/__snapshots__/Main.test.ts.snap index 06ba9d68c7..9591a27626 100644 --- a/test/__snapshots__/Main.test.ts.snap +++ b/test/__snapshots__/Main.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `357705`; +exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `357740`; exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `195889`; exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `195889`; -exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `167045`; +exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `167023`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80532`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80510`; -exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `70044`; +exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 2`] = `70022`; diff --git a/test/__snapshots__/RToken.test.ts.snap b/test/__snapshots__/RToken.test.ts.snap index f50430c9b4..ee00025ae6 100644 --- a/test/__snapshots__/RToken.test.ts.snap +++ b/test/__snapshots__/RToken.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `787453`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `787553`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `614457`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `614557`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `589230`; +exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `589266`; exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56658`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 9e0d532f8e..4222c553e9 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1384418`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1370490`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1510705`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1506234`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `747331`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `760457`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1680908`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1665048`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1613640`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1607593`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1702037`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1695355`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `202908`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index 81fa8bb746..4275db1832 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -12,16 +12,16 @@ exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229377`; exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212277`; -exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1008567`; +exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1025829`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `773918`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `774070`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1181227`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1181105`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `311446`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `266512`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `739718`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `739870`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `242306`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index dbc65bb91d..f4ec1f241e 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -1,8 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`StRSRP1 contract Gas Reporting Stake 1`] = `139717`; +exports[`StRSRP1 contract Gas Reporting Stake 1`] = `156559`; -exports[`StRSRP1 contract Gas Reporting Stake 2`] = `151759`; +exports[`StRSRP1 contract Gas Reporting Stake 2`] = `134917`; exports[`StRSRP1 contract Gas Reporting Transfer 1`] = `63409`; @@ -10,10 +10,10 @@ exports[`StRSRP1 contract Gas Reporting Transfer 2`] = `41509`; exports[`StRSRP1 contract Gas Reporting Transfer 3`] = `58621`; -exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `222609`; +exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `241951`; exports[`StRSRP1 contract Gas Reporting Unstake 2`] = `139758`; -exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `572011`; +exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `572112`; -exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `526015`; +exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `526116`; diff --git a/test/plugins/__snapshots__/Collateral.test.ts.snap b/test/plugins/__snapshots__/Collateral.test.ts.snap index 83c6bf2eb6..926d33902f 100644 --- a/test/plugins/__snapshots__/Collateral.test.ts.snap +++ b/test/plugins/__snapshots__/Collateral.test.ts.snap @@ -1,8 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral contracts Gas Reporting refresh() after hard default 1`] = `71881`; +exports[`Collateral contracts Gas Reporting refresh() after full price timeout 1`] = `46228`; -exports[`Collateral contracts Gas Reporting refresh() after hard default 2`] = `75163`; +exports[`Collateral contracts Gas Reporting refresh() after hard default 1`] = `71859`; + +exports[`Collateral contracts Gas Reporting refresh() after hard default 2`] = `75141`; + +exports[`Collateral contracts Gas Reporting refresh() after oracle timeout 1`] = `46228`; exports[`Collateral contracts Gas Reporting refresh() during + after soft default 1`] = `61571`; diff --git a/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap index 607d7bd001..9900368976 100644 --- a/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69299`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69288`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after full price timeout 2`] = `67631`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after full price timeout 2`] = `67620`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after hard default 1`] = `72125`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after hard default 1`] = `72103`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after hard default 2`] = `64443`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after hard default 2`] = `64421`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `69299`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `69288`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `67631`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `67620`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after soft default 1`] = `67290`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after soft default 1`] = `67279`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after soft default 2`] = `67290`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after soft default 2`] = `67279`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during SOUND 1`] = `87706`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during SOUND 1`] = `87684`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during SOUND 2`] = `87706`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during SOUND 2`] = `87684`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during soft default 1`] = `89730`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during soft default 1`] = `89634`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during soft default 2`] = `87988`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during soft default 2`] = `87966`; diff --git a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap index 79711c5fe3..70324e7f45 100644 --- a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `74365`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `74354`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `72697`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `72686`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `72960`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `72938`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `65278`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `65256`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `74365`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `74354`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `72697`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `72686`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `91095`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `91147`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `91095`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `91073`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `92307`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `92285`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92233`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92211`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `127304`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `127356`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `91510`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `91414`; diff --git a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap index 37480626fe..01c0ef5979 100644 --- a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `60337`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `60326`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55868`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55857`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `99413`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `99391`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `91730`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `91708`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `60337`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `60326`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55868`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55857`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `55527`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `55516`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `55527`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `55516`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `91657`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `91635`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `91657`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `91635`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `99208`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `99186`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `91939`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `91917`; diff --git a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap index bf09f1f35e..0bde8f5436 100644 --- a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59824`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59813`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55355`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55344`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `98317`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `98295`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `90634`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `90612`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59824`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59813`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55355`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `55344`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `55014`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `55003`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `55014`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `55003`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `90631`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `90609`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `90631`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `90609`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `98182`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `98160`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `90913`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `90891`; diff --git a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap index ed6522b5ce..053ccfb3b7 100644 --- a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `119347`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `119350`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `117679`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `117681`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `76242`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `76220`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `68560`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `68538`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `119347`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `119350`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `117679`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `117681`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `138781`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `138759`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `138707`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `138685`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `139845`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `139836`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `139845`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `139836`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `174990`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `174982`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `139048`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `139039`; diff --git a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap index be30eecd44..45d7e08f78 100644 --- a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `109063`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `109052`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `104326`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `104315`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `134471`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `134449`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `126788`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `126766`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `109063`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `109052`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `104326`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `104315`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `107053`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `107042`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `103985`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `103974`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `126785`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `126763`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `126785`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `126763`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `134336`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `134314`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `127067`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `127045`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap index adac76df06..cc198f1a9e 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `52705`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `52713`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48237`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48245`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `251539`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `251771`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `246657`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `246889`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `52705`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `52713`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48237`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48245`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `47896`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `47904`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `47896`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `47904`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `246654`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `246886`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `246654`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `246886`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `254250`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `254482`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `246982`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `247214`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap index 74b5d0e51d..1023f3eb49 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69638`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `104049`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `65170`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `99581`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `226883`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `226544`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `222001`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `221662`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `101429`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `101430`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `96961`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `96962`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `96620`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `96621`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `96620`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `96621`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `221998`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `221659`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `221998`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `221659`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `209488`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `209203`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `202220`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `201935`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap index d725ca2610..69fbe0cd6b 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61895`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61884`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57427`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57416`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `200033`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `199560`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `195151`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `194678`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61895`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61884`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57427`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57416`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57086`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57075`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57086`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57075`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `195148`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `194675`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `195148`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `194675`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `182161`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `181820`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `174893`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `174552`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap index 699536b70f..85b9903370 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `52705`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `52713`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48237`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48245`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `251539`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `251771`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `246657`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `246889`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `52705`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `52713`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48237`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48245`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `47896`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `47904`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `47896`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `47904`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `246654`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `246886`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `246654`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `246886`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `254250`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `254482`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `246982`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `247214`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap index 34a9b7f027..c5b9191271 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69638`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `104049`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `65170`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `99581`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `226883`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `226544`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `222001`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `221662`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `101429`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `101430`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `96961`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `96962`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `96620`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `96621`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `96620`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `96621`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `221998`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `221659`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `221998`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `221659`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `209488`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `209203`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `202220`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `201935`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap index 8b01856f7e..ba0b229d7d 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61895`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61884`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57427`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57416`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `200033`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `199560`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `195151`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `194678`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61895`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `61884`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57427`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `57416`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57086`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `57075`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57086`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `57075`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `195148`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `194675`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `195148`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `194675`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `182161`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `181820`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `174893`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `174552`; diff --git a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap index 178c2fa1e9..08efe17a75 100644 --- a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `116754`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `116743`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `108442`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `108431`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `131283`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 1`] = `131261`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `123323`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `123301`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `116573`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `116562`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `108442`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `108431`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `111428`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `111417`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `108101`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `108090`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `123320`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `123298`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `123320`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `123298`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `131148`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `131126`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `123602`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `123580`; diff --git a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap index 35d0ae91c9..fdf9179172 100644 --- a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap @@ -1,97 +1,97 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117347`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117350`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `115679`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `115681`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `140981`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `140959`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139167`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139145`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117347`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117350`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115679`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115681`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `115338`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `115327`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `115338`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `115327`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139164`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139155`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139164`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139155`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141114`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141106`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139520`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139511`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117539`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117542`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `115871`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `115873`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `141237`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `141215`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139423`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139401`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117539`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117542`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115871`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115873`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `115530`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `115519`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `115530`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `115519`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139420`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139411`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139420`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139411`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141370`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141362`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139702`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139693`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `125829`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `125832`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `124161`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `124163`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `149949`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `149927`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `148205`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `148183`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `125829`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `125832`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `124161`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `124163`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `123820`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `123809`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `123820`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `123809`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `148202`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `148193`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `148202`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `148193`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `150082`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `150074`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `148484`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `148475`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `120477`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `120480`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `118809`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `118811`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `144383`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `144361`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `142569`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `142547`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `120477`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `120480`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `118809`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `118811`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `118468`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `118457`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `118468`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `118457`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `142496`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `142487`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `142566`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `142557`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `144446`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `144438`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `142848`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `142839`; diff --git a/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap index 04d291e85c..a4797620cc 100644 --- a/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `58995`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `58984`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `54258`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `54247`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59695`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `59684`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58026`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `58015`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `73831`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `73809`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `73831`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `73809`; diff --git a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap index 09f0a2c8ad..97b5bdc2e6 100644 --- a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `88044`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `88033`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `83575`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `83564`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `132898`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 1`] = `132876`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `125215`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after hard default 2`] = `125193`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `88044`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `88033`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `83575`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `83564`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `83234`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 1`] = `83223`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `83234`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after soft default 2`] = `83223`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `125212`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `125190`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `125212`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `125190`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `129963`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 1`] = `129941`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `125494`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() during soft default 2`] = `125472`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap index 97e0228cdb..1dc9e48d20 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap @@ -1,73 +1,73 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134222`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134211`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129753`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129742`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 1`] = `179834`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 1`] = `179812`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 2`] = `172151`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after hard default 2`] = `172129`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134222`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134211`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129753`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129742`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 1`] = `129412`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 1`] = `129401`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 2`] = `129412`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 2`] = `129401`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `172148`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `172126`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 2`] = `172148`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 2`] = `172126`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 1`] = `179699`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 1`] = `179677`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 2`] = `172430`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during soft default 2`] = `172408`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134425`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134414`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129956`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129945`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `180240`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 1`] = `180218`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `172557`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after hard default 2`] = `172535`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134425`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `134414`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129956`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129945`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `129615`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `129604`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `129615`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `129604`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `172554`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `172532`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `172554`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 2`] = `172532`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `180105`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 1`] = `180083`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `172836`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during soft default 2`] = `172814`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133578`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133567`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129109`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129098`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 1`] = `178546`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 1`] = `178524`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 2`] = `170863`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after hard default 2`] = `170841`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `133578`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `133567`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129109`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129098`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 1`] = `128768`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 1`] = `128757`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 2`] = `128768`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 2`] = `128757`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `170860`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `170838`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 2`] = `170860`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 2`] = `170838`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 1`] = `178411`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 1`] = `178389`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 2`] = `171142`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during soft default 2`] = `171120`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap index 2c8af6504a..f767cb5650 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap @@ -1,49 +1,49 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133645`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133634`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129176`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129165`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 1`] = `199843`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 1`] = `199810`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 2`] = `192160`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after hard default 2`] = `192127`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `182889`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `182878`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `178420`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `178409`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 1`] = `178079`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 1`] = `178068`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 2`] = `178079`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 2`] = `178068`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `192157`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `192124`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 2`] = `192157`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 2`] = `192124`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 1`] = `199708`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 1`] = `199675`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 2`] = `192439`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during soft default 2`] = `192406`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `167277`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `167266`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `162808`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `162797`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 1`] = `239107`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 1`] = `239074`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 2`] = `231424`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after hard default 2`] = `231391`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `222153`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `222142`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `217684`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `217673`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 1`] = `217343`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 1`] = `217332`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 2`] = `217343`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 2`] = `217332`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `231421`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `231388`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `231421`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `231388`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 1`] = `238972`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 1`] = `238939`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 2`] = `231703`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during soft default 2`] = `231670`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap index 8bc1fa5575..c0751869de 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `201563`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `201552`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `197094`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `197083`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 1`] = `217785`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 1`] = `217763`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 2`] = `210102`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after hard default 2`] = `210080`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `201563`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `201552`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `197094`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `197083`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `210099`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `210077`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `210099`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `210077`; diff --git a/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap index 4c876c0ecb..1827ad33b7 100644 --- a/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap @@ -1,25 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `70887`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `70876`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `66418`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `66407`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 1`] = `113943`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 1`] = `113921`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 2`] = `106260`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after hard default 2`] = `106238`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `70887`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `70876`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `66418`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `66407`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after soft default 1`] = `66077`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after soft default 1`] = `66066`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after soft default 2`] = `66077`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after soft default 2`] = `66066`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `106257`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `106235`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `106257`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during SOUND 2`] = `106235`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during soft default 1`] = `113808`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during soft default 1`] = `113786`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during soft default 2`] = `106539`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() during soft default 2`] = `106517`; diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index 89a9ca08e6..fa7f07dd1b 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12092382`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12090932`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9830758`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9829293`; exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2281990`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13617164`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13615367`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20897690`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20701409`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10991870`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10991970`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8713158`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8713193`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `6561504`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `6561514`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `15130053`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `14454943`; From 1adc381a6702074e2cf6990f14f98078b7ad91a5 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 3 Oct 2023 10:24:16 -0400 Subject: [PATCH 440/462] remove some bad merge kruft --- CHANGELOG.md | 69 ---------------------------------------------------- 1 file changed, 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 156df3212c..95af3e13a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -290,75 +290,6 @@ Across all collateral, `tryPrice()` was updated to exclude revenueHiding conside - Add EasyAuction regression test for Broker false positive (observed during USDC de-peg) - Add EasyAuction extreme tests -======= - -- `StRSRVotes` [+0 slots] - - Add `stakeAndDelegate(uint256 rsrAmount, address delegate)` function to encourage people to receive voting weight upon staking - -### Facades - -Remove `FacadeMonitor` - now redundant with `nextRecollateralizationAuction()` and `revenueOverview()` - -- `FacadeAct` - Summary: Remove unused `getActCalldata()` and add way to run revenue auctions - - - Remove `getActCalldata(..)` - - Remove `canRunRecollateralizationAuctions(..)` - - Remove `runRevenueAuctions(..)` - - Add `revenueOverview(IRevenueTrader) returns ( IERC20[] memory erc20s, bool[] memory canStart, uint256[] memory surpluses, uint256[] memory minTradeAmounts)` - - Add `nextRecollateralizationAuction(..) returns (bool canStart, IERC20 sell, IERC20 buy, uint256 sellAmount)` - - Modify all functions to work on both 3.0.0 and 2.1.0 RTokens - -- `FacadeRead` - Summary: Add new data summary views frontends may be interested in - - - Remove `basketNonce` from `redeem(.., uint48 basketNonce)` - - Add `redeemCustom(.., uint48[] memory basketNonces, uint192[] memory portions)` callstatic to simulate multi-basket redemptions - - Remove `traderBalances(..)` - - Add `balancesAcrossAllTraders(IBackingManager) returns (IERC20[] memory erc20s, uint256[] memory balances, uint256[] memory balancesNeededByBackingManager)` - -- `FacadeWrite` - Summary: More expressive and fine-grained control over the set of pausers and freezers - - - Do not automatically grant Guardian PAUSER/SHORT_FREEZER/LONG_FREEZER - - Do not automatically grant Owner PAUSER/SHORT_FREEZER/LONG_FREEZER - - Add ability to initialize with multiple pausers, short freezers, and long freezers - - Modify `setupGovernance(.., address owner, address guardian, address pauser)` -> `setupGovernance(.., GovernanceRoles calldata govRoles)` - -## Plugins - -### DutchTrade - -A cheaper, simpler, trading method. Intended to be the new dominant trading method, with GnosisTrade (batch auctions) available as a backup option. Generally speaking the batch auction length can be kept shorter than the dutch auction length. - -DutchTrade implements a four-stage, single-lot, falling price dutch auction: - -1. In the first 20% of the auction, the price falls from 1000x the best price to the best price in a geometric/exponential decay as a price manipulation defense mechanism. Bids are not expected to occur (but note: unlike the GnosisTrade batch auction, this mechanism is not resistant to _arbitrary_ price manipulation). If a bid occurs, then trading for the pair of tokens is disabled as long as the trade was started by the BackingManager. -2. Between 20% and 45%, the price falls linearly from 1.5x the best price to the best price. -3. Between 45% and 95%, the price falls linearly from the best price to the worst price. -4. Over the last 5% of the auction, the price remains constant at the worst price. - -Duration: 30 min (default) - -### Assets and Collateral - -- Add `version() return (string)` getter to pave way for separation of asset versioning and core protocol versioning -- Deprecate `claimRewards()` -- Add `lastSave()` to `RTokenAsset` -- Remove `CurveVolatileCollateral` -- Switch `CToken*Collateral` (Compound V2) to using a CTokenVault ERC20 rather than the raw cToken -- Bugfix: `lotPrice()` now begins at 100% the lastSavedPrice, instead of below 100%. It can be at 100% for up to the oracleTimeout in the worst-case. -- Bugfix: Handle oracle deprecation as indicated by the `aggregator()` being set to the zero address -- Bugfix: `AnkrStakedETHCollateral`/`CBETHCollateral`/`RethCollateral` now correctly detects soft default (note that Ankr still requires a new oracle before it can be deployed) -- Bugfix: Adjust `Curve*Collateral` and `RTokenAsset` to treat FIX_MAX correctly as +inf -- Bugfix: Continue updating cached price after collateral default (impacts all appreciating collateral) - -### Testing - -- Add generic collateral testing suite at `test/plugins/individual-collateral/collateralTests.ts` -- Add EasyAuction regression test for Broker false positive (observed during USDC de-peg) -- Add EasyAuction extreme tests - ### Documentation - Add `docs/plugin-addresses.md` as well as accompanying script for generation at `scripts/collateral-params.ts` From 9a67530411c9659ff30347d80e9f43ba4e0229bb Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Tue, 3 Oct 2023 10:31:57 -0400 Subject: [PATCH 441/462] comments and cosmetics --- contracts/p0/Broker.sol | 6 +++--- contracts/p0/mixins/TradingLib.sol | 2 +- contracts/p1/Broker.sol | 6 +++--- docs/collateral.md | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index 51fcf6ac10..d3e0da2041 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -240,7 +240,7 @@ contract BrokerP0 is ComponentP0, IBroker { ); require(dutchAuctionLength > 0, "dutch auctions not enabled"); require( - priceIsCurrent(req.sell) && priceIsCurrent(req.buy), + priceNotDecayed(req.sell) && priceNotDecayed(req.buy), "dutch auctions require live prices" ); @@ -257,8 +257,8 @@ contract BrokerP0 is ComponentP0, IBroker { return trade; } - /// @return true if the price is current, or it's the RTokenAsset - function priceIsCurrent(IAsset asset) private view returns (bool) { + /// @return true iff the price is not decayed, or it's the RTokenAsset + function priceNotDecayed(IAsset asset) private view returns (bool) { return asset.lastSave() == block.timestamp || address(asset.erc20()) == address(main.rToken()); } diff --git a/contracts/p0/mixins/TradingLib.sol b/contracts/p0/mixins/TradingLib.sol index e43c2a800d..b9dc3ca787 100644 --- a/contracts/p0/mixins/TradingLib.sol +++ b/contracts/p0/mixins/TradingLib.sol @@ -348,7 +348,7 @@ library TradingLibP0 { // (2) Lose minTradeVolume to dust (why: auctions can return tokens) // Q: Why is this precisely where we should take out minTradeVolume? - // A: Our use of isEnoughToSell always uses the low price (low, technically), + // A: Our use of isEnoughToSell always uses the low price, // so min trade volumes are always assesed based on low prices. At this point // in the calculation we have already calculated the UoA amount corresponding to // the excess token balance based on its low price, so we are already set up diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index a83603c226..2f9a222145 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -266,7 +266,7 @@ contract BrokerP1 is ComponentP1, IBroker { ); require(dutchAuctionLength > 0, "dutch auctions not enabled"); require( - priceIsCurrent(req.sell) && priceIsCurrent(req.buy), + priceNotDecayed(req.sell) && priceNotDecayed(req.buy), "dutch auctions require live prices" ); @@ -284,8 +284,8 @@ contract BrokerP1 is ComponentP1, IBroker { return trade; } - /// @return true if the price is current, or it's the RTokenAsset - function priceIsCurrent(IAsset asset) private view returns (bool) { + /// @return true iff the price is not decayed, or it's the RTokenAsset + function priceNotDecayed(IAsset asset) private view returns (bool) { return asset.lastSave() == block.timestamp || address(asset.erc20()) == address(rToken); } diff --git a/docs/collateral.md b/docs/collateral.md index c5289dd1f5..9655107da4 100644 --- a/docs/collateral.md +++ b/docs/collateral.md @@ -247,7 +247,7 @@ There is a simple ERC20 wrapper that can be easily extended at [RewardableERC20W Because it’s called at the beginning of many transactions, `refresh()` should never revert. If `refresh()` encounters a critical error, it should change the Collateral contract’s state so that `status()` becomes `DISABLED`. -To prevent `refresh()` from reverting due to overflow or other numeric errors, the base collateral plugin [Fiat Collateral](../contracts/plugins/asset/FiatCollateral.sol) has a `tryPrice()` function that encapsulates both the oracle lookup as well as any subsequent math required. This function is always executed via a try-catch in `price()`/`refresh()`. Extenders of this contract should not have to override any of these three functions, just `tryPrice()`. +To prevent `refresh()` from reverting due to overflow or other numeric errors, the base collateral plugin [Fiat Collateral](../contracts/plugins/assets/FiatCollateral.sol) has a `tryPrice()` function that encapsulates both the oracle lookup as well as any subsequent math required. This function is always executed via a try-catch in `price()`/`refresh()`. Extenders of this contract should not have to override any of these three functions, just `tryPrice()`. ### The `IFFY` status should be temporary. @@ -361,9 +361,9 @@ The difference between the upper and lower estimate should not exceed 5%, though Lower estimate must be <= upper estimate. -Should return `(0, FIX_MAX)` if pricing data is unavailable or stale. +Under no price data, the low estimate shoulddecay downwards and high estimate upwards. -Recommend decaying low estimate downwards and high estimate upwards over time. +Should return `(0, FIX_MAX)` if pricing data is _completely_ unavailable or stale. Should be gas-efficient. From 6a8a2bfa5cb5f7730203332b9d56d465aa72278d Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:30:54 -0300 Subject: [PATCH 442/462] Trust M-01 & M-02: RevenueTrader.settleTrade() (#966) --- contracts/p0/RevenueTrader.sol | 20 ++- contracts/p1/RevenueTrader.sol | 13 +- test/Revenues.test.ts | 313 +++++++++++++++++++++++++++++++++ 3 files changed, 338 insertions(+), 8 deletions(-) diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index 1d47a5e494..7a7ee84dd8 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -32,14 +32,12 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) - public - override(ITrading, TradingP0) - notTradingPausedOrFrozen - returns (ITrade trade) - { + function settleTrade(IERC20 sell) public override(ITrading, TradingP0) returns (ITrade trade) { trade = super.settleTrade(sell); - _distributeTokenToBuy(); + if ((!main.tradingPausedOrFrozen()) && _nonZeroDistribution()) { + _distributeTokenToBuy(); + } + // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } @@ -80,6 +78,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { { require(erc20s.length > 0, "empty erc20s list"); require(erc20s.length == kinds.length, "length mismatch"); + require(_nonZeroDistribution(), "zero distribution"); main.assetRegistry().refresh(); IAsset assetToBuy = main.assetRegistry().toAsset(tokenToBuy); @@ -132,4 +131,11 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { tokenToBuy.safeApprove(address(main.distributor()), bal); main.distributor().distribute(tokenToBuy, bal); } + + function _nonZeroDistribution() private view returns (bool) { + RevenueTotals memory revTotals = main.distributor().totals(); + return + (tokenToBuy == main.rsr() && revTotals.rsrTotal > 0) || + (address(tokenToBuy) == address(main.rToken()) && revTotals.rTokenTotal > 0); + } } diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 1065fb96e7..a5e65e984a 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -53,7 +53,10 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { /// @custom:interaction function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { trade = super.settleTrade(sell); // nonReentrant - _distributeTokenToBuy(); + if ((!main.tradingPausedOrFrozen()) && _nonZeroDistribution()) { + _distributeTokenToBuy(); + } + // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } @@ -107,6 +110,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { uint256 len = erc20s.length; require(len > 0, "empty erc20s list"); require(len == kinds.length, "length mismatch"); + require(_nonZeroDistribution(), "zero distribution"); // Calculate if the trade involves any RToken // Distribute tokenToBuy if supplied in ERC20s list @@ -182,6 +186,13 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { distributor.distribute(tokenToBuy, bal); } + function _nonZeroDistribution() private view returns (bool) { + RevenueTotals memory revTotals = distributor.totals(); + return + (tokenToBuy == rsr && revTotals.rsrTotal > 0) || + (address(tokenToBuy) == address(rToken) && revTotals.rTokenTotal > 0); + } + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 8c85df62d4..33905d7d78 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2126,6 +2126,319 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(furnace.address)).to.equal(0) }) + it('Should not start trades if no distribution defined', async () => { + // Check funds in Backing Manager and destinations + expect(await rsr.balanceOf(backingManager.address)).to.equal(0) + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + + // Set f = 0, avoid dropping tokens + await expect( + distributor + .connect(owner) + .setDistribution(FURNACE_DEST, { rTokenDist: bn(1), rsrDist: bn(0) }) + ) + .to.emit(distributor, 'DistributionSet') + .withArgs(FURNACE_DEST, bn(1), bn(0)) + await expect( + distributor + .connect(owner) + .setDistribution(STRSR_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) + ) + .to.emit(distributor, 'DistributionSet') + .withArgs(STRSR_DEST, bn(0), bn(0)) + + await expect( + rsrTrader.manageTokens([rsr.address], [TradeKind.BATCH_AUCTION]) + ).to.be.revertedWith('zero distribution') + + // Check funds, nothing changed + expect(await rsr.balanceOf(backingManager.address)).to.equal(0) + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + }) + + it('Should handle no distribution defined when settling trade', async () => { + // Set COMP tokens as reward + rewardAmountCOMP = bn('0.8e18') + + // COMP Rewards + await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + + // Collect revenue + // Expected values based on Prices between COMP and RSR/RToken = 1 to 1 (for simplification) + const sellAmt: BigNumber = rewardAmountCOMP.mul(60).div(100) // due to f = 60% + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + const sellAmtRToken: BigNumber = rewardAmountCOMP.sub(sellAmt) // Remainder + const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) + + await expectEvents(backingManager.claimRewards(), [ + { + contract: token3, + name: 'RewardsClaimed', + args: [compToken.address, rewardAmountCOMP], + emitted: true, + }, + { + contract: token2, + name: 'RewardsClaimed', + args: [aaveToken.address, bn(0)], + emitted: true, + }, + ]) + + // Check status of destinations at this point + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: rsrTrader, + name: 'TradeStarted', + args: [anyValue, compToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + args: [ + anyValue, + compToken.address, + rToken.address, + sellAmtRToken, + withinQuad(minBuyAmtRToken), + ], + emitted: true, + }, + ]) + + const auctionTimestamp: number = await getLatestBlockTimestamp() + + // Check auctions registered + // COMP -> RSR Auction + await expectTrade(rsrTrader, { + sell: compToken.address, + buy: rsr.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) + + // COMP -> RToken Auction + await expectTrade(rTokenTrader, { + sell: compToken.address, + buy: rToken.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('1'), + }) + + // Check funds in Market + expect(await compToken.balanceOf(gnosis.address)).to.equal(rewardAmountCOMP) + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Perform Mock Bids for RSR and RToken (addr1 has balance) + await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) + await rToken.connect(addr1).approve(gnosis.address, minBuyAmtRToken) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: minBuyAmt, + }) + await gnosis.placeBid(1, { + bidder: addr1.address, + sellAmount: sellAmtRToken, + buyAmount: minBuyAmtRToken, + }) + + // Set no distribution for StRSR + // Set f = 0, avoid dropping tokens + await expect( + distributor + .connect(owner) + .setDistribution(FURNACE_DEST, { rTokenDist: bn(1), rsrDist: bn(0) }) + ) + .to.emit(distributor, 'DistributionSet') + .withArgs(FURNACE_DEST, bn(1), bn(0)) + await expect( + distributor + .connect(owner) + .setDistribution(STRSR_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) + ) + .to.emit(distributor, 'DistributionSet') + .withArgs(STRSR_DEST, bn(0), bn(0)) + + // Close auctions + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: rsrTrader, + name: 'TradeSettled', + args: [anyValue, compToken.address, rsr.address, sellAmt, minBuyAmt], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeSettled', + args: [anyValue, compToken.address, rToken.address, sellAmtRToken, minBuyAmtRToken], + emitted: true, + }, + { + contract: rsrTrader, + name: 'TradeStarted', + emitted: false, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + emitted: false, + }, + ]) + + // Check balances + // StRSR - Still in trader, was not distributed due to zero distribution + expect(await rsr.balanceOf(rsrTrader.address)).to.equal(minBuyAmt) + expect(await rsr.balanceOf(stRSR.address)).to.equal(bn(0)) + + // Furnace - RTokens transferred to destination + expect(await rToken.balanceOf(rTokenTrader.address)).to.equal(bn(0)) + expect(await rToken.balanceOf(furnace.address)).to.closeTo( + minBuyAmtRToken, + minBuyAmtRToken.div(bn('1e15')) + ) + }) + + it('Should allow to settle trade (and not distribute) even if trading paused or frozen', async () => { + // Set COMP tokens as reward + rewardAmountCOMP = bn('0.8e18') + + // COMP Rewards + await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + + // Collect revenue + // Expected values based on Prices between COMP and RSR/RToken = 1 to 1 (for simplification) + const sellAmt: BigNumber = rewardAmountCOMP.mul(60).div(100) // due to f = 60% + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + const sellAmtRToken: BigNumber = rewardAmountCOMP.sub(sellAmt) // Remainder + const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) + + await expectEvents(backingManager.claimRewards(), [ + { + contract: token3, + name: 'RewardsClaimed', + args: [compToken.address, rewardAmountCOMP], + emitted: true, + }, + { + contract: token2, + name: 'RewardsClaimed', + args: [aaveToken.address, bn(0)], + emitted: true, + }, + ]) + + // Check status of destinations at this point + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: rsrTrader, + name: 'TradeStarted', + args: [anyValue, compToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + args: [ + anyValue, + compToken.address, + rToken.address, + sellAmtRToken, + withinQuad(minBuyAmtRToken), + ], + emitted: true, + }, + ]) + + const auctionTimestamp: number = await getLatestBlockTimestamp() + + // Check auctions registered + // COMP -> RSR Auction + await expectTrade(rsrTrader, { + sell: compToken.address, + buy: rsr.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) + + // COMP -> RToken Auction + await expectTrade(rTokenTrader, { + sell: compToken.address, + buy: rToken.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('1'), + }) + + // Check funds in Market + expect(await compToken.balanceOf(gnosis.address)).to.equal(rewardAmountCOMP) + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Perform Mock Bids for RSR and RToken (addr1 has balance) + await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) + await rToken.connect(addr1).approve(gnosis.address, minBuyAmtRToken) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: minBuyAmt, + }) + await gnosis.placeBid(1, { + bidder: addr1.address, + sellAmount: sellAmtRToken, + buyAmount: minBuyAmtRToken, + }) + + // Pause Trading + await main.connect(owner).pauseTrading() + + // Close auctions + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: rsrTrader, + name: 'TradeSettled', + args: [anyValue, compToken.address, rsr.address, sellAmt, minBuyAmt], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeSettled', + args: [anyValue, compToken.address, rToken.address, sellAmtRToken, minBuyAmtRToken], + emitted: true, + }, + { + contract: rsrTrader, + name: 'TradeStarted', + emitted: false, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + emitted: false, + }, + ]) + + // Distribution did not occurr, funds are in Traders + expect(await rsr.balanceOf(rsrTrader.address)).to.equal(minBuyAmt) + expect(await rToken.balanceOf(rTokenTrader.address)).to.equal(minBuyAmtRToken) + + expect(await rsr.balanceOf(stRSR.address)).to.equal(bn(0)) + expect(await rToken.balanceOf(furnace.address)).to.equal(bn(0)) + }) + it('Should trade even if price for buy token = 0', async () => { // Set AAVE tokens as reward rewardAmountAAVE = bn('1e18') From ae598c6876a47c53d54dd7edb9c93d832770c1e2 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:40:56 -0300 Subject: [PATCH 443/462] Trust M-11: Fix storage slots (#968) --- contracts/p1/Broker.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 2f9a222145..0c233a8020 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -294,5 +294,5 @@ contract BrokerP1 is ComponentP1, IBroker { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[42] private __gap; + uint256[41] private __gap; } From ce38d3bd9bc16cf06f8064038355752a2c8a53e8 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 9 Oct 2023 15:28:24 -0300 Subject: [PATCH 444/462] Revert "Trust M-01 & M-02: RevenueTrader.settleTrade() (#966)" This reverts commit 6a8a2bfa5cb5f7730203332b9d56d465aa72278d. --- contracts/p0/RevenueTrader.sol | 20 +-- contracts/p1/RevenueTrader.sol | 13 +- test/Revenues.test.ts | 313 --------------------------------- 3 files changed, 8 insertions(+), 338 deletions(-) diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index 7a7ee84dd8..1d47a5e494 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -32,12 +32,14 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) public override(ITrading, TradingP0) returns (ITrade trade) { + function settleTrade(IERC20 sell) + public + override(ITrading, TradingP0) + notTradingPausedOrFrozen + returns (ITrade trade) + { trade = super.settleTrade(sell); - if ((!main.tradingPausedOrFrozen()) && _nonZeroDistribution()) { - _distributeTokenToBuy(); - } - + _distributeTokenToBuy(); // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } @@ -78,7 +80,6 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { { require(erc20s.length > 0, "empty erc20s list"); require(erc20s.length == kinds.length, "length mismatch"); - require(_nonZeroDistribution(), "zero distribution"); main.assetRegistry().refresh(); IAsset assetToBuy = main.assetRegistry().toAsset(tokenToBuy); @@ -131,11 +132,4 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { tokenToBuy.safeApprove(address(main.distributor()), bal); main.distributor().distribute(tokenToBuy, bal); } - - function _nonZeroDistribution() private view returns (bool) { - RevenueTotals memory revTotals = main.distributor().totals(); - return - (tokenToBuy == main.rsr() && revTotals.rsrTotal > 0) || - (address(tokenToBuy) == address(main.rToken()) && revTotals.rTokenTotal > 0); - } } diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index a5e65e984a..1065fb96e7 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -53,10 +53,7 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { /// @custom:interaction function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { trade = super.settleTrade(sell); // nonReentrant - if ((!main.tradingPausedOrFrozen()) && _nonZeroDistribution()) { - _distributeTokenToBuy(); - } - + _distributeTokenToBuy(); // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } @@ -110,7 +107,6 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { uint256 len = erc20s.length; require(len > 0, "empty erc20s list"); require(len == kinds.length, "length mismatch"); - require(_nonZeroDistribution(), "zero distribution"); // Calculate if the trade involves any RToken // Distribute tokenToBuy if supplied in ERC20s list @@ -186,13 +182,6 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { distributor.distribute(tokenToBuy, bal); } - function _nonZeroDistribution() private view returns (bool) { - RevenueTotals memory revTotals = distributor.totals(); - return - (tokenToBuy == rsr && revTotals.rsrTotal > 0) || - (address(tokenToBuy) == address(rToken) && revTotals.rTokenTotal > 0); - } - /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 33905d7d78..8c85df62d4 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2126,319 +2126,6 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(furnace.address)).to.equal(0) }) - it('Should not start trades if no distribution defined', async () => { - // Check funds in Backing Manager and destinations - expect(await rsr.balanceOf(backingManager.address)).to.equal(0) - expect(await rsr.balanceOf(stRSR.address)).to.equal(0) - expect(await rToken.balanceOf(furnace.address)).to.equal(0) - - // Set f = 0, avoid dropping tokens - await expect( - distributor - .connect(owner) - .setDistribution(FURNACE_DEST, { rTokenDist: bn(1), rsrDist: bn(0) }) - ) - .to.emit(distributor, 'DistributionSet') - .withArgs(FURNACE_DEST, bn(1), bn(0)) - await expect( - distributor - .connect(owner) - .setDistribution(STRSR_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) - ) - .to.emit(distributor, 'DistributionSet') - .withArgs(STRSR_DEST, bn(0), bn(0)) - - await expect( - rsrTrader.manageTokens([rsr.address], [TradeKind.BATCH_AUCTION]) - ).to.be.revertedWith('zero distribution') - - // Check funds, nothing changed - expect(await rsr.balanceOf(backingManager.address)).to.equal(0) - expect(await rsr.balanceOf(stRSR.address)).to.equal(0) - expect(await rToken.balanceOf(furnace.address)).to.equal(0) - }) - - it('Should handle no distribution defined when settling trade', async () => { - // Set COMP tokens as reward - rewardAmountCOMP = bn('0.8e18') - - // COMP Rewards - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) - - // Collect revenue - // Expected values based on Prices between COMP and RSR/RToken = 1 to 1 (for simplification) - const sellAmt: BigNumber = rewardAmountCOMP.mul(60).div(100) // due to f = 60% - const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) - - const sellAmtRToken: BigNumber = rewardAmountCOMP.sub(sellAmt) // Remainder - const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) - - await expectEvents(backingManager.claimRewards(), [ - { - contract: token3, - name: 'RewardsClaimed', - args: [compToken.address, rewardAmountCOMP], - emitted: true, - }, - { - contract: token2, - name: 'RewardsClaimed', - args: [aaveToken.address, bn(0)], - emitted: true, - }, - ]) - - // Check status of destinations at this point - expect(await rsr.balanceOf(stRSR.address)).to.equal(0) - expect(await rToken.balanceOf(furnace.address)).to.equal(0) - - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: rsrTrader, - name: 'TradeStarted', - args: [anyValue, compToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], - emitted: true, - }, - { - contract: rTokenTrader, - name: 'TradeStarted', - args: [ - anyValue, - compToken.address, - rToken.address, - sellAmtRToken, - withinQuad(minBuyAmtRToken), - ], - emitted: true, - }, - ]) - - const auctionTimestamp: number = await getLatestBlockTimestamp() - - // Check auctions registered - // COMP -> RSR Auction - await expectTrade(rsrTrader, { - sell: compToken.address, - buy: rsr.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), - }) - - // COMP -> RToken Auction - await expectTrade(rTokenTrader, { - sell: compToken.address, - buy: rToken.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('1'), - }) - - // Check funds in Market - expect(await compToken.balanceOf(gnosis.address)).to.equal(rewardAmountCOMP) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // Perform Mock Bids for RSR and RToken (addr1 has balance) - await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) - await rToken.connect(addr1).approve(gnosis.address, minBuyAmtRToken) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: minBuyAmt, - }) - await gnosis.placeBid(1, { - bidder: addr1.address, - sellAmount: sellAmtRToken, - buyAmount: minBuyAmtRToken, - }) - - // Set no distribution for StRSR - // Set f = 0, avoid dropping tokens - await expect( - distributor - .connect(owner) - .setDistribution(FURNACE_DEST, { rTokenDist: bn(1), rsrDist: bn(0) }) - ) - .to.emit(distributor, 'DistributionSet') - .withArgs(FURNACE_DEST, bn(1), bn(0)) - await expect( - distributor - .connect(owner) - .setDistribution(STRSR_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) - ) - .to.emit(distributor, 'DistributionSet') - .withArgs(STRSR_DEST, bn(0), bn(0)) - - // Close auctions - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: rsrTrader, - name: 'TradeSettled', - args: [anyValue, compToken.address, rsr.address, sellAmt, minBuyAmt], - emitted: true, - }, - { - contract: rTokenTrader, - name: 'TradeSettled', - args: [anyValue, compToken.address, rToken.address, sellAmtRToken, minBuyAmtRToken], - emitted: true, - }, - { - contract: rsrTrader, - name: 'TradeStarted', - emitted: false, - }, - { - contract: rTokenTrader, - name: 'TradeStarted', - emitted: false, - }, - ]) - - // Check balances - // StRSR - Still in trader, was not distributed due to zero distribution - expect(await rsr.balanceOf(rsrTrader.address)).to.equal(minBuyAmt) - expect(await rsr.balanceOf(stRSR.address)).to.equal(bn(0)) - - // Furnace - RTokens transferred to destination - expect(await rToken.balanceOf(rTokenTrader.address)).to.equal(bn(0)) - expect(await rToken.balanceOf(furnace.address)).to.closeTo( - minBuyAmtRToken, - minBuyAmtRToken.div(bn('1e15')) - ) - }) - - it('Should allow to settle trade (and not distribute) even if trading paused or frozen', async () => { - // Set COMP tokens as reward - rewardAmountCOMP = bn('0.8e18') - - // COMP Rewards - await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) - - // Collect revenue - // Expected values based on Prices between COMP and RSR/RToken = 1 to 1 (for simplification) - const sellAmt: BigNumber = rewardAmountCOMP.mul(60).div(100) // due to f = 60% - const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) - - const sellAmtRToken: BigNumber = rewardAmountCOMP.sub(sellAmt) // Remainder - const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) - - await expectEvents(backingManager.claimRewards(), [ - { - contract: token3, - name: 'RewardsClaimed', - args: [compToken.address, rewardAmountCOMP], - emitted: true, - }, - { - contract: token2, - name: 'RewardsClaimed', - args: [aaveToken.address, bn(0)], - emitted: true, - }, - ]) - - // Check status of destinations at this point - expect(await rsr.balanceOf(stRSR.address)).to.equal(0) - expect(await rToken.balanceOf(furnace.address)).to.equal(0) - - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: rsrTrader, - name: 'TradeStarted', - args: [anyValue, compToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], - emitted: true, - }, - { - contract: rTokenTrader, - name: 'TradeStarted', - args: [ - anyValue, - compToken.address, - rToken.address, - sellAmtRToken, - withinQuad(minBuyAmtRToken), - ], - emitted: true, - }, - ]) - - const auctionTimestamp: number = await getLatestBlockTimestamp() - - // Check auctions registered - // COMP -> RSR Auction - await expectTrade(rsrTrader, { - sell: compToken.address, - buy: rsr.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('0'), - }) - - // COMP -> RToken Auction - await expectTrade(rTokenTrader, { - sell: compToken.address, - buy: rToken.address, - endTime: auctionTimestamp + Number(config.batchAuctionLength), - externalId: bn('1'), - }) - - // Check funds in Market - expect(await compToken.balanceOf(gnosis.address)).to.equal(rewardAmountCOMP) - - // Advance time till auction ended - await advanceTime(config.batchAuctionLength.add(100).toString()) - - // Perform Mock Bids for RSR and RToken (addr1 has balance) - await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) - await rToken.connect(addr1).approve(gnosis.address, minBuyAmtRToken) - await gnosis.placeBid(0, { - bidder: addr1.address, - sellAmount: sellAmt, - buyAmount: minBuyAmt, - }) - await gnosis.placeBid(1, { - bidder: addr1.address, - sellAmount: sellAmtRToken, - buyAmount: minBuyAmtRToken, - }) - - // Pause Trading - await main.connect(owner).pauseTrading() - - // Close auctions - await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ - { - contract: rsrTrader, - name: 'TradeSettled', - args: [anyValue, compToken.address, rsr.address, sellAmt, minBuyAmt], - emitted: true, - }, - { - contract: rTokenTrader, - name: 'TradeSettled', - args: [anyValue, compToken.address, rToken.address, sellAmtRToken, minBuyAmtRToken], - emitted: true, - }, - { - contract: rsrTrader, - name: 'TradeStarted', - emitted: false, - }, - { - contract: rTokenTrader, - name: 'TradeStarted', - emitted: false, - }, - ]) - - // Distribution did not occurr, funds are in Traders - expect(await rsr.balanceOf(rsrTrader.address)).to.equal(minBuyAmt) - expect(await rToken.balanceOf(rTokenTrader.address)).to.equal(minBuyAmtRToken) - - expect(await rsr.balanceOf(stRSR.address)).to.equal(bn(0)) - expect(await rToken.balanceOf(furnace.address)).to.equal(bn(0)) - }) - it('Should trade even if price for buy token = 0', async () => { // Set AAVE tokens as reward rewardAmountAAVE = bn('1e18') From ef0a1c3f15c39ef05e20c574f6bdce7ed64c60f7 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:26:14 -0300 Subject: [PATCH 445/462] Trust M-01 & M-02: RevenueTrader.settleTrade() (#969) Co-authored-by: Taylor Brent --- contracts/p0/RevenueTrader.sol | 20 ++- contracts/p1/RevenueTrader.sol | 11 +- test/Revenues.test.ts | 313 +++++++++++++++++++++++++++++++++ 3 files changed, 336 insertions(+), 8 deletions(-) diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index 1d47a5e494..a1186f2924 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -32,14 +32,12 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { /// @param sell The sell token in the trade /// @return trade The ITrade contract settled /// @custom:interaction - function settleTrade(IERC20 sell) - public - override(ITrading, TradingP0) - notTradingPausedOrFrozen - returns (ITrade trade) - { + function settleTrade(IERC20 sell) public override(ITrading, TradingP0) returns (ITrade trade) { trade = super.settleTrade(sell); - _distributeTokenToBuy(); + try this.distributeTokenToBuy() {} catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + } // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } @@ -80,6 +78,14 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { { require(erc20s.length > 0, "empty erc20s list"); require(erc20s.length == kinds.length, "length mismatch"); + + RevenueTotals memory revTotals = main.distributor().totals(); + require( + (tokenToBuy == main.rsr() && revTotals.rsrTotal > 0) || + (address(tokenToBuy) == address(main.rToken()) && revTotals.rTokenTotal > 0), + "zero distribution" + ); + main.assetRegistry().refresh(); IAsset assetToBuy = main.assetRegistry().toAsset(tokenToBuy); diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 1065fb96e7..577babb963 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -53,7 +53,10 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { /// @custom:interaction function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { trade = super.settleTrade(sell); // nonReentrant - _distributeTokenToBuy(); + try this.distributeTokenToBuy() {} catch (bytes memory errData) { + // see: docs/solidity-style.md#Catching-Empty-Data + if (errData.length == 0) revert(); // solhint-disable-line reason-string + } // unlike BackingManager, do _not_ chain trades; b2b trades of the same token are unlikely } @@ -107,6 +110,12 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { uint256 len = erc20s.length; require(len > 0, "empty erc20s list"); require(len == kinds.length, "length mismatch"); + RevenueTotals memory revTotals = distributor.totals(); + require( + (tokenToBuy == rsr && revTotals.rsrTotal > 0) || + (address(tokenToBuy) == address(rToken) && revTotals.rTokenTotal > 0), + "zero distribution" + ); // Calculate if the trade involves any RToken // Distribute tokenToBuy if supplied in ERC20s list diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 8c85df62d4..33905d7d78 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2126,6 +2126,319 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rToken.balanceOf(furnace.address)).to.equal(0) }) + it('Should not start trades if no distribution defined', async () => { + // Check funds in Backing Manager and destinations + expect(await rsr.balanceOf(backingManager.address)).to.equal(0) + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + + // Set f = 0, avoid dropping tokens + await expect( + distributor + .connect(owner) + .setDistribution(FURNACE_DEST, { rTokenDist: bn(1), rsrDist: bn(0) }) + ) + .to.emit(distributor, 'DistributionSet') + .withArgs(FURNACE_DEST, bn(1), bn(0)) + await expect( + distributor + .connect(owner) + .setDistribution(STRSR_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) + ) + .to.emit(distributor, 'DistributionSet') + .withArgs(STRSR_DEST, bn(0), bn(0)) + + await expect( + rsrTrader.manageTokens([rsr.address], [TradeKind.BATCH_AUCTION]) + ).to.be.revertedWith('zero distribution') + + // Check funds, nothing changed + expect(await rsr.balanceOf(backingManager.address)).to.equal(0) + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + }) + + it('Should handle no distribution defined when settling trade', async () => { + // Set COMP tokens as reward + rewardAmountCOMP = bn('0.8e18') + + // COMP Rewards + await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + + // Collect revenue + // Expected values based on Prices between COMP and RSR/RToken = 1 to 1 (for simplification) + const sellAmt: BigNumber = rewardAmountCOMP.mul(60).div(100) // due to f = 60% + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + const sellAmtRToken: BigNumber = rewardAmountCOMP.sub(sellAmt) // Remainder + const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) + + await expectEvents(backingManager.claimRewards(), [ + { + contract: token3, + name: 'RewardsClaimed', + args: [compToken.address, rewardAmountCOMP], + emitted: true, + }, + { + contract: token2, + name: 'RewardsClaimed', + args: [aaveToken.address, bn(0)], + emitted: true, + }, + ]) + + // Check status of destinations at this point + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: rsrTrader, + name: 'TradeStarted', + args: [anyValue, compToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + args: [ + anyValue, + compToken.address, + rToken.address, + sellAmtRToken, + withinQuad(minBuyAmtRToken), + ], + emitted: true, + }, + ]) + + const auctionTimestamp: number = await getLatestBlockTimestamp() + + // Check auctions registered + // COMP -> RSR Auction + await expectTrade(rsrTrader, { + sell: compToken.address, + buy: rsr.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) + + // COMP -> RToken Auction + await expectTrade(rTokenTrader, { + sell: compToken.address, + buy: rToken.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('1'), + }) + + // Check funds in Market + expect(await compToken.balanceOf(gnosis.address)).to.equal(rewardAmountCOMP) + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Perform Mock Bids for RSR and RToken (addr1 has balance) + await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) + await rToken.connect(addr1).approve(gnosis.address, minBuyAmtRToken) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: minBuyAmt, + }) + await gnosis.placeBid(1, { + bidder: addr1.address, + sellAmount: sellAmtRToken, + buyAmount: minBuyAmtRToken, + }) + + // Set no distribution for StRSR + // Set f = 0, avoid dropping tokens + await expect( + distributor + .connect(owner) + .setDistribution(FURNACE_DEST, { rTokenDist: bn(1), rsrDist: bn(0) }) + ) + .to.emit(distributor, 'DistributionSet') + .withArgs(FURNACE_DEST, bn(1), bn(0)) + await expect( + distributor + .connect(owner) + .setDistribution(STRSR_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) + ) + .to.emit(distributor, 'DistributionSet') + .withArgs(STRSR_DEST, bn(0), bn(0)) + + // Close auctions + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: rsrTrader, + name: 'TradeSettled', + args: [anyValue, compToken.address, rsr.address, sellAmt, minBuyAmt], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeSettled', + args: [anyValue, compToken.address, rToken.address, sellAmtRToken, minBuyAmtRToken], + emitted: true, + }, + { + contract: rsrTrader, + name: 'TradeStarted', + emitted: false, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + emitted: false, + }, + ]) + + // Check balances + // StRSR - Still in trader, was not distributed due to zero distribution + expect(await rsr.balanceOf(rsrTrader.address)).to.equal(minBuyAmt) + expect(await rsr.balanceOf(stRSR.address)).to.equal(bn(0)) + + // Furnace - RTokens transferred to destination + expect(await rToken.balanceOf(rTokenTrader.address)).to.equal(bn(0)) + expect(await rToken.balanceOf(furnace.address)).to.closeTo( + minBuyAmtRToken, + minBuyAmtRToken.div(bn('1e15')) + ) + }) + + it('Should allow to settle trade (and not distribute) even if trading paused or frozen', async () => { + // Set COMP tokens as reward + rewardAmountCOMP = bn('0.8e18') + + // COMP Rewards + await compoundMock.setRewards(backingManager.address, rewardAmountCOMP) + + // Collect revenue + // Expected values based on Prices between COMP and RSR/RToken = 1 to 1 (for simplification) + const sellAmt: BigNumber = rewardAmountCOMP.mul(60).div(100) // due to f = 60% + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + const sellAmtRToken: BigNumber = rewardAmountCOMP.sub(sellAmt) // Remainder + const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) + + await expectEvents(backingManager.claimRewards(), [ + { + contract: token3, + name: 'RewardsClaimed', + args: [compToken.address, rewardAmountCOMP], + emitted: true, + }, + { + contract: token2, + name: 'RewardsClaimed', + args: [aaveToken.address, bn(0)], + emitted: true, + }, + ]) + + // Check status of destinations at this point + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: rsrTrader, + name: 'TradeStarted', + args: [anyValue, compToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + args: [ + anyValue, + compToken.address, + rToken.address, + sellAmtRToken, + withinQuad(minBuyAmtRToken), + ], + emitted: true, + }, + ]) + + const auctionTimestamp: number = await getLatestBlockTimestamp() + + // Check auctions registered + // COMP -> RSR Auction + await expectTrade(rsrTrader, { + sell: compToken.address, + buy: rsr.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('0'), + }) + + // COMP -> RToken Auction + await expectTrade(rTokenTrader, { + sell: compToken.address, + buy: rToken.address, + endTime: auctionTimestamp + Number(config.batchAuctionLength), + externalId: bn('1'), + }) + + // Check funds in Market + expect(await compToken.balanceOf(gnosis.address)).to.equal(rewardAmountCOMP) + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Perform Mock Bids for RSR and RToken (addr1 has balance) + await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) + await rToken.connect(addr1).approve(gnosis.address, minBuyAmtRToken) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: minBuyAmt, + }) + await gnosis.placeBid(1, { + bidder: addr1.address, + sellAmount: sellAmtRToken, + buyAmount: minBuyAmtRToken, + }) + + // Pause Trading + await main.connect(owner).pauseTrading() + + // Close auctions + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: rsrTrader, + name: 'TradeSettled', + args: [anyValue, compToken.address, rsr.address, sellAmt, minBuyAmt], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeSettled', + args: [anyValue, compToken.address, rToken.address, sellAmtRToken, minBuyAmtRToken], + emitted: true, + }, + { + contract: rsrTrader, + name: 'TradeStarted', + emitted: false, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + emitted: false, + }, + ]) + + // Distribution did not occurr, funds are in Traders + expect(await rsr.balanceOf(rsrTrader.address)).to.equal(minBuyAmt) + expect(await rToken.balanceOf(rTokenTrader.address)).to.equal(minBuyAmtRToken) + + expect(await rsr.balanceOf(stRSR.address)).to.equal(bn(0)) + expect(await rToken.balanceOf(furnace.address)).to.equal(bn(0)) + }) + it('Should trade even if price for buy token = 0', async () => { // Set AAVE tokens as reward rewardAmountAAVE = bn('1e18') From 535dbc3f28d307817088374b1145cb92a5e08886 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:47:50 -0300 Subject: [PATCH 446/462] TRUST M-06: Keep buffer when minting rtoken revenue (#972) --- contracts/p0/BackingManager.sol | 11 ++-- contracts/p1/BackingManager.sol | 9 +-- test/Revenues.test.ts | 100 ++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 8 deletions(-) diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index c22df732c7..18e2427b8f 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -170,12 +170,15 @@ contract BackingManagerP0 is TradingP0, IBackingManager { // Mint revenue RToken // Keep backingBuffer worth of collateral before recognizing revenue - uint192 needed = main.rToken().basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); // {BU} - if (basketsHeld.bottom.gt(needed)) { - main.rToken().mint(basketsHeld.bottom.minus(needed)); - needed = main.rToken().basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); // keep buffer + { + uint192 baskets = (basketsHeld.bottom.div(FIX_ONE + backingBuffer)); + if (baskets > main.rToken().basketsNeeded()) { + main.rToken().mint(baskets - main.rToken().basketsNeeded()); + } } + uint192 needed = main.rToken().basketsNeeded().mul(FIX_ONE.plus(backingBuffer)); // {BU} + // Handout excess assets above what is needed, including any newly minted RToken RevenueTotals memory totals = main.distributor().totals(); for (uint256 i = 0; i < erc20s.length; i++) { diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index a64ff3f412..edb22151d2 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -220,12 +220,13 @@ contract BackingManagerP1 is TradingP1, IBackingManager { // Mint revenue RToken // Keep backingBuffer worth of collateral before recognizing revenue - uint192 needed = rToken.basketsNeeded().mul(FIX_ONE + backingBuffer); // {BU} - if (basketsHeld.bottom > needed) { - rToken.mint(basketsHeld.bottom - needed); - needed = rToken.basketsNeeded().mul(FIX_ONE + backingBuffer); // keep buffer + uint192 baskets = (basketsHeld.bottom.div(FIX_ONE + backingBuffer)); + if (baskets > rToken.basketsNeeded()) { + rToken.mint(baskets - rToken.basketsNeeded()); } + uint192 needed = rToken.basketsNeeded().mul(FIX_ONE + backingBuffer); // {BU} + // At this point, even though basketsNeeded may have changed, we are: // - We're fully collateralized // - The BU exchange rate {BU/rTok} did not decrease diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 33905d7d78..b8526f1395 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -4172,6 +4172,106 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await token2.balanceOf(rsrTrader.address)).to.equal(0) expect(await token2.balanceOf(rTokenTrader.address)).to.equal(0) }) + + it('Should handle backingBuffer when minting RTokens from collateral appreciation', async () => { + // Set distribution for RToken only (f=0) + await distributor + .connect(owner) + .setDistribution(FURNACE_DEST, { rTokenDist: bn(1), rsrDist: bn(0) }) + + await distributor + .connect(owner) + .setDistribution(STRSR_DEST, { rTokenDist: bn(0), rsrDist: bn(0) }) + + // Set Backing buffer + const backingBuffer = fp('0.05') + await backingManager.connect(owner).setBackingBuffer(backingBuffer) + + // Issue additional RTokens + const newIssueAmount = bn('900e18') + await rToken.connect(addr1).issue(newIssueAmount) + + // Check Price and Assets value + const totalIssuedAmount = issueAmount.add(newIssueAmount) + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + totalIssuedAmount + ) + expect(await rToken.totalSupply()).to.equal(totalIssuedAmount) + + // Change redemption rate for AToken and CToken to double + await token2.setExchangeRate(fp('1.10')) + await token3.setExchangeRate(fp('1.10')) + await collateral2.refresh() + await collateral3.refresh() + + // Check Price (unchanged) and Assets value (now 10% higher) + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + totalIssuedAmount.mul(110).div(100) + ) + expect(await rToken.totalSupply()).to.equal(totalIssuedAmount) + + // Check status of destinations at this point + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(rsrTrader.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + + // Set expected minting, based on f = 0.6 + const excessRevenue = totalIssuedAmount + .mul(110) + .div(100) + .mul(BN_SCALE_FACTOR) + .div(fp('1').add(backingBuffer)) + .sub(await rToken.basketsNeeded()) + + // Set expected auction values + const expectedToFurnace = excessRevenue + const currentTotalSupply: BigNumber = await rToken.totalSupply() + const newTotalSupply: BigNumber = currentTotalSupply.add(excessRevenue) + + // Collect revenue and mint new tokens + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: rToken, + name: 'Transfer', + args: [ZERO_ADDRESS, backingManager.address, withinQuad(excessRevenue)], + emitted: true, + }, + { + contract: rsrTrader, + name: 'TradeStarted', + emitted: false, + }, + ]) + + // Check Price (unchanged) and Assets value - Supply has increased 10% + await expectRTokenPrice(rTokenAsset.address, fp('1'), ORACLE_ERROR) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + totalIssuedAmount.mul(110).div(100) + ) + expect(await rToken.totalSupply()).to.be.closeTo( + newTotalSupply, + newTotalSupply.mul(5).div(1000) + ) // within 0.5% + + // Check destinations after newly minted tokens + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(rsrTrader.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.be.closeTo( + expectedToFurnace, + expectedToFurnace.mul(5).div(1000) + ) + + // Check Price and Assets value - RToken price increases due to melting + const updatedRTokenPrice: BigNumber = newTotalSupply + .mul(BN_SCALE_FACTOR) + .div(await rToken.totalSupply()) + await expectRTokenPrice(rTokenAsset.address, updatedRTokenPrice, ORACLE_ERROR) + expect(await facadeTest.callStatic.totalAssetValue(rToken.address)).to.equal( + totalIssuedAmount.mul(110).div(100) + ) + }) }) context('With simple basket of ATokens and CTokens: no issued RTokens', function () { From ab79276de8f10727e83ee175ea19ed7587fda334 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:48:33 -0300 Subject: [PATCH 447/462] TRUST Recommendation 1: reward and underlying checks in RewardERC20 wrappers (#976) --- .../assets/erc20/RewardableERC20Wrapper.sol | 4 ++++ .../assets/erc20/RewardableERC4626Vault.sol | 4 +++- test/plugins/RewardableERC20.test.ts | 23 ++++++++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol b/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol index e2a4ec927f..6ae34a21a8 100644 --- a/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol +++ b/contracts/plugins/assets/erc20/RewardableERC20Wrapper.sol @@ -30,6 +30,10 @@ abstract contract RewardableERC20Wrapper is RewardableERC20 { string memory _symbol, IERC20 _rewardToken ) ERC20(_name, _symbol) RewardableERC20(_rewardToken, _underlying.decimals()) { + require( + address(_rewardToken) != address(_underlying), + "reward and underlying cannot match" + ); underlying = _underlying; underlyingDecimals = _underlying.decimals(); } diff --git a/contracts/plugins/assets/erc20/RewardableERC4626Vault.sol b/contracts/plugins/assets/erc20/RewardableERC4626Vault.sol index 284f717c2e..3966e66ea9 100644 --- a/contracts/plugins/assets/erc20/RewardableERC4626Vault.sol +++ b/contracts/plugins/assets/erc20/RewardableERC4626Vault.sol @@ -27,7 +27,9 @@ abstract contract RewardableERC4626Vault is ERC4626, RewardableERC20 { ) ERC4626(_asset, _name, _symbol) RewardableERC20(_rewardToken, _asset.decimals() + _decimalsOffset()) - {} + { + require(address(_rewardToken) != address(_asset), "reward and asset cannot match"); + } // solhint-enable no-empty-blocks diff --git a/test/plugins/RewardableERC20.test.ts b/test/plugins/RewardableERC20.test.ts index abc94deb66..55d71e5698 100644 --- a/test/plugins/RewardableERC20.test.ts +++ b/test/plugins/RewardableERC20.test.ts @@ -24,6 +24,7 @@ interface RewardableERC20Fixture { rewardableVault: RewardableERC4626VaultTest | RewardableERC20WrapperTest rewardableAsset: ERC20MockRewarding rewardToken: ERC20MockDecimals + rewardableVaultFactory: ContractFactory } // 18 cases: test two wrappers with 2 combinations of decimals [6, 8, 18] @@ -76,6 +77,7 @@ for (const wrapperName of wrapperNames) { rewardableVault, rewardableAsset, rewardToken, + rewardableVaultFactory, } } return fixture @@ -123,6 +125,7 @@ for (const wrapperName of wrapperNames) { let rewardableVault: RewardableERC20WrapperTest | RewardableERC4626VaultTest let rewardableAsset: ERC20MockRewarding let rewardToken: ERC20MockDecimals + let rewardableVaultFactory: ContractFactory // Main let alice: Wallet @@ -141,7 +144,8 @@ for (const wrapperName of wrapperNames) { beforeEach(async () => { // Deploy fixture - ;({ rewardableVault, rewardableAsset, rewardToken } = await loadFixture(fixture)) + ;({ rewardableVault, rewardableAsset, rewardToken, rewardableVaultFactory } = + await loadFixture(fixture)) await rewardableAsset.mint(alice.address, initBalance) await rewardableAsset.connect(alice).approve(rewardableVault.address, initBalance) @@ -223,6 +227,23 @@ for (const wrapperName of wrapperNames) { ) }) + it('checks reward and underlying token are not the same', async () => { + const errorMsg = + wrapperName == Wrapper.ERC4626 + ? 'reward and asset cannot match' + : 'reward and underlying cannot match' + + // Attempt to deploy with same reward and underlying + await expect( + rewardableVaultFactory.deploy( + rewardableAsset.address, + 'Rewarding Test Asset Vault', + 'vrewardTEST', + rewardableAsset.address + ) + ).to.be.revertedWith(errorMsg) + }) + it('1 wei supply', async () => { await rewardableVault.connect(alice).deposit('1', alice.address) expect(await rewardableVault.rewardsPerShare()).to.equal(bn(0)) From 2de6b0d2bb75c4ccd30bfa0128b4d932b91a3b5a Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Tue, 17 Oct 2023 10:31:17 -0300 Subject: [PATCH 448/462] fix lint in Revenue traders --- contracts/p0/RevenueTrader.sol | 2 ++ contracts/p1/RevenueTrader.sol | 2 ++ 2 files changed, 4 insertions(+) diff --git a/contracts/p0/RevenueTrader.sol b/contracts/p0/RevenueTrader.sol index a1186f2924..21fa3994ab 100644 --- a/contracts/p0/RevenueTrader.sol +++ b/contracts/p0/RevenueTrader.sol @@ -34,6 +34,8 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader { /// @custom:interaction function settleTrade(IERC20 sell) public override(ITrading, TradingP0) returns (ITrade trade) { trade = super.settleTrade(sell); + + // solhint-disable-next-line no-empty-blocks try this.distributeTokenToBuy() {} catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index 577babb963..bda4be0793 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -53,6 +53,8 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { /// @custom:interaction function settleTrade(IERC20 sell) public override(ITrading, TradingP1) returns (ITrade trade) { trade = super.settleTrade(sell); // nonReentrant + + // solhint-disable-next-line no-empty-blocks try this.distributeTokenToBuy() {} catch (bytes memory errData) { // see: docs/solidity-style.md#Catching-Empty-Data if (errData.length == 0) revert(); // solhint-disable-line reason-string From 29c0f4c34f3fb84fb096d870c2048197348c5969 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:38:09 -0300 Subject: [PATCH 449/462] TRUST QA-2: DefaultThreshold check for Non-Self referential (#980) --- .../plugins/assets/EURFiatCollateral.sol | 2 + contracts/plugins/assets/FiatCollateral.sol | 4 + contracts/plugins/assets/L2LSDCollateral.sol | 1 + .../plugins/assets/NonFiatCollateral.sol | 2 + .../assets/aave-v3/AaveV3FiatCollateral.sol | 4 +- .../assets/aave/ATokenFiatCollateral.sol | 4 +- .../assets/ankr/AnkrStakedEthCollateral.sol | 1 + .../plugins/assets/cbeth/CBETHCollateral.sol | 1 + .../assets/cbeth/CBETHCollateralL2.sol | 1 + .../compoundv2/CTokenFiatCollateral.sol | 2 + .../compoundv2/CTokenNonFiatCollateral.sol | 1 + .../assets/compoundv3/CTokenV3Collateral.sol | 1 + .../plugins/assets/dsr/SDaiCollateral.sol | 1 + .../assets/frax-eth/SFraxEthCollateral.sol | 4 +- .../assets/lido/LidoStakedEthCollateral.sol | 2 + .../morpho-aave/MorphoFiatCollateral.sol | 1 + .../assets/rocket-eth/RethCollateral.sol | 1 + .../stargate/StargatePoolFiatCollateral.sol | 1 + test/plugins/Collateral.test.ts | 99 +++++++++++++++++++ .../aave-v3/AaveV3FiatCollateral.test.ts | 1 + .../aave/ATokenFiatCollateral.test.ts | 20 +++- .../ankr/AnkrEthCollateralTestSuite.test.ts | 1 + .../cbeth/CBETHCollateral.test.ts | 1 + .../cbeth/CBETHCollateralL2.test.ts | 1 + .../individual-collateral/collateralTests.ts | 7 ++ .../compoundv2/CTokenFiatCollateral.test.ts | 18 ++++ .../compoundv3/CometTestSuite.test.ts | 1 + .../dsr/SDaiCollateralTestSuite.test.ts | 1 + .../flux-finance/FTokenFiatCollateral.test.ts | 1 + .../frax-eth/SFrxEthTestSuite.test.ts | 1 + .../lido/LidoStakedEthTestSuite.test.ts | 1 + .../MorphoAAVEFiatCollateral.test.ts | 1 + .../MorphoAAVENonFiatCollateral.test.ts | 1 + ...orphoAAVESelfReferentialCollateral.test.ts | 1 + .../individual-collateral/pluginTestTypes.ts | 3 + .../RethCollateralTestSuite.test.ts | 1 + .../stargate/StargateUSDCTestSuite.test.ts | 1 + 37 files changed, 191 insertions(+), 4 deletions(-) diff --git a/contracts/plugins/assets/EURFiatCollateral.sol b/contracts/plugins/assets/EURFiatCollateral.sol index 67d0c12f34..dfc36ff73e 100644 --- a/contracts/plugins/assets/EURFiatCollateral.sol +++ b/contracts/plugins/assets/EURFiatCollateral.sol @@ -27,6 +27,8 @@ contract EURFiatCollateral is FiatCollateral { ) FiatCollateral(config) { require(address(targetUnitChainlinkFeed_) != address(0), "missing targetUnit feed"); require(targetUnitOracleTimeout_ > 0, "targetUnitOracleTimeout zero"); + require(config.defaultThreshold > 0, "defaultThreshold zero"); + targetUnitChainlinkFeed = targetUnitChainlinkFeed_; targetUnitOracleTimeout = targetUnitOracleTimeout_; } diff --git a/contracts/plugins/assets/FiatCollateral.sol b/contracts/plugins/assets/FiatCollateral.sol index ac6e4576bd..d3afad43c5 100644 --- a/contracts/plugins/assets/FiatCollateral.sol +++ b/contracts/plugins/assets/FiatCollateral.sol @@ -75,6 +75,10 @@ contract FiatCollateral is ICollateral, Asset { } require(config.delayUntilDefault <= 1209600, "delayUntilDefault too long"); + // Note: This contract is designed to allow setting defaultThreshold = 0 to disable + // default checks. You can apply the check below to child contracts when required + // require(config.defaultThreshold > 0, "defaultThreshold zero"); + targetName = config.targetName; delayUntilDefault = config.delayUntilDefault; diff --git a/contracts/plugins/assets/L2LSDCollateral.sol b/contracts/plugins/assets/L2LSDCollateral.sol index 0fc8e40884..c2d7cd3cd6 100644 --- a/contracts/plugins/assets/L2LSDCollateral.sol +++ b/contracts/plugins/assets/L2LSDCollateral.sol @@ -30,6 +30,7 @@ abstract contract L2LSDCollateral is AppreciatingFiatCollateral { ) AppreciatingFiatCollateral(config, revenueHiding) { require(address(_exchangeRateChainlinkFeed) != address(0), "missing exchangeRate feed"); require(_exchangeRateChainlinkTimeout != 0, "exchangeRateChainlinkTimeout zero"); + require(config.defaultThreshold > 0, "defaultThreshold zero"); exchangeRateChainlinkFeed = _exchangeRateChainlinkFeed; exchangeRateChainlinkTimeout = _exchangeRateChainlinkTimeout; diff --git a/contracts/plugins/assets/NonFiatCollateral.sol b/contracts/plugins/assets/NonFiatCollateral.sol index 2e6b3c531f..1923dea24a 100644 --- a/contracts/plugins/assets/NonFiatCollateral.sol +++ b/contracts/plugins/assets/NonFiatCollateral.sol @@ -27,6 +27,8 @@ contract NonFiatCollateral is FiatCollateral { ) FiatCollateral(config) { require(address(targetUnitChainlinkFeed_) != address(0), "missing targetUnit feed"); require(targetUnitOracleTimeout_ > 0, "targetUnitOracleTimeout zero"); + require(config.defaultThreshold > 0, "defaultThreshold zero"); + targetUnitChainlinkFeed = targetUnitChainlinkFeed_; targetUnitOracleTimeout = targetUnitOracleTimeout_; } diff --git a/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol b/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol index 2edfd5d65b..ba1843351c 100644 --- a/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol +++ b/contracts/plugins/assets/aave-v3/AaveV3FiatCollateral.sol @@ -20,7 +20,9 @@ contract AaveV3FiatCollateral is AppreciatingFiatCollateral { /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide constructor(CollateralConfig memory config, uint192 revenueHiding) AppreciatingFiatCollateral(config, revenueHiding) - {} + { + require(config.defaultThreshold > 0, "defaultThreshold zero"); + } // solhint-enable no-empty-blocks diff --git a/contracts/plugins/assets/aave/ATokenFiatCollateral.sol b/contracts/plugins/assets/aave/ATokenFiatCollateral.sol index f6e98c267e..14e72a72ca 100644 --- a/contracts/plugins/assets/aave/ATokenFiatCollateral.sol +++ b/contracts/plugins/assets/aave/ATokenFiatCollateral.sol @@ -41,7 +41,9 @@ contract ATokenFiatCollateral is AppreciatingFiatCollateral { /// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide constructor(CollateralConfig memory config, uint192 revenueHiding) AppreciatingFiatCollateral(config, revenueHiding) - {} + { + require(config.defaultThreshold > 0, "defaultThreshold zero"); + } // solhint-enable no-empty-blocks diff --git a/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol b/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol index 594db5465e..59e921e774 100644 --- a/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol +++ b/contracts/plugins/assets/ankr/AnkrStakedEthCollateral.sol @@ -33,6 +33,7 @@ contract AnkrStakedEthCollateral is AppreciatingFiatCollateral { ) AppreciatingFiatCollateral(config, revenueHiding) { require(address(_targetPerTokChainlinkFeed) != address(0), "missing targetPerTok feed"); require(_targetPerTokChainlinkTimeout != 0, "targetPerTokChainlinkTimeout zero"); + require(config.defaultThreshold > 0, "defaultThreshold zero"); targetPerTokChainlinkFeed = _targetPerTokChainlinkFeed; targetPerTokChainlinkTimeout = _targetPerTokChainlinkTimeout; diff --git a/contracts/plugins/assets/cbeth/CBETHCollateral.sol b/contracts/plugins/assets/cbeth/CBETHCollateral.sol index 5c190e6050..40eb3a9d6e 100644 --- a/contracts/plugins/assets/cbeth/CBETHCollateral.sol +++ b/contracts/plugins/assets/cbeth/CBETHCollateral.sol @@ -32,6 +32,7 @@ contract CBEthCollateral is AppreciatingFiatCollateral { ) AppreciatingFiatCollateral(config, revenueHiding) { require(address(_targetPerTokChainlinkFeed) != address(0), "missing targetPerTok feed"); require(_targetPerTokChainlinkTimeout != 0, "targetPerTokChainlinkTimeout zero"); + require(config.defaultThreshold > 0, "defaultThreshold zero"); targetPerTokChainlinkFeed = _targetPerTokChainlinkFeed; targetPerTokChainlinkTimeout = _targetPerTokChainlinkTimeout; diff --git a/contracts/plugins/assets/cbeth/CBETHCollateralL2.sol b/contracts/plugins/assets/cbeth/CBETHCollateralL2.sol index b745028f54..4e98b7c3f2 100644 --- a/contracts/plugins/assets/cbeth/CBETHCollateralL2.sol +++ b/contracts/plugins/assets/cbeth/CBETHCollateralL2.sol @@ -41,6 +41,7 @@ contract CBEthCollateralL2 is L2LSDCollateral { { require(address(_targetPerTokChainlinkFeed) != address(0), "missing targetPerTok feed"); require(_targetPerTokChainlinkTimeout != 0, "targetPerTokChainlinkTimeout zero"); + require(config.defaultThreshold > 0, "defaultThreshold zero"); targetPerTokChainlinkFeed = _targetPerTokChainlinkFeed; targetPerTokChainlinkTimeout = _targetPerTokChainlinkTimeout; diff --git a/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol index a60744893a..ce76a72635 100644 --- a/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenFiatCollateral.sol @@ -29,6 +29,8 @@ contract CTokenFiatCollateral is AppreciatingFiatCollateral { constructor(CollateralConfig memory config, uint192 revenueHiding) AppreciatingFiatCollateral(config, revenueHiding) { + require(config.defaultThreshold > 0, "defaultThreshold zero"); + ICToken _cToken = ICToken(address(config.erc20)); address _underlying = _cToken.underlying(); uint8 _referenceERC20Decimals; diff --git a/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol b/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol index f0a44584b5..3d7dcae18f 100644 --- a/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol +++ b/contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol @@ -30,6 +30,7 @@ contract CTokenNonFiatCollateral is CTokenFiatCollateral { ) CTokenFiatCollateral(config, revenueHiding) { require(address(targetUnitChainlinkFeed_) != address(0), "missing targetUnit feed"); require(targetUnitOracleTimeout_ > 0, "targetUnitOracleTimeout zero"); + require(config.defaultThreshold > 0, "defaultThreshold zero"); targetUnitChainlinkFeed = targetUnitChainlinkFeed_; targetUnitOracleTimeout = targetUnitOracleTimeout_; } diff --git a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol index 5e7bd1238c..281688968c 100644 --- a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol +++ b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol @@ -39,6 +39,7 @@ contract CTokenV3Collateral is AppreciatingFiatCollateral { uint192 revenueHiding, uint256 reservesThresholdIffy_ ) AppreciatingFiatCollateral(config, revenueHiding) { + require(config.defaultThreshold > 0, "defaultThreshold zero"); rewardERC20 = ICusdcV3Wrapper(address(config.erc20)).rewardERC20(); comet = IComet(address(ICusdcV3Wrapper(address(erc20)).underlyingComet())); reservesThresholdIffy = reservesThresholdIffy_; diff --git a/contracts/plugins/assets/dsr/SDaiCollateral.sol b/contracts/plugins/assets/dsr/SDaiCollateral.sol index 8e7643575f..5401b2ad5f 100644 --- a/contracts/plugins/assets/dsr/SDaiCollateral.sol +++ b/contracts/plugins/assets/dsr/SDaiCollateral.sol @@ -35,6 +35,7 @@ contract SDaiCollateral is AppreciatingFiatCollateral { uint192 revenueHiding, IPot _pot ) AppreciatingFiatCollateral(config, revenueHiding) { + require(config.defaultThreshold > 0, "defaultThreshold zero"); pot = _pot; } diff --git a/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol b/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol index 4697ec0da0..c318a047e3 100644 --- a/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol +++ b/contracts/plugins/assets/frax-eth/SFraxEthCollateral.sol @@ -23,7 +23,9 @@ contract SFraxEthCollateral is AppreciatingFiatCollateral { /// @param config.chainlinkFeed Feed units: {UoA/target} constructor(CollateralConfig memory config, uint192 revenueHiding) AppreciatingFiatCollateral(config, revenueHiding) - {} + { + require(config.defaultThreshold > 0, "defaultThreshold zero"); + } // solhint-enable no-empty-blocks diff --git a/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol b/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol index 783896f2c0..9267f40e76 100644 --- a/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol +++ b/contracts/plugins/assets/lido/LidoStakedEthCollateral.sol @@ -35,6 +35,8 @@ contract LidoStakedEthCollateral is AppreciatingFiatCollateral { ) AppreciatingFiatCollateral(config, revenueHiding) { require(address(_targetPerRefChainlinkFeed) != address(0), "missing targetPerRef feed"); require(_targetPerRefChainlinkTimeout > 0, "targetPerRefChainlinkTimeout zero"); + require(config.defaultThreshold > 0, "defaultThreshold zero"); + targetPerRefChainlinkFeed = _targetPerRefChainlinkFeed; targetPerRefChainlinkTimeout = _targetPerRefChainlinkTimeout; } diff --git a/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol b/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol index 5959b944ec..248c24084c 100644 --- a/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol +++ b/contracts/plugins/assets/morpho-aave/MorphoFiatCollateral.sol @@ -28,6 +28,7 @@ contract MorphoFiatCollateral is AppreciatingFiatCollateral { AppreciatingFiatCollateral(config, revenueHiding) { require(address(config.erc20) != address(0), "missing erc20"); + require(config.defaultThreshold > 0, "defaultThreshold zero"); MorphoTokenisedDeposit vault = MorphoTokenisedDeposit(address(config.erc20)); oneShare = 10**vault.decimals(); refDecimals = int8(uint8(IERC20Metadata(vault.asset()).decimals())); diff --git a/contracts/plugins/assets/rocket-eth/RethCollateral.sol b/contracts/plugins/assets/rocket-eth/RethCollateral.sol index f7f4386650..97c58aaef4 100644 --- a/contracts/plugins/assets/rocket-eth/RethCollateral.sol +++ b/contracts/plugins/assets/rocket-eth/RethCollateral.sol @@ -31,6 +31,7 @@ contract RethCollateral is AppreciatingFiatCollateral { ) AppreciatingFiatCollateral(config, revenueHiding) { require(address(_targetPerTokChainlinkFeed) != address(0), "missing targetPerTok feed"); require(_targetPerTokChainlinkTimeout != 0, "targetPerTokChainlinkTimeout zero"); + require(config.defaultThreshold > 0, "defaultThreshold zero"); targetPerTokChainlinkFeed = _targetPerTokChainlinkFeed; targetPerTokChainlinkTimeout = _targetPerTokChainlinkTimeout; diff --git a/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol b/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol index 8aaf4b2381..d31d7b04df 100644 --- a/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol +++ b/contracts/plugins/assets/stargate/StargatePoolFiatCollateral.sol @@ -22,6 +22,7 @@ contract StargatePoolFiatCollateral is AppreciatingFiatCollateral { constructor(CollateralConfig memory config, uint192 revenueHiding) AppreciatingFiatCollateral(config, revenueHiding) { + require(config.defaultThreshold > 0, "defaultThreshold zero"); pool = StargateRewardableWrapper(address(config.erc20)).pool(); } diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index 1ab4d36a39..f7337177e1 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -262,6 +262,44 @@ describe('Collateral contracts', () => { ).to.be.revertedWith('targetName missing') }) + it('Should not allow 0 defaultThreshold', async () => { + // ATokenFiatCollateral + await expect( + ATokenFiatCollateralFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await tokenCollateral.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: aToken.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: bn(0), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + REVENUE_HIDING + ) + ).to.be.revertedWith('defaultThreshold zero') + + // CTokenFiatCollateral + await expect( + CTokenFiatCollateralFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: await tokenCollateral.chainlinkFeed(), + oracleError: ORACLE_ERROR, + erc20: cToken.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: bn(0), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + REVENUE_HIDING + ) + ).to.be.revertedWith('defaultThreshold zero') + }) + it('Should not allow missing delayUntilDefault', async () => { await expect( FiatCollateralFactory.deploy({ @@ -1236,6 +1274,26 @@ describe('Collateral contracts', () => { ).to.be.revertedWith('targetUnitOracleTimeout zero') }) + it('Should not allow 0 defaultThreshold', async () => { + await expect( + NonFiatCollFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: referenceUnitOracle.address, + oracleError: ORACLE_ERROR, + erc20: nonFiatToken.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('BTC'), + defaultThreshold: bn(0), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + targetUnitOracle.address, + ORACLE_TIMEOUT + ) + ).to.be.revertedWith('defaultThreshold zero') + }) + it('Should setup collateral correctly', async function () { // Non-Fiat Token expect(await nonFiatCollateral.isCollateral()).to.equal(true) @@ -1530,6 +1588,27 @@ describe('Collateral contracts', () => { ).to.be.revertedWith('targetUnitOracleTimeout zero') }) + it('Should not allow 0 defaultThreshold', async () => { + await expect( + CTokenNonFiatFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: referenceUnitOracle.address, + oracleError: ORACLE_ERROR, + erc20: cNonFiatTokenVault.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('BTC'), + defaultThreshold: bn(0), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + targetUnitOracle.address, + ORACLE_TIMEOUT, + REVENUE_HIDING + ) + ).to.be.revertedWith('defaultThreshold zero') + }) + it('Should setup collateral correctly', async function () { // Non-Fiat Token expect(await cTokenNonFiatCollateral.isCollateral()).to.equal(true) @@ -2366,6 +2445,26 @@ describe('Collateral contracts', () => { ).to.be.revertedWith('targetUnitOracleTimeout zero') }) + it('Should not allow 0 defaultThreshold', async () => { + await expect( + EURFiatCollateralFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: referenceUnitOracle.address, + oracleError: ORACLE_ERROR, + erc20: eurFiatToken.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('BTC'), + defaultThreshold: bn(0), + delayUntilDefault: DELAY_UNTIL_DEFAULT, + }, + targetUnitOracle.address, + ORACLE_TIMEOUT + ) + ).to.be.revertedWith('defaultThreshold zero') + }) + it('Should not revert during refresh when price2 is 0', async () => { const targetFeedAddr = await eurFiatCollateral.targetUnitChainlinkFeed() const targetFeed = await ethers.getContractAt('MockV3Aggregator', targetFeedAddr) diff --git a/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts b/test/plugins/individual-collateral/aave-v3/AaveV3FiatCollateral.test.ts index 8566f70d76..687ab7a073 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 = { itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itHasRevenueHiding: it, + itChecksNonZeroDefaultThreshold: it, itIsPricedByPeg: true, chainlinkDefaultAnswer: 1e8, itChecksPriceChanges: it, diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index 8e38035704..91713c469d 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -428,7 +428,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi // Validate constructor arguments // Note: Adapt it to your plugin constructor validations it('Should validate constructor arguments correctly', async () => { - // stkAAVEtroller + // Missing erc20 await expect( ATokenFiatCollateralFactory.deploy( { @@ -445,6 +445,24 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi REVENUE_HIDING ) ).to.be.revertedWith('missing erc20') + + // defaultThreshold = 0 + await expect( + ATokenFiatCollateralFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI as string, + oracleError: ORACLE_ERROR, + erc20: staticAToken.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: bn(0), + delayUntilDefault, + }, + REVENUE_HIDING + ) + ).to.be.revertedWith('defaultThreshold zero') }) }) diff --git a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts index 0635aba4c3..e21a0f66a0 100644 --- a/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/ankr/AnkrEthCollateralTestSuite.test.ts @@ -289,6 +289,7 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, + itChecksNonZeroDefaultThreshold: it, resetFork, collateralName: 'AnkrStakedETH', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts index 2dc585a5b8..8f5bb5efe1 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateral.test.ts @@ -246,6 +246,7 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, + itChecksNonZeroDefaultThreshold: it, resetFork, collateralName: 'CBEthCollateral', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts index 489f89d3df..fbc3f6874b 100644 --- a/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts +++ b/test/plugins/individual-collateral/cbeth/CBETHCollateralL2.test.ts @@ -277,6 +277,7 @@ const opts = { itChecksRefPerTokDefault: it, itChecksPriceChanges: it, itHasRevenueHiding: it, + itChecksNonZeroDefaultThreshold: it, resetFork, collateralName: 'CBEthCollateralL2', chainlinkDefaultAnswer, diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 8af1117c26..9de866af6a 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -61,6 +61,7 @@ export default function fn( itChecksTargetPerRefDefault, itChecksRefPerTokDefault, itChecksPriceChanges, + itChecksNonZeroDefaultThreshold, itHasRevenueHiding, itIsPricedByPeg, resetFork, @@ -110,6 +111,12 @@ export default function fn( ) }) + itChecksNonZeroDefaultThreshold('does not allow 0 defaultThreshold', async () => { + await expect(deployCollateral({ defaultThreshold: bn('0') })).to.be.revertedWith( + 'defaultThreshold zero' + ) + }) + describe('collateral-specific tests', collateralSpecificConstructorTests) }) diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index d3d0af540e..3725338def 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -474,6 +474,24 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi REVENUE_HIDING ) ).to.be.revertedWith('referenceERC20Decimals missing') + + // defaultThreshold = 0 + await expect( + CTokenCollateralFactory.deploy( + { + priceTimeout: PRICE_TIMEOUT, + chainlinkFeed: networkConfig[chainId].chainlinkFeeds.DAI as string, + oracleError: ORACLE_ERROR, + erc20: cDaiVault.address, + maxTradeVolume: config.rTokenMaxTradeVolume, + oracleTimeout: ORACLE_TIMEOUT, + targetName: ethers.utils.formatBytes32String('USD'), + defaultThreshold: bn(0), + delayUntilDefault, + }, + REVENUE_HIDING + ) + ).to.be.revertedWith('defaultThreshold zero') }) }) diff --git a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts index 22365aaab9..66af764996 100644 --- a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts @@ -399,6 +399,7 @@ const opts = { itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it.skip, // implemented in this file itIsPricedByPeg: true, resetFork, diff --git a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts index 43b5f8593a..f604c19eb2 100644 --- a/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/dsr/SDaiCollateralTestSuite.test.ts @@ -213,6 +213,7 @@ const opts = { itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, resetFork, collateralName: 'SDaiCollateral', diff --git a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts index d38beecde3..ea7c5554f2 100644 --- a/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/flux-finance/FTokenFiatCollateral.test.ts @@ -256,6 +256,7 @@ all.forEach((curr: FTokenEnumeration) => { itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, resetFork, collateralName: curr.testName, diff --git a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts index 701789f733..681f53ae1b 100644 --- a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts @@ -249,6 +249,7 @@ const opts = { itChecksTargetPerRefDefault: it.skip, itChecksRefPerTokDefault: it.skip, itChecksPriceChanges: it, + itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it.skip, // implemnted in this file resetFork, collateralName: 'SFraxEthCollateral', diff --git a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts index 2d44d02990..1f4213ac61 100644 --- a/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/lido/LidoStakedEthTestSuite.test.ts @@ -267,6 +267,7 @@ const opts = { itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, resetFork, collateralName: 'LidoStakedETH', diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts index a62a4c84ba..a1c99cfffa 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVEFiatCollateral.test.ts @@ -330,6 +330,7 @@ const makeAaveFiatCollateralTestSuite = ( itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), collateralName, diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts index 5d5a91ee51..f9a0339f9a 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVENonFiatCollateral.test.ts @@ -235,6 +235,7 @@ const makeAaveNonFiatCollateralTestSuite = ( itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, itIsPricedByPeg: true, resetFork: getResetFork(FORK_BLOCK), diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts index 6a5b06d5a2..8e934cedca 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAAVESelfReferentialCollateral.test.ts @@ -229,6 +229,7 @@ const opts = { itChecksTargetPerRefDefault: it.skip, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksNonZeroDefaultThreshold: it.skip, itHasRevenueHiding: it, resetFork: getResetFork(FORK_BLOCK), collateralName: 'MorphoAAVEV2SelfReferentialCollateral - WETH', diff --git a/test/plugins/individual-collateral/pluginTestTypes.ts b/test/plugins/individual-collateral/pluginTestTypes.ts index 11b73fbaa1..34bfefbb20 100644 --- a/test/plugins/individual-collateral/pluginTestTypes.ts +++ b/test/plugins/individual-collateral/pluginTestTypes.ts @@ -100,6 +100,9 @@ export interface CollateralTestSuiteFixtures // toggle on or off: tests that focus on revenue hiding (off if plugin does not hide revenue) itHasRevenueHiding: Mocha.TestFunction | Mocha.PendingTestFunction + // toggle on or off: tests that check that defaultThreshold is not zero + itChecksNonZeroDefaultThreshold: Mocha.TestFunction | Mocha.PendingTestFunction + // does the peg price matter for the results of tryPrice()? itIsPricedByPeg?: boolean diff --git a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts index e39347b7d9..d10488770e 100644 --- a/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts +++ b/test/plugins/individual-collateral/rocket-eth/RethCollateralTestSuite.test.ts @@ -274,6 +274,7 @@ const opts = { itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, itChecksPriceChanges: it, + itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, resetFork, collateralName: 'RocketPoolETH', diff --git a/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts b/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts index 9db7362b1e..e6353d174c 100644 --- a/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts +++ b/test/plugins/individual-collateral/stargate/StargateUSDCTestSuite.test.ts @@ -267,6 +267,7 @@ export const stableOpts = { itClaimsRewards: it.skip, // reward growth not supported in mock itChecksTargetPerRefDefault: it, itChecksRefPerTokDefault: it, + itChecksNonZeroDefaultThreshold: it, itHasRevenueHiding: it, itIsPricedByPeg: true, chainlinkDefaultAnswer: 1e8, From a007c472a0603a31e9b12d18c082a71686f14229 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 18 Oct 2023 14:21:39 -0400 Subject: [PATCH 450/462] Trust M-07: RTokenAsset.refresh() (#964) --- CHANGELOG.md | 4 +- README.md | 1 + contracts/facade/FacadeRead.sol | 6 +- contracts/facade/FacadeTest.sol | 1 - contracts/interfaces/IAssetRegistry.sol | 2 +- contracts/p0/BackingManager.sol | 2 - contracts/p0/Furnace.sol | 12 +-- contracts/p0/Main.sol | 3 +- contracts/p1/AssetRegistry.sol | 2 + contracts/p1/BackingManager.sol | 2 - contracts/p1/Furnace.sol | 12 +-- contracts/p1/Main.sol | 3 +- contracts/p1/RToken.sol | 5 -- contracts/p1/RevenueTrader.sol | 6 +- contracts/plugins/assets/RTokenAsset.sol | 5 ++ docs/pause-freeze-states.md | 73 +++++++++++++++++++ test/Furnace.test.ts | 49 +++++++++---- test/__snapshots__/FacadeWrite.test.ts.snap | 2 +- test/__snapshots__/Furnace.test.ts.snap | 34 ++++----- test/__snapshots__/Main.test.ts.snap | 2 +- test/__snapshots__/RToken.test.ts.snap | 6 +- .../Recollateralization.test.ts.snap | 12 +-- test/__snapshots__/Revenues.test.ts.snap | 4 +- test/__snapshots__/ZZStRSR.test.ts.snap | 8 +- test/plugins/Asset.test.ts | 24 ++++++ .../ATokenFiatCollateral.test.ts.snap | 8 +- .../SDaiCollateralTestSuite.test.ts.snap | 2 +- .../StargateETHTestSuite.test.ts.snap | 49 ------------- .../StargateUSDCTestSuite.test.ts.snap | 25 +++++++ .../__snapshots__/MaxBasketSize.test.ts.snap | 16 ++-- 30 files changed, 225 insertions(+), 155 deletions(-) create mode 100644 docs/pause-freeze-states.md delete mode 100644 test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap create mode 100644 test/plugins/individual-collateral/stargate/__snapshots__/StargateUSDCTestSuite.test.ts.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index 95af3e13a9..1b73a106a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Upgrade Steps -- Required -Upgrade `BackingManager`, `Broker`, and _all_ assets. ERC20s do not need to be upgraded. +Upgrade all core contracts and _all_ assets. ERC20s do not need to be upgraded. Use `Deployer.deployRTokenAsset()` to create a new `RTokenAsset` instance. This asset should be swapped too. Then, call `Broker.cacheComponents()`. @@ -16,6 +16,8 @@ Then, call `Broker.cacheComponents()`. - Remove `lotPrice()` - `Broker` [+1 slot] - Disallow starting dutch trades with non-RTokenAsset assets when `lastSave() != block.timestamp` +- `Furnace` + - Allow melting while frozen ## Plugins diff --git a/README.md b/README.md index 1c569eb492..e8ab78faec 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ For a much more detailed explanation of the economic design, including an hour-l - [Testing with Echidna](https://github.com/reserve-protocol/protocol/blob/master/docs/using-echidna.md): Notes so far on setup and usage of Echidna (which is decidedly an integration-in-progress!) - [Deployment](https://github.com/reserve-protocol/protocol/blob/master/docs/deployment.md): How to do test deployments in our environment. - [System Design](https://github.com/reserve-protocol/protocol/blob/master/docs/system-design.md): The overall architecture of our system, and some detailed descriptions about what our protocol is _intended_ to do. +- [Pause and Freeze States](https://github.com/reserve-protocol/protocol/blob/master/docs/pause-freeze-states.md): An overview of which protocol functions are halted in the paused and frozen states. - [Deployment Variables](https://github.com/reserve-protocol/protocol/blob/master/docs/deployment-variables.md) A detailed description of the governance variables of the protocol. - [Our Solidity Style](https://github.com/reserve-protocol/protocol/blob/master/docs/solidity-style.md): Common practices, details, and conventions relevant to reading and writing our Solidity source code, estpecially where those go beyond standard practice. - [Writing Collateral Plugins](https://github.com/reserve-protocol/protocol/blob/master/docs/collateral.md): An overview of how to develop collateral plugins and the concepts / questions involved. diff --git a/contracts/facade/FacadeRead.sol b/contracts/facade/FacadeRead.sol index 10f60d9182..a42d5c6135 100644 --- a/contracts/facade/FacadeRead.sol +++ b/contracts/facade/FacadeRead.sol @@ -33,7 +33,6 @@ contract FacadeRead is IFacadeRead { // Poke Main main.assetRegistry().refresh(); - main.furnace().melt(); // {BU} BasketRange memory basketsHeld = main.basketHandler().basketsHeldBy(account); @@ -74,7 +73,6 @@ contract FacadeRead is IFacadeRead { // Poke Main reg.refresh(); - main.furnace().melt(); // Compute # of baskets to create `amount` qRTok uint192 baskets = (rTok.totalSupply() > 0) // {BU} @@ -120,7 +118,6 @@ contract FacadeRead is IFacadeRead { // Poke Main main.assetRegistry().refresh(); - main.furnace().melt(); uint256 supply = rTok.totalSupply(); @@ -202,7 +199,7 @@ contract FacadeRead is IFacadeRead { IBasketHandler basketHandler = rToken.main().basketHandler(); // solhint-disable-next-line no-empty-blocks - try rToken.main().furnace().melt() {} catch {} + try rToken.main().furnace().melt() {} catch {} // <3.1.0 RTokens may revert while frozen (erc20s, deposits) = basketHandler.quote(FIX_ONE, CEIL); @@ -241,7 +238,6 @@ contract FacadeRead is IFacadeRead { { IMain main = rToken.main(); main.assetRegistry().refresh(); - main.furnace().melt(); erc20s = main.assetRegistry().erc20s(); balances = new uint256[](erc20s.length); diff --git a/contracts/facade/FacadeTest.sol b/contracts/facade/FacadeTest.sol index d16c6ab6d3..78ee2173e0 100644 --- a/contracts/facade/FacadeTest.sol +++ b/contracts/facade/FacadeTest.sol @@ -97,7 +97,6 @@ contract FacadeTest is IFacadeTest { // Poke Main reg.refresh(); - main.furnace().melt(); address backingManager = address(main.backingManager()); IERC20 rsr = main.rsr(); diff --git a/contracts/interfaces/IAssetRegistry.sol b/contracts/interfaces/IAssetRegistry.sol index caeaac2f3e..add18d69b5 100644 --- a/contracts/interfaces/IAssetRegistry.sol +++ b/contracts/interfaces/IAssetRegistry.sol @@ -34,7 +34,7 @@ interface IAssetRegistry is IComponent { function init(IMain main_, IAsset[] memory assets_) external; /// Fully refresh all asset state - /// @custom:interaction + /// @custom:refresher function refresh() external; /// Register `asset` diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 18e2427b8f..2d4bb0de23 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -86,7 +86,6 @@ contract BackingManagerP0 is TradingP0, IBackingManager { /// @custom:interaction function rebalance(TradeKind kind) external notTradingPausedOrFrozen { main.assetRegistry().refresh(); - main.furnace().melt(); // DoS prevention: unless caller is self, require 1 empty block between like-kind auctions require( @@ -149,7 +148,6 @@ contract BackingManagerP0 is TradingP0, IBackingManager { require(ArrayLib.allUnique(erc20s), "duplicate tokens"); main.assetRegistry().refresh(); - main.furnace().melt(); require(tradesOpen == 0, "trade open"); require(main.basketHandler().isReady(), "basket not ready"); diff --git a/contracts/p0/Furnace.sol b/contracts/p0/Furnace.sol index aa99a8140c..ea0a404a2e 100644 --- a/contracts/p0/Furnace.sol +++ b/contracts/p0/Furnace.sol @@ -36,7 +36,7 @@ contract FurnaceP0 is ComponentP0, IFurnace { /// Performs any melting that has vested since last call. /// @custom:refresher - function melt() public notFrozen { + function melt() public { if (uint48(block.timestamp) < uint64(lastPayout) + PERIOD) return; // # of whole periods that have passed since lastPayout @@ -58,15 +58,9 @@ contract FurnaceP0 is ComponentP0, IFurnace { /// Ratio setting /// @custom:governance function setRatio(uint192 ratio_) public governance { - if (lastPayout > 0) { - // solhint-disable-next-line no-empty-blocks - try this.melt() {} catch { - uint48 numPeriods = uint48((block.timestamp) - lastPayout) / PERIOD; - lastPayout += numPeriods * PERIOD; - lastPayoutBal = main.rToken().balanceOf(address(this)); - } - } require(ratio_ <= MAX_RATIO, "invalid ratio"); + melt(); // cannot revert + // The ratio can safely be set to 0, though it is not recommended emit RatioSet(ratio, ratio_); ratio = ratio_; diff --git a/contracts/p0/Main.sol b/contracts/p0/Main.sol index 9493b72c5c..1859ad8ecb 100644 --- a/contracts/p0/Main.sol +++ b/contracts/p0/Main.sol @@ -37,8 +37,7 @@ contract MainP0 is Versioned, Initializable, Auth, ComponentRegistry, IMain { /// @custom:refresher function poke() external { - assetRegistry.refresh(); - if (!frozen()) furnace.melt(); + assetRegistry.refresh(); // runs furnace.melt() stRSR.payoutRewards(); // NOT basketHandler.refreshBasket } diff --git a/contracts/p1/AssetRegistry.sol b/contracts/p1/AssetRegistry.sol index 098e47f93a..c556a96120 100644 --- a/contracts/p1/AssetRegistry.sol +++ b/contracts/p1/AssetRegistry.sol @@ -57,6 +57,8 @@ contract AssetRegistryP1 is ComponentP1, IAssetRegistry { // tracks basket status on basketHandler function refresh() public { // It's a waste of gas to require notPausedOrFrozen because assets can be updated directly + // Assuming an RTokenAsset is registered, furnace.melt() will also be called + uint256 length = _erc20s.length(); for (uint256 i = 0; i < length; ++i) { assets[IERC20(_erc20s.at(i))].refresh(); diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index edb22151d2..da8473e662 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -113,7 +113,6 @@ contract BackingManagerP1 is TradingP1, IBackingManager { function rebalance(TradeKind kind) external nonReentrant notTradingPausedOrFrozen { // == Refresh == assetRegistry.refresh(); - furnace.melt(); // DoS prevention: unless caller is self, require 1 empty block between like-kind auctions require( @@ -184,7 +183,6 @@ contract BackingManagerP1 is TradingP1, IBackingManager { require(ArrayLib.allUnique(erc20s), "duplicate tokens"); assetRegistry.refresh(); - furnace.melt(); BasketRange memory basketsHeld = basketHandler.basketsHeldBy(address(this)); diff --git a/contracts/p1/Furnace.sol b/contracts/p1/Furnace.sol index 923ba33737..63dcc695d4 100644 --- a/contracts/p1/Furnace.sol +++ b/contracts/p1/Furnace.sol @@ -71,7 +71,7 @@ contract FurnaceP1 is ComponentP1, IFurnace { // actions: // rToken.melt(payoutAmount), paying payoutAmount to RToken holders - function melt() external notFrozen { + function melt() public { if (uint48(block.timestamp) < uint64(lastPayout) + PERIOD) return; // # of whole periods that have passed since lastPayout @@ -90,15 +90,9 @@ contract FurnaceP1 is ComponentP1, IFurnace { /// Ratio setting /// @custom:governance function setRatio(uint192 ratio_) public governance { - if (lastPayout > 0) { - // solhint-disable-next-line no-empty-blocks - try this.melt() {} catch { - uint48 numPeriods = uint48((block.timestamp) - lastPayout) / PERIOD; - lastPayout += numPeriods * PERIOD; - lastPayoutBal = rToken.balanceOf(address(this)); - } - } require(ratio_ <= MAX_RATIO, "invalid ratio"); + melt(); // cannot revert + // The ratio can safely be set to 0 to turn off payouts, though it is not recommended emit RatioSet(ratio, ratio_); ratio = ratio_; diff --git a/contracts/p1/Main.sol b/contracts/p1/Main.sol index 43bddcaed7..21781ca082 100644 --- a/contracts/p1/Main.sol +++ b/contracts/p1/Main.sol @@ -43,10 +43,9 @@ contract MainP1 is Versioned, Initializable, Auth, ComponentRegistry, UUPSUpgrad /// @dev Not intended to be used in production, only for equivalence with P0 function poke() external { // == Refresher == - assetRegistry.refresh(); + assetRegistry.refresh(); // runs furnace.melt() // == CE block == - if (!frozen()) furnace.melt(); stRSR.payoutRewards(); } diff --git a/contracts/p1/RToken.sol b/contracts/p1/RToken.sol index 77f15bef54..cfbbdcd923 100644 --- a/contracts/p1/RToken.sol +++ b/contracts/p1/RToken.sol @@ -108,7 +108,6 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { // == Refresh == assetRegistry.refresh(); - furnace.melt(); // == Checks-effects block == @@ -182,8 +181,6 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { function redeemTo(address recipient, uint256 amount) public notFrozen { // == Refresh == assetRegistry.refresh(); - // solhint-disable-next-line no-empty-blocks - try furnace.melt() {} catch {} // nice for the redeemer, but not necessary // == Checks and Effects == @@ -254,8 +251,6 @@ contract RTokenP1 is ComponentP1, ERC20PermitUpgradeable, IRToken { ) external notFrozen { // == Refresh == assetRegistry.refresh(); - // solhint-disable-next-line no-empty-blocks - try furnace.melt() {} catch {} // nice for the redeemer, but not necessary // == Checks and Effects == diff --git a/contracts/p1/RevenueTrader.sol b/contracts/p1/RevenueTrader.sol index bda4be0793..a220e6ca10 100644 --- a/contracts/p1/RevenueTrader.sol +++ b/contracts/p1/RevenueTrader.sol @@ -134,10 +134,8 @@ contract RevenueTraderP1 is TradingP1, IRevenueTrader { IAsset assetToBuy = assetRegistry.toAsset(tokenToBuy); // Refresh everything if RToken is involved - if (involvesRToken) { - assetRegistry.refresh(); - furnace.melt(); - } else { + if (involvesRToken) assetRegistry.refresh(); + else { // Otherwise: refresh just the needed assets and nothing more for (uint256 i = 0; i < len; ++i) { assetRegistry.toAsset(erc20s[i]).refresh(); diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index 2dd0010f07..f187651e34 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -22,6 +22,7 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { IBasketHandler public immutable basketHandler; IAssetRegistry public immutable assetRegistry; IBackingManager public immutable backingManager; + IFurnace public immutable furnace; IERC20Metadata public immutable erc20; @@ -41,6 +42,7 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { basketHandler = main.basketHandler(); assetRegistry = main.assetRegistry(); backingManager = main.backingManager(); + furnace = main.furnace(); erc20 = IERC20Metadata(address(erc20_)); erc20Decimals = erc20_.decimals(); @@ -81,6 +83,9 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { function refresh() public virtual override { // No need to save lastPrice; can piggyback off the backing collateral's saved prices + if (msg.sender != address(assetRegistry)) assetRegistry.refresh(); + furnace.melt(); + cachedOracleData.cachedAtTime = 0; // force oracle refresh } diff --git a/docs/pause-freeze-states.md b/docs/pause-freeze-states.md new file mode 100644 index 0000000000..009ab64b20 --- /dev/null +++ b/docs/pause-freeze-states.md @@ -0,0 +1,73 @@ +# Pause Freeze States + +Some protocol functions may be halted while the protocol is either (i) issuance-paused; (ii) trading-paused; or (iii) frozen. Below is a table that shows which protocol interactions (`@custom:interaction`) and refreshers (`@custom:refresher`) execute during paused/frozen states, as of the 3.1.0 release. + +All governance functions (`@custom:governance`) remain enabled during all paused/frozen states. They are not mentioned here. + +A :heavy_check_mark: indicates the function still executes in this state. +A :x: indicates it reverts. + +| Function | Issuance-Paused | Trading-Paused | Frozen | +| --------------------------------------- | ------------------ | ----------------------- | ----------------------- | +| `BackingManager.claimRewards()` | :heavy_check_mark: | :x: | :x: | +| `BackingManager.claimRewardsSingle()` | :heavy_check_mark: | :x: | :x: | +| `BackingManager.grantRTokenAllowance()` | :heavy_check_mark: | :heavy_check_mark: | :x: | +| `BackingManager.forwardRevenue()` | :heavy_check_mark: | :x: | :x: | +| `BackingManager.rebalance()` | :heavy_check_mark: | :x: | :x: | +| `BackingManager.settleTrade()` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| `BasketHandler.refreshBasket()` | :heavy_check_mark: | :x: (unless governance) | :x: (unless governance) | +| `Broker.openTrade()` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| `Broker.reportViolation()` | :heavy_check_mark: | :x: | :x: | +| `Distributor.distribute()` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| `Furnace.melt()` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| `Main.poke()` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| `RevenueTrader.claimRewards()` | :heavy_check_mark: | :x: | :x: | +| `RevenueTrader.claimRewardsSingle()` | :heavy_check_mark: | :x: | :x: | +| `RevenueTrader.distributeTokenToBuy()` | :heavy_check_mark: | :x: | :x: | +| `RevenueTrader.manageTokens()` | :heavy_check_mark: | :x: | :x: | +| `RevenueTrader.returnTokens()` | :heavy_check_mark: | :x: | :x: | +| `RevenueTrader.settleTrade()` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| `RToken.issue()` | :x: | :heavy_check_mark: | :x: | +| `RToken.issueTo()` | :x: | :heavy_check_mark: | :x: | +| `RToken.redeem()` | :heavy_check_mark: | :heavy_check_mark: | :x: | +| `RToken.redeemTo()` | :heavy_check_mark: | :heavy_check_mark: | :x: | +| `RToken.redeemCustom()` | :heavy_check_mark: | :heavy_check_mark: | :x: | +| `StRSR.cancelUnstake()` | :heavy_check_mark: | :heavy_check_mark: | :x: | +| `StRSR.payoutRewards()` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| `StRSR.stake()` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| `StRSR.seizeRSR()` | :heavy_check_mark: | :x: | :x: | +| `StRSR.unstake()` | :heavy_check_mark: | :x: | :x: | +| `StRSR.withdraw()` | :heavy_check_mark: | :x: | :x: | + +## Issuance-pause + +The issuance-paused states indicates that RToken issuance should be paused, and _only_ that. It is a narrow control knob that is designed solely to protect against a case where bad debt is being injected into the protocol, say, because default detection for an asset has a false negative. + +## Trading-pause + +The trading-paused state has significantly more scope than the issuance-paused state. It is designed to prevent against cases where the protocol may trade unneccesarily. Many other functions in addition to just `BackingManager.rebalance()` and `RevenueTrader.manageTokens()` are halted. In general anything that manages the backing and revenue for an RToken is halted. This may become neccessary to use due to (among other things): + +- An asset's `price()` malfunctions or is manipulated +- A collateral's default detection has a false positive or negative + +## Freezing + +The scope of freezing is the largest, and it should be used least frequently. Nearly all protocol interactions (`@custom:interaction`) are halted. Any refreshers (`@custom:refresher`) remain enabled, as well as `StRSR.stake()` and the "wrap up" routine `*.settleTrade()`. + +An important function of freezing is to provide a finite time for governance to push through a repair proposal an RToken in the event that a 0-day is discovered that requires a contract upgrade. + +### `Furnace.melt()` + +It is necessary for `Furnace.melt()` to remain emabled in order to allow `RTokenAsset.refresh()` to update its `price()`. Any revenue RToken that has already accumulated at the Furnace will continue to be melted, but the flow of new revenue RToken into the contract is halted. + +### `StRSR.payoutRewards()` + +It is necessary for `StRSR.payoutRewards()` to remain enabled in order for `StRSR.stake()` to use the up-to-date StRSR-RSR exchange rate. If it did not, then in the event of freezing there would be an unfair benefit to new stakers. Any revenue RSR that has already accumulated at the StRSR contract will continue to be paid out, but the flow of new revenue RSR into the contract is halted. + +### `StRSR.stake()` + +It is important for `StRSR.stake()` to remain emabled while frozen in order to allow honest RSR to flow into an RToken to vote against malicious governance proposals. + +### `*.settleTrade()` + +The settleTrade functionality must remain enabled in order to maintain the property that dutch auctions will discover the optimal price. If settleTrade were halted, it could become possible for a dutch auction to clear at a much lower price than it should have, simply because bidding was disabled during the earlier portion of the auction. diff --git a/test/Furnace.test.ts b/test/Furnace.test.ts index 15776210be..84d16f32c7 100644 --- a/test/Furnace.test.ts +++ b/test/Furnace.test.ts @@ -204,9 +204,9 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { await furnace.connect(addr1).melt() }) - it('Should not melt if frozen #fast', async () => { + it('Should melt if frozen #fast', async () => { await main.connect(owner).freezeShort() - await expect(furnace.connect(addr1).melt()).to.be.revertedWith('frozen') + await furnace.connect(addr1).melt() }) it('Should not melt any funds in the initial block #fast', async () => { @@ -450,40 +450,57 @@ describe(`FurnaceP${IMPLEMENTATION} contract`, () => { it('Regression test -- C4 June 2023 Issue #29', async () => { // https://github.com/code-423n4/2023-06-reserve-findings/issues/29 + const firstRatio = fp('1e-6') + const secondRatio = fp('1e-4') + const mintAmount = fp('100') + + // Set ratio to something cleaner + await expect(furnace.connect(owner).setRatio(firstRatio)) + .to.emit(furnace, 'RatioSet') + .withArgs(config.rewardRatio, firstRatio) + // Transfer to Furnace and do first melt - await rToken.connect(addr1).transfer(furnace.address, bn('10e18')) + await rToken.connect(addr1).transfer(furnace.address, mintAmount) await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) await furnace.melt() // Should have updated lastPayout + lastPayoutBal expect(await furnace.lastPayout()).to.be.closeTo(await getLatestBlockTimestamp(), 12) expect(await furnace.lastPayout()).to.be.lte(await getLatestBlockTimestamp()) - expect(await furnace.lastPayoutBal()).to.equal(bn('10e18')) + expect(await furnace.lastPayoutBal()).to.equal(mintAmount) - // Advance 99 periods -- should melt at old ratio - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + 99 * Number(ONE_PERIOD)) + // Advance 100 periods -- should melt at old ratio + await setNextBlockTimestamp( + Number(await getLatestBlockTimestamp()) + 100 * Number(ONE_PERIOD) + ) - // Freeze and change ratio + // Freeze and change ratio (melting as a pre-step) await main.connect(owner).freezeForever() - const maxRatio = bn('1e14') - await expect(furnace.connect(owner).setRatio(maxRatio)) + await expect(furnace.connect(owner).setRatio(secondRatio)) .to.emit(furnace, 'RatioSet') - .withArgs(config.rewardRatio, maxRatio) + .withArgs(firstRatio, secondRatio) - // Should have updated lastPayout + lastPayoutBal + // Should have melted expect(await furnace.lastPayout()).to.be.closeTo(await getLatestBlockTimestamp(), 12) expect(await furnace.lastPayout()).to.be.lte(await getLatestBlockTimestamp()) - expect(await furnace.lastPayoutBal()).to.equal(bn('10e18')) // no change + expect(await furnace.lastPayoutBal()).to.eq(fp('99.990000494983830300')) - // Unfreeze and advance 1 period + // Unfreeze and advance 100 periods await main.connect(owner).unfreeze() - await setNextBlockTimestamp(Number(await getLatestBlockTimestamp()) + Number(ONE_PERIOD)) + await setNextBlockTimestamp( + Number(await getLatestBlockTimestamp()) + 100 * Number(ONE_PERIOD) + ) await expect(furnace.melt()).to.emit(rToken, 'Melted') - // Should have updated lastPayout + lastPayoutBal + // Should have updated lastPayout + lastPayoutBal and melted at new ratio expect(await furnace.lastPayout()).to.be.closeTo(await getLatestBlockTimestamp(), 12) expect(await furnace.lastPayout()).to.be.lte(await getLatestBlockTimestamp()) - expect(await furnace.lastPayoutBal()).to.equal(bn('9.999e18')) + expect(await furnace.lastPayoutBal()).to.equal(fp('98.995033865808581644')) + // if the ratio were not increased 100x, this would be more like 99.980001989868666200 + + // Total supply should have decreased by the cumulative melted amount + expect(await rToken.totalSupply()).to.equal(mintAmount.add(await furnace.lastPayoutBal())) + expect(await rToken.basketsNeeded()).to.equal(mintAmount.mul(2)) }) }) diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index 4b4c7894ff..143651b3a3 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8497592`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8572904`; exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464253`; diff --git a/test/__snapshots__/Furnace.test.ts.snap b/test/__snapshots__/Furnace.test.ts.snap index af06969a2f..e905f3eec0 100644 --- a/test/__snapshots__/Furnace.test.ts.snap +++ b/test/__snapshots__/Furnace.test.ts.snap @@ -1,35 +1,35 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 1`] = `83931`; +exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 1`] = `71626`; -exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 2`] = `89820`; +exports[`FurnaceP1 contract Gas Reporting Melt - A million periods, all at once 2`] = `77515`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 1`] = `83931`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 1`] = `71626`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 2`] = `78303`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 2`] = `65998`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 3`] = `78303`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 3`] = `65998`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 4`] = `78303`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 4`] = `65998`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 5`] = `78303`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 5`] = `65998`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 6`] = `78303`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 6`] = `65998`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 7`] = `78303`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 7`] = `65998`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 8`] = `78303`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 8`] = `65998`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 9`] = `78303`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 9`] = `65998`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 10`] = `78303`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 10`] = `65998`; -exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 11`] = `78303`; +exports[`FurnaceP1 contract Gas Reporting Melt - Many periods, one after the other 11`] = `65998`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 1`] = `64031`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 1`] = `51726`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 2`] = `80663`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 2`] = `68358`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 3`] = `78303`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 3`] = `65998`; -exports[`FurnaceP1 contract Gas Reporting Melt - One period 4`] = `40761`; +exports[`FurnaceP1 contract Gas Reporting Melt - One period 4`] = `28452`; diff --git a/test/__snapshots__/Main.test.ts.snap b/test/__snapshots__/Main.test.ts.snap index 9591a27626..ffecb35bc5 100644 --- a/test/__snapshots__/Main.test.ts.snap +++ b/test/__snapshots__/Main.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `357740`; +exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `393837`; exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `195889`; diff --git a/test/__snapshots__/RToken.test.ts.snap b/test/__snapshots__/RToken.test.ts.snap index ee00025ae6..c725eeb995 100644 --- a/test/__snapshots__/RToken.test.ts.snap +++ b/test/__snapshots__/RToken.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `787553`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `782158`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `614557`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `609158`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `589266`; +exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `583862`; exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56658`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 4222c553e9..ddde5b4fe3 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1370490`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1365473`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1506234`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1500861`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `760457`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `755062`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1665048`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1659319`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1607593`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1602220`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `174808`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1695355`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1689982`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `202908`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index 4275db1832..bb7a77e725 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -12,11 +12,11 @@ exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229377`; exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212277`; -exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1025829`; +exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1020522`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `774070`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1181105`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1175816`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `311446`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index f4ec1f241e..63500d7165 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`StRSRP1 contract Gas Reporting Stake 1`] = `156559`; +exports[`StRSRP1 contract Gas Reporting Stake 1`] = `139717`; exports[`StRSRP1 contract Gas Reporting Stake 2`] = `134917`; @@ -10,10 +10,10 @@ exports[`StRSRP1 contract Gas Reporting Transfer 2`] = `41509`; exports[`StRSRP1 contract Gas Reporting Transfer 3`] = `58621`; -exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `241951`; +exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `222609`; exports[`StRSRP1 contract Gas Reporting Unstake 2`] = `139758`; -exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `572112`; +exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `606273`; -exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `526116`; +exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `536407`; diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index c0a49a79a5..95ad7cd34b 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -34,6 +34,7 @@ import { RTokenAsset, StaticATokenMock, TestIBackingManager, + TestIFurnace, TestIRToken, USDCMock, UnpricedAssetMock, @@ -88,6 +89,7 @@ describe('Assets contracts #fast', () => { let wallet: Wallet let assetRegistry: IAssetRegistry let backingManager: TestIBackingManager + let furnace: TestIFurnace // Factory let AssetFactory: ContractFactory @@ -112,6 +114,7 @@ describe('Assets contracts #fast', () => { assetRegistry, backingManager, config, + furnace, rToken, rTokenAsset, } = await loadFixture(defaultFixture)) @@ -481,6 +484,27 @@ describe('Assets contracts #fast', () => { await expectUnpriced(rTokenAsset.address) }) + it('Regression test -- RTokenAsset.refresh() should refresh everything', async () => { + // AssetRegistry should refresh + const lastRefreshed = await assetRegistry.lastRefresh() + await rTokenAsset.refresh() + expect(await assetRegistry.lastRefresh()).to.be.gt(lastRefreshed) + + // Furnace should melt + const lastPayout = await furnace.lastPayout() + await advanceTime(12) + await rTokenAsset.refresh() + expect(await furnace.lastPayout()).to.be.gt(lastPayout) + + // Should clear oracle cache + await rTokenAsset.forceUpdatePrice() + let [, cachedAtTime] = await rTokenAsset.cachedOracleData() + expect(cachedAtTime).to.be.gt(0) + await rTokenAsset.refresh() + ;[, cachedAtTime] = await rTokenAsset.cachedOracleData() + expect(cachedAtTime).to.eq(0) + }) + it('Should be able to refresh saved prices', async () => { // Check initial prices - use RSR as example let currBlockTimestamp: number = await getLatestBlockTimestamp() diff --git a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap index 70324e7f45..579773bc8c 100644 --- a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap @@ -14,12 +14,12 @@ exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `91147`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `91073`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `91147`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `92285`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `92211`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92211`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92285`; exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `127356`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `91414`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `91488`; diff --git a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap index 08efe17a75..eb9ff18626 100644 --- a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `116743`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `116848`; exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `108431`; diff --git a/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap b/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap deleted file mode 100644 index 3eb6942200..0000000000 --- a/test/plugins/individual-collateral/stargate/__snapshots__/StargateETHTestSuite.test.ts.snap +++ /dev/null @@ -1,49 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `55263`; - -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `50795`; - -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `68999`; - -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `66385`; - -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `55263`; - -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `50795`; - -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `50454`; - -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `50454`; - -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `66260`; - -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `66260`; - -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `73810`; - -exports[`Collateral: Stargate ETH Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `66542`; - -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `55263`; - -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `50795`; - -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `68999`; - -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `66385`; - -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `55263`; - -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `50795`; - -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `50454`; - -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `50454`; - -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `66260`; - -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `66260`; - -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `73810`; - -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `66542`; diff --git a/test/plugins/individual-collateral/stargate/__snapshots__/StargateUSDCTestSuite.test.ts.snap b/test/plugins/individual-collateral/stargate/__snapshots__/StargateUSDCTestSuite.test.ts.snap new file mode 100644 index 0000000000..fc27356e8c --- /dev/null +++ b/test/plugins/individual-collateral/stargate/__snapshots__/StargateUSDCTestSuite.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `56094`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `51626`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 1`] = `74579`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after hard default 2`] = `66897`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `56094`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `51626`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `51285`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `51285`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `66894`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 2`] = `66894`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 1`] = `74444`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during soft default 2`] = `67176`; diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index fa7f07dd1b..156f632337 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12090932`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12082293`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9829293`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9836895`; exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2281990`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13615367`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13653640`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20701409`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20688663`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10991970`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10984043`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8713193`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8707789`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `6561514`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `6592417`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `14454943`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `14441485`; From de352e1eadb169961ab722496a2aa5bd21663508 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:06:39 -0300 Subject: [PATCH 451/462] TRUST L-7: Restriction on reportViolation (#981) Co-authored-by: Taylor Brent --- contracts/p0/Broker.sol | 2 +- contracts/p1/Broker.sol | 4 +- docs/pause-freeze-states.md | 2 +- test/Broker.test.ts | 22 ------- test/Revenues.test.ts | 122 ++++++++++++++++++++++++++++++++++++ 5 files changed, 126 insertions(+), 26 deletions(-) diff --git a/contracts/p0/Broker.sol b/contracts/p0/Broker.sol index d3e0da2041..b53c2e0417 100644 --- a/contracts/p0/Broker.sol +++ b/contracts/p0/Broker.sol @@ -92,7 +92,7 @@ contract BrokerP0 is ComponentP0, IBroker { /// Disable the broker until re-enabled by governance /// @custom:protected - function reportViolation() external notTradingPausedOrFrozen { + function reportViolation() external { require(trades[_msgSender()], "unrecognized trade contract"); ITrade trade = ITrade(_msgSender()); TradeKind kind = trade.KIND(); diff --git a/contracts/p1/Broker.sol b/contracts/p1/Broker.sol index 0c233a8020..0111d25bc3 100644 --- a/contracts/p1/Broker.sol +++ b/contracts/p1/Broker.sol @@ -136,9 +136,9 @@ contract BrokerP1 is ComponentP1, IBroker { /// Disable the broker until re-enabled by governance /// @custom:protected - // checks: not paused (trading), not frozen, caller is a Trade this contract cloned + // checks: caller is a Trade this contract cloned // effects: disabled' = true - function reportViolation() external notTradingPausedOrFrozen { + function reportViolation() external { require(trades[_msgSender()], "unrecognized trade contract"); ITrade trade = ITrade(_msgSender()); TradeKind kind = trade.KIND(); diff --git a/docs/pause-freeze-states.md b/docs/pause-freeze-states.md index 009ab64b20..17b2785fcd 100644 --- a/docs/pause-freeze-states.md +++ b/docs/pause-freeze-states.md @@ -17,7 +17,7 @@ A :x: indicates it reverts. | `BackingManager.settleTrade()` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | `BasketHandler.refreshBasket()` | :heavy_check_mark: | :x: (unless governance) | :x: (unless governance) | | `Broker.openTrade()` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| `Broker.reportViolation()` | :heavy_check_mark: | :x: | :x: | +| `Broker.reportViolation()` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | `Distributor.distribute()` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | `Furnace.melt()` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | `Main.poke()` | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 90a34a56e2..251d0e1a5b 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -574,28 +574,6 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { // Check nothing changed expect(await broker.batchTradeDisabled()).to.equal(false) }) - - it('Should not allow to report violation if paused or frozen', async () => { - // Check not disabled - expect(await broker.batchTradeDisabled()).to.equal(false) - - await main.connect(owner).pauseTrading() - - await expect(broker.connect(addr1).reportViolation()).to.be.revertedWith( - 'frozen or trading paused' - ) - - await main.connect(owner).unpauseTrading() - - await main.connect(owner).freezeShort() - - await expect(broker.connect(addr1).reportViolation()).to.be.revertedWith( - 'frozen or trading paused' - ) - - // Check nothing changed - expect(await broker.batchTradeDisabled()).to.equal(false) - }) }) describe('Trades', () => { diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index b8526f1395..d631a6f2ed 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -2627,11 +2627,133 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { }, ]) + // Check broker disabled (batch) + expect(await broker.batchTradeDisabled()).to.equal(true) + // Check funds at destinations expect(await rsr.balanceOf(stRSR.address)).to.be.closeTo(minBuyAmt.sub(10), 50) expect(await rToken.balanceOf(furnace.address)).to.be.closeTo(minBuyAmtRToken.sub(10), 50) }) + it('Should report violation even if paused or frozen', async () => { + // This test needs to be in this file and not Broker.test.ts because settleTrade() + // requires the BackingManager _actually_ started the trade + + rewardAmountAAVE = bn('0.5e18') + + // AAVE Rewards + await token2.setRewards(backingManager.address, rewardAmountAAVE) + + // Collect revenue + // Expected values based on Prices between AAVE and RSR/RToken = 1 to 1 (for simplification) + const sellAmt: BigNumber = rewardAmountAAVE.mul(60).div(100) // due to f = 60% + const minBuyAmt: BigNumber = await toMinBuyAmt(sellAmt, fp('1'), fp('1')) + + const sellAmtRToken: BigNumber = rewardAmountAAVE.sub(sellAmt) // Remainder + const minBuyAmtRToken: BigNumber = await toMinBuyAmt(sellAmtRToken, fp('1'), fp('1')) + + // Claim rewards + await facadeTest.claimRewards(rToken.address) + + // Check status of destinations at this point + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + + // Run auctions + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: rsrTrader, + name: 'TradeStarted', + args: [anyValue, aaveToken.address, rsr.address, sellAmt, withinQuad(minBuyAmt)], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + args: [ + anyValue, + aaveToken.address, + rToken.address, + sellAmtRToken, + withinQuad(minBuyAmtRToken), + ], + emitted: true, + }, + ]) + + // Advance time till auction ended + await advanceTime(config.batchAuctionLength.add(100).toString()) + + // Perform Mock Bids for RSR and RToken (addr1 has balance) + // In order to force deactivation we provide an amount below minBuyAmt, this will represent for our tests an invalid behavior although in a real scenario would retrigger auction + // NOTE: DIFFERENT BEHAVIOR WILL BE OBSERVED ON PRODUCTION GNOSIS AUCTIONS + await rsr.connect(addr1).approve(gnosis.address, minBuyAmt) + await rToken.connect(addr1).approve(gnosis.address, minBuyAmtRToken) + await gnosis.placeBid(0, { + bidder: addr1.address, + sellAmount: sellAmt, + buyAmount: minBuyAmt.sub(10), // Forces in our mock an invalid behavior + }) + await gnosis.placeBid(1, { + bidder: addr1.address, + sellAmount: sellAmtRToken, + buyAmount: minBuyAmtRToken.sub(10), // Forces in our mock an invalid behavior + }) + + // Freeze protocol + await main.connect(owner).freezeShort() + + // Close auctions - Will end trades and also report violation + await expectEvents(facadeTest.runAuctionsForAllTraders(rToken.address), [ + { + contract: broker, + name: 'BatchTradeDisabledSet', + args: [false, true], + emitted: true, + }, + { + contract: rsrTrader, + name: 'TradeSettled', + args: [anyValue, aaveToken.address, rsr.address, sellAmt, minBuyAmt.sub(10)], + emitted: true, + }, + { + contract: rTokenTrader, + name: 'TradeSettled', + args: [ + anyValue, + aaveToken.address, + rToken.address, + sellAmtRToken, + minBuyAmtRToken.sub(10), + ], + emitted: true, + }, + { + contract: rsrTrader, + name: 'TradeStarted', + emitted: false, + }, + { + contract: rTokenTrader, + name: 'TradeStarted', + emitted: false, + }, + ]) + + // Check broker disabled (batch) + expect(await broker.batchTradeDisabled()).to.equal(true) + + // Funds are not distributed if paused or frozen + expect(await rsr.balanceOf(stRSR.address)).to.equal(0) + expect(await rsr.balanceOf(rsrTrader.address)).to.be.closeTo(minBuyAmt.sub(10), 50) + expect(await rToken.balanceOf(furnace.address)).to.equal(0) + expect(await rToken.balanceOf(rTokenTrader.address)).to.be.closeTo( + minBuyAmtRToken.sub(10), + 50 + ) + }) + it('Should not report violation when Dutch Auction clears in geometric phase', async () => { // This test needs to be in this file and not Broker.test.ts because settleTrade() // requires the BackingManager _actually_ started the trade From fcade85cbcd00d7b485453caf2b076ae199c2bdb Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Thu, 19 Oct 2023 10:08:15 -0400 Subject: [PATCH 452/462] Trst-M-11 (#979) --- contracts/plugins/assets/RTokenAsset.sol | 34 +++++++++++------- .../CurveStableRTokenMetapoolCollateral.sol | 5 +++ contracts/plugins/mocks/AssetMock.sol | 9 ++++- .../CrvStableRTokenMetapoolTestSuite.test.ts | 35 +++++++++++++++++++ .../CvxStableRTokenMetapoolTestSuite.test.ts | 35 +++++++++++++++++++ 5 files changed, 104 insertions(+), 14 deletions(-) diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index f187651e34..f82f2ee185 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -19,12 +19,14 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { // Component addresses are not mutable in protocol, so it's safe to cache these IMain public immutable main; - IBasketHandler public immutable basketHandler; IAssetRegistry public immutable assetRegistry; IBackingManager public immutable backingManager; + IBasketHandler public immutable basketHandler; IFurnace public immutable furnace; + IERC20 public immutable rsr; + IStRSR public immutable stRSR; - IERC20Metadata public immutable erc20; + IERC20Metadata public immutable erc20; // The RToken uint8 public immutable erc20Decimals; @@ -39,10 +41,12 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { require(maxTradeVolume_ > 0, "invalid max trade volume"); main = erc20_.main(); - basketHandler = main.basketHandler(); assetRegistry = main.assetRegistry(); backingManager = main.backingManager(); + basketHandler = main.basketHandler(); furnace = main.furnace(); + rsr = main.rsr(); + stRSR = main.stRSR(); erc20 = IERC20Metadata(address(erc20_)); erc20Decimals = erc20_.decimals(); @@ -79,18 +83,15 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { assert(low <= high); // not obviously true } - // solhint-disable no-empty-blocks function refresh() public virtual override { // No need to save lastPrice; can piggyback off the backing collateral's saved prices - if (msg.sender != address(assetRegistry)) assetRegistry.refresh(); furnace.melt(); + if (msg.sender != address(assetRegistry)) assetRegistry.refresh(); cachedOracleData.cachedAtTime = 0; // force oracle refresh } - // solhint-enable no-empty-blocks - /// Should not revert /// @dev See `tryPrice` caveat about possible compounding error in calculating price /// @return {UoA/tok} The lower end of the price estimate @@ -130,10 +131,15 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { // solhint-enable no-empty-blocks + /// Force an update to the cache, including refreshing underlying assets + /// @dev Can revert if RToken is unpriced function forceUpdatePrice() external { _updateCachedPrice(); } + /// @dev Can revert if RToken is unpriced + /// @return rTokenPrice {UoA/tok} The mean price estimate + /// @return updatedAt {s} The timestamp of the cache update function latestPrice() external returns (uint192 rTokenPrice, uint256 updatedAt) { // Situations that require an update, from most common to least common. if ( @@ -145,15 +151,17 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { _updateCachedPrice(); } - return (cachedOracleData.cachedPrice, cachedOracleData.cachedAtTime); + rTokenPrice = cachedOracleData.cachedPrice; + updatedAt = cachedOracleData.cachedAtTime; } // ==== Private ==== // Update Oracle Data function _updateCachedPrice() internal { - (uint192 low, uint192 high) = price(); + assetRegistry.refresh(); // will call furnace.melt() + (uint192 low, uint192 high) = price(); require(low != 0 && high != FIX_MAX, "invalid price"); cachedOracleData = CachedOracleData( @@ -183,12 +191,12 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { TradingContext memory ctx; ctx.basketsHeld = basketsHeld; + ctx.ar = assetRegistry; ctx.bm = backingManager; ctx.bh = basketHandler; - ctx.ar = assetRegistry; - ctx.stRSR = main.stRSR(); - ctx.rsr = main.rsr(); - ctx.rToken = main.rToken(); + ctx.rsr = rsr; + ctx.rToken = IRToken(address(erc20)); + ctx.stRSR = stRSR; ctx.minTradeVolume = backingManager.minTradeVolume(); ctx.maxTradeSlippage = backingManager.maxTradeSlippage(); diff --git a/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol b/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol index 420e002f4a..780a083a8b 100644 --- a/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableRTokenMetapoolCollateral.sol @@ -42,6 +42,11 @@ contract CurveStableRTokenMetapoolCollateral is CurveStableMetapoolCollateral { pairedAssetRegistry = IRToken(address(pairedToken)).main().assetRegistry(); } + function refresh() public override { + pairedAssetRegistry.refresh(); // refresh all registered assets + super.refresh(); // already handles all necessary default checks + } + /// Can revert, used by `_anyDepeggedOutsidePool()` /// Should not return FIX_MAX for low /// @return lowPaired {UoA/pairedTok} The low price estimate of the paired token diff --git a/contracts/plugins/mocks/AssetMock.sol b/contracts/plugins/mocks/AssetMock.sol index c1b495380f..0396a5ea35 100644 --- a/contracts/plugins/mocks/AssetMock.sol +++ b/contracts/plugins/mocks/AssetMock.sol @@ -4,6 +4,8 @@ pragma solidity 0.8.19; import "../assets/Asset.sol"; contract AssetMock is Asset { + bool public stale; + uint192 private lowPrice; uint192 private highPrice; @@ -40,13 +42,18 @@ contract AssetMock is Asset { uint192 ) { + require(!stale, "stale price"); return (lowPrice, highPrice, 0); } /// Should not revert /// Refresh saved prices function refresh() public virtual override { - // pass + stale = false; + } + + function setStale(bool _stale) external { + stale = _stale; } function setPrice(uint192 low, uint192 high) external { diff --git a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts index a97702d5af..fd62e8ee75 100644 --- a/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/crv/CrvStableRTokenMetapoolTestSuite.test.ts @@ -241,6 +241,41 @@ const collateralSpecificStatusTests = () => { // refresh() should not revert await collateral.refresh() }) + + it('Regression test -- refreshes inner RTokenAsset on refresh()', async () => { + const [collateral] = await deployCollateral({}) + const initialPrice = await collateral.price() + expect(initialPrice[0]).to.be.gt(0) + expect(initialPrice[1]).to.be.lt(MAX_UINT192) + + // Swap out eUSD's RTokenAsset with a mock one + const AssetMockFactory = await ethers.getContractFactory('AssetMock') + const mockRTokenAsset = await AssetMockFactory.deploy( + bn('1'), // unused + ONE_ADDRESS, // unused + bn('1'), // unused + eUSD, + bn('1'), // unused + bn('1') // unused + ) + const eUSDAssetRegistry = await ethers.getContractAt( + 'IAssetRegistry', + '0x9B85aC04A09c8C813c37de9B3d563C2D3F936162' + ) + await whileImpersonating('0xc8Ee187A5e5c9dC9b42414Ddf861FFc615446a2c', async (signer) => { + await eUSDAssetRegistry.connect(signer).swapRegistered(mockRTokenAsset.address) + }) + + // Set RTokenAsset price to stale + await mockRTokenAsset.setStale(true) + expect(await mockRTokenAsset.stale()).to.be.true + + // Refresh CurveStableRTokenMetapoolCollateral + await collateral.refresh() + + // Stale should be false again + expect(await mockRTokenAsset.stale()).to.be.false + }) } /* diff --git a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts index bfb1f30180..ab50ef36a4 100644 --- a/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts +++ b/test/plugins/individual-collateral/curve/cvx/CvxStableRTokenMetapoolTestSuite.test.ts @@ -243,6 +243,41 @@ const collateralSpecificStatusTests = () => { // refresh() should not revert await collateral.refresh() }) + + it('Regression test -- refreshes inner RTokenAsset on refresh()', async () => { + const [collateral] = await deployCollateral({}) + const initialPrice = await collateral.price() + expect(initialPrice[0]).to.be.gt(0) + expect(initialPrice[1]).to.be.lt(MAX_UINT192) + + // Swap out eUSD's RTokenAsset with a mock one + const AssetMockFactory = await ethers.getContractFactory('AssetMock') + const mockRTokenAsset = await AssetMockFactory.deploy( + bn('1'), // unused + ONE_ADDRESS, // unused + bn('1'), // unused + eUSD, + bn('1'), // unused + bn('1') // unused + ) + const eUSDAssetRegistry = await ethers.getContractAt( + 'IAssetRegistry', + '0x9B85aC04A09c8C813c37de9B3d563C2D3F936162' + ) + await whileImpersonating('0xc8Ee187A5e5c9dC9b42414Ddf861FFc615446a2c', async (signer) => { + await eUSDAssetRegistry.connect(signer).swapRegistered(mockRTokenAsset.address) + }) + + // Set RTokenAsset price to stale + await mockRTokenAsset.setStale(true) + expect(await mockRTokenAsset.stale()).to.be.true + + // Refresh CurveStableRTokenMetapoolCollateral + await collateral.refresh() + + // Stale should be false again + expect(await mockRTokenAsset.stale()).to.be.false + }) } /* From 35b063c281418991f320aecfb9f84528032b5bf9 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Fri, 20 Oct 2023 10:51:54 -0300 Subject: [PATCH 453/462] TRUST M-4: Call reward accounting functions (#984) Co-authored-by: Taylor Brent --- contracts/p0/BackingManager.sol | 1 + contracts/p0/Distributor.sol | 13 +++++++++ contracts/p1/BackingManager.sol | 4 ++- contracts/p1/Distributor.sol | 13 +++++++++ test/Revenues.test.ts | 48 +++++++++++++++++++++++++++++++-- 5 files changed, 76 insertions(+), 3 deletions(-) diff --git a/contracts/p0/BackingManager.sol b/contracts/p0/BackingManager.sol index 2d4bb0de23..946534141e 100644 --- a/contracts/p0/BackingManager.sol +++ b/contracts/p0/BackingManager.sol @@ -164,6 +164,7 @@ contract BackingManagerP0 is TradingP0, IBackingManager { uint256 rsrBal = main.rsr().balanceOf(address(this)); if (rsrBal > 0) { main.rsr().safeTransfer(address(main.stRSR()), rsrBal); + main.stRSR().payoutRewards(); } // Mint revenue RToken diff --git a/contracts/p0/Distributor.sol b/contracts/p0/Distributor.sol index 8c5f429b69..9f43240c5e 100644 --- a/contracts/p0/Distributor.sol +++ b/contracts/p0/Distributor.sol @@ -70,6 +70,8 @@ contract DistributorP0 is ComponentP0, IDistributor { // Evenly distribute revenue tokens per distribution share. // This rounds "early", and that's deliberate! + bool accountRewards = false; + for (uint256 i = 0; i < destinations.length(); i++) { address addrTo = destinations.at(i); @@ -81,12 +83,23 @@ contract DistributorP0 is ComponentP0, IDistributor { if (addrTo == FURNACE) { addrTo = address(main.furnace()); + if (transferAmt > 0) accountRewards = true; } else if (addrTo == ST_RSR) { addrTo = address(main.stRSR()); + if (transferAmt > 0) accountRewards = true; } erc20.safeTransferFrom(_msgSender(), addrTo, transferAmt); } emit RevenueDistributed(erc20, _msgSender(), amount); + + // Perform reward accounting + if (accountRewards) { + if (isRSR) { + main.stRSR().payoutRewards(); + } else { + main.furnace().melt(); + } + } } /// Returns the rsr + rToken shareTotals diff --git a/contracts/p1/BackingManager.sol b/contracts/p1/BackingManager.sol index da8473e662..7763ddb771 100644 --- a/contracts/p1/BackingManager.sol +++ b/contracts/p1/BackingManager.sol @@ -210,10 +210,12 @@ contract BackingManagerP1 is TradingP1, IBackingManager { * RToken traders according to the distribution totals. */ - // Forward any RSR held to StRSR pool; RSR should never be sold for RToken yield + // Forward any RSR held to StRSR pool and payout rewards + // RSR should never be sold for RToken yield if (rsr.balanceOf(address(this)) > 0) { // For CEI, this is an interaction "within our system" even though RSR is already live IERC20(address(rsr)).safeTransfer(address(stRSR), rsr.balanceOf(address(this))); + stRSR.payoutRewards(); } // Mint revenue RToken diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index bce53c5eff..04fedb57ee 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -112,6 +112,8 @@ contract DistributorP1 is ComponentP1, IDistributor { address furnaceAddr = furnace; // gas-saver address stRSRAddr = stRSR; // gas-saver + bool accountRewards = false; + for (uint256 i = 0; i < destinations.length(); ++i) { address addrTo = destinations.at(i); @@ -123,8 +125,10 @@ contract DistributorP1 is ComponentP1, IDistributor { if (addrTo == FURNACE) { addrTo = furnaceAddr; + if (transferAmt > 0) accountRewards = true; } else if (addrTo == ST_RSR) { addrTo = stRSRAddr; + if (transferAmt > 0) accountRewards = true; } transfers[numTransfers] = Transfer({ @@ -141,6 +145,15 @@ contract DistributorP1 is ComponentP1, IDistributor { Transfer memory t = transfers[i]; IERC20Upgradeable(address(t.erc20)).safeTransferFrom(caller, t.addrTo, t.amount); } + + // Perform reward accounting + if (accountRewards) { + if (isRSR) { + main.stRSR().payoutRewards(); + } else { + main.furnace().melt(); + } + } } /// The rsr and rToken shareTotals diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index d631a6f2ed..f4bcf4b8d0 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -13,6 +13,7 @@ import { CollateralStatus, TradeKind, MAX_UINT192, + ONE_PERIOD, } from '../common/constants' import { expectEvents } from '../common/events' import { bn, divCeil, fp, near } from '../common/numbers' @@ -554,7 +555,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ) }) - it('Should forward RSR revenue directly to StRSR', async () => { + it('Should forward RSR revenue directly to StRSR and call payoutRewards()', async () => { const amount = bn('2000e18') await rsr.connect(owner).mint(backingManager.address, amount) expect(await rsr.balanceOf(backingManager.address)).to.equal(amount) @@ -562,7 +563,23 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(await rsr.balanceOf(rsrTrader.address)).to.equal(0) expect(await rsr.balanceOf(rTokenTrader.address)).to.equal(0) - await expect(backingManager.forwardRevenue([rsr.address])).to.emit(rsr, 'Transfer') + // Advance to the end of noop period + await advanceTime(Number(ONE_PERIOD)) + + await expectEvents(backingManager.forwardRevenue([rsr.address]), [ + { + contract: rsr, + name: 'Transfer', + args: [backingManager.address, stRSR.address, amount], + emitted: true, + }, + { + contract: stRSR, + name: 'RewardsPaid', + emitted: true, + }, + ]) + expect(await rsr.balanceOf(backingManager.address)).to.equal(0) expect(await rsr.balanceOf(stRSR.address)).to.equal(amount) expect(await rsr.balanceOf(rsrTrader.address)).to.equal(0) @@ -688,6 +705,33 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(newRTokenTotal).equal(bn(0)) }) + it('Should account rewards when distributing tokenToBuy', async () => { + // 1. StRSR.payoutRewards() + const stRSRBal = await rsr.balanceOf(stRSR.address) + await rsr.connect(owner).mint(rsrTrader.address, issueAmount) + await advanceTime(Number(ONE_PERIOD)) + await expect(rsrTrader.distributeTokenToBuy()).to.emit(stRSR, 'RewardsPaid') + const expectedAmountStRSR = stRSRBal.add(issueAmount) + expect(await rsr.balanceOf(stRSR.address)).to.be.closeTo(expectedAmountStRSR, 100) + + // 2. Furnace.melt() + // Transfer RTokens to Furnace (to trigger melting later) + const hndAmt: BigNumber = bn('10e18') + await rToken.connect(addr1).transfer(furnace.address, hndAmt) + await advanceTime(Number(ONE_PERIOD)) + await furnace.melt() + + // Transfer and distribute tokens in Trader (will melt) + await advanceTime(Number(ONE_PERIOD)) + await rToken.connect(addr1).transfer(rTokenTrader.address, hndAmt) + await expect(rTokenTrader.distributeTokenToBuy()).to.emit(rToken, 'Melted') + const expectedAmountFurnace = hndAmt.mul(2) + expect(await rToken.balanceOf(furnace.address)).to.be.closeTo( + expectedAmountFurnace, + expectedAmountFurnace.div(1000) + ) // within 0.1% + }) + it('Should update distribution even if distributeTokenToBuy() reverts', async () => { // Check initial status const [rTokenTotal, rsrTotal] = await distributor.totals() From 124e40115d1b2d1cd628901f83c56516b66ab1f7 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Fri, 20 Oct 2023 19:09:58 -0400 Subject: [PATCH 454/462] gas snapshots --- test/__snapshots__/Broker.test.ts.snap | 10 +-- test/__snapshots__/FacadeWrite.test.ts.snap | 2 +- test/__snapshots__/Main.test.ts.snap | 2 +- test/__snapshots__/RToken.test.ts.snap | 6 +- .../Recollateralization.test.ts.snap | 18 ++--- test/__snapshots__/Revenues.test.ts.snap | 26 +++---- test/__snapshots__/ZZStRSR.test.ts.snap | 8 +-- .../__snapshots__/CTokenVaultGas.test.ts.snap | 25 ------- .../AaveV3FiatCollateral.test.ts.snap | 12 +--- .../ATokenFiatCollateral.test.ts.snap | 17 +++-- .../AnkrEthCollateralTestSuite.test.ts.snap | 2 +- .../CBETHCollateral.test.ts.snap | 2 +- .../CTokenFiatCollateral.test.ts.snap | 16 ++--- .../__snapshots__/CometTestSuite.test.ts.snap | 2 +- .../CrvStableMetapoolSuite.test.ts.snap | 2 +- ...StableRTokenMetapoolTestSuite.test.ts.snap | 24 +++---- .../CrvStableTestSuite.test.ts.snap | 2 +- .../CvxStableMetapoolSuite.test.ts.snap | 2 +- ...StableRTokenMetapoolTestSuite.test.ts.snap | 24 +++---- .../CvxStableTestSuite.test.ts.snap | 2 +- .../SDaiCollateralTestSuite.test.ts.snap | 6 +- .../FTokenFiatCollateral.test.ts.snap | 68 +++++++++---------- .../SFrxEthTestSuite.test.ts.snap | 2 +- .../LidoStakedEthTestSuite.test.ts.snap | 2 +- .../MorphoAAVEFiatCollateral.test.ts.snap | 6 +- .../MorphoAAVENonFiatCollateral.test.ts.snap | 4 +- ...AAVESelfReferentialCollateral.test.ts.snap | 2 +- .../RethCollateralTestSuite.test.ts.snap | 2 +- .../StargateUSDCTestSuite.test.ts.snap | 4 ++ .../__snapshots__/MaxBasketSize.test.ts.snap | 18 ++--- 30 files changed, 144 insertions(+), 174 deletions(-) delete mode 100644 test/integration/__snapshots__/CTokenVaultGas.test.ts.snap diff --git a/test/__snapshots__/Broker.test.ts.snap b/test/__snapshots__/Broker.test.ts.snap index 635ba04aab..ceb61ab16e 100644 --- a/test/__snapshots__/Broker.test.ts.snap +++ b/test/__snapshots__/Broker.test.ts.snap @@ -10,12 +10,12 @@ exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Open Trade 3`] = `371 exports[`BrokerP1 contract #fast Gas Reporting DutchTrade Settle Trade 1`] = `63333`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Initialize Trade 1`] = `451427`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Initialize Trade 1`] = `449950`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 1`] = `541279`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 1`] = `539802`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 2`] = `529117`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 2`] = `527640`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 3`] = `531255`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Open Trade 3`] = `529778`; -exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Settle Trade 1`] = `113056`; +exports[`BrokerP1 contract #fast Gas Reporting GnosisTrade Settle Trade 1`] = `113028`; diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index 143651b3a3..71ba5454b2 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8572904`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8532211`; exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464253`; diff --git a/test/__snapshots__/Main.test.ts.snap b/test/__snapshots__/Main.test.ts.snap index ffecb35bc5..80025274f2 100644 --- a/test/__snapshots__/Main.test.ts.snap +++ b/test/__snapshots__/Main.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `393837`; +exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `393877`; exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `195889`; diff --git a/test/__snapshots__/RToken.test.ts.snap b/test/__snapshots__/RToken.test.ts.snap index c725eeb995..78da6ac501 100644 --- a/test/__snapshots__/RToken.test.ts.snap +++ b/test/__snapshots__/RToken.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `782158`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 1`] = `782198`; -exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `609158`; +exports[`RTokenP1 contract Gas Reporting Issuance: within block 2`] = `609198`; -exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `583862`; +exports[`RTokenP1 contract Gas Reporting Redemption 1`] = `583902`; exports[`RTokenP1 contract Gas Reporting Transfer 1`] = `56658`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index ddde5b4fe3..6b70c5b36c 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1365473`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1363847`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1500861`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1499568`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `755062`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `738601`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1659319`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1656950`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `174808`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `174781`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1602220`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1599095`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `174808`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `174781`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1689982`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1687568`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `202908`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `202854`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index bb7a77e725..ac389f42c3 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -1,27 +1,27 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 1`] = `164974`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 1`] = `168005`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `165027`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 2`] = `168058`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `165027`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 3`] = `168058`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `208624`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 4`] = `211655`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `229377`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 5`] = `232408`; -exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `212277`; +exports[`Revenues - P1 Gas Reporting Claim and Sweep Rewards 6`] = `215308`; -exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1020522`; +exports[`Revenues - P1 Gas Reporting Selling RToken 1`] = `1044877`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `774070`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `796514`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1175816`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1198554`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `311446`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `367726`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `266512`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `317915`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `739870`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `762314`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `242306`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `284933`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index 63500d7165..08449237d2 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`StRSRP1 contract Gas Reporting Stake 1`] = `139717`; +exports[`StRSRP1 contract Gas Reporting Stake 1`] = `156559`; exports[`StRSRP1 contract Gas Reporting Stake 2`] = `134917`; @@ -10,10 +10,10 @@ exports[`StRSRP1 contract Gas Reporting Transfer 2`] = `41509`; exports[`StRSRP1 contract Gas Reporting Transfer 3`] = `58621`; -exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `222609`; +exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `241951`; exports[`StRSRP1 contract Gas Reporting Unstake 2`] = `139758`; -exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `606273`; +exports[`StRSRP1 contract Gas Reporting Withdraw 1`] = `606313`; -exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `536407`; +exports[`StRSRP1 contract Gas Reporting Withdraw 2`] = `536447`; diff --git a/test/integration/__snapshots__/CTokenVaultGas.test.ts.snap b/test/integration/__snapshots__/CTokenVaultGas.test.ts.snap deleted file mode 100644 index a9ec5a85ce..0000000000 --- a/test/integration/__snapshots__/CTokenVaultGas.test.ts.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CTokenVault contract Gas Reporting, cTokens Unwrapped Issue RToken 1`] = `816857`; - -exports[`CTokenVault contract Gas Reporting, cTokens Unwrapped Issue RToken 2`] = `677455`; - -exports[`CTokenVault contract Gas Reporting, cTokens Unwrapped Redeem RToken 1`] = `679421`; - -exports[`CTokenVault contract Gas Reporting, cTokens Unwrapped Transfer 1`] = `159307`; - -exports[`CTokenVault contract Gas Reporting, cTokens Unwrapped Transfer 2`] = `127937`; - -exports[`CTokenVault contract Gas Reporting, cTokens Unwrapped Transfer 3`] = `110849`; - -exports[`CTokenVault contract Gas Reporting, cTokens Wrapped Issue RToken 1`] = `965241`; - -exports[`CTokenVault contract Gas Reporting, cTokens Wrapped Issue RToken 2`] = `753143`; - -exports[`CTokenVault contract Gas Reporting, cTokens Wrapped Redeem RToken 1`] = `748958`; - -exports[`CTokenVault contract Gas Reporting, cTokens Wrapped Transfer 1`] = `310005`; - -exports[`CTokenVault contract Gas Reporting, cTokens Wrapped Transfer 2`] = `193085`; - -exports[`CTokenVault contract Gas Reporting, cTokens Wrapped Transfer 3`] = `175997`; diff --git a/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap index e3fbc7a99f..75cffdf3ef 100644 --- a/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap @@ -1,14 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -<<<<<<< HEAD -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69288`; -======= exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting ERC20 transfer 1`] = `53509`; exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting ERC20 transfer 2`] = `36409`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69299`; ->>>>>>> master +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69288`; exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after full price timeout 2`] = `67620`; @@ -28,10 +24,6 @@ exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during SOUND 2`] = `87684`; -<<<<<<< HEAD exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during soft default 1`] = `89634`; -======= -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during soft default 1`] = `89656`; ->>>>>>> master -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during soft default 2`] = `87966`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during soft default 2`] = `88040`; diff --git a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap index 2b2ca446a4..236eb477ff 100644 --- a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap @@ -4,7 +4,7 @@ exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting ERC20 Wrapper t exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting ERC20 Wrapper transfer 2`] = `53409`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `74365`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `74354`; exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `72686`; @@ -16,15 +16,14 @@ exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `72686`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `91169`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `91073`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `91147`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `91073`; -<<<<<<< HEAD -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `92211`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `92285`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92285`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `127378`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92211`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `91436`; ->>>>>>> master +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `127282`; + +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `91414`; diff --git a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap index 4dca33bfd7..64458dcd1a 100644 --- a/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/ankr/__snapshots__/AnkrEthCollateralTestSuite.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting ERC20 exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting ERC20 transfer 2`] = `43994`; -exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `60337`; +exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `60326`; exports[`Collateral: AnkrStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55857`; diff --git a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap index aeedbbd68d..f7366a3fb9 100644 --- a/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/cbeth/__snapshots__/CBETHCollateral.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting ERC2 exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting ERC20 transfer 2`] = `48379`; -exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59824`; +exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `59813`; exports[`Collateral: CBEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `55344`; diff --git a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap index 35e48fb603..23638304fb 100644 --- a/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/compoundv2/__snapshots__/CTokenFiatCollateral.test.ts.snap @@ -4,26 +4,26 @@ exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting ERC20 transfer exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting ERC20 transfer 2`] = `173113`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `119361`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 1`] = `119350`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `117692`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after full price timeout 2`] = `117681`; exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 1`] = `76220`; exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after hard default 2`] = `68538`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `119361`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 1`] = `119350`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `117692`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after oracle timeout 2`] = `117681`; exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 1`] = `138759`; exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after soft default 2`] = `138685`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `139858`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `139836`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `139858`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `139836`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `175004`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `174982`; -exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `139061`; +exports[`CTokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `139039`; diff --git a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap index 0d9ab7955c..68d964d679 100644 --- a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting ERC20 exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting ERC20 transfer 2`] = `90521`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `109063`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `109052`; exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `104315`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap index 92e650e5ae..0181aff186 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collatera exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting ERC20 Wrapper transfer 2`] = `360937`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `52705`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `52713`; exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48245`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap index 2108bd826b..d4cbecc6e1 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap @@ -4,26 +4,26 @@ exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper col exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting ERC20 Wrapper transfer 2`] = `385743`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69638`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `485294`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `99581`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `480900`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `226544`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `594808`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `221662`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `589778`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `101430`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `478472`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `96962`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `474078`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `96621`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `544737`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `96621`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `536931`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `221659`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `713285`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `221659`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `713507`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `209203`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `701199`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `201935`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `693635`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap index 4d56f35fab..ffa84bf243 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableTestSuite.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functi exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting ERC20 Wrapper transfer 2`] = `369452`; -exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61895`; +exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61884`; exports[`Collateral: CurveStableCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57416`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap index f66c4f606f..9b9c0827d4 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collat exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting ERC20 Wrapper transfer 2`] = `172551`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `52705`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `52713`; exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `48245`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap index 20ba537bf2..c1e15dc5e3 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap @@ -4,26 +4,26 @@ exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting ERC20 Wrapper transfer 2`] = `175188`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `69638`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `485294`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `99581`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `480826`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `226544`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `594734`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `221662`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `589852`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `101430`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `478546`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `96962`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `474078`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `96621`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `544811`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `96621`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `536931`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `221659`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `713211`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `221659`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `713581`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `209203`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `701125`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `201935`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `693635`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap index de3a1022a7..80285bbb17 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableTestSuite.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral fun exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting ERC20 Wrapper transfer 2`] = `123705`; -exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61895`; +exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `61884`; exports[`Collateral: CurveStableCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `57416`; diff --git a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap index 744ad759c1..c999a57b94 100644 --- a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting ERC20 exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting ERC20 transfer 2`] = `34259`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `116754`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `116743`; exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `108431`; @@ -24,6 +24,6 @@ exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refre exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `123298`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `131222`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 1`] = `131126`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `123676`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during soft default 2`] = `123580`; diff --git a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap index e1abcec332..e9d415b72d 100644 --- a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap @@ -4,110 +4,110 @@ exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting ERC2 exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting ERC20 transfer 2`] = `105831`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117361`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117350`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `115692`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `115681`; exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `140959`; exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139145`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117361`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117350`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115692`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115681`; exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `115327`; exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `115327`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139177`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139155`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139177`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139155`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141202`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141106`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139459`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139511`; exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting ERC20 transfer 1`] = `142854`; exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting ERC20 transfer 2`] = `105831`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117553`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117542`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `115884`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `115873`; exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `141215`; exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `139401`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117553`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `117542`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115884`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115873`; exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `115519`; exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `115519`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139433`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139411`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139433`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `139411`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141458`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `141362`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139789`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `139693`; exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting ERC20 transfer 1`] = `142854`; exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting ERC20 transfer 2`] = `105831`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `125843`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `125832`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `124174`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `124163`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `150019`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `149927`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `148135`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `148183`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `125843`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `125832`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `124174`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `124163`; exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `123809`; exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `123809`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `148215`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `148193`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `148145`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `148193`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `150096`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `150074`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `148427`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `148475`; exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting ERC20 transfer 1`] = `142854`; exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting ERC20 transfer 2`] = `105831`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `120491`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `120480`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `118822`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `118811`; exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `144361`; exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `142547`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `120491`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `120480`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `118822`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `118811`; exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `118457`; exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `118457`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `142579`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `142487`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `142509`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 2`] = `142557`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `144530`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 1`] = `144438`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `142861`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during soft default 2`] = `142839`; diff --git a/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap index e5ccda2c70..58a27e8317 100644 --- a/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/frax-eth/__snapshots__/SFrxEthTestSuite.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting E exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting ERC20 transfer 2`] = `34204`; -exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `58995`; +exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `58984`; exports[`Collateral: SFraxEthCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `54247`; diff --git a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap index ef1848e9b3..7abbfa8057 100644 --- a/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/lido/__snapshots__/LidoStakedEthTestSuite.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting ERC20 exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting ERC20 transfer 2`] = `34564`; -exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `88044`; +exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `88033`; exports[`Collateral: LidoStakedETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `83564`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap index b87d60f332..48f07dc32e 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality G exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting ERC20 transfer 2`] = `56781`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134222`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134211`; exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129742`; @@ -32,7 +32,7 @@ exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting ERC20 transfer 2`] = `56781`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134425`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `134414`; exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129945`; @@ -60,7 +60,7 @@ exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting ERC20 transfer 2`] = `56781`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133578`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133567`; exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129098`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap index 145a8cd7a9..a89c7240b4 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionali exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting ERC20 transfer 2`] = `56781`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133645`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 1`] = `133634`; exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after full price timeout 2`] = `129165`; @@ -32,7 +32,7 @@ exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functional exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting ERC20 transfer 2`] = `56781`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `167277`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `167266`; exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `162797`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap index 1b16470a46..a420cba2b6 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVESelfReferentialCollateral.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral fun exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting ERC20 transfer 2`] = `56781`; -exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `201563`; +exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `201552`; exports[`Collateral: MorphoAAVEV2SelfReferentialCollateral - WETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `197083`; diff --git a/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap index a4cdca2e47..93f294ae7d 100644 --- a/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/rocket-eth/__snapshots__/RethCollateralTestSuite.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting ERC20 exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting ERC20 transfer 2`] = `42321`; -exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `70887`; +exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 1`] = `70876`; exports[`Collateral: RocketPoolETH collateral functionality Gas Reporting refresh() after full price timeout 2`] = `66407`; diff --git a/test/plugins/individual-collateral/stargate/__snapshots__/StargateUSDCTestSuite.test.ts.snap b/test/plugins/individual-collateral/stargate/__snapshots__/StargateUSDCTestSuite.test.ts.snap index fc27356e8c..3afcb28f2e 100644 --- a/test/plugins/individual-collateral/stargate/__snapshots__/StargateUSDCTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/stargate/__snapshots__/StargateUSDCTestSuite.test.ts.snap @@ -1,5 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting ERC20 transfer 1`] = `109345`; + +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting ERC20 transfer 2`] = `92245`; + exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 1`] = `56094`; exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after full price timeout 2`] = `51626`; diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index 156f632337..73f9cbd319 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12082293`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12082333`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9836895`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9823929`; -exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2281990`; +exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2436571`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13653640`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13653680`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20688663`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20685978`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10984043`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10984083`; -exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8707789`; +exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 2`] = `8720835`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `6592417`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `6592449`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `14441485`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `14438756`; From 6fccc54b0deac8fba7ef4f5fa461c01c2e94a1f1 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Mon, 23 Oct 2023 20:10:15 -0300 Subject: [PATCH 455/462] TRUST QA-4: Smoother exposedRefPrice in case of default (#987) Co-authored-by: Taylor Brent --- .../assets/AppreciatingFiatCollateral.sol | 2 +- contracts/plugins/assets/L2LSDCollateral.sol | 2 +- .../assets/compoundv3/CTokenV3Collateral.sol | 2 +- .../assets/curve/CurveStableCollateral.sol | 2 +- .../individual-collateral/collateralTests.ts | 15 +++++++++++++++ .../compoundv3/CometTestSuite.test.ts | 9 +++++++++ .../curve/collateralTests.ts | 7 +++++++ .../frax-eth/SFrxEthTestSuite.test.ts | 18 ++++++++++++++---- 8 files changed, 49 insertions(+), 8 deletions(-) diff --git a/contracts/plugins/assets/AppreciatingFiatCollateral.sol b/contracts/plugins/assets/AppreciatingFiatCollateral.sol index bf7cef6022..60e575cf71 100644 --- a/contracts/plugins/assets/AppreciatingFiatCollateral.sol +++ b/contracts/plugins/assets/AppreciatingFiatCollateral.sol @@ -88,7 +88,7 @@ abstract contract AppreciatingFiatCollateral is FiatCollateral { // uint192(<) is equivalent to Fix.lt if (underlyingRefPerTok < exposedReferencePrice) { - exposedReferencePrice = hiddenReferencePrice; + exposedReferencePrice = underlyingRefPerTok; markStatus(CollateralStatus.DISABLED); } else if (hiddenReferencePrice > exposedReferencePrice) { exposedReferencePrice = hiddenReferencePrice; diff --git a/contracts/plugins/assets/L2LSDCollateral.sol b/contracts/plugins/assets/L2LSDCollateral.sol index c2d7cd3cd6..60b0bd8329 100644 --- a/contracts/plugins/assets/L2LSDCollateral.sol +++ b/contracts/plugins/assets/L2LSDCollateral.sol @@ -53,7 +53,7 @@ abstract contract L2LSDCollateral is AppreciatingFiatCollateral { // uint192(<) is equivalent to Fix.lt if (underlyingRefPerTok < exposedReferencePrice) { - exposedReferencePrice = hiddenReferencePrice; + exposedReferencePrice = underlyingRefPerTok; markStatus(CollateralStatus.DISABLED); } else if (hiddenReferencePrice > exposedReferencePrice) { exposedReferencePrice = hiddenReferencePrice; diff --git a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol index 281688968c..92f8a2d203 100644 --- a/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol +++ b/contracts/plugins/assets/compoundv3/CTokenV3Collateral.sol @@ -77,7 +77,7 @@ contract CTokenV3Collateral is AppreciatingFiatCollateral { // uint192(<) is equivalent to Fix.lt if (underlyingRefPerTok < exposedReferencePrice) { - exposedReferencePrice = hiddenReferencePrice; + exposedReferencePrice = underlyingRefPerTok; markStatus(CollateralStatus.DISABLED); } else if (hiddenReferencePrice > exposedReferencePrice) { exposedReferencePrice = hiddenReferencePrice; diff --git a/contracts/plugins/assets/curve/CurveStableCollateral.sol b/contracts/plugins/assets/curve/CurveStableCollateral.sol index f5706f75e6..8a21dfda9b 100644 --- a/contracts/plugins/assets/curve/CurveStableCollateral.sol +++ b/contracts/plugins/assets/curve/CurveStableCollateral.sol @@ -88,7 +88,7 @@ contract CurveStableCollateral is AppreciatingFiatCollateral, PoolTokens { // uint192(<) is equivalent to Fix.lt if (underlyingRefPerTok < exposedReferencePrice) { - exposedReferencePrice = hiddenReferencePrice; + exposedReferencePrice = underlyingRefPerTok; markStatus(CollateralStatus.DISABLED); } else if (hiddenReferencePrice > exposedReferencePrice) { exposedReferencePrice = hiddenReferencePrice; diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index 2abd30838e..e077f3567f 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -343,14 +343,29 @@ export default function fn( }) // Should remain SOUND after a 1% decrease + let refPerTok = await ctx.collateral.refPerTok() await reduceRefPerTok(ctx, 1) // 1% decrease await ctx.collateral.refresh() expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) + // refPerTok should be unchanged + expect(await ctx.collateral.refPerTok()).to.be.closeTo( + refPerTok, + refPerTok.div(bn('1e3')) + ) // within 1-part-in-1-thousand + // Should become DISABLED if drops more than that + refPerTok = await ctx.collateral.refPerTok() await reduceRefPerTok(ctx, 1) // another 1% decrease await ctx.collateral.refresh() expect(await ctx.collateral.status()).to.equal(CollateralStatus.DISABLED) + + // refPerTok should have fallen 1% + refPerTok = refPerTok.sub(refPerTok.div(100)) + expect(await ctx.collateral.refPerTok()).to.be.closeTo( + refPerTok, + refPerTok.div(bn('1e3')) + ) // within 1-part-in-1-thousand }) it('reverts if Chainlink feed reverts or runs out of gas, maintains status', async () => { diff --git a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts index ccc8a97982..7c91bd2064 100644 --- a/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CometTestSuite.test.ts @@ -357,6 +357,7 @@ const collateralSpecificStatusTests = () => { }) // Should remain SOUND after a 1% decrease + let refPerTok = await collateral.refPerTok() let currentExchangeRate = await wcusdcV3Mock.exchangeRate() await wcusdcV3Mock.setMockExchangeRate( true, @@ -365,7 +366,11 @@ const collateralSpecificStatusTests = () => { await collateral.refresh() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + // refPerTok should be unchanged + expect(await collateral.refPerTok()).to.be.closeTo(refPerTok, refPerTok.div(bn('1e3'))) // within 1-part-in-1-thousand + // Should become DISABLED if drops more than that + refPerTok = await collateral.refPerTok() currentExchangeRate = await wcusdcV3Mock.exchangeRate() await wcusdcV3Mock.setMockExchangeRate( true, @@ -373,6 +378,10 @@ const collateralSpecificStatusTests = () => { ) await collateral.refresh() expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) + + // refPerTok should have fallen 1% + refPerTok = refPerTok.sub(refPerTok.div(100)) + expect(await collateral.refPerTok()).to.be.closeTo(refPerTok, refPerTok.div(bn('1e3'))) // within 1-part-in-1-thousand }) } diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index 2770210796..3f9628fc7c 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -638,6 +638,7 @@ export default function fn( expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) // Decrease refPerTok by 1 part in a million + const refPerTok = await ctx.collateral.refPerTok() const currentExchangeRate = await ctx.curvePool.get_virtual_price() const newVirtualPrice = currentExchangeRate.sub(currentExchangeRate.div(bn('1e6'))) await ctx.curvePool.setVirtualPrice(newVirtualPrice) @@ -650,11 +651,17 @@ export default function fn( expect(await ctx.collateral.status()).to.equal(CollateralStatus.SOUND) expect(await ctx.collateral.whenDefault()).to.equal(MAX_UINT48) + // refPerTok should be unchanged + expect(await ctx.collateral.refPerTok()).to.equal(refPerTok) + // One quanta more of decrease results in default await ctx.curvePool.setVirtualPrice(newVirtualPrice.sub(2)) // sub 2 to compenstate for rounding await expect(ctx.collateral.refresh()).to.emit(ctx.collateral, 'CollateralStatusChanged') expect(await ctx.collateral.status()).to.equal(CollateralStatus.DISABLED) expect(await ctx.collateral.whenDefault()).to.equal(await getLatestBlockTimestamp()) + + // refPerTok should have fallen exactly 2e-18 + expect(await ctx.collateral.refPerTok()).to.equal(refPerTok.sub(2)) }) describe('collateral-specific tests', collateralSpecificStatusTests) diff --git a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts index 681f53ae1b..7ac77c01d1 100644 --- a/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts +++ b/test/plugins/individual-collateral/frax-eth/SFrxEthTestSuite.test.ts @@ -198,7 +198,7 @@ const collateralSpecificConstructorTests = () => {} // eslint-disable-next-line @typescript-eslint/no-empty-function const collateralSpecificStatusTests = () => { - it('does revenue hiding', async () => { + it('does revenue hiding correctly', async () => { const MockFactory = await ethers.getContractFactory('SfraxEthMock') const erc20 = (await MockFactory.deploy()) as SfraxEthMock let currentPPS = await (await ethers.getContractAt('IsfrxEth', SFRX_ETH)).pricePerShare() @@ -215,14 +215,24 @@ const collateralSpecificStatusTests = () => { }) // Should remain SOUND after a 1% decrease - await erc20.setPricePerShare(currentPPS.sub(currentPPS.div(100))) + let refPerTok = await collateral.refPerTok() + const newPPS = currentPPS.sub(currentPPS.div(100)) + await erc20.setPricePerShare(newPPS) await collateral.refresh() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - // Should become DISABLED if drops more than that - await erc20.setPricePerShare(currentPPS.sub(currentPPS.div(99))) + // refPerTok should be unchanged + expect(await collateral.refPerTok()).to.be.closeTo(refPerTok, refPerTok.div(bn('1e3'))) // within 1-part-in-1-thousand + + // Should become DISABLED if drops another 1% + refPerTok = await collateral.refPerTok() + await erc20.setPricePerShare(newPPS.sub(newPPS.div(100))) await collateral.refresh() expect(await collateral.status()).to.equal(CollateralStatus.DISABLED) + + // refPerTok should have fallen 1% + refPerTok = refPerTok.sub(refPerTok.div(100)) + expect(await collateral.refPerTok()).to.be.closeTo(refPerTok, refPerTok.div(bn('1e3'))) // within 1-part-in-1-thousand }) } From c1e9f043ec617485cc1f35d4ea7e9828208b0800 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Mon, 23 Oct 2023 20:50:00 -0400 Subject: [PATCH 456/462] Trst m 14 (#982) --- .github/workflows/tests.yml | 1 - common/configuration.ts | 2 +- contracts/plugins/assets/Asset.sol | 4 +- .../phase1-common/3_deploy_rsrAsset.ts | 4 +- .../phase2-assets/1_deploy_assets.ts | 6 +- .../phase2-assets/2_deploy_collateral.ts | 280 ++++++++++++++++-- .../phase2-assets/assets/deploy_crv.ts | 4 +- .../phase2-assets/assets/deploy_cvx.ts | 4 +- .../collaterals/deploy_aave_v3_usdbc.ts | 4 +- .../collaterals/deploy_aave_v3_usdc.ts | 6 +- .../collaterals/deploy_cbeth_collateral.ts | 12 +- .../deploy_compound_v2_collateral.ts | 18 +- .../deploy_convex_rToken_metapool_plugin.ts | 9 +- .../deploy_convex_stable_metapool_plugin.ts | 8 +- .../deploy_convex_stable_plugin.ts | 10 +- .../deploy_convex_volatile_plugin.ts | 142 --------- .../deploy_ctokenv3_usdbc_collateral.ts | 6 +- .../deploy_ctokenv3_usdc_collateral.ts | 6 +- .../deploy_curve_rToken_metapool_plugin.ts | 9 +- .../deploy_curve_stable_metapool_plugin.ts | 8 +- .../collaterals/deploy_curve_stable_plugin.ts | 10 +- .../deploy_curve_volatile_plugin.ts | 142 --------- .../collaterals/deploy_dsr_sdai.ts | 4 +- .../deploy_flux_finance_collateral.ts | 10 +- .../deploy_lido_wsteth_collateral.ts | 6 +- .../deploy_morpho_aavev2_plugin.ts | 14 +- .../deploy_rocket_pool_reth_collateral.ts | 8 +- .../deploy_stargate_usdc_collateral.ts | 19 +- .../deploy_stargate_usdt_collateral.ts | 4 +- scripts/deployment/utils.ts | 9 +- scripts/verification/6_verify_collateral.ts | 32 +- .../verify_aave_v3_usdbc.ts | 4 +- .../collateral-plugins/verify_aave_v3_usdc.ts | 6 +- .../collateral-plugins/verify_cbeth.ts | 12 +- .../verify_convex_stable.ts | 10 +- .../verify_convex_stable_metapool.ts | 8 +- .../verify_convex_stable_rtoken_metapool.ts | 9 +- .../verify_convex_volatile.ts | 98 ------ .../collateral-plugins/verify_curve_stable.ts | 10 +- .../verify_curve_stable_metapool.ts | 8 +- .../verify_curve_stable_rtoken_metapool.ts | 9 +- .../verify_curve_volatile.ts | 98 ------ .../collateral-plugins/verify_cusdbcv3.ts | 6 +- .../collateral-plugins/verify_cusdcv3.ts | 6 +- .../collateral-plugins/verify_morpho.ts | 16 +- .../collateral-plugins/verify_reth.ts | 6 +- .../collateral-plugins/verify_wsteth.ts | 6 +- test/Broker.test.ts | 8 +- test/Main.test.ts | 25 +- test/RTokenExtremes.test.ts | 4 +- test/Recollateralization.test.ts | 9 +- test/Revenues.test.ts | 17 +- test/fixtures.ts | 20 +- test/integration/AssetPlugins.test.ts | 25 +- test/integration/EasyAuction.test.ts | 4 +- test/integration/fixtures.ts | 30 +- test/integration/fork-block-numbers.ts | 2 +- test/plugins/Asset.test.ts | 9 +- test/plugins/Collateral.test.ts | 129 ++++---- .../aave/ATokenFiatCollateral.test.ts | 20 +- .../compoundv2/CTokenFiatCollateral.test.ts | 22 +- .../plugins/individual-collateral/fixtures.ts | 4 +- .../MorphoAaveV2TokenisedDeposit.test.ts | 5 +- test/scenario/BadCollateralPlugin.test.ts | 4 +- test/scenario/ComplexBasket.test.ts | 26 +- test/scenario/MaxBasketSize.test.ts | 8 +- test/scenario/NestedRTokens.test.ts | 4 +- test/scenario/NontrivialPeg.test.ts | 6 +- test/scenario/RevenueHiding.test.ts | 4 +- test/scenario/SetProtocol.test.ts | 8 +- 70 files changed, 611 insertions(+), 895 deletions(-) delete mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts delete mode 100644 scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts delete mode 100644 scripts/verification/collateral-plugins/verify_convex_volatile.ts delete mode 100644 scripts/verification/collateral-plugins/verify_curve_volatile.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 62e19d8665..8c1d16e8da 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,6 @@ jobs: - run: yarn devchain & env: MAINNET_RPC_URL: https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 - FORK_BLOCK: 18329921 FORK_NETWORK: mainnet - run: yarn deploy:run --network localhost env: diff --git a/common/configuration.ts b/common/configuration.ts index e6e1f846e3..f17059cb08 100644 --- a/common/configuration.ts +++ b/common/configuration.ts @@ -675,7 +675,7 @@ export const MAX_THROTTLE_PCT_RATE = BigNumber.from(10).pow(18) export const GNOSIS_MAX_TOKENS = BigNumber.from(7).mul(BigNumber.from(10).pow(28)) // Timestamps -export const MAX_ORACLE_TIMEOUT = BigNumber.from(2).pow(48).sub(1) +export const MAX_ORACLE_TIMEOUT = BigNumber.from(2).pow(48).sub(1).sub(300) export const MAX_TRADING_DELAY = 31536000 // 1 year export const MIN_WARMUP_PERIOD = 60 // 1 minute export const MAX_WARMUP_PERIOD = 31536000 // 1 year diff --git a/contracts/plugins/assets/Asset.sol b/contracts/plugins/assets/Asset.sol index ed98e9a1d7..3b858a9eab 100644 --- a/contracts/plugins/assets/Asset.sol +++ b/contracts/plugins/assets/Asset.sol @@ -7,6 +7,8 @@ import "../../interfaces/IAsset.sol"; import "./OracleLib.sol"; import "./VersionedAsset.sol"; +uint48 constant ORACLE_TIMEOUT_BUFFER = 300; // {s} 5 minutes + contract Asset is IAsset, VersionedAsset { using FixLib for uint192; using OracleLib for AggregatorV3Interface; @@ -62,7 +64,7 @@ contract Asset is IAsset, VersionedAsset { erc20 = erc20_; erc20Decimals = erc20.decimals(); maxTradeVolume = maxTradeVolume_; - oracleTimeout = oracleTimeout_; + oracleTimeout = oracleTimeout_ + ORACLE_TIMEOUT_BUFFER; // add 300s as a buffer } /// Can revert, used by other contract functions in order to catch errors diff --git a/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts b/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts index e67a33f602..f33da05e81 100644 --- a/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts +++ b/scripts/deployment/phase1-common/3_deploy_rsrAsset.ts @@ -6,7 +6,7 @@ import { networkConfig } from '../../../common/configuration' import { ZERO_ADDRESS } from '../../../common/constants' import { fp } from '../../../common/numbers' import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../../deployment/common' -import { priceTimeout, oracleTimeout, validateImplementations } from '../../deployment/utils' +import { priceTimeout, validateImplementations } from '../../deployment/utils' import { Asset } from '../../../typechain' let rsrAsset: Asset @@ -36,7 +36,7 @@ async function main() { tokenAddress: deployments.prerequisites.RSR, rewardToken: ZERO_ADDRESS, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24h + oracleTimeout: '86400', // 24h }) rsrAsset = await ethers.getContractAt('Asset', rsrAssetAddr) diff --git a/scripts/deployment/phase2-assets/1_deploy_assets.ts b/scripts/deployment/phase2-assets/1_deploy_assets.ts index cab49a5515..93a8a69392 100644 --- a/scripts/deployment/phase2-assets/1_deploy_assets.ts +++ b/scripts/deployment/phase2-assets/1_deploy_assets.ts @@ -10,7 +10,7 @@ import { IAssetCollDeployments, fileExists, } from '../../deployment/common' -import { priceTimeout, oracleTimeout } from '../../deployment/utils' +import { priceTimeout } from '../../deployment/utils' import { Asset } from '../../../typechain' async function main() { @@ -44,7 +44,7 @@ async function main() { oracleError: fp('0.01').toString(), // 1% tokenAddress: networkConfig[chainId].tokens.stkAAVE, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '3600', // 1 hr }) await (await ethers.getContractAt('Asset', stkAAVEAsset)).refresh() @@ -60,7 +60,7 @@ async function main() { oracleError: fp('0.01').toString(), // 1% tokenAddress: networkConfig[chainId].tokens.COMP, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '3600', // 1 hr }) await (await ethers.getContractAt('Asset', compAsset)).refresh() diff --git a/scripts/deployment/phase2-assets/2_deploy_collateral.ts b/scripts/deployment/phase2-assets/2_deploy_collateral.ts index cd8fdaa334..5a58c3bea8 100644 --- a/scripts/deployment/phase2-assets/2_deploy_collateral.ts +++ b/scripts/deployment/phase2-assets/2_deploy_collateral.ts @@ -12,7 +12,7 @@ import { getDeploymentFilename, fileExists, } from '../common' -import { combinedError, priceTimeout, oracleTimeout, revenueHiding } from '../utils' +import { combinedError, priceTimeout, revenueHiding } from '../utils' import { ICollateral, ATokenMock, StaticATokenLM } from '../../../typechain' async function main() { @@ -43,7 +43,7 @@ async function main() { let collateral: ICollateral /******** Deploy Fiat Collateral - DAI **************************/ - const daiOracleTimeout = baseL2Chains.includes(hre.network.name) ? 86400 : 3600 // 24 hr (Base) or 1 hour + const daiOracleTimeout = baseL2Chains.includes(hre.network.name) ? '86400' : '3600' // 24 hr (Base) or 1 hour const daiOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% if (networkConfig[chainId].tokens.DAI && networkConfig[chainId].chainlinkFeeds.DAI) { @@ -53,7 +53,7 @@ async function main() { oracleError: daiOracleError.toString(), tokenAddress: networkConfig[chainId].tokens.DAI, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, daiOracleTimeout).toString(), + oracleTimeout: daiOracleTimeout, targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(daiOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h @@ -69,7 +69,7 @@ async function main() { fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) } - const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleTimeout = '86400' // 24 hr const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% /******** Deploy Fiat Collateral - USDC **************************/ @@ -80,7 +80,7 @@ async function main() { oracleError: usdcOracleError.toString(), tokenAddress: networkConfig[chainId].tokens.USDC, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24 hr + oracleTimeout: usdcOracleTimeout, // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdcOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h @@ -97,7 +97,7 @@ async function main() { } /******** Deploy Fiat Collateral - USDT **************************/ - const usdtOracleTimeout = 86400 // 24 hr + const usdtOracleTimeout = '86400' // 24 hr const usdtOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% if (networkConfig[chainId].tokens.USDT && networkConfig[chainId].chainlinkFeeds.USDT) { @@ -107,7 +107,7 @@ async function main() { oracleError: usdtOracleError.toString(), tokenAddress: networkConfig[chainId].tokens.USDT, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, usdtOracleTimeout).toString(), // 24 hr + oracleTimeout: usdtOracleTimeout, // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdtOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h @@ -132,7 +132,7 @@ async function main() { oracleError: fp('0.01').toString(), // 1% tokenAddress: networkConfig[chainId].tokens.USDP, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.02').toString(), // 2% delayUntilDefault: bn('86400').toString(), // 24h @@ -156,7 +156,7 @@ async function main() { oracleError: fp('0.003').toString(), // 0.3% tokenAddress: networkConfig[chainId].tokens.TUSD, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + oracleTimeout: '86400', // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.013').toString(), // 1.3% delayUntilDefault: bn('86400').toString(), // 24h @@ -179,7 +179,7 @@ async function main() { oracleError: fp('0.005').toString(), // 0.5% tokenAddress: networkConfig[chainId].tokens.BUSD, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + oracleTimeout: '86400', // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.015').toString(), // 1.5% delayUntilDefault: bn('86400').toString(), // 24h @@ -203,7 +203,7 @@ async function main() { oracleError: usdcOracleError.toString(), tokenAddress: networkConfig[chainId].tokens.USDbC, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24 hr + oracleTimeout: usdcOracleTimeout, // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdcOracleError).toString(), // 1.3% delayUntilDefault: bn('86400').toString(), // 24h @@ -249,7 +249,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% staticAToken: adaiStaticToken.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -293,7 +293,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% staticAToken: ausdcStaticToken.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + oracleTimeout: '86400', // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -337,7 +337,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% staticAToken: ausdtStaticToken.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + oracleTimeout: '86400', // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -380,7 +380,7 @@ async function main() { oracleError: fp('0.005').toString(), // 0.5% staticAToken: abusdStaticToken.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + oracleTimeout: '86400', // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.015').toString(), // 1.5% delayUntilDefault: bn('86400').toString(), // 24h @@ -424,7 +424,7 @@ async function main() { oracleError: fp('0.01').toString(), // 1% staticAToken: ausdpStaticToken.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.02').toString(), // 2% delayUntilDefault: bn('86400').toString(), // 24h @@ -445,6 +445,242 @@ async function main() { const btcOracleError = fp('0.005') // 0.5% const combinedBTCWBTCError = combinedError(wbtcOracleError, btcOracleError) + /*** Compound V2 not available in Base L2s */ + if (!baseL2Chains.includes(hre.network.name)) { + /******** Deploy CToken Fiat Collateral - cDAI **************************/ + const CTokenFactory = await ethers.getContractFactory('CTokenWrapper') + const cDai = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cDAI!) + + const cDaiVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cDAI!, + `${await cDai.name()} Vault`, + `${await cDai.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) + + await cDaiVault.deployed() + + console.log( + `Deployed Vault for cDAI on ${hre.network.name} (${chainId}): ${cDaiVault.address} ` + ) + + const { collateral: cDaiCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.DAI, + oracleError: fp('0.0025').toString(), // 0.25% + cToken: cDaiVault.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: '3600', // 1 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.0125').toString(), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', cDaiCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.cDAI = cDaiCollateral + assetCollDeployments.erc20s.cDAI = cDaiVault.address + deployedCollateral.push(cDaiCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + /******** Deploy CToken Fiat Collateral - cUSDC **************************/ + const cUsdc = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDC!) + + const cUsdcVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cUSDC!, + `${await cUsdc.name()} Vault`, + `${await cUsdc.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) + + await cUsdcVault.deployed() + + console.log( + `Deployed Vault for cUSDC on ${hre.network.name} (${chainId}): ${cUsdcVault.address} ` + ) + + const { collateral: cUsdcCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.USDC, + oracleError: fp('0.0025').toString(), // 0.25% + cToken: cUsdcVault.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: '86400', // 24 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.0125').toString(), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', cUsdcCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.cUSDC = cUsdcCollateral + assetCollDeployments.erc20s.cUSDC = cUsdcVault.address + deployedCollateral.push(cUsdcCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + /******** Deploy CToken Fiat Collateral - cUSDT **************************/ + const cUsdt = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDT!) + + const cUsdtVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cUSDT!, + `${await cUsdt.name()} Vault`, + `${await cUsdt.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) + + await cUsdtVault.deployed() + + console.log( + `Deployed Vault for cUSDT on ${hre.network.name} (${chainId}): ${cUsdtVault.address} ` + ) + + const { collateral: cUsdtCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.USDT, + oracleError: fp('0.0025').toString(), // 0.25% + cToken: cUsdtVault.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: '86400', // 24 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.0125').toString(), // 1.25% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', cUsdtCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.cUSDT = cUsdtCollateral + assetCollDeployments.erc20s.cUSDT = cUsdtVault.address + deployedCollateral.push(cUsdtCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + /******** Deploy CToken Fiat Collateral - cUSDP **************************/ + const cUsdp = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cUSDP!) + + const cUsdpVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cUSDP!, + `${await cUsdp.name()} Vault`, + `${await cUsdp.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) + + await cUsdpVault.deployed() + + console.log( + `Deployed Vault for cUSDP on ${hre.network.name} (${chainId}): ${cUsdpVault.address} ` + ) + + const { collateral: cUsdpCollateral } = await hre.run('deploy-ctoken-fiat-collateral', { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.USDP, + oracleError: fp('0.01').toString(), // 1% + cToken: cUsdpVault.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: '3600', // 1 hr + targetName: hre.ethers.utils.formatBytes32String('USD'), + defaultThreshold: fp('0.02').toString(), // 2% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', cUsdpCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.cUSDP = cUsdpCollateral + assetCollDeployments.erc20s.cUSDP = cUsdpVault.address + deployedCollateral.push(cUsdpCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + /******** Deploy CToken Non-Fiat Collateral - cWBTC **************************/ + const cWBTC = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cWBTC!) + + const cWBTCVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cWBTC!, + `${await cWBTC.name()} Vault`, + `${await cWBTC.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) + + await cWBTCVault.deployed() + + console.log( + `Deployed Vault for cWBTC on ${hre.network.name} (${chainId}): ${cWBTCVault.address} ` + ) + + const { collateral: cWBTCCollateral } = await hre.run('deploy-ctoken-nonfiat-collateral', { + priceTimeout: priceTimeout.toString(), + referenceUnitFeed: networkConfig[chainId].chainlinkFeeds.WBTC, + targetUnitFeed: networkConfig[chainId].chainlinkFeeds.BTC, + combinedOracleError: combinedBTCWBTCError.toString(), + cToken: cWBTCVault.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: '86400', // 24 hr + targetUnitOracleTimeout: '3600', // 1 hr + targetName: hre.ethers.utils.formatBytes32String('BTC'), + defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% + delayUntilDefault: bn('86400').toString(), // 24h + revenueHiding: revenueHiding.toString(), + }) + collateral = await ethers.getContractAt('ICollateral', cWBTCCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.cWBTC = cWBTCCollateral + assetCollDeployments.erc20s.cWBTC = cWBTCVault.address + deployedCollateral.push(cWBTCCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + + /******** Deploy CToken Self-Referential Collateral - cETH **************************/ + const cETH = await ethers.getContractAt('IERC20Metadata', networkConfig[chainId].tokens.cETH!) + + const cETHVault = await CTokenFactory.deploy( + networkConfig[chainId].tokens.cETH!, + `${await cETH.name()} Vault`, + `${await cETH.symbol()}-VAULT`, + networkConfig[chainId].COMPTROLLER! + ) + + await cETHVault.deployed() + + console.log( + `Deployed Vault for cETH on ${hre.network.name} (${chainId}): ${cETHVault.address} ` + ) + + const { collateral: cETHCollateral } = await hre.run( + 'deploy-ctoken-selfreferential-collateral', + { + priceTimeout: priceTimeout.toString(), + priceFeed: networkConfig[chainId].chainlinkFeeds.ETH, + oracleError: fp('0.005').toString(), // 0.5% + cToken: cETHVault.address, + maxTradeVolume: fp('1e6').toString(), // $1m, + oracleTimeout: '3600', // 1 hr + targetName: hre.ethers.utils.formatBytes32String('ETH'), + revenueHiding: revenueHiding.toString(), + referenceERC20Decimals: '18', + } + ) + collateral = await ethers.getContractAt('ICollateral', cETHCollateral) + await (await collateral.refresh()).wait() + expect(await collateral.status()).to.equal(CollateralStatus.SOUND) + + assetCollDeployments.collateral.cETH = cETHCollateral + assetCollDeployments.erc20s.cETH = cETHVault.address + deployedCollateral.push(cETHCollateral.toString()) + + fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) + } + /******** Deploy Non-Fiat Collateral - wBTC **************************/ if ( networkConfig[chainId].tokens.WBTC && @@ -458,8 +694,8 @@ async function main() { combinedOracleError: combinedBTCWBTCError.toString(), tokenAddress: networkConfig[chainId].tokens.WBTC, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetUnitOracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '86400', // 24 hr + targetUnitOracleTimeout: '3600', // 1 hr targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% delayUntilDefault: bn('86400').toString(), // 24h @@ -477,7 +713,7 @@ async function main() { /******** Deploy Self Referential Collateral - wETH **************************/ if (networkConfig[chainId].tokens.WETH && networkConfig[chainId].chainlinkFeeds.ETH) { - const ethOracleTimeout = baseL2Chains.includes(hre.network.name) ? 1200 : 3600 // 20 min (Base) or 1 hr + const ethOracleTimeout = baseL2Chains.includes(hre.network.name) ? '1200' : '3600' // 20 min (Base) or 1 hr const ethOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.0015') : fp('0.005') // 0.15% (Base) or 0.5% const { collateral: wETHCollateral } = await hre.run('deploy-selfreferential-collateral', { @@ -486,7 +722,7 @@ async function main() { oracleError: ethOracleError.toString(), tokenAddress: networkConfig[chainId].tokens.WETH, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, ethOracleTimeout).toString(), + oracleTimeout: ethOracleTimeout, targetName: hre.ethers.utils.formatBytes32String('ETH'), }) collateral = await ethers.getContractAt('ICollateral', wETHCollateral) @@ -515,8 +751,8 @@ async function main() { oracleError: eurtError.toString(), // 2% tokenAddress: networkConfig[chainId].tokens.EURT, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetUnitOracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + oracleTimeout: '86400', // 24 hr + targetUnitOracleTimeout: '86400', // 24 hr targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: fp('0.03').toString(), // 3% delayUntilDefault: bn('86400').toString(), // 24h diff --git a/scripts/deployment/phase2-assets/assets/deploy_crv.ts b/scripts/deployment/phase2-assets/assets/deploy_crv.ts index f0db202b9b..80eb3f6c19 100644 --- a/scripts/deployment/phase2-assets/assets/deploy_crv.ts +++ b/scripts/deployment/phase2-assets/assets/deploy_crv.ts @@ -10,7 +10,7 @@ import { IAssetCollDeployments, fileExists, } from '../../../deployment/common' -import { priceTimeout, oracleTimeout } from '../../../deployment/utils' +import { priceTimeout } from '../../../deployment/utils' import { Asset } from '../../../../typechain' async function main() { @@ -43,7 +43,7 @@ async function main() { oracleError: fp('0.01').toString(), // 1% tokenAddress: networkConfig[chainId].tokens.CRV, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + oracleTimeout: '86400', // 24 hr }) await (await ethers.getContractAt('Asset', crvAsset)).refresh() diff --git a/scripts/deployment/phase2-assets/assets/deploy_cvx.ts b/scripts/deployment/phase2-assets/assets/deploy_cvx.ts index 1c5aaa57ce..cb7eb2d5d2 100644 --- a/scripts/deployment/phase2-assets/assets/deploy_cvx.ts +++ b/scripts/deployment/phase2-assets/assets/deploy_cvx.ts @@ -10,7 +10,7 @@ import { IAssetCollDeployments, fileExists, } from '../../../deployment/common' -import { priceTimeout, oracleTimeout } from '../../../deployment/utils' +import { priceTimeout } from '../../../deployment/utils' import { Asset } from '../../../../typechain' async function main() { @@ -43,7 +43,7 @@ async function main() { oracleError: fp('0.02').toString(), // 2% tokenAddress: networkConfig[chainId].tokens.CVX, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + oracleTimeout: '86400', // 24 hr }) await (await ethers.getContractAt('Asset', cvxAsset)).refresh() diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdbc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdbc.ts index e7fbc98512..f930201a21 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdbc.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdbc.ts @@ -13,7 +13,7 @@ import { } from '../../common' import { bn, fp } from '#/common/numbers' import { AaveV3FiatCollateral } from '../../../../typechain' -import { priceTimeout, revenueHiding, oracleTimeout } from '../../utils' +import { priceTimeout, revenueHiding } from '../../utils' // This file specifically deploys Aave V3 USDC collateral @@ -77,7 +77,7 @@ async function main() { oracleError: fp('0.003'), // 3% erc20: erc20.address, maxTradeVolume: fp('1e6'), - oracleTimeout: oracleTimeout(chainId, bn('86400')), // 24 hr + oracleTimeout: '86400', // 24 hr targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.013'), delayUntilDefault: bn('86400'), diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdc.ts b/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdc.ts index 2d56f9d3f9..2d4eb8112d 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdc.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_aave_v3_usdc.ts @@ -13,7 +13,7 @@ import { } from '../../common' import { bn, fp } from '#/common/numbers' import { AaveV3FiatCollateral } from '../../../../typechain' -import { priceTimeout, revenueHiding, oracleTimeout } from '../../utils' +import { priceTimeout, revenueHiding } from '../../utils' // This file specifically deploys Aave V3 USDC collateral @@ -68,7 +68,7 @@ async function main() { ) /******** Deploy Aave V3 USDC collateral plugin **************************/ - const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleTimeout = '86400' // 24 hr const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% const CollateralFactory = await ethers.getContractFactory('AaveV3FiatCollateral') @@ -79,7 +79,7 @@ async function main() { oracleError: usdcOracleError, erc20: erc20.address, maxTradeVolume: fp('1e6'), - oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout), + oracleTimeout: usdcOracleTimeout, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdcOracleError), delayUntilDefault: bn('86400'), diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_cbeth_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_cbeth_collateral.ts index eab6850157..18984099fe 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_cbeth_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_cbeth_collateral.ts @@ -12,7 +12,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { priceTimeout, oracleTimeout, combinedError } from '../../utils' +import { priceTimeout, combinedError } from '../../utils' import { CBEthCollateral, CBEthCollateralL2, @@ -62,14 +62,14 @@ async function main() { oracleError: oracleError.toString(), // 0.5% & 2%, erc20: networkConfig[chainId].tokens.cbETH!, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, + oracleTimeout: '3600', // 1 hr, targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.02').add(oracleError).toString(), // ~4.5% delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4').toString(), // revenueHiding = 0.01% networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed - oracleTimeout(chainId, '86400').toString() // refPerTokChainlinkTimeout + '86400' // refPerTokChainlinkTimeout ) await collateral.deployed() await (await collateral.refresh()).wait() @@ -89,16 +89,16 @@ async function main() { oracleError: oracleError.toString(), // 0.15% & 0.5%, erc20: networkConfig[chainId].tokens.cbETH!, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '1200').toString(), // 20 min + oracleTimeout: '1200', // 20 min targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.02').add(oracleError).toString(), // ~2.5% delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4').toString(), // revenueHiding = 0.01% networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed - oracleTimeout(chainId, '86400').toString(), // refPerTokChainlinkTimeout + '86400', // refPerTokChainlinkTimeout networkConfig[chainId].chainlinkFeeds.cbETHETHexr!, // exchangeRateChainlinkFeed - oracleTimeout(chainId, '86400').toString() // exchangeRateChainlinkTimeout + '86400' // exchangeRateChainlinkTimeout ) await collateral.deployed() await (await collateral.refresh()).wait() diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_compound_v2_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_compound_v2_collateral.ts index d328a311dd..89b2464c55 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_compound_v2_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_compound_v2_collateral.ts @@ -12,8 +12,8 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { combinedError, priceTimeout, oracleTimeout, revenueHiding } from '../../utils' -import { ICollateral, ATokenMock, StaticATokenLM } from '../../../../typechain' +import { combinedError, priceTimeout, revenueHiding } from '../../utils' +import { ICollateral } from '../../../../typechain' async function main() { // ==== Read Configuration ==== @@ -71,7 +71,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% cToken: cDaiVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -109,7 +109,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% cToken: cUsdcVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + oracleTimeout: '86400', // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -147,7 +147,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% cToken: cUsdtVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + oracleTimeout: '86400', // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -185,7 +185,7 @@ async function main() { oracleError: fp('0.01').toString(), // 1% cToken: cUsdpVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.02').toString(), // 2% delayUntilDefault: bn('86400').toString(), // 24h @@ -224,8 +224,8 @@ async function main() { combinedOracleError: combinedBTCWBTCError.toString(), cToken: cWBTCVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr - targetUnitOracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '86400', // 24 hr + targetUnitOracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('BTC'), defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% delayUntilDefault: bn('86400').toString(), // 24h @@ -265,7 +265,7 @@ async function main() { oracleError: fp('0.005').toString(), // 0.5% cToken: cETHVault.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('ETH'), revenueHiding: revenueHiding.toString(), referenceERC20Decimals: '18', diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts index dae7875b30..e7b53d4f51 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_rToken_metapool_plugin.ts @@ -14,7 +14,7 @@ import { fileExists, } from '../../common' import { CurveStableRTokenMetapoolCollateral } from '../../../../typechain' -import { revenueHiding, oracleTimeout } from '../../utils' +import { revenueHiding } from '../../utils' import { CurvePoolType, DEFAULT_THRESHOLD, @@ -87,7 +87,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, @@ -98,10 +98,7 @@ async function main() { curvePool: FRAX_BP, poolType: CurvePoolType.Plain, feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, FRAX_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], - ], + oracleTimeouts: [[FRAX_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT]], oracleErrors: [[FRAX_ORACLE_ERROR], [USDC_ORACLE_ERROR]], lpToken: FRAX_BP_TOKEN, }, diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts index 72d0f7debe..4a2c1c6eab 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_metapool_plugin.ts @@ -13,7 +13,7 @@ import { fileExists, } from '../../common' import { CurveStableMetapoolCollateral } from '../../../../typechain' -import { revenueHiding, oracleTimeout } from '../../utils' +import { revenueHiding } from '../../utils' import { CurvePoolType, DELAY_UNTIL_DEFAULT, @@ -105,11 +105,7 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], - ], + oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, }, diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts index 97a8fc4f2b..bc52c467c9 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_convex_stable_plugin.ts @@ -14,7 +14,7 @@ import { fileExists, } from '../../common' import { CurveStableCollateral } from '../../../../typechain' -import { revenueHiding, oracleTimeout } from '../../utils' +import { revenueHiding } from '../../utils' import { CurvePoolType, DAI_ORACLE_ERROR, @@ -88,7 +88,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -99,11 +99,7 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], - ], + oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, } diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts deleted file mode 100644 index ab71316676..0000000000 --- a/scripts/deployment/phase2-assets/collaterals/deploy_convex_volatile_plugin.ts +++ /dev/null @@ -1,142 +0,0 @@ -import fs from 'fs' -import hre, { ethers } from 'hardhat' -import { getChainId } from '../../../../common/blockchain-utils' -import { networkConfig } from '../../../../common/configuration' -import { bn } from '../../../../common/numbers' -import { expect } from 'chai' -import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' -import { - getDeploymentFile, - getAssetCollDeploymentFilename, - IAssetCollDeployments, - IDeployments, - getDeploymentFilename, - fileExists, -} from '../../common' -import { CurveVolatileCollateral } from '../../../../typechain' -import { revenueHiding, oracleTimeout } from '../../utils' -import { - CurvePoolType, - BTC_USD_ORACLE_ERROR, - BTC_ORACLE_TIMEOUT, - BTC_USD_FEED, - DEFAULT_THRESHOLD, - DELAY_UNTIL_DEFAULT, - MAX_TRADE_VOL, - PRICE_TIMEOUT, - TRI_CRYPTO, - TRI_CRYPTO_TOKEN, - TRI_CRYPTO_CVX_POOL_ID, - WBTC_BTC_ORACLE_ERROR, - WETH_ORACLE_TIMEOUT, - WBTC_BTC_FEED, - WBTC_ORACLE_TIMEOUT, - WETH_USD_FEED, - WETH_ORACLE_ERROR, - USDT_ORACLE_ERROR, - USDT_ORACLE_TIMEOUT, - USDT_USD_FEED, -} from '../../../../test/plugins/individual-collateral/curve/constants' - -// This file specifically deploys Convex Volatile Plugin for Tricrypto - -async function main() { - // ==== Read Configuration ==== - const [deployer] = await hre.ethers.getSigners() - - const chainId = await getChainId(hre) - - console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) - with burner account: ${deployer.address}`) - - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - - // Get phase1 deployment - const phase1File = getDeploymentFilename(chainId) - if (!fileExists(phase1File)) { - throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) - } - const deployments = getDeploymentFile(phase1File) - - // Check previous step completed - const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) - const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) - - const deployedCollateral: string[] = [] - - /******** Deploy Convex Volatile Pool for 3pool **************************/ - - const CvxMining = await ethers.getContractAt('CvxMining', deployments.cvxMiningLib) - const CurveVolatileCollateralFactory = await hre.ethers.getContractFactory( - 'CurveVolatileCollateral' - ) - const ConvexStakingWrapperFactory = await ethers.getContractFactory('ConvexStakingWrapper', { - libraries: { CvxMining: CvxMining.address }, - }) - - const w3Pool = await ConvexStakingWrapperFactory.deploy() - await w3Pool.deployed() - await (await w3Pool.initialize(TRI_CRYPTO_CVX_POOL_ID)).wait() - - console.log( - `Deployed wrapper for Convex Volatile TriCrypto on ${hre.network.name} (${chainId}): ${w3Pool.address} ` - ) - - const collateral = await CurveVolatileCollateralFactory.connect( - deployer - ).deploy( - { - erc20: w3Pool.address, - targetName: ethers.utils.formatBytes32String('TRICRYPTO'), - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero - oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: oracleTimeout(chainId, USDT_ORACLE_TIMEOUT), // max of oracleTimeouts - maxTradeVolume: MAX_TRADE_VOL, - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - }, - revenueHiding.toString(), - { - nTokens: 3, - curvePool: TRI_CRYPTO, - poolType: CurvePoolType.Plain, - feeds: [[USDT_USD_FEED], [WBTC_BTC_FEED, BTC_USD_FEED], [WETH_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, WBTC_ORACLE_TIMEOUT), oracleTimeout(chainId, BTC_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, WETH_ORACLE_TIMEOUT)], - ], - oracleErrors: [ - [USDT_ORACLE_ERROR], - [WBTC_BTC_ORACLE_ERROR, BTC_USD_ORACLE_ERROR], - [WETH_ORACLE_ERROR], - ], - lpToken: TRI_CRYPTO_TOKEN, - } - ) - await collateral.deployed() - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - console.log( - `Deployed Convex Volatile Collateral to ${hre.network.name} (${chainId}): ${collateral.address}` - ) - - assetCollDeployments.collateral.cvxTriCrypto = collateral.address - assetCollDeployments.erc20s.cvxTriCrypto = w3Pool.address - deployedCollateral.push(collateral.address.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - console.log(`Deployed collateral to ${hre.network.name} (${chainId}) - New deployments: ${deployedCollateral} - Deployment file: ${assetCollDeploymentFilename}`) -} - -main().catch((error) => { - console.error(error) - process.exitCode = 1 -}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts index 3f4755ff72..21e78893af 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdbc_collateral.ts @@ -12,7 +12,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { priceTimeout, oracleTimeout, revenueHiding } from '../../utils' +import { priceTimeout, revenueHiding } from '../../utils' import { CTokenV3Collateral } from '../../../../typechain' import { ContractFactory } from 'ethers' @@ -61,7 +61,7 @@ async function main() { const CTokenV3Factory: ContractFactory = await hre.ethers.getContractFactory('CTokenV3Collateral') - const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleTimeout = '86400' // 24 hr const usdcOracleError = fp('0.003') // 0.3% (Base) const collateral = await CTokenV3Factory.connect(deployer).deploy( @@ -71,7 +71,7 @@ async function main() { oracleError: usdcOracleError.toString(), erc20: erc20.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24h hr, + oracleTimeout: usdcOracleTimeout, // 24h hr, targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdcOracleError).toString(), // 1% + 0.3% delayUntilDefault: bn('86400').toString(), // 24h diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts index 873b8fbcc0..a05ac5bbc7 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_ctokenv3_usdc_collateral.ts @@ -12,7 +12,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { priceTimeout, oracleTimeout, revenueHiding } from '../../utils' +import { priceTimeout, revenueHiding } from '../../utils' import { CTokenV3Collateral } from '../../../../typechain' import { ContractFactory } from 'ethers' @@ -59,7 +59,7 @@ async function main() { const CTokenV3Factory: ContractFactory = await hre.ethers.getContractFactory('CTokenV3Collateral') - const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleTimeout = '86400' // 24 hr const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% const collateral = await CTokenV3Factory.connect(deployer).deploy( @@ -69,7 +69,7 @@ async function main() { oracleError: usdcOracleError.toString(), erc20: erc20.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24h hr, + oracleTimeout: usdcOracleTimeout, // 24h hr, targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdcOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts index e886d6d806..c2d760a2f9 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_curve_rToken_metapool_plugin.ts @@ -13,7 +13,7 @@ import { fileExists, } from '../../common' import { CurveStableRTokenMetapoolCollateral } from '../../../../typechain' -import { revenueHiding, oracleTimeout } from '../../utils' +import { revenueHiding } from '../../utils' import { CRV, CurvePoolType, @@ -88,7 +88,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, @@ -99,10 +99,7 @@ async function main() { curvePool: FRAX_BP, poolType: CurvePoolType.Plain, feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, FRAX_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], - ], + oracleTimeouts: [[FRAX_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT]], oracleErrors: [[FRAX_ORACLE_ERROR], [USDC_ORACLE_ERROR]], lpToken: FRAX_BP_TOKEN, }, diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_metapool_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_metapool_plugin.ts index f97882d214..e62840d7e1 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_metapool_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_metapool_plugin.ts @@ -12,7 +12,7 @@ import { fileExists, } from '../../common' import { CurveStableMetapoolCollateral } from '../../../../typechain' -import { revenueHiding, oracleTimeout } from '../../utils' +import { revenueHiding } from '../../utils' import { CRV, CurvePoolType, @@ -105,11 +105,7 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], - ], + oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, }, diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts index b2d6462d6c..6b9f415d01 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_curve_stable_plugin.ts @@ -13,7 +13,7 @@ import { fileExists, } from '../../common' import { CurveStableCollateral } from '../../../../typechain' -import { revenueHiding, oracleTimeout } from '../../utils' +import { revenueHiding } from '../../utils' import { CRV, CurvePoolType, @@ -89,7 +89,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -100,11 +100,7 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], - ], + oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, } diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts deleted file mode 100644 index f3b6e3e615..0000000000 --- a/scripts/deployment/phase2-assets/collaterals/deploy_curve_volatile_plugin.ts +++ /dev/null @@ -1,142 +0,0 @@ -import fs from 'fs' -import hre, { ethers } from 'hardhat' -import { getChainId } from '../../../../common/blockchain-utils' -import { networkConfig } from '../../../../common/configuration' -import { bn } from '../../../../common/numbers' -import { expect } from 'chai' -import { CollateralStatus, ONE_ADDRESS } from '../../../../common/constants' -import { - getDeploymentFile, - getAssetCollDeploymentFilename, - IAssetCollDeployments, - getDeploymentFilename, - fileExists, -} from '../../common' -import { CurveVolatileCollateral } from '../../../../typechain' -import { revenueHiding, oracleTimeout } from '../../utils' -import { - CRV, - CurvePoolType, - BTC_USD_ORACLE_ERROR, - BTC_ORACLE_TIMEOUT, - BTC_USD_FEED, - DEFAULT_THRESHOLD, - DELAY_UNTIL_DEFAULT, - MAX_TRADE_VOL, - PRICE_TIMEOUT, - TRI_CRYPTO, - TRI_CRYPTO_TOKEN, - TRI_CRYPTO_GAUGE, - WBTC_BTC_ORACLE_ERROR, - WETH_ORACLE_TIMEOUT, - WBTC_BTC_FEED, - WBTC_ORACLE_TIMEOUT, - WETH_USD_FEED, - WETH_ORACLE_ERROR, - USDT_ORACLE_ERROR, - USDT_ORACLE_TIMEOUT, - USDT_USD_FEED, -} from '../../../../test/plugins/individual-collateral/curve/constants' - -// Deploy Curve Volatile Plugin for Tricrypto - -async function main() { - // ==== Read Configuration ==== - const [deployer] = await hre.ethers.getSigners() - - const chainId = await getChainId(hre) - - console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) - with burner account: ${deployer.address}`) - - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - - // Get phase1 deployment - const phase1File = getDeploymentFilename(chainId) - if (!fileExists(phase1File)) { - throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) - } - - // Check previous step completed - const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) - const assetCollDeployments = getDeploymentFile(assetCollDeploymentFilename) - - const deployedCollateral: string[] = [] - - /******** Deploy Curve Volatile Pool for 3pool **************************/ - - const CurveVolatileCollateralFactory = await hre.ethers.getContractFactory( - 'CurveVolatileCollateral' - ) - const CurveStakingWrapperFactory = await ethers.getContractFactory('CurveGaugeWrapper') - const w3Pool = await CurveStakingWrapperFactory.deploy( - TRI_CRYPTO_TOKEN, - 'Wrapped Curve.fi USD-BTC-ETH', - 'wcrv3crypto', - CRV, - TRI_CRYPTO_GAUGE - ) - await w3Pool.deployed() - - console.log( - `Deployed wrapper for Curve Volatile TriCrypto on ${hre.network.name} (${chainId}): ${w3Pool.address} ` - ) - - const collateral = await CurveVolatileCollateralFactory.connect( - deployer - ).deploy( - { - erc20: w3Pool.address, - targetName: ethers.utils.formatBytes32String('TRICRYPTO'), - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero - oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: oracleTimeout(chainId, USDT_ORACLE_TIMEOUT), // max of oracleTimeouts - maxTradeVolume: MAX_TRADE_VOL, - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - }, - revenueHiding.toString(), - { - nTokens: 3, - curvePool: TRI_CRYPTO, - poolType: CurvePoolType.Plain, - feeds: [[USDT_USD_FEED], [WBTC_BTC_FEED, BTC_USD_FEED], [WETH_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, WBTC_ORACLE_TIMEOUT), oracleTimeout(chainId, BTC_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, WETH_ORACLE_TIMEOUT)], - ], - oracleErrors: [ - [USDT_ORACLE_ERROR], - [WBTC_BTC_ORACLE_ERROR, BTC_USD_ORACLE_ERROR], - [WETH_ORACLE_ERROR], - ], - lpToken: TRI_CRYPTO_TOKEN, - } - ) - await collateral.deployed() - await (await collateral.refresh()).wait() - expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - - console.log( - `Deployed Curve Volatile Collateral to ${hre.network.name} (${chainId}): ${collateral.address}` - ) - - assetCollDeployments.collateral.crvTriCrypto = collateral.address - assetCollDeployments.erc20s.crvTriCrypto = w3Pool.address - deployedCollateral.push(collateral.address.toString()) - - fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) - - console.log(`Deployed collateral to ${hre.network.name} (${chainId}) - New deployments: ${deployedCollateral} - Deployment file: ${assetCollDeploymentFilename}`) -} - -main().catch((error) => { - console.error(error) - process.exitCode = 1 -}) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_dsr_sdai.ts b/scripts/deployment/phase2-assets/collaterals/deploy_dsr_sdai.ts index 26ab8341ac..d06853b7ce 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_dsr_sdai.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_dsr_sdai.ts @@ -13,7 +13,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { priceTimeout, oracleTimeout } from '../../utils' +import { priceTimeout } from '../../utils' import { SDaiCollateral } from '../../../../typechain' import { ContractFactory } from 'ethers' @@ -54,7 +54,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% erc20: networkConfig[chainId].tokens.sDAI, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts index af7258ec4c..6acbcccf8f 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_flux_finance_collateral.ts @@ -12,7 +12,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { priceTimeout, oracleTimeout, revenueHiding } from '../../utils' +import { priceTimeout, revenueHiding } from '../../utils' import { ICollateral } from '../../../../typechain' async function main() { @@ -49,7 +49,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% cToken: fUsdc.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + oracleTimeout: '86400', // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -74,7 +74,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% cToken: fUsdt.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + oracleTimeout: '86400', // 24 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -99,7 +99,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% cToken: fDai.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -124,7 +124,7 @@ async function main() { oracleError: fp('0.01').toString(), // 1% cToken: fFrax.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.02').toString(), // 2% delayUntilDefault: bn('86400').toString(), // 24h diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts index bc6a8b160a..30884eae2d 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_lido_wsteth_collateral.ts @@ -12,7 +12,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { priceTimeout, oracleTimeout } from '../../utils' +import { priceTimeout } from '../../utils' import { LidoStakedEthCollateral } from '../../../../typechain' import { ContractFactory } from 'ethers' @@ -79,14 +79,14 @@ async function main() { oracleError: fp('0.01').toString(), // 1%: only for stETHUSD feed erc20: networkConfig[chainId].tokens.wstETH, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, + oracleTimeout: '3600', // 1 hr, targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.025').toString(), // 2.5% = 2% + 0.5% stethEth feed oracleError delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4').toString(), // revenueHiding = 0.01% stethEthOracleAddress, // targetPerRefChainlinkFeed - oracleTimeout(chainId, '86400').toString() // targetPerRefChainlinkTimeout + '86400' // targetPerRefChainlinkTimeout ) await collateral.deployed() await (await collateral.refresh()).wait() diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts b/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts index 19962dd88f..4c74574653 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_morpho_aavev2_plugin.ts @@ -11,7 +11,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { priceTimeout, oracleTimeout, combinedError } from '../../utils' +import { priceTimeout, combinedError } from '../../utils' async function main() { // ==== Read Configuration ==== @@ -126,7 +126,7 @@ async function main() { priceTimeout: priceTimeout.toString(), oracleError: stablesOracleError.toString(), maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 1 hr + oracleTimeout: '86400', // 1 hr targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: stablesOracleError.add(fp('0.01')), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -184,7 +184,7 @@ async function main() { priceTimeout: priceTimeout, oracleError: combinedBTCWBTCError, maxTradeVolume: fp('1e6'), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: fp('0.01').add(combinedBTCWBTCError), // ~3.5% delayUntilDefault: bn('86400'), // 24h @@ -193,7 +193,7 @@ async function main() { }, revenueHiding, networkConfig[chainId].chainlinkFeeds.WBTC!, // {target/ref} - oracleTimeout(chainId, '86400').toString() // 1 hr + '86400' // 1 hr ) assetCollDeployments.collateral.maWBTC = collateral.address deployedCollateral.push(collateral.address.toString()) @@ -207,7 +207,7 @@ async function main() { priceTimeout: priceTimeout, oracleError: fp('0.005'), maxTradeVolume: fp('1e6'), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0'), // 0% -- no soft default for self-referential collateral delayUntilDefault: bn('86400'), // 24h @@ -236,7 +236,7 @@ async function main() { priceTimeout: priceTimeout, oracleError: combinedOracleErrors, maxTradeVolume: fp('1e6'), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.01').add(combinedOracleErrors), // ~1.5% delayUntilDefault: bn('86400'), // 24h @@ -245,7 +245,7 @@ async function main() { }, revenueHiding, networkConfig[chainId].chainlinkFeeds.stETHETH!, // {target/ref} - oracleTimeout(chainId, '86400').toString() // 1 hr + '86400' // 1 hr ) assetCollDeployments.collateral.maStETH = collateral.address deployedCollateral.push(collateral.address.toString()) diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts index 6d39259061..d90520b97a 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_rocket_pool_reth_collateral.ts @@ -12,8 +12,8 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { priceTimeout, oracleTimeout, combinedError } from '../../utils' -import { MockV3Aggregator, RethCollateral } from '../../../../typechain' +import { priceTimeout, combinedError } from '../../utils' +import { RethCollateral } from '../../../../typechain' import { ContractFactory } from 'ethers' async function main() { @@ -70,14 +70,14 @@ async function main() { oracleError: oracleError.toString(), // 0.5% & 2% erc20: networkConfig[chainId].tokens.rETH, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, + oracleTimeout: '3600', // 1 hr, targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.02').add(oracleError).toString(), // ~4.5% delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4').toString(), // revenueHiding = 0.01% rethOracleAddress, // refPerTokChainlinkFeed - oracleTimeout(chainId, '86400').toString() // refPerTokChainlinkTimeout + '86400' // refPerTokChainlinkTimeout ) await collateral.deployed() await (await collateral.refresh()).wait() diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts index 5db4436ea9..dfd837767f 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdc_collateral.ts @@ -12,17 +12,12 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { revenueHiding, priceTimeout, oracleTimeout } from '../../utils' +import { revenueHiding, priceTimeout } from '../../utils' import { StargatePoolFiatCollateral, StargatePoolFiatCollateral__factory, } from '../../../../typechain' import { ContractFactory } from 'ethers' - -import { - STAKING_CONTRACT, - SUSDC, -} from '../../../../test/plugins/individual-collateral/stargate/constants' import { useEnv } from '#/utils/env' async function main() { @@ -51,8 +46,10 @@ async function main() { /******** Deploy Stargate USDC Wrapper **************************/ - const WrapperFactory: ContractFactory = await hre.ethers.getContractFactory('StargateRewardableWrapper') - let chainIdKey = useEnv('FORK_NETWORK', 'mainnet') == 'mainnet' ? '1' : '8453' + const WrapperFactory: ContractFactory = await hre.ethers.getContractFactory( + 'StargateRewardableWrapper' + ) + const chainIdKey = useEnv('FORK_NETWORK', 'mainnet') == 'mainnet' ? '1' : '8453' let USDC_NAME = 'USDC' let name = 'Wrapped Stargate USDC' let symbol = 'wsgUSDC' @@ -93,7 +90,7 @@ async function main() { oracleError: oracleError.toString(), erc20: erc20.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainIdKey, '86400').toString(), // 24h hr, + oracleTimeout: '86400', // 24h hr, targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(oracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h @@ -104,7 +101,9 @@ async function main() { await (await collateral.refresh()).wait() expect(await collateral.status()).to.equal(CollateralStatus.SOUND) - console.log(`Deployed Stargate ${USDC_NAME} to ${hre.network.name} (${chainIdKey}): ${collateral.address}`) + console.log( + `Deployed Stargate ${USDC_NAME} to ${hre.network.name} (${chainIdKey}): ${collateral.address}` + ) if (chainIdKey == '8453') { assetCollDeployments.collateral.wsgUSDbC = collateral.address diff --git a/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdt_collateral.ts b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdt_collateral.ts index 8a43556a52..4ac4e4c6c8 100644 --- a/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdt_collateral.ts +++ b/scripts/deployment/phase2-assets/collaterals/deploy_stargate_usdt_collateral.ts @@ -12,7 +12,7 @@ import { getDeploymentFilename, fileExists, } from '../../common' -import { revenueHiding, priceTimeout, oracleTimeout } from '../../utils' +import { revenueHiding, priceTimeout } from '../../utils' import { StargatePoolFiatCollateral, StargatePoolFiatCollateral__factory, @@ -77,7 +77,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25%, erc20: erc20.address, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24h hr, + oracleTimeout: '86400', // 24h hr, targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h diff --git a/scripts/deployment/utils.ts b/scripts/deployment/utils.ts index 84dad2be5e..a8c2083e09 100644 --- a/scripts/deployment/utils.ts +++ b/scripts/deployment/utils.ts @@ -2,7 +2,7 @@ import hre, { tenderly } from 'hardhat' import * as readline from 'readline' import axios from 'axios' import { exec } from 'child_process' -import { BigNumber, BigNumberish } from 'ethers' +import { BigNumber } from 'ethers' import { bn, fp } from '../../common/numbers' import { IComponents, baseL2Chains } from '../../common/configuration' import { isValidContract } from '../../common/blockchain-utils' @@ -13,13 +13,6 @@ export const priceTimeout = bn('604800') // 1 week export const revenueHiding = fp('1e-6') // 1 part in a million -export const longOracleTimeout = bn('4294967296') - -// Returns the base plus 1 minute -export const oracleTimeout = (chainId: string, base: BigNumberish) => { - return chainId == '1' || chainId == '8453' ? bn('60').add(base) : longOracleTimeout -} - export const combinedError = (x: BigNumber, y: BigNumber): BigNumber => { return fp('1').add(x).mul(fp('1').add(y)).div(fp('1')).sub(fp('1')) } diff --git a/scripts/verification/6_verify_collateral.ts b/scripts/verification/6_verify_collateral.ts index 9da0d9791d..fbcfe84d23 100644 --- a/scripts/verification/6_verify_collateral.ts +++ b/scripts/verification/6_verify_collateral.ts @@ -8,13 +8,7 @@ import { getAssetCollDeploymentFilename, IAssetCollDeployments, } from '../deployment/common' -import { - combinedError, - priceTimeout, - oracleTimeout, - revenueHiding, - verifyContract, -} from '../deployment/utils' +import { combinedError, priceTimeout, revenueHiding, verifyContract } from '../deployment/utils' import { ATokenMock, ATokenFiatCollateral, ICToken, CTokenFiatCollateral } from '../../typechain' let deployments: IAssetCollDeployments @@ -47,7 +41,7 @@ async function main() { oracleError: daiOracleError.toString(), erc20: networkConfig[chainId].tokens.DAI, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, daiOracleTimeout).toString(), + oracleTimeout: daiOracleTimeout, targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(daiOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h @@ -71,7 +65,7 @@ async function main() { oracleError: usdcOracleError.toString(), erc20: networkConfig[chainId].tokens.USDbC, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), + oracleTimeout: usdcOracleTimeout, targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdcOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h @@ -112,7 +106,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% erc20: await aTokenCollateral.erc20(), maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -151,7 +145,7 @@ async function main() { oracleError: fp('0.0025').toString(), // 0.25% erc20: deployments.erc20s.cDAI, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), + oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0125').toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -176,13 +170,13 @@ async function main() { oracleError: combinedBTCWBTCError.toString(), erc20: deployments.erc20s.cWBTC, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 24 hr + oracleTimeout: '86400', // 24 hr targetName: hre.ethers.utils.formatBytes32String('BTC'), defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% delayUntilDefault: bn('86400').toString(), // 24h }, networkConfig[chainId].chainlinkFeeds.BTC, - oracleTimeout(chainId, '3600').toString(), + '3600', revenueHiding.toString(), ], 'contracts/plugins/assets/compoundv2/CTokenNonFiatCollateral.sol:CTokenNonFiatCollateral' @@ -198,7 +192,7 @@ async function main() { oracleError: fp('0.005').toString(), // 0.5% erc20: deployments.erc20s.cETH, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), + oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: '0', delayUntilDefault: '0', @@ -219,13 +213,13 @@ async function main() { oracleError: combinedBTCWBTCError.toString(), erc20: networkConfig[chainId].tokens.WBTC, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), + oracleTimeout: '86400', // 24h targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: fp('0.01').add(combinedBTCWBTCError).toString(), // ~3.5% delayUntilDefault: bn('86400').toString(), // 24h }, networkConfig[chainId].chainlinkFeeds.BTC, - oracleTimeout(chainId, '3600').toString(), + '3600', ], 'contracts/plugins/assets/NonFiatCollateral.sol:NonFiatCollateral' ) @@ -244,7 +238,7 @@ async function main() { oracleError: ethOracleError.toString(), // 0.5% erc20: networkConfig[chainId].tokens.WETH, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, ethOracleTimeout).toString(), + oracleTimeout: ethOracleTimeout, targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: '0', delayUntilDefault: '0', @@ -264,13 +258,13 @@ async function main() { oracleError: fp('0.02').toString(), // 2% erc20: networkConfig[chainId].tokens.EURT, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), + oracleTimeout: '86400', // 24hr targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: fp('0.03').toString(), // 3% delayUntilDefault: bn('86400').toString(), // 24h }, networkConfig[chainId].chainlinkFeeds.EUR, - oracleTimeout(chainId, '86400').toString(), + '86400', ], 'contracts/plugins/assets/EURFiatCollateral.sol:EURFiatCollateral' ) diff --git a/scripts/verification/collateral-plugins/verify_aave_v3_usdbc.ts b/scripts/verification/collateral-plugins/verify_aave_v3_usdbc.ts index edb092d1af..3a373573dc 100644 --- a/scripts/verification/collateral-plugins/verify_aave_v3_usdbc.ts +++ b/scripts/verification/collateral-plugins/verify_aave_v3_usdbc.ts @@ -7,7 +7,7 @@ import { IAssetCollDeployments, } from '../../deployment/common' import { fp, bn } from '../../../common/numbers' -import { priceTimeout, oracleTimeout, verifyContract, revenueHiding } from '../../deployment/utils' +import { priceTimeout, verifyContract, revenueHiding } from '../../deployment/utils' let deployments: IAssetCollDeployments @@ -54,7 +54,7 @@ async function main() { priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, oracleError: fp('0.003').toString(), // 3% - oracleTimeout: oracleTimeout(chainId, bn('86400')).toString(), // 24 hr + oracleTimeout: '86400', // 24 hr maxTradeVolume: fp('1e6').toString(), defaultThreshold: fp('0.013').toString(), delayUntilDefault: bn('86400').toString(), diff --git a/scripts/verification/collateral-plugins/verify_aave_v3_usdc.ts b/scripts/verification/collateral-plugins/verify_aave_v3_usdc.ts index 1486c37cab..3ffce9a0a5 100644 --- a/scripts/verification/collateral-plugins/verify_aave_v3_usdc.ts +++ b/scripts/verification/collateral-plugins/verify_aave_v3_usdc.ts @@ -7,7 +7,7 @@ import { IAssetCollDeployments, } from '../../deployment/common' import { fp, bn } from '../../../common/numbers' -import { priceTimeout, oracleTimeout, verifyContract, revenueHiding } from '../../deployment/utils' +import { priceTimeout, verifyContract, revenueHiding } from '../../deployment/utils' let deployments: IAssetCollDeployments @@ -39,7 +39,7 @@ async function main() { ) /******** Verify Aave V3 USDC plugin **************************/ - const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleTimeout = '86400' // 24 hr const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% await verifyContract( @@ -52,7 +52,7 @@ async function main() { priceTimeout: priceTimeout.toString(), chainlinkFeed: networkConfig[chainId].chainlinkFeeds.USDC!, oracleError: usdcOracleError.toString(), - oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24 hr + oracleTimeout: usdcOracleTimeout, // 24 hr maxTradeVolume: fp('1e6').toString(), defaultThreshold: fp('0.01').add(usdcOracleError).toString(), delayUntilDefault: bn('86400').toString(), diff --git a/scripts/verification/collateral-plugins/verify_cbeth.ts b/scripts/verification/collateral-plugins/verify_cbeth.ts index 4e58ad88d5..9b52d6323b 100644 --- a/scripts/verification/collateral-plugins/verify_cbeth.ts +++ b/scripts/verification/collateral-plugins/verify_cbeth.ts @@ -7,7 +7,7 @@ import { getAssetCollDeploymentFilename, IAssetCollDeployments, } from '../../deployment/common' -import { priceTimeout, oracleTimeout, verifyContract, combinedError } from '../../deployment/utils' +import { priceTimeout, verifyContract, combinedError } from '../../deployment/utils' let deployments: IAssetCollDeployments @@ -40,14 +40,14 @@ async function main() { oracleError: oracleError.toString(), // 0.5% & 2% erc20: networkConfig[chainId].tokens.cbETH!, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.02').add(oracleError).toString(), // ~4.5% delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4'), // revenueHiding = 0.01% networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed - oracleTimeout(chainId, '86400').toString(), // refPerTokChainlinkTimeout + '86400', // refPerTokChainlinkTimeout ], 'contracts/plugins/assets/cbeth/CBETHCollateral.sol:CBEthCollateral' ) @@ -63,16 +63,16 @@ async function main() { oracleError: oracleError.toString(), // 0.15% & 0.5%, erc20: networkConfig[chainId].tokens.cbETH!, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '1200').toString(), // 20 min + oracleTimeout: '1200', // 20 min targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.02').add(oracleError).toString(), // ~2.5% delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4'), // revenueHiding = 0.01% networkConfig[chainId].chainlinkFeeds.cbETH!, // refPerTokChainlinkFeed - oracleTimeout(chainId, '86400').toString(), // refPerTokChainlinkTimeout + '86400', // refPerTokChainlinkTimeout networkConfig[chainId].chainlinkFeeds.cbETHETHexr!, // exchangeRateChainlinkFeed - oracleTimeout(chainId, '86400').toString(), // exchangeRateChainlinkTimeout + '86400', // exchangeRateChainlinkTimeout ], 'contracts/plugins/assets/cbeth/CBETHCollateralL2.sol:CBEthCollateralL2' ) diff --git a/scripts/verification/collateral-plugins/verify_convex_stable.ts b/scripts/verification/collateral-plugins/verify_convex_stable.ts index 3c22ae2557..cd7ffc0e86 100644 --- a/scripts/verification/collateral-plugins/verify_convex_stable.ts +++ b/scripts/verification/collateral-plugins/verify_convex_stable.ts @@ -11,7 +11,7 @@ import { IDeployments, } from '../../deployment/common' import { verifyContract } from '../../deployment/utils' -import { revenueHiding, oracleTimeout } from '../../deployment/utils' +import { revenueHiding } from '../../deployment/utils' import { CurvePoolType, DAI_ORACLE_ERROR, @@ -85,7 +85,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -96,11 +96,7 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], - ], + oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, }, diff --git a/scripts/verification/collateral-plugins/verify_convex_stable_metapool.ts b/scripts/verification/collateral-plugins/verify_convex_stable_metapool.ts index 440f8854b2..fedb2d418a 100644 --- a/scripts/verification/collateral-plugins/verify_convex_stable_metapool.ts +++ b/scripts/verification/collateral-plugins/verify_convex_stable_metapool.ts @@ -7,7 +7,7 @@ import { IAssetCollDeployments, } from '../../deployment/common' import { verifyContract } from '../../deployment/utils' -import { revenueHiding, oracleTimeout } from '../../deployment/utils' +import { revenueHiding } from '../../deployment/utils' import { CurvePoolType, DAI_ORACLE_ERROR, @@ -75,11 +75,7 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], - ], + oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, }, diff --git a/scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts b/scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts index 771f6bc767..400c23d10e 100644 --- a/scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts +++ b/scripts/verification/collateral-plugins/verify_convex_stable_rtoken_metapool.ts @@ -9,7 +9,7 @@ import { IAssetCollDeployments, } from '../../deployment/common' import { verifyContract } from '../../deployment/utils' -import { revenueHiding, oracleTimeout } from '../../deployment/utils' +import { revenueHiding } from '../../deployment/utils' import { CurvePoolType, DEFAULT_THRESHOLD, @@ -59,7 +59,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), + oracleTimeout: USDC_ORACLE_TIMEOUT, maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, @@ -70,10 +70,7 @@ async function main() { curvePool: FRAX_BP, poolType: CurvePoolType.Plain, feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, FRAX_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], - ], + oracleTimeouts: [[FRAX_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT]], oracleErrors: [[FRAX_ORACLE_ERROR], [USDC_ORACLE_ERROR]], lpToken: FRAX_BP_TOKEN, }, diff --git a/scripts/verification/collateral-plugins/verify_convex_volatile.ts b/scripts/verification/collateral-plugins/verify_convex_volatile.ts deleted file mode 100644 index 8c48da0e56..0000000000 --- a/scripts/verification/collateral-plugins/verify_convex_volatile.ts +++ /dev/null @@ -1,98 +0,0 @@ -import hre, { ethers } from 'hardhat' -import { getChainId } from '../../../common/blockchain-utils' -import { developmentChains, networkConfig } from '../../../common/configuration' -import { bn } from '../../../common/numbers' -import { ONE_ADDRESS } from '../../../common/constants' -import { - getDeploymentFile, - getAssetCollDeploymentFilename, - IAssetCollDeployments, -} from '../../deployment/common' -import { verifyContract } from '../../deployment/utils' -import { revenueHiding, oracleTimeout } from '../../deployment/utils' -import { - CurvePoolType, - BTC_USD_ORACLE_ERROR, - BTC_ORACLE_TIMEOUT, - BTC_USD_FEED, - DEFAULT_THRESHOLD, - DELAY_UNTIL_DEFAULT, - MAX_TRADE_VOL, - PRICE_TIMEOUT, - TRI_CRYPTO, - TRI_CRYPTO_TOKEN, - WBTC_BTC_ORACLE_ERROR, - WETH_ORACLE_TIMEOUT, - WBTC_BTC_FEED, - WBTC_ORACLE_TIMEOUT, - WETH_USD_FEED, - WETH_ORACLE_ERROR, - USDT_ORACLE_ERROR, - USDT_ORACLE_TIMEOUT, - USDT_USD_FEED, -} from '../../../test/plugins/individual-collateral/curve/constants' - -let deployments: IAssetCollDeployments - -async function main() { - // ********** Read config ********** - const chainId = await getChainId(hre) - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - - if (developmentChains.includes(hre.network.name)) { - throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) - } - - const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) - deployments = getDeploymentFile(assetCollDeploymentFilename) - - const wTriCrypto = await ethers.getContractAt( - 'CurveVolatileCollateral', - deployments.collateral.cvxTriCrypto as string - ) - - /******** Verify TriCrypto plugin **************************/ - await verifyContract( - chainId, - deployments.collateral.cvxTriCrypto, - [ - { - erc20: await wTriCrypto.erc20(), - targetName: ethers.utils.formatBytes32String('TRICRYPTO'), - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero - oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero - maxTradeVolume: MAX_TRADE_VOL, - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - }, - revenueHiding.toString(), - { - nTokens: 3, - curvePool: TRI_CRYPTO, - poolType: CurvePoolType.Plain, - feeds: [[USDT_USD_FEED], [WBTC_BTC_FEED, BTC_USD_FEED], [WETH_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, WBTC_ORACLE_TIMEOUT), oracleTimeout(chainId, BTC_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, WETH_ORACLE_TIMEOUT)], - ], - oracleErrors: [ - [USDT_ORACLE_ERROR], - [WBTC_BTC_ORACLE_ERROR, BTC_USD_ORACLE_ERROR], - [WETH_ORACLE_ERROR], - ], - lpToken: TRI_CRYPTO_TOKEN, - }, - ], - 'contracts/plugins/assets/convex/CurveVolatileCollateral.sol:CurveVolatileCollateral' - ) -} - -main().catch((error) => { - console.error(error) - process.exitCode = 1 -}) diff --git a/scripts/verification/collateral-plugins/verify_curve_stable.ts b/scripts/verification/collateral-plugins/verify_curve_stable.ts index ce1120f618..3f4b66190a 100644 --- a/scripts/verification/collateral-plugins/verify_curve_stable.ts +++ b/scripts/verification/collateral-plugins/verify_curve_stable.ts @@ -9,7 +9,7 @@ import { IAssetCollDeployments, } from '../../deployment/common' import { verifyContract } from '../../deployment/utils' -import { revenueHiding, oracleTimeout } from '../../deployment/utils' +import { revenueHiding } from '../../deployment/utils' import { CRV, CurvePoolType, @@ -72,7 +72,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -83,11 +83,7 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], - ], + oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, }, diff --git a/scripts/verification/collateral-plugins/verify_curve_stable_metapool.ts b/scripts/verification/collateral-plugins/verify_curve_stable_metapool.ts index 60be29f1e0..e1b433bbd5 100644 --- a/scripts/verification/collateral-plugins/verify_curve_stable_metapool.ts +++ b/scripts/verification/collateral-plugins/verify_curve_stable_metapool.ts @@ -7,7 +7,7 @@ import { IAssetCollDeployments, } from '../../deployment/common' import { verifyContract } from '../../deployment/utils' -import { revenueHiding, oracleTimeout } from '../../deployment/utils' +import { revenueHiding } from '../../deployment/utils' import { CurvePoolType, DAI_ORACLE_ERROR, @@ -75,11 +75,7 @@ async function main() { curvePool: THREE_POOL, poolType: CurvePoolType.Plain, feeds: [[DAI_USD_FEED], [USDC_USD_FEED], [USDT_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, DAI_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], - ], + oracleTimeouts: [[DAI_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT], [USDT_ORACLE_TIMEOUT]], oracleErrors: [[DAI_ORACLE_ERROR], [USDC_ORACLE_ERROR], [USDT_ORACLE_ERROR]], lpToken: THREE_POOL_TOKEN, }, diff --git a/scripts/verification/collateral-plugins/verify_curve_stable_rtoken_metapool.ts b/scripts/verification/collateral-plugins/verify_curve_stable_rtoken_metapool.ts index a48df02d1f..43d2172f10 100644 --- a/scripts/verification/collateral-plugins/verify_curve_stable_rtoken_metapool.ts +++ b/scripts/verification/collateral-plugins/verify_curve_stable_rtoken_metapool.ts @@ -9,7 +9,7 @@ import { IAssetCollDeployments, } from '../../deployment/common' import { verifyContract } from '../../deployment/utils' -import { revenueHiding, oracleTimeout } from '../../deployment/utils' +import { revenueHiding } from '../../deployment/utils' import { CurvePoolType, DEFAULT_THRESHOLD, @@ -59,7 +59,7 @@ async function main() { priceTimeout: PRICE_TIMEOUT, chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: oracleTimeout(chainId, USDC_ORACLE_TIMEOUT), // max of oracleTimeouts + oracleTimeout: USDC_ORACLE_TIMEOUT, // max of oracleTimeouts maxTradeVolume: MAX_TRADE_VOL, defaultThreshold: DEFAULT_THRESHOLD, // 2%: 1% error on FRAX oracle + 1% base defaultThreshold delayUntilDefault: RTOKEN_DELAY_UNTIL_DEFAULT, @@ -70,10 +70,7 @@ async function main() { curvePool: FRAX_BP, poolType: CurvePoolType.Plain, feeds: [[FRAX_USD_FEED], [USDC_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, FRAX_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, USDC_ORACLE_TIMEOUT)], - ], + oracleTimeouts: [[FRAX_ORACLE_TIMEOUT], [USDC_ORACLE_TIMEOUT]], oracleErrors: [[FRAX_ORACLE_ERROR], [USDC_ORACLE_ERROR]], lpToken: FRAX_BP_TOKEN, }, diff --git a/scripts/verification/collateral-plugins/verify_curve_volatile.ts b/scripts/verification/collateral-plugins/verify_curve_volatile.ts deleted file mode 100644 index 2f5c53b2c1..0000000000 --- a/scripts/verification/collateral-plugins/verify_curve_volatile.ts +++ /dev/null @@ -1,98 +0,0 @@ -import hre, { ethers } from 'hardhat' -import { getChainId } from '../../../common/blockchain-utils' -import { developmentChains, networkConfig } from '../../../common/configuration' -import { bn } from '../../../common/numbers' -import { ONE_ADDRESS } from '../../../common/constants' -import { - getDeploymentFile, - getAssetCollDeploymentFilename, - IAssetCollDeployments, -} from '../../deployment/common' -import { verifyContract } from '../../deployment/utils' -import { revenueHiding, oracleTimeout } from '../../deployment/utils' -import { - CurvePoolType, - BTC_USD_ORACLE_ERROR, - BTC_ORACLE_TIMEOUT, - BTC_USD_FEED, - DEFAULT_THRESHOLD, - DELAY_UNTIL_DEFAULT, - MAX_TRADE_VOL, - PRICE_TIMEOUT, - TRI_CRYPTO, - TRI_CRYPTO_TOKEN, - WBTC_BTC_ORACLE_ERROR, - WETH_ORACLE_TIMEOUT, - WBTC_BTC_FEED, - WBTC_ORACLE_TIMEOUT, - WETH_USD_FEED, - WETH_ORACLE_ERROR, - USDT_ORACLE_ERROR, - USDT_ORACLE_TIMEOUT, - USDT_USD_FEED, -} from '../../../test/plugins/individual-collateral/curve/constants' - -let deployments: IAssetCollDeployments - -async function main() { - // ********** Read config ********** - const chainId = await getChainId(hre) - if (!networkConfig[chainId]) { - throw new Error(`Missing network configuration for ${hre.network.name}`) - } - - if (developmentChains.includes(hre.network.name)) { - throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) - } - - const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) - deployments = getDeploymentFile(assetCollDeploymentFilename) - - const wTriCrypto = await ethers.getContractAt( - 'CurveVolatileCollateral', - deployments.collateral.crvTriCrypto as string - ) - - /******** Verify TriCrypto plugin **************************/ - await verifyContract( - chainId, - deployments.collateral.crvTriCrypto, - [ - { - erc20: await wTriCrypto.erc20(), - targetName: ethers.utils.formatBytes32String('TRICRYPTO'), - priceTimeout: PRICE_TIMEOUT, - chainlinkFeed: ONE_ADDRESS, // unused but cannot be zero - oracleError: bn('1'), // unused but cannot be zero - oracleTimeout: bn('1'), // unused but cannot be zero - maxTradeVolume: MAX_TRADE_VOL, - defaultThreshold: DEFAULT_THRESHOLD, - delayUntilDefault: DELAY_UNTIL_DEFAULT, - }, - revenueHiding.toString(), - { - nTokens: 3, - curvePool: TRI_CRYPTO, - poolType: CurvePoolType.Plain, - feeds: [[USDT_USD_FEED], [WBTC_BTC_FEED, BTC_USD_FEED], [WETH_USD_FEED]], - oracleTimeouts: [ - [oracleTimeout(chainId, USDT_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, WBTC_ORACLE_TIMEOUT), oracleTimeout(chainId, BTC_ORACLE_TIMEOUT)], - [oracleTimeout(chainId, WETH_ORACLE_TIMEOUT)], - ], - oracleErrors: [ - [USDT_ORACLE_ERROR], - [WBTC_BTC_ORACLE_ERROR, BTC_USD_ORACLE_ERROR], - [WETH_ORACLE_ERROR], - ], - lpToken: TRI_CRYPTO_TOKEN, - }, - ], - 'contracts/plugins/assets/convex/CurveVolatileCollateral.sol:CurveVolatileCollateral' - ) -} - -main().catch((error) => { - console.error(error) - process.exitCode = 1 -}) diff --git a/scripts/verification/collateral-plugins/verify_cusdbcv3.ts b/scripts/verification/collateral-plugins/verify_cusdbcv3.ts index c3a6cb314e..d0eb672ef2 100644 --- a/scripts/verification/collateral-plugins/verify_cusdbcv3.ts +++ b/scripts/verification/collateral-plugins/verify_cusdbcv3.ts @@ -7,7 +7,7 @@ import { getAssetCollDeploymentFilename, IAssetCollDeployments, } from '../../deployment/common' -import { priceTimeout, oracleTimeout, verifyContract, revenueHiding } from '../../deployment/utils' +import { priceTimeout, verifyContract, revenueHiding } from '../../deployment/utils' let deployments: IAssetCollDeployments @@ -50,7 +50,7 @@ async function main() { /******** Verify Collateral - wcUSDbCv3 **************************/ - const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleTimeout = '86400' // 24 hr const usdcOracleError = fp('0.003') // 0.3% (Base) await verifyContract( @@ -63,7 +63,7 @@ async function main() { oracleError: usdcOracleError.toString(), erc20: await collateral.erc20(), maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24h hr, + oracleTimeout: usdcOracleTimeout, // 24h hr, targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdcOracleError).toString(), // 1% + 0.3% delayUntilDefault: bn('86400').toString(), // 24h diff --git a/scripts/verification/collateral-plugins/verify_cusdcv3.ts b/scripts/verification/collateral-plugins/verify_cusdcv3.ts index 62c1389289..09a6eceb34 100644 --- a/scripts/verification/collateral-plugins/verify_cusdcv3.ts +++ b/scripts/verification/collateral-plugins/verify_cusdcv3.ts @@ -7,7 +7,7 @@ import { getAssetCollDeploymentFilename, IAssetCollDeployments, } from '../../deployment/common' -import { priceTimeout, oracleTimeout, verifyContract, revenueHiding } from '../../deployment/utils' +import { priceTimeout, verifyContract, revenueHiding } from '../../deployment/utils' let deployments: IAssetCollDeployments @@ -45,7 +45,7 @@ async function main() { /******** Verify Collateral - wcUSDCv3 **************************/ - const usdcOracleTimeout = 86400 // 24 hr + const usdcOracleTimeout = '86400' // 24 hr const usdcOracleError = baseL2Chains.includes(hre.network.name) ? fp('0.003') : fp('0.0025') // 0.3% (Base) or 0.25% await verifyContract( @@ -58,7 +58,7 @@ async function main() { oracleError: usdcOracleError.toString(), erc20: await collateral.erc20(), maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, usdcOracleTimeout).toString(), // 24h hr, + oracleTimeout: usdcOracleTimeout, // 24h hr, targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01').add(usdcOracleError).toString(), delayUntilDefault: bn('86400').toString(), // 24h diff --git a/scripts/verification/collateral-plugins/verify_morpho.ts b/scripts/verification/collateral-plugins/verify_morpho.ts index ba7658f5af..4f9e6d832b 100644 --- a/scripts/verification/collateral-plugins/verify_morpho.ts +++ b/scripts/verification/collateral-plugins/verify_morpho.ts @@ -7,13 +7,7 @@ import { getAssetCollDeploymentFilename, IAssetCollDeployments, } from '../../deployment/common' -import { - combinedError, - priceTimeout, - oracleTimeout, - verifyContract, - revenueHiding, -} from '../../deployment/utils' +import { combinedError, priceTimeout, verifyContract, revenueHiding } from '../../deployment/utils' let deployments: IAssetCollDeployments @@ -64,7 +58,7 @@ async function main() { priceTimeout: priceTimeout.toString(), oracleError: fp('0.0025').toString(), // 0.25% maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '86400').toString(), // 1 hr + oracleTimeout: '86400', // 1 hr targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.0025').add(fp('0.01')).toString(), // 1.25% delayUntilDefault: bn('86400').toString(), // 24h @@ -92,7 +86,7 @@ async function main() { priceTimeout: priceTimeout.toString(), oracleError: combinedBTCWBTCError.toString(), // 0.25% maxTradeVolume: fp('1e6'), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: fp('0.01').add(combinedBTCWBTCError), // ~3.5% delayUntilDefault: bn('86400'), // 24h @@ -101,7 +95,7 @@ async function main() { }, revenueHiding, networkConfig[chainId].chainlinkFeeds.WBTC!, - oracleTimeout(chainId, '86400').toString(), // 1 hr + '86400', // 1 hr ], 'contracts/plugins/assets/morpho-aave/MorphoNonFiatCollateral.sol:MorphoNonFiatCollateral' ) @@ -121,7 +115,7 @@ async function main() { priceTimeout: priceTimeout, oracleError: fp('0.005'), maxTradeVolume: fp('1e6'), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600'), // 1 hr + oracleTimeout: '3600', // 1 hr targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0'), // 0% -- no soft default for self-referential collateral delayUntilDefault: bn('86400'), // 24h diff --git a/scripts/verification/collateral-plugins/verify_reth.ts b/scripts/verification/collateral-plugins/verify_reth.ts index 324a081859..077cc76e0d 100644 --- a/scripts/verification/collateral-plugins/verify_reth.ts +++ b/scripts/verification/collateral-plugins/verify_reth.ts @@ -7,7 +7,7 @@ import { getAssetCollDeploymentFilename, IAssetCollDeployments, } from '../../deployment/common' -import { priceTimeout, oracleTimeout, verifyContract, combinedError } from '../../deployment/utils' +import { priceTimeout, verifyContract, combinedError } from '../../deployment/utils' let deployments: IAssetCollDeployments @@ -37,14 +37,14 @@ async function main() { oracleError: oracleError.toString(), // 0.5% & 2%, erc20: networkConfig[chainId].tokens.rETH, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, + oracleTimeout: '3600', // 1 hr, targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.02').add(oracleError).toString(), // ~4.5% delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4'), // revenueHiding = 0.01% networkConfig[chainId].chainlinkFeeds.rETH, // refPerTokChainlinkFeed - oracleTimeout(chainId, '86400').toString(), // refPerTokChainlinkTimeout + '86400', // refPerTokChainlinkTimeout ], 'contracts/plugins/assets/rocket-eth/RethCollateral.sol:RethCollateral' ) diff --git a/scripts/verification/collateral-plugins/verify_wsteth.ts b/scripts/verification/collateral-plugins/verify_wsteth.ts index c0b73b2fb0..b84c9aad57 100644 --- a/scripts/verification/collateral-plugins/verify_wsteth.ts +++ b/scripts/verification/collateral-plugins/verify_wsteth.ts @@ -7,7 +7,7 @@ import { getAssetCollDeploymentFilename, IAssetCollDeployments, } from '../../deployment/common' -import { priceTimeout, oracleTimeout, verifyContract } from '../../deployment/utils' +import { priceTimeout, verifyContract } from '../../deployment/utils' let deployments: IAssetCollDeployments @@ -38,14 +38,14 @@ async function main() { oracleError: fp('0.01').toString(), // 1%: only for stETHUSD feed erc20: networkConfig[chainId].tokens.wstETH, maxTradeVolume: fp('1e6').toString(), // $1m, - oracleTimeout: oracleTimeout(chainId, '3600').toString(), // 1 hr, + oracleTimeout: '3600', // 1 hr, targetName: hre.ethers.utils.formatBytes32String('ETH'), defaultThreshold: fp('0.025').toString(), // 2.5% = 2% + 0.5% stethETH feed oracleError delayUntilDefault: bn('86400').toString(), // 24h }, fp('1e-4'), // revenueHiding = 0.01% networkConfig[chainId].chainlinkFeeds.stETHETH, // targetPerRefChainlinkFeed - oracleTimeout(chainId, '86400').toString(), // targetPerRefChainlinkTimeout + '86400', // targetPerRefChainlinkTimeout ], 'contracts/plugins/assets/lido/LidoStakedEthCollateral.sol:LidoStakedEthCollateral' ) diff --git a/test/Broker.test.ts b/test/Broker.test.ts index 251d0e1a5b..633d1fed48 100644 --- a/test/Broker.test.ts +++ b/test/Broker.test.ts @@ -42,7 +42,7 @@ import { Implementation, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, SLOW, } from './fixtures' @@ -1251,7 +1251,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { oracleError: ORACLE_ERROR, erc20: token0.address, maxTradeVolume: bn(500), - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1416,7 +1416,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { oracleError: bn('1'), // minimize erc20: sellTok.address, maxTradeVolume: MAX_UINT192, - oracleTimeout: MAX_UINT48, + oracleTimeout: MAX_UINT48.sub(300), targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01'), // shouldn't matter delayUntilDefault: bn('604800'), // shouldn't matter @@ -1428,7 +1428,7 @@ describe(`BrokerP${IMPLEMENTATION} contract #fast`, () => { oracleError: bn('1'), // minimize erc20: buyTok.address, maxTradeVolume: MAX_UINT192, - oracleTimeout: MAX_UINT48, + oracleTimeout: MAX_UINT48.sub(300), targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01'), // shouldn't matter delayUntilDefault: bn('604800'), // shouldn't matter diff --git a/test/Main.test.ts b/test/Main.test.ts index 7556200088..de7d519b73 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -71,6 +71,7 @@ import { IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, REVENUE_HIDING, } from './fixtures' @@ -1181,7 +1182,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: newToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: await collateral0.oracleTimeout(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: await ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral0.delayUntilDefault(), @@ -1203,7 +1204,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: newToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: await collateral0.oracleTimeout(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: await ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral0.delayUntilDefault(), @@ -1229,7 +1230,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: await gasGuzzlingColl.erc20(), maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: await collateral0.oracleTimeout(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: await ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral0.delayUntilDefault(), @@ -1628,7 +1629,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: erc20s[5].address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: await collateral2.oracleTimeout(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral2.delayUntilDefault(), @@ -1724,7 +1725,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: eurToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: await collateral0.oracleTimeout(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral1.delayUntilDefault(), @@ -1947,7 +1948,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: await collateral0.erc20(), maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: await collateral0.oracleTimeout(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('NEW_TARGET'), defaultThreshold: fp('0.01'), delayUntilDefault: await collateral0.delayUntilDefault(), @@ -2802,7 +2803,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: await collateral2.erc20(), maxTradeVolume: await collateral2.maxTradeVolume(), - oracleTimeout: await collateral2.oracleTimeout(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral2.delayUntilDefault(), @@ -2837,7 +2838,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: await collateral2.erc20(), maxTradeVolume: await collateral2.maxTradeVolume(), - oracleTimeout: await collateral2.oracleTimeout(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral2.delayUntilDefault(), @@ -2862,7 +2863,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: await collateral2.erc20(), maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: await collateral2.oracleTimeout(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral2.delayUntilDefault(), @@ -3084,7 +3085,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: await collateral2.erc20(), maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: await collateral2.oracleTimeout(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral2.delayUntilDefault(), @@ -3128,7 +3129,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: await collateral2.erc20(), maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: await collateral2.oracleTimeout(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral2.delayUntilDefault(), @@ -3158,7 +3159,7 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: token0.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: await collateral0.oracleTimeout(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: await ethers.utils.formatBytes32String('NEW TARGET'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral0.delayUntilDefault(), diff --git a/test/RTokenExtremes.test.ts b/test/RTokenExtremes.test.ts index 229960812c..f5c8afa994 100644 --- a/test/RTokenExtremes.test.ts +++ b/test/RTokenExtremes.test.ts @@ -21,7 +21,7 @@ import { IMPLEMENTATION, ORACLE_ERROR, SLOW, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, defaultFixtureNoBasket, } from './fixtures' @@ -66,7 +66,7 @@ describe(`RTokenP${IMPLEMENTATION} contract`, () => { oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: fp('1e36'), - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01'), delayUntilDefault: bn(86400), diff --git a/test/Recollateralization.test.ts b/test/Recollateralization.test.ts index 37cfe6955e..30796f9b8d 100644 --- a/test/Recollateralization.test.ts +++ b/test/Recollateralization.test.ts @@ -51,6 +51,7 @@ import { IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, } from './fixtures' import snapshotGasCost from './utils/snapshotGasCost' @@ -643,7 +644,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR, erc20: token1.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await collateral1.delayUntilDefault(), @@ -656,7 +657,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR, erc20: backupToken1.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await backupCollateral1.delayUntilDefault(), @@ -2145,7 +2146,7 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR, erc20: token0.address, maxTradeVolume: fp('25'), - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: await backupCollateral1.delayUntilDefault(), @@ -3248,7 +3249,6 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { collateral1.address, collateral0.address, issueAmount, - config.minTradeVolume, config.maxTradeSlippage ), bn('1e12') @@ -3312,7 +3312,6 @@ describe(`Recollateralization - P${IMPLEMENTATION}`, () => { collateral0.address, collateral1.address, issueAmount, - config.minTradeVolume, config.maxTradeSlippage ), bn('1e12') // decimals diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index f4bcf4b8d0..1c7ad79df4 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -60,6 +60,7 @@ import { REVENUE_HIDING, ORACLE_ERROR, ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, } from './fixtures' import { expectRTokenPrice, setOraclePrice } from './utils/oracles' @@ -1214,7 +1215,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, aaveToken.address, bn(606), // 2 qTok auction at $300 (after accounting for price.high) - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) // Set a very high price @@ -1295,7 +1296,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, aaveToken.address, MAX_UINT192, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -1306,7 +1307,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, rsr.address, MAX_UINT192, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -1474,7 +1475,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, aaveToken.address, fp('1'), - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -1673,7 +1674,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, aaveToken.address, fp('1'), - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -1872,7 +1873,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, aaveToken.address, fp('1'), - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -3376,7 +3377,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR, erc20: token2.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.05'), delayUntilDefault: await collateral2.delayUntilDefault(), @@ -4578,7 +4579,7 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, compToken.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) diff --git a/test/fixtures.ts b/test/fixtures.ts index da44a54b75..8726a3e434 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -71,7 +71,9 @@ export const SLOW = !!useEnv('SLOW') export const PRICE_TIMEOUT = bn('604800') // 1 week -export const ORACLE_TIMEOUT = bn('281474976710655').div(100) // type(uint48).max / 100 +export const ORACLE_TIMEOUT_PRE_BUFFER = bn('281474976710655').div(100) // type(uint48).max / 100 + +export const ORACLE_TIMEOUT = ORACLE_TIMEOUT_PRE_BUFFER.add(300) export const ORACLE_ERROR = fp('0.01') // 1% oracle error @@ -183,7 +185,7 @@ async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: defaultThreshold, delayUntilDefault: delayUntilDefault, @@ -203,7 +205,7 @@ async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: defaultThreshold, delayUntilDefault: delayUntilDefault, @@ -223,7 +225,7 @@ async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: defaultThreshold, delayUntilDefault: delayUntilDefault, @@ -252,7 +254,7 @@ async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: defaultThreshold, delayUntilDefault: delayUntilDefault, @@ -280,7 +282,7 @@ async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: defaultThreshold, delayUntilDefault: delayUntilDefault, @@ -499,7 +501,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = ORACLE_ERROR, rsr.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) await rsrAsset.refresh() @@ -631,7 +633,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = ORACLE_ERROR, aaveToken.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) await aaveAsset.refresh() @@ -646,7 +648,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = ORACLE_ERROR, compToken.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) await compAsset.refresh() diff --git a/test/integration/AssetPlugins.test.ts b/test/integration/AssetPlugins.test.ts index 1729c21c9b..71ae4bf11d 100644 --- a/test/integration/AssetPlugins.test.ts +++ b/test/integration/AssetPlugins.test.ts @@ -8,6 +8,7 @@ import { IMPLEMENTATION, ORACLE_ERROR, ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, REVENUE_HIDING, } from '../fixtures' @@ -1122,7 +1123,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, ORACLE_ERROR, networkConfig[chainId].tokens.stkAAVE || '', config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) await setOraclePrice(zeroPriceAsset.address, bn('1e10')) @@ -1202,7 +1203,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: dai.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -1288,7 +1289,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -1376,7 +1377,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: stataDai.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -1455,13 +1456,13 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: wbtc.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold, delayUntilDefault, }, mockChainlinkFeed.address, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) await setOraclePrice(zeroPriceNonFiatCollateral.address, bn('1e10')) await zeroPriceNonFiatCollateral.refresh() @@ -1535,13 +1536,13 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: cWBTCVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold, delayUntilDefault, }, mockChainlinkFeed.address, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, REVENUE_HIDING ) ) @@ -1610,7 +1611,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: weth.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: bn('0'), delayUntilDefault, @@ -1693,7 +1694,7 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: cETHVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: bn('0'), delayUntilDefault, @@ -1776,13 +1777,13 @@ describeFork(`Asset Plugins - Integration - Mainnet Forking P${IMPLEMENTATION}`, oracleError: ORACLE_ERROR, erc20: eurt.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold, delayUntilDefault, }, mockChainlinkFeed.address, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) await setOraclePrice(invalidPriceEURCollateral.address, bn('1e10')) await invalidPriceEURCollateral.refresh() diff --git a/test/integration/EasyAuction.test.ts b/test/integration/EasyAuction.test.ts index af2f439042..355d92f00c 100644 --- a/test/integration/EasyAuction.test.ts +++ b/test/integration/EasyAuction.test.ts @@ -753,7 +753,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function oracleError: ORACLE_ERROR, // shouldn't matter erc20: sellTok.address, maxTradeVolume: MAX_UINT192, - oracleTimeout: MAX_UINT48, + oracleTimeout: MAX_UINT48.sub(300), targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01'), // shouldn't matter delayUntilDefault: bn('604800'), // shouldn't matter @@ -765,7 +765,7 @@ describeFork(`Gnosis EasyAuction Mainnet Forking - P${IMPLEMENTATION}`, function oracleError: ORACLE_ERROR, // shouldn't matter erc20: buyTok.address, maxTradeVolume: MAX_UINT192, - oracleTimeout: MAX_UINT48, + oracleTimeout: MAX_UINT48.sub(300), targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: fp('0.01'), // shouldn't matter delayUntilDefault: bn('604800'), // shouldn't matter diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index f22f9a1f1e..206cfb2afe 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -60,7 +60,7 @@ import { Implementation, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, REVENUE_HIDING, } from '../fixtures' @@ -190,7 +190,7 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -219,7 +219,7 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: vault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -254,7 +254,7 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: staticErc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -280,13 +280,13 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String(targetName), defaultThreshold, delayUntilDefault, }, targetUnitOracleAddr, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) await coll.refresh() return [erc20, coll] @@ -314,13 +314,13 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: vault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String(targetName), defaultThreshold, delayUntilDefault, }, targetUnitOracleAddr, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, REVENUE_HIDING ) await coll.refresh() @@ -339,7 +339,7 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String(targetName), defaultThreshold: bn(0), delayUntilDefault, @@ -371,7 +371,7 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: vault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String(targetName), defaultThreshold: bn(0), delayUntilDefault, @@ -399,13 +399,13 @@ export async function collateralFixture( oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String(targetName), defaultThreshold, delayUntilDefault, }, targetUnitOracleAddr, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) await coll.refresh() return [erc20, coll] @@ -696,7 +696,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = ORACLE_ERROR, rsr.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) await rsrAsset.refresh() @@ -820,7 +820,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = ORACLE_ERROR, aaveToken.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) await aaveAsset.refresh() @@ -834,7 +834,7 @@ const makeDefaultFixture = async (setBasket: boolean): Promise = ORACLE_ERROR, compToken.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) await compAsset.refresh() diff --git a/test/integration/fork-block-numbers.ts b/test/integration/fork-block-numbers.ts index cf5b9d0336..30df303eda 100644 --- a/test/integration/fork-block-numbers.ts +++ b/test/integration/fork-block-numbers.ts @@ -5,7 +5,7 @@ const forkBlockNumber = { 'mainnet-deployment': 15690042, // Ethereum 'flux-finance': 16836855, // Ethereum 'mainnet-2.0': 17522362, // Ethereum - default: 16934828, // Ethereum + default: 18371215, // Ethereum } export default forkBlockNumber diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index 95ad7cd34b..4dd32dc769 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -45,6 +45,7 @@ import { IMPLEMENTATION, Implementation, ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, ORACLE_ERROR, PRICE_TIMEOUT, VERSION, @@ -430,7 +431,7 @@ describe('Assets contracts #fast', () => { oracleError: ORACLE_ERROR, erc20: await collateral0.erc20(), maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -467,7 +468,7 @@ describe('Assets contracts #fast', () => { oracleError: ORACLE_ERROR, erc20: await collateral0.erc20(), maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -555,7 +556,7 @@ describe('Assets contracts #fast', () => { ORACLE_ERROR, rsr.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -640,7 +641,7 @@ describe('Assets contracts #fast', () => { ORACLE_ERROR, rsr.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index f7337177e1..e52458f39e 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -52,6 +52,7 @@ import { Collateral, defaultFixture, ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, ORACLE_ERROR, PRICE_TIMEOUT, REVENUE_HIDING, @@ -254,7 +255,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: token.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.constants.HashZero, defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -272,7 +273,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: aToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: bn(0), delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -290,7 +291,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: bn(0), delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -308,7 +309,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: token.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), @@ -324,7 +325,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: aToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), @@ -342,7 +343,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), @@ -360,7 +361,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: token.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: MAX_DELAY_UNTIL_DEFAULT + 1, @@ -376,7 +377,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: aToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: MAX_DELAY_UNTIL_DEFAULT + 1, @@ -394,7 +395,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: MAX_DELAY_UNTIL_DEFAULT + 1, @@ -413,7 +414,7 @@ describe('Collateral contracts', () => { oracleError: bn('0'), erc20: token.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -429,7 +430,7 @@ describe('Collateral contracts', () => { oracleError: bn('0'), erc20: aToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -447,7 +448,7 @@ describe('Collateral contracts', () => { oracleError: bn('0'), erc20: cToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), @@ -466,7 +467,7 @@ describe('Collateral contracts', () => { oracleError: fp('1'), erc20: token.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -482,7 +483,7 @@ describe('Collateral contracts', () => { oracleError: fp('1'), erc20: aToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -500,7 +501,7 @@ describe('Collateral contracts', () => { oracleError: fp('1'), erc20: cToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -521,7 +522,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: aToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -539,7 +540,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -775,7 +776,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: aToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1180,13 +1181,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, targetUnitOracle.address, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) await nonFiatCollateral.refresh() @@ -1203,13 +1204,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), }, targetUnitOracle.address, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ).to.be.revertedWith('delayUntilDefault zero') }) @@ -1223,13 +1224,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, ZERO_ADDRESS, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ).to.be.revertedWith('missing targetUnit feed') }) @@ -1243,13 +1244,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, targetUnitOracle.address, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ).to.be.revertedWith('missing chainlink feed') }) @@ -1263,7 +1264,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1283,13 +1284,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: bn(0), delayUntilDefault: DELAY_UNTIL_DEFAULT, }, targetUnitOracle.address, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ).to.be.revertedWith('defaultThreshold zero') }) @@ -1379,13 +1380,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, targetUnitOracle.address, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -1407,13 +1408,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: nonFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, invalidChainlinkFeed.address, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) // Reverting with no reason @@ -1468,13 +1469,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, targetUnitOracle.address, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, REVENUE_HIDING ) await cTokenNonFiatCollateral.refresh() @@ -1492,13 +1493,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), }, targetUnitOracle.address, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, REVENUE_HIDING ) ).to.be.revertedWith('delayUntilDefault zero') @@ -1513,13 +1514,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, targetUnitOracle.address, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, fp('1') ) ).to.be.revertedWith('revenueHiding out of range') @@ -1534,13 +1535,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, targetUnitOracle.address, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, REVENUE_HIDING ) ).to.be.revertedWith('missing chainlink feed') @@ -1555,13 +1556,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, }, ZERO_ADDRESS, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, REVENUE_HIDING ) ).to.be.revertedWith('missing targetUnit feed') @@ -1576,7 +1577,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1597,13 +1598,13 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: bn(0), delayUntilDefault: DELAY_UNTIL_DEFAULT, }, targetUnitOracle.address, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, REVENUE_HIDING ) ).to.be.revertedWith('defaultThreshold zero') @@ -1816,7 +1817,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1845,7 +1846,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cNonFiatTokenVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1887,7 +1888,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: selfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: 0, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1927,7 +1928,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: selfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: bn(100), delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -1992,7 +1993,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: selfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: 0, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2042,7 +2043,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cSelfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: 0, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2066,7 +2067,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cSelfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2086,7 +2087,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cSelfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: bn(0), delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2106,7 +2107,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cSelfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: bn(200), delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2301,7 +2302,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: cSelfRefToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: 0, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2351,7 +2352,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2374,7 +2375,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: bn(0), @@ -2394,7 +2395,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2414,7 +2415,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2434,7 +2435,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2454,7 +2455,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: bn(0), delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2557,7 +2558,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -2585,7 +2586,7 @@ describe('Collateral contracts', () => { oracleError: ORACLE_ERROR, erc20: eurFiatToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index 952114c5fb..0ba9baf74e 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -10,7 +10,13 @@ import { PRICE_TIMEOUT, REVENUE_HIDING, } from '../../../fixtures' -import { DefaultFixture, Fixture, getDefaultFixture, ORACLE_TIMEOUT } from '../fixtures' +import { + DefaultFixture, + Fixture, + getDefaultFixture, + ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, +} from '../fixtures' import { getChainId } from '../../../../common/blockchain-utils' import forkBlockNumber from '../../../integration/fork-block-numbers' import { @@ -209,7 +215,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi ORACLE_ERROR, stkAave.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -233,7 +239,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: staticAToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -437,7 +443,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: ZERO_ADDRESS, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -455,7 +461,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: staticAToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: bn(0), delayUntilDefault, @@ -699,7 +705,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: staticAToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -724,7 +730,7 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: staticAToken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index 7b09c9e880..aefae34d5a 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -10,7 +10,13 @@ import { PRICE_TIMEOUT, REVENUE_HIDING, } from '../../../fixtures' -import { DefaultFixture, Fixture, getDefaultFixture, ORACLE_TIMEOUT } from '../fixtures' +import { + DefaultFixture, + Fixture, + getDefaultFixture, + ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, +} from '../fixtures' import { getChainId } from '../../../../common/blockchain-utils' import forkBlockNumber from '../../../integration/fork-block-numbers' import { @@ -212,7 +218,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi ORACLE_ERROR, compToken.address, config.rTokenMaxTradeVolume, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) @@ -235,7 +241,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -430,7 +436,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: ZERO_ADDRESS, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -466,7 +472,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: vault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -484,7 +490,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: bn(0), delayUntilDefault, @@ -712,7 +718,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, @@ -737,7 +743,7 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi oracleError: ORACLE_ERROR, erc20: cDaiVault.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold, delayUntilDefault, diff --git a/test/plugins/individual-collateral/fixtures.ts b/test/plugins/individual-collateral/fixtures.ts index b1b5e9e1fa..19897cfd91 100644 --- a/test/plugins/individual-collateral/fixtures.ts +++ b/test/plugins/individual-collateral/fixtures.ts @@ -31,7 +31,9 @@ import { RecollateralizationLibP1, } from '../../../typechain' -export const ORACLE_TIMEOUT = bn('500000000') // 5700d - large for tests only +export const ORACLE_TIMEOUT_PRE_BUFFER = bn('500000000') // 5700d - large for tests only + +export const ORACLE_TIMEOUT = ORACLE_TIMEOUT_PRE_BUFFER.add(300) export type Fixture = () => Promise diff --git a/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts b/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts index 20d9a1406a..e40322631c 100644 --- a/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts +++ b/test/plugins/individual-collateral/morpho-aave/MorphoAaveV2TokenisedDeposit.test.ts @@ -7,6 +7,8 @@ import { formatUnits, parseUnits } from 'ethers/lib/utils' import { expect } from 'chai' import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' import { bn } from '#/common/numbers' +import { getResetFork } from '../helpers' +import { FORK_BLOCK } from './constants' type ITokenSymbol = keyof ITokens const networkConfigToUse = networkConfig[31337] @@ -179,7 +181,8 @@ const execTestForToken = ({ type ITestContext = ReturnType extends Promise ? U : never let context: ITestContext - // const resetFork = getResetFork(17591000) + before(getResetFork(FORK_BLOCK)) + beforeEach(async () => { context = await loadFixture(beforeEachFn) }) diff --git a/test/scenario/BadCollateralPlugin.test.ts b/test/scenario/BadCollateralPlugin.test.ts index ec2e04c0ee..9745c962b5 100644 --- a/test/scenario/BadCollateralPlugin.test.ts +++ b/test/scenario/BadCollateralPlugin.test.ts @@ -27,7 +27,7 @@ import { defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, REVENUE_HIDING, } from '../fixtures' @@ -104,7 +104,7 @@ describe(`Bad Collateral Plugin - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR, erc20: token0.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, diff --git a/test/scenario/ComplexBasket.test.ts b/test/scenario/ComplexBasket.test.ts index 9dd384a82c..6b7479212c 100644 --- a/test/scenario/ComplexBasket.test.ts +++ b/test/scenario/ComplexBasket.test.ts @@ -34,7 +34,7 @@ import { defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, REVENUE_HIDING, } from '../fixtures' @@ -172,7 +172,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { ORACLE_ERROR, rsr.address, MAX_TRADE_VOLUME, - ORACLE_TIMEOUT + ORACLE_TIMEOUT_PRE_BUFFER ) ) await assetRegistry.connect(owner).swapRegistered(newRSRAsset.address) @@ -203,7 +203,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR.toString(), tokenAddress: usdToken.address, // DAI Token maxTradeVolume: MAX_TRADE_VOLUME.toString(), - oracleTimeout: ORACLE_TIMEOUT.toString(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER.toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD.toString(), delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), @@ -227,8 +227,8 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR.toString(), tokenAddress: eurToken.address, maxTradeVolume: MAX_TRADE_VOLUME.toString(), - oracleTimeout: ORACLE_TIMEOUT.toString(), - targetUnitOracleTimeout: ORACLE_TIMEOUT.toString(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER.toString(), + targetUnitOracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER.toString(), targetName: ethers.utils.formatBytes32String('EUR'), defaultThreshold: DEFAULT_THRESHOLD.toString(), delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), @@ -248,7 +248,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR.toString(), cToken: cUSDTokenVault.address, maxTradeVolume: MAX_TRADE_VOLUME.toString(), - oracleTimeout: ORACLE_TIMEOUT.toString(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER.toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD.toString(), delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), @@ -269,7 +269,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR.toString(), staticAToken: aUSDToken.address, maxTradeVolume: MAX_TRADE_VOLUME.toString(), - oracleTimeout: ORACLE_TIMEOUT.toString(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER.toString(), targetName: hre.ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD.toString(), delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), @@ -293,8 +293,8 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { combinedOracleError: ORACLE_ERROR.toString(), tokenAddress: wbtc.address, maxTradeVolume: MAX_TRADE_VOLUME.toString(), - oracleTimeout: ORACLE_TIMEOUT.toString(), - targetUnitOracleTimeout: ORACLE_TIMEOUT.toString(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER.toString(), + targetUnitOracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER.toString(), targetName: ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD.toString(), delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), @@ -323,8 +323,8 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { combinedOracleError: ORACLE_ERROR.toString(), cToken: cWBTCVault.address, maxTradeVolume: MAX_TRADE_VOLUME.toString(), - oracleTimeout: ORACLE_TIMEOUT.toString(), - targetUnitOracleTimeout: ORACLE_TIMEOUT.toString(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER.toString(), + targetUnitOracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER.toString(), targetName: hre.ethers.utils.formatBytes32String('BTC'), defaultThreshold: DEFAULT_THRESHOLD.toString(), delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), @@ -349,7 +349,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR.toString(), tokenAddress: weth.address, maxTradeVolume: MAX_TRADE_VOLUME.toString(), - oracleTimeout: ORACLE_TIMEOUT.toString(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER.toString(), targetName: hre.ethers.utils.formatBytes32String('ETH'), noOutput: true, }) @@ -380,7 +380,7 @@ describe(`Complex Basket - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR.toString(), cToken: cETHVault.address, maxTradeVolume: MAX_TRADE_VOLUME.toString(), - oracleTimeout: ORACLE_TIMEOUT.toString(), + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER.toString(), targetName: hre.ethers.utils.formatBytes32String('ETH'), revenueHiding: REVENUE_HIDING.toString(), referenceERC20Decimals: bn(18).toString(), diff --git a/test/scenario/MaxBasketSize.test.ts b/test/scenario/MaxBasketSize.test.ts index a3ab632140..f1380b63f7 100644 --- a/test/scenario/MaxBasketSize.test.ts +++ b/test/scenario/MaxBasketSize.test.ts @@ -28,7 +28,7 @@ import { defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, REVENUE_HIDING, } from '../fixtures' @@ -158,7 +158,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR, erc20: erc20.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -198,7 +198,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR, erc20: atoken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -245,7 +245,7 @@ describe(`Max Basket Size - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR, erc20: ctoken.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, diff --git a/test/scenario/NestedRTokens.test.ts b/test/scenario/NestedRTokens.test.ts index 6386b158fd..38b11aba25 100644 --- a/test/scenario/NestedRTokens.test.ts +++ b/test/scenario/NestedRTokens.test.ts @@ -22,7 +22,7 @@ import { DefaultFixture, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, REVENUE_HIDING, } from '../fixtures' @@ -119,7 +119,7 @@ describe(`Nested RTokens - P${IMPLEMENTATION}`, () => { oracleError: ORACLE_ERROR, erc20: staticATokenERC20.address, maxTradeVolume: one.config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, diff --git a/test/scenario/NontrivialPeg.test.ts b/test/scenario/NontrivialPeg.test.ts index 70e0fa263f..c247b1cf98 100644 --- a/test/scenario/NontrivialPeg.test.ts +++ b/test/scenario/NontrivialPeg.test.ts @@ -23,7 +23,7 @@ import { defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, } from '../fixtures' @@ -82,7 +82,7 @@ describe(`The peg (target/ref) should be arbitrary - P${IMPLEMENTATION}`, () => oracleError: ORACLE_ERROR, erc20: token1.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -124,7 +124,7 @@ describe(`The peg (target/ref) should be arbitrary - P${IMPLEMENTATION}`, () => oracleError: ORACLE_ERROR, erc20: token0.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, diff --git a/test/scenario/RevenueHiding.test.ts b/test/scenario/RevenueHiding.test.ts index 8b1cfa00fb..815ff2f7fb 100644 --- a/test/scenario/RevenueHiding.test.ts +++ b/test/scenario/RevenueHiding.test.ts @@ -25,7 +25,7 @@ import { defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, } from '../fixtures' @@ -116,7 +116,7 @@ describe(`RevenueHiding basket collateral (/w CTokenFiatCollateral) - P${IMPLEME oracleError: ORACLE_ERROR, erc20: cDAI.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('USD'), defaultThreshold: DEFAULT_THRESHOLD, delayUntilDefault: DELAY_UNTIL_DEFAULT, diff --git a/test/scenario/SetProtocol.test.ts b/test/scenario/SetProtocol.test.ts index a2a67dd94a..a9021be240 100644 --- a/test/scenario/SetProtocol.test.ts +++ b/test/scenario/SetProtocol.test.ts @@ -25,7 +25,7 @@ import { defaultFixtureNoBasket, IMPLEMENTATION, ORACLE_ERROR, - ORACLE_TIMEOUT, + ORACLE_TIMEOUT_PRE_BUFFER, PRICE_TIMEOUT, } from '../fixtures' @@ -91,7 +91,7 @@ describe(`Linear combination of self-referential collateral - P${IMPLEMENTATION} oracleError: ORACLE_ERROR, erc20: token0.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('ETH'), defaultThreshold: bn(0), delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -106,7 +106,7 @@ describe(`Linear combination of self-referential collateral - P${IMPLEMENTATION} oracleError: ORACLE_ERROR, erc20: token1.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('MKR'), defaultThreshold: bn(0), delayUntilDefault: DELAY_UNTIL_DEFAULT, @@ -121,7 +121,7 @@ describe(`Linear combination of self-referential collateral - P${IMPLEMENTATION} oracleError: ORACLE_ERROR, erc20: token2.address, maxTradeVolume: config.rTokenMaxTradeVolume, - oracleTimeout: ORACLE_TIMEOUT, + oracleTimeout: ORACLE_TIMEOUT_PRE_BUFFER, targetName: ethers.utils.formatBytes32String('COMP'), defaultThreshold: bn(0), delayUntilDefault: DELAY_UNTIL_DEFAULT, From 6bc0b02db25828b389048d228d1bfbf0607bc3b4 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Tue, 24 Oct 2023 11:34:30 -0300 Subject: [PATCH 457/462] TRUST QA-3: Set correct version (#985) --- contracts/mixins/Versioned.sol | 2 +- contracts/plugins/assets/VersionedAsset.sol | 2 +- test/fixtures.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/mixins/Versioned.sol b/contracts/mixins/Versioned.sol index 7518551125..c70c7a8857 100644 --- a/contracts/mixins/Versioned.sol +++ b/contracts/mixins/Versioned.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.19; import "../interfaces/IVersioned.sol"; // This value should be updated on each release -string constant VERSION = "3.0.1"; +string constant VERSION = "3.1.0"; /** * @title Versioned diff --git a/contracts/plugins/assets/VersionedAsset.sol b/contracts/plugins/assets/VersionedAsset.sol index ac8371e7f2..b36945769d 100644 --- a/contracts/plugins/assets/VersionedAsset.sol +++ b/contracts/plugins/assets/VersionedAsset.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.19; import "../../interfaces/IVersioned.sol"; // This value should be updated on each release -string constant ASSET_VERSION = "3.0.1"; +string constant ASSET_VERSION = "3.1.0"; /** * @title VersionedAsset diff --git a/test/fixtures.ts b/test/fixtures.ts index 8726a3e434..6244d68ff3 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -80,7 +80,7 @@ export const ORACLE_ERROR = fp('0.01') // 1% oracle error export const REVENUE_HIDING = fp('0') // no revenue hiding by default; test individually // This will have to be updated on each release -export const VERSION = '3.0.1' +export const VERSION = '3.1.0' export type Collateral = | FiatCollateral From 8d96002a90ceb7cc60accf4292e957e008496c23 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Tue, 24 Oct 2023 11:34:54 -0300 Subject: [PATCH 458/462] TRUST QA-5/10/11: Optimizations on BasketHandler (#986) --- contracts/p0/BasketHandler.sol | 4 +++- contracts/p1/BasketHandler.sol | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 7cec1d292d..03e99b1401 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -183,6 +183,8 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } emit BasketSet(nonce, basket.erc20s, refAmts, true); disabled = true; + + trackStatus(); } /// Switch the basket, only callable directly by governance or after a default @@ -199,7 +201,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { require( main.hasRole(OWNER, _msgSender()) || - (status() == CollateralStatus.DISABLED && !main.tradingPausedOrFrozen()), + (lastStatus == CollateralStatus.DISABLED && !main.tradingPausedOrFrozen()), "basket unrefreshable" ); _switchBasket(); diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index ebcfb1dca2..6a0753c1b6 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -121,6 +121,8 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { for (uint256 i = 0; i < len; ++i) refAmts[i] = basket.refAmts[basket.erc20s[i]]; emit BasketSet(nonce, basket.erc20s, refAmts, true); disabled = true; + + trackStatus(); } /// Switch the basket, only callable directly by governance or after a default @@ -137,7 +139,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { require( main.hasRole(OWNER, _msgSender()) || - (status() == CollateralStatus.DISABLED && !main.tradingPausedOrFrozen()), + (lastStatus == CollateralStatus.DISABLED && !main.tradingPausedOrFrozen()), "basket unrefreshable" ); _switchBasket(); @@ -403,7 +405,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { for (uint256 k = 0; k < len; ++k) { if (b.erc20s[j] == erc20sAll[k]) { erc20Index = k; - continue; + break; } } From c20ea916ee5655c9d4b71219908a993db207f7c4 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:19:52 -0300 Subject: [PATCH 459/462] TRUST QA-6/7: Optimizations in Distributor (#988) Co-authored-by: Taylor Brent --- contracts/facade/FacadeTest.sol | 3 +++ contracts/p0/Distributor.sol | 4 ++-- contracts/p1/Distributor.sol | 13 ++++--------- contracts/plugins/mocks/RevenueTraderBackComp.sol | 4 +++- test/Revenues.test.ts | 11 +++++++++++ 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/contracts/facade/FacadeTest.sol b/contracts/facade/FacadeTest.sol index 78ee2173e0..9b57538a4c 100644 --- a/contracts/facade/FacadeTest.sol +++ b/contracts/facade/FacadeTest.sol @@ -66,6 +66,7 @@ contract FacadeTest is IFacadeTest { erc20s ); try main.rsrTrader().manageTokens(rsrERC20s, rsrKinds) {} catch {} + try main.rsrTrader().distributeTokenToBuy() {} catch {} // Start exact RToken auctions (IERC20[] memory rTokenERC20s, TradeKind[] memory rTokenKinds) = traderERC20s( @@ -74,6 +75,7 @@ contract FacadeTest is IFacadeTest { erc20s ); try main.rTokenTrader().manageTokens(rTokenERC20s, rTokenKinds) {} catch {} + try main.rTokenTrader().distributeTokenToBuy() {} catch {} // solhint-enable no-empty-blocks } @@ -133,6 +135,7 @@ contract FacadeTest is IFacadeTest { IERC20[] memory traderERC20sAll = new IERC20[](erc20sAll.length); for (uint256 i = 0; i < erc20sAll.length; ++i) { if ( + erc20sAll[i] != trader.tokenToBuy() && address(trader.trades(erc20sAll[i])) == address(0) && erc20sAll[i].balanceOf(address(trader)) > 1 ) { diff --git a/contracts/p0/Distributor.sol b/contracts/p0/Distributor.sol index 9f43240c5e..264d7bfe7e 100644 --- a/contracts/p0/Distributor.sol +++ b/contracts/p0/Distributor.sol @@ -63,8 +63,8 @@ contract DistributorP0 is ComponentP0, IDistributor { { RevenueTotals memory revTotals = totals(); uint256 totalShares = isRSR ? revTotals.rsrTotal : revTotals.rTokenTotal; - require(totalShares > 0, "nothing to distribute"); - tokensPerShare = amount / totalShares; + if (totalShares > 0) tokensPerShare = amount / totalShares; + require(tokensPerShare > 0, "nothing to distribute"); } // Evenly distribute revenue tokens per distribution share. diff --git a/contracts/p1/Distributor.sol b/contracts/p1/Distributor.sol index 04fedb57ee..776e19fe5a 100644 --- a/contracts/p1/Distributor.sol +++ b/contracts/p1/Distributor.sol @@ -68,7 +68,6 @@ contract DistributorP1 is ComponentP1, IDistributor { } struct Transfer { - IERC20 erc20; address addrTo; uint256 amount; } @@ -99,8 +98,8 @@ contract DistributorP1 is ComponentP1, IDistributor { { RevenueTotals memory revTotals = totals(); uint256 totalShares = isRSR ? revTotals.rsrTotal : revTotals.rTokenTotal; - require(totalShares > 0, "nothing to distribute"); - tokensPerShare = amount / totalShares; + if (totalShares > 0) tokensPerShare = amount / totalShares; + require(tokensPerShare > 0, "nothing to distribute"); } // Evenly distribute revenue tokens per distribution share. @@ -131,11 +130,7 @@ contract DistributorP1 is ComponentP1, IDistributor { if (transferAmt > 0) accountRewards = true; } - transfers[numTransfers] = Transfer({ - erc20: erc20, - addrTo: addrTo, - amount: transferAmt - }); + transfers[numTransfers] = Transfer({ addrTo: addrTo, amount: transferAmt }); numTransfers++; } emit RevenueDistributed(erc20, caller, amount); @@ -143,7 +138,7 @@ contract DistributorP1 is ComponentP1, IDistributor { // == Interactions == for (uint256 i = 0; i < numTransfers; i++) { Transfer memory t = transfers[i]; - IERC20Upgradeable(address(t.erc20)).safeTransferFrom(caller, t.addrTo, t.amount); + IERC20Upgradeable(address(erc20)).safeTransferFrom(caller, t.addrTo, t.amount); } // Perform reward accounting diff --git a/contracts/plugins/mocks/RevenueTraderBackComp.sol b/contracts/plugins/mocks/RevenueTraderBackComp.sol index ed76f53346..73069f15ad 100644 --- a/contracts/plugins/mocks/RevenueTraderBackComp.sol +++ b/contracts/plugins/mocks/RevenueTraderBackComp.sol @@ -14,8 +14,10 @@ contract RevenueTraderCompatibleV2 is RevenueTraderP1, IRevenueTraderComp { erc20s[0] = sell; TradeKind[] memory kinds = new TradeKind[](1); kinds[0] = TradeKind.DUTCH_AUCTION; + // Mirror V3 logic (only the section relevant to tests) - this.manageTokens(erc20s, kinds); + // solhint-disable-next-line no-empty-blocks + try this.manageTokens(erc20s, kinds) {} catch {} } function version() public pure virtual override(Versioned, IVersioned) returns (string memory) { diff --git a/test/Revenues.test.ts b/test/Revenues.test.ts index 1c7ad79df4..3858ba4290 100644 --- a/test/Revenues.test.ts +++ b/test/Revenues.test.ts @@ -706,6 +706,17 @@ describe(`Revenues - P${IMPLEMENTATION}`, () => { expect(newRTokenTotal).equal(bn(0)) }) + it('Should avoid zero transfers when distributing tokenToBuy', async () => { + // Distribute with no balance + await expect(rsrTrader.distributeTokenToBuy()).to.be.revertedWith('nothing to distribute') + expect(await rsr.balanceOf(stRSR.address)).to.equal(bn(0)) + + // Small amount which ends in zero distribution due to rounding + await rsr.connect(owner).mint(rsrTrader.address, bn(1)) + await expect(rsrTrader.distributeTokenToBuy()).to.be.revertedWith('nothing to distribute') + expect(await rsr.balanceOf(stRSR.address)).to.equal(bn(0)) + }) + it('Should account rewards when distributing tokenToBuy', async () => { // 1. StRSR.payoutRewards() const stRSRBal = await rsr.balanceOf(stRSR.address) From 5aa2d4ee67fd3fb967c775a662d62713e828e1b3 Mon Sep 17 00:00:00 2001 From: Taylor Brent Date: Wed, 25 Oct 2023 10:06:50 -0700 Subject: [PATCH 460/462] gas snapshots --- test/__snapshots__/FacadeWrite.test.ts.snap | 2 +- test/__snapshots__/Main.test.ts.snap | 6 +++--- .../Recollateralization.test.ts.snap | 10 +++++----- test/__snapshots__/Revenues.test.ts.snap | 6 +++--- test/__snapshots__/ZZStRSR.test.ts.snap | 4 ++-- .../AaveV3FiatCollateral.test.ts.snap | 4 ++-- .../ATokenFiatCollateral.test.ts.snap | 4 ++-- .../__snapshots__/CometTestSuite.test.ts.snap | 4 ++-- .../CrvStableMetapoolSuite.test.ts.snap | 4 ++-- ...StableRTokenMetapoolTestSuite.test.ts.snap | 18 ++++++++--------- .../CvxStableMetapoolSuite.test.ts.snap | 4 ++-- ...StableRTokenMetapoolTestSuite.test.ts.snap | 6 +++--- .../SDaiCollateralTestSuite.test.ts.snap | 8 ++++---- .../FTokenFiatCollateral.test.ts.snap | 20 +++++++++---------- .../MorphoAAVEFiatCollateral.test.ts.snap | 12 +++++------ .../MorphoAAVENonFiatCollateral.test.ts.snap | 8 ++++---- .../StargateUSDCTestSuite.test.ts.snap | 4 ++-- .../__snapshots__/MaxBasketSize.test.ts.snap | 8 ++++---- 18 files changed, 66 insertions(+), 66 deletions(-) diff --git a/test/__snapshots__/FacadeWrite.test.ts.snap b/test/__snapshots__/FacadeWrite.test.ts.snap index 71ba5454b2..76f00fe010 100644 --- a/test/__snapshots__/FacadeWrite.test.ts.snap +++ b/test/__snapshots__/FacadeWrite.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8532211`; +exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 1 - RToken Deployment 1`] = `8464999`; exports[`FacadeWrite contract Deployment Process Gas Reporting Phase 2 - Deploy governance 1`] = `5464253`; diff --git a/test/__snapshots__/Main.test.ts.snap b/test/__snapshots__/Main.test.ts.snap index 80025274f2..cbee6c9389 100644 --- a/test/__snapshots__/Main.test.ts.snap +++ b/test/__snapshots__/Main.test.ts.snap @@ -2,11 +2,11 @@ exports[`MainP1 contract Gas Reporting Asset Registry - Refresh 1`] = `393877`; -exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `195889`; +exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 1`] = `245334`; -exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `195889`; +exports[`MainP1 contract Gas Reporting Asset Registry - Register Asset 2`] = `245334`; -exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `167023`; +exports[`MainP1 contract Gas Reporting Asset Registry - Swap Registered Asset 1`] = `223993`; exports[`MainP1 contract Gas Reporting Asset Registry - Unregister Asset 1`] = `80510`; diff --git a/test/__snapshots__/Recollateralization.test.ts.snap b/test/__snapshots__/Recollateralization.test.ts.snap index 6b70c5b36c..236df3d703 100644 --- a/test/__snapshots__/Recollateralization.test.ts.snap +++ b/test/__snapshots__/Recollateralization.test.ts.snap @@ -1,19 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1363847`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 1`] = `1364203`; exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 2`] = `1499568`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `738601`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - DutchTrade 3`] = `751607`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1656950`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 1`] = `1656594`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 2`] = `174781`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1599095`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 3`] = `1599451`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 4`] = `174781`; -exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1687568`; +exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 5`] = `1687212`; exports[`Recollateralization - P1 Gas Reporting rebalance() - GnosisTrade 6`] = `202854`; diff --git a/test/__snapshots__/Revenues.test.ts.snap b/test/__snapshots__/Revenues.test.ts.snap index ac389f42c3..575c2d39f9 100644 --- a/test/__snapshots__/Revenues.test.ts.snap +++ b/test/__snapshots__/Revenues.test.ts.snap @@ -18,10 +18,10 @@ exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 1`] = `796514` exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 2`] = `1198554`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `367726`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 3`] = `367672`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `317915`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 4`] = `317861`; exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 5`] = `762314`; -exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `284933`; +exports[`Revenues - P1 Gas Reporting Settle Trades / Manage Funds 6`] = `284880`; diff --git a/test/__snapshots__/ZZStRSR.test.ts.snap b/test/__snapshots__/ZZStRSR.test.ts.snap index 08449237d2..3af605e7b9 100644 --- a/test/__snapshots__/ZZStRSR.test.ts.snap +++ b/test/__snapshots__/ZZStRSR.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`StRSRP1 contract Gas Reporting Stake 1`] = `156559`; +exports[`StRSRP1 contract Gas Reporting Stake 1`] = `139717`; exports[`StRSRP1 contract Gas Reporting Stake 2`] = `134917`; @@ -10,7 +10,7 @@ exports[`StRSRP1 contract Gas Reporting Transfer 2`] = `41509`; exports[`StRSRP1 contract Gas Reporting Transfer 3`] = `58621`; -exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `241951`; +exports[`StRSRP1 contract Gas Reporting Unstake 1`] = `222609`; exports[`StRSRP1 contract Gas Reporting Unstake 2`] = `139758`; diff --git a/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap index 75cffdf3ef..9f00d8e491 100644 --- a/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/aave-v3/__snapshots__/AaveV3FiatCollateral.test.ts.snap @@ -16,9 +16,9 @@ exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `67620`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after soft default 1`] = `67279`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after soft default 1`] = `87625`; -exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after soft default 2`] = `67279`; +exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() after soft default 2`] = `87625`; exports[`Collateral: Aave V3 Fiat Collateral (USDC) collateral functionality Gas Reporting refresh() during SOUND 1`] = `87684`; diff --git a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap index 236eb477ff..13f8ae6777 100644 --- a/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/aave/__snapshots__/ATokenFiatCollateral.test.ts.snap @@ -22,8 +22,8 @@ exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() after exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 1`] = `92285`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92211`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during SOUND 2`] = `92285`; exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 1`] = `127282`; -exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `91414`; +exports[`ATokenFiatCollateral - Mainnet Forking P1 Gas Reporting refresh() during soft default 2`] = `91488`; diff --git a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap index 68d964d679..d2dee358c6 100644 --- a/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/compoundv3/__snapshots__/CometTestSuite.test.ts.snap @@ -16,9 +16,9 @@ exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refre exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `104315`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `107042`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `132572`; -exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `103974`; +exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `126704`; exports[`Collateral: CompoundV3USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `126763`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap index 0181aff186..4e3d02729f 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableMetapoolSuite.test.ts.snap @@ -16,9 +16,9 @@ exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collatera exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48245`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `47904`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `79713`; -exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `47904`; +exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `79713`; exports[`Collateral: CurveStableMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `246886`; diff --git a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap index d4cbecc6e1..acdeaf5b1f 100644 --- a/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/crv/__snapshots__/CrvStableRTokenMetapoolTestSuite.test.ts.snap @@ -4,17 +4,17 @@ exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper col exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting ERC20 Wrapper transfer 2`] = `385743`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `485294`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `485368`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `480900`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `480752`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `594808`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `594734`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `589778`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `589852`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `478472`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `478620`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `474078`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `474226`; exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `544737`; @@ -22,8 +22,8 @@ exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper col exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `713285`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `713507`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during SOUND 2`] = `713359`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `701199`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 1`] = `701051`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `693635`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - CurveGaugeWrapper collateral functionality Gas Reporting refresh() during soft default 2`] = `693709`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap index 9b9c0827d4..7920079d2a 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableMetapoolSuite.test.ts.snap @@ -16,9 +16,9 @@ exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collat exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `48245`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `47904`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `79713`; -exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `47904`; +exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 2`] = `79713`; exports[`Collateral: CurveStableMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() during SOUND 1`] = `246886`; diff --git a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap index c1e15dc5e3..5470886e16 100644 --- a/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/curve/cvx/__snapshots__/CvxStableRTokenMetapoolTestSuite.test.ts.snap @@ -4,17 +4,17 @@ exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting ERC20 Wrapper transfer 2`] = `175188`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `485294`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 1`] = `485368`; exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after full price timeout 2`] = `480826`; exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 1`] = `594734`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `589852`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after hard default 2`] = `589778`; exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `478546`; -exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `474078`; +exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `474004`; exports[`Collateral: CurveStableRTokenMetapoolCollateral - ConvexStakingWrapper collateral functionality Gas Reporting refresh() after soft default 1`] = `544811`; diff --git a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap index c999a57b94..b8cde38466 100644 --- a/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/dsr/__snapshots__/SDaiCollateralTestSuite.test.ts.snap @@ -4,7 +4,7 @@ exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting ERC20 exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting ERC20 transfer 2`] = `34259`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `116743`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 1`] = `117203`; exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `108431`; @@ -12,13 +12,13 @@ exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refre exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after hard default 2`] = `123301`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `116562`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `116802`; exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `108431`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `111417`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 1`] = `126640`; -exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `108090`; +exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() after soft default 2`] = `123313`; exports[`Collateral: SDaiCollateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `123298`; diff --git a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap index e9d415b72d..da1cb92e3a 100644 --- a/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/flux-finance/__snapshots__/FTokenFiatCollateral.test.ts.snap @@ -16,9 +16,9 @@ exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refr exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115681`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `115327`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `139157`; -exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `115327`; +exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `139083`; exports[`Collateral: fDAI Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139155`; @@ -44,9 +44,9 @@ exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting ref exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `115873`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `115519`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `139413`; -exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `115519`; +exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `139339`; exports[`Collateral: fFRAX Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `139411`; @@ -64,7 +64,7 @@ exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting ref exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after full price timeout 2`] = `124163`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `149927`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `149997`; exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `148183`; @@ -72,9 +72,9 @@ exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting ref exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `124163`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `123809`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `148121`; -exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `123809`; +exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `148121`; exports[`Collateral: fUSDC Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `148193`; @@ -94,15 +94,15 @@ exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting ref exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 1`] = `144361`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `142547`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after hard default 2`] = `142477`; exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 1`] = `120480`; exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `118811`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `118457`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 1`] = `142485`; -exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `118457`; +exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() after soft default 2`] = `142415`; exports[`Collateral: fUSDT Collateral collateral functionality Gas Reporting refresh() during SOUND 1`] = `142487`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap index 48f07dc32e..049d644503 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVEFiatCollateral.test.ts.snap @@ -16,9 +16,9 @@ exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality G exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129742`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 1`] = `129401`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 1`] = `172067`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 2`] = `129401`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() after soft default 2`] = `172067`; exports[`Collateral: MorphoAAVEV2FiatCollateral - DAI collateral functionality Gas Reporting refresh() during SOUND 1`] = `172126`; @@ -44,9 +44,9 @@ exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129945`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `129604`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 1`] = `172473`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `129604`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() after soft default 2`] = `172473`; exports[`Collateral: MorphoAAVEV2FiatCollateral - USDC collateral functionality Gas Reporting refresh() during SOUND 1`] = `172532`; @@ -72,9 +72,9 @@ exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `129098`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 1`] = `128757`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 1`] = `170779`; -exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 2`] = `128757`; +exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() after soft default 2`] = `170779`; exports[`Collateral: MorphoAAVEV2FiatCollateral - USDT collateral functionality Gas Reporting refresh() during SOUND 1`] = `170838`; diff --git a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap index a89c7240b4..26e77e6a88 100644 --- a/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap +++ b/test/plugins/individual-collateral/morpho-aave/__snapshots__/MorphoAAVENonFiatCollateral.test.ts.snap @@ -16,9 +16,9 @@ exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionali exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `178409`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 1`] = `178068`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 1`] = `192065`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 2`] = `178068`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() after soft default 2`] = `192065`; exports[`Collateral: MorphoAAVEV2NonFiatCollateral - WBTC collateral functionality Gas Reporting refresh() during SOUND 1`] = `192124`; @@ -44,9 +44,9 @@ exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functional exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `217673`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 1`] = `217332`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 1`] = `231329`; -exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 2`] = `217332`; +exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() after soft default 2`] = `231329`; exports[`Collateral: MorphoAAVEV2NonFiatCollateral - stETH collateral functionality Gas Reporting refresh() during SOUND 1`] = `231388`; diff --git a/test/plugins/individual-collateral/stargate/__snapshots__/StargateUSDCTestSuite.test.ts.snap b/test/plugins/individual-collateral/stargate/__snapshots__/StargateUSDCTestSuite.test.ts.snap index 3afcb28f2e..8f043c0861 100644 --- a/test/plugins/individual-collateral/stargate/__snapshots__/StargateUSDCTestSuite.test.ts.snap +++ b/test/plugins/individual-collateral/stargate/__snapshots__/StargateUSDCTestSuite.test.ts.snap @@ -16,9 +16,9 @@ exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting r exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after oracle timeout 2`] = `51626`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `51285`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 1`] = `66835`; -exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `51285`; +exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() after soft default 2`] = `66835`; exports[`Collateral: Stargate USDC Pool collateral functionality Gas Reporting refresh() during SOUND 1`] = `66894`; diff --git a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap index 73f9cbd319..3be1a0a11c 100644 --- a/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap +++ b/test/scenario/__snapshots__/MaxBasketSize.test.ts.snap @@ -2,13 +2,13 @@ exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 1`] = `12082333`; -exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9823929`; +exports[`Max Basket Size - P1 ATokens/CTokens Should Issue/Redeem with max basket correctly 2`] = `9836935`; exports[`Max Basket Size - P1 ATokens/CTokens Should claim rewards correctly 1`] = `2436571`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13653680`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 1`] = `13653324`; -exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `20685978`; +exports[`Max Basket Size - P1 ATokens/CTokens Should switch basket correctly 2`] = `21071119`; exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket correctly 1`] = `10984083`; @@ -16,4 +16,4 @@ exports[`Max Basket Size - P1 Fiatcoins Should Issue/Redeem with max basket corr exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 1`] = `6592449`; -exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `14438756`; +exports[`Max Basket Size - P1 Fiatcoins Should switch basket correctly 2`] = `14823897`; From a7f76a70c4c5e12e3afa79624ea1793601881030 Mon Sep 17 00:00:00 2001 From: "Julian M. Rodriguez" <56316686+julianmrodri@users.noreply.github.com> Date: Wed, 25 Oct 2023 17:17:45 -0300 Subject: [PATCH 461/462] TRUST QA-21: minimum voting delay (#990) --- contracts/plugins/governance/Governance.sol | 23 ++- test/FacadeWrite.test.ts | 4 +- test/Governance.test.ts | 172 +++++++++++++++++++- 3 files changed, 191 insertions(+), 8 deletions(-) diff --git a/contracts/plugins/governance/Governance.sol b/contracts/plugins/governance/Governance.sol index c20978fa02..96de231912 100644 --- a/contracts/plugins/governance/Governance.sol +++ b/contracts/plugins/governance/Governance.sol @@ -8,6 +8,9 @@ import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.so import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; import "../../interfaces/IStRSRVotes.sol"; +import "../../libraries/NetworkConfigLib.sol"; + +uint256 constant ONE_DAY = 86400; // {s} /* * @title Governance @@ -30,7 +33,9 @@ contract Governance is // 100% uint256 public constant ONE_HUNDRED_PERCENT = 1e8; // {micro %} - // solhint-disable no-empty-blocks + // solhint-disable-next-line var-name-mixedcase + uint256 public immutable MIN_VOTING_DELAY; // {block} equal to ONE_DAY + constructor( IStRSRVotes token_, TimelockController timelock_, @@ -44,7 +49,12 @@ contract Governance is GovernorVotes(IVotes(address(token_))) GovernorVotesQuorumFraction(quorumPercent) GovernorTimelockControl(timelock_) - {} + { + MIN_VOTING_DELAY = + (ONE_DAY + NetworkConfigLib.blocktime() - 1) / + NetworkConfigLib.blocktime(); // ONE_DAY, in blocks + requireValidVotingDelay(votingDelay_); + } // solhint-enable no-empty-blocks @@ -56,6 +66,11 @@ contract Governance is return super.votingPeriod(); } + function setVotingDelay(uint256 newVotingDelay) public override { + requireValidVotingDelay(newVotingDelay); + super.setVotingDelay(newVotingDelay); // has onlyGovernance modifier + } + /// @return {qStRSR} The number of votes required in order for a voter to become a proposer function proposalThreshold() public @@ -175,4 +190,8 @@ contract Governance is uint256 currentEra = IStRSRVotes(address(token)).currentEra(); return currentEra == pastEra; } + + function requireValidVotingDelay(uint256 newVotingDelay) private view { + require(newVotingDelay >= MIN_VOTING_DELAY, "invalid votingDelay"); + } } diff --git a/test/FacadeWrite.test.ts b/test/FacadeWrite.test.ts index 97210ce749..9176c71ac0 100644 --- a/test/FacadeWrite.test.ts +++ b/test/FacadeWrite.test.ts @@ -195,8 +195,8 @@ describe('FacadeWrite contract', () => { // Set governance params govParams = { - votingDelay: bn(5), // 5 blocks - votingPeriod: bn(100), // 100 blocks + votingDelay: bn(7200), // 1 day + votingPeriod: bn(21600), // 3 days proposalThresholdAsMicroPercent: bn(1e6), // 1% quorumPercent: bn(4), // 4% timelockDelay: bn(60 * 60 * 24), // 1 day diff --git a/test/Governance.test.ts b/test/Governance.test.ts index 83dffb413a..53b7f7d2f1 100644 --- a/test/Governance.test.ts +++ b/test/Governance.test.ts @@ -59,8 +59,8 @@ describeP1(`Governance - P${IMPLEMENTATION}`, () => { let initialBal: BigNumber const MIN_DELAY = 7 * 60 * 60 * 24 // 7 days - const VOTING_DELAY = 5 // 5 blocks - const VOTING_PERIOD = 100 // 100 blocks + const VOTING_DELAY = 7200 // 1 day (in blocks) + const VOTING_PERIOD = 21600 // 3 days (in blocks) const PROPOSAL_THRESHOLD = 1e6 // 1% const QUORUM_PERCENTAGE = 4 // 4% @@ -306,13 +306,39 @@ describeP1(`Governance - P${IMPLEMENTATION}`, () => { expect(await governor.supportsInterface(interfaceID._hex)).to.equal(true) }) + + it('Should perform validations on votingDelay at deployment', async () => { + // Attempt to deploy with 0 voting delay + await expect( + GovernorFactory.deploy( + stRSRVotes.address, + timelock.address, + bn(0), + VOTING_PERIOD, + PROPOSAL_THRESHOLD, + QUORUM_PERCENTAGE + ) + ).to.be.revertedWith('invalid votingDelay') + + // Attempt to deploy with voting delay below minium (1 day) + await expect( + GovernorFactory.deploy( + stRSRVotes.address, + timelock.address, + bn(2000), // less than 1 day + VOTING_PERIOD, + PROPOSAL_THRESHOLD, + QUORUM_PERCENTAGE + ) + ).to.be.revertedWith('invalid votingDelay') + }) }) describe('Proposals', () => { // Proposal details const newValue: BigNumber = bn('360') - const proposalDescription = 'Proposal #1 - Update Trading Delay to 360' - const proposalDescHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(proposalDescription)) + let proposalDescription = 'Proposal #1 - Update Trading Delay to 360' + let proposalDescHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(proposalDescription)) let encodedFunctionCall: string let stkAmt1: BigNumber let stkAmt2: BigNumber @@ -873,5 +899,143 @@ describeP1(`Governance - P${IMPLEMENTATION}`, () => { // Check role was granted expect(await main.hasRole(SHORT_FREEZER, other.address)).to.equal(true) }) + + it('Should allow to update GovernorSettings via governance', async () => { + // Attempt to update if not governance + await expect(governor.setVotingDelay(bn(14400))).to.be.revertedWith( + 'Governor: onlyGovernance' + ) + + // Attempt to update without governance process in place + await whileImpersonating(timelock.address, async (signer) => { + await expect(governor.connect(signer).setVotingDelay(bn(14400))).to.be.reverted + }) + + // Update votingDelay via proposal + encodedFunctionCall = governor.interface.encodeFunctionData('setVotingDelay', [ + VOTING_DELAY * 2, + ]) + proposalDescription = 'Proposal #2 - Update Voting Delay to double' + proposalDescHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(proposalDescription)) + + // Check current value + expect(await governor.votingDelay()).to.equal(VOTING_DELAY) + + // Propose + const proposeTx = await governor + .connect(addr1) + .propose([governor.address], [0], [encodedFunctionCall], proposalDescription) + + const proposeReceipt = await proposeTx.wait(1) + const proposalId = proposeReceipt.events![0].args!.proposalId + + // Check proposal state + expect(await governor.state(proposalId)).to.equal(ProposalState.Pending) + + // Advance time to start voting + await advanceBlocks(VOTING_DELAY + 1) + + // Check proposal state + expect(await governor.state(proposalId)).to.equal(ProposalState.Active) + + const voteWay = 1 // for + + // vote + await governor.connect(addr1).castVote(proposalId, voteWay) + await advanceBlocks(1) + + // Advance time till voting is complete + await advanceBlocks(VOTING_PERIOD + 1) + + // Finished voting - Check proposal state + expect(await governor.state(proposalId)).to.equal(ProposalState.Succeeded) + + // Queue propoal + await governor + .connect(addr1) + .queue([governor.address], [0], [encodedFunctionCall], proposalDescHash) + + // Check proposal state + expect(await governor.state(proposalId)).to.equal(ProposalState.Queued) + + // Advance time required by timelock + await advanceTime(MIN_DELAY + 1) + await advanceBlocks(1) + + // Execute + await governor + .connect(addr1) + .execute([governor.address], [0], [encodedFunctionCall], proposalDescHash) + + // Check proposal state + expect(await governor.state(proposalId)).to.equal(ProposalState.Executed) + + // Check value was updated + expect(await governor.votingDelay()).to.equal(VOTING_DELAY * 2) + }) + + it('Should perform validations on votingDelay when updating', async () => { + // Update via proposal - Invalid value + encodedFunctionCall = governor.interface.encodeFunctionData('setVotingDelay', [bn(7100)]) + proposalDescription = 'Proposal #2 - Update Voting Delay to invalid' + proposalDescHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(proposalDescription)) + + // Check current value + expect(await governor.votingDelay()).to.equal(VOTING_DELAY) + + // Propose + const proposeTx = await governor + .connect(addr1) + .propose([governor.address], [0], [encodedFunctionCall], proposalDescription) + + const proposeReceipt = await proposeTx.wait(1) + const proposalId = proposeReceipt.events![0].args!.proposalId + + // Check proposal state + expect(await governor.state(proposalId)).to.equal(ProposalState.Pending) + + // Advance time to start voting + await advanceBlocks(VOTING_DELAY + 1) + + // Check proposal state + expect(await governor.state(proposalId)).to.equal(ProposalState.Active) + + const voteWay = 1 // for + + // vote + await governor.connect(addr1).castVote(proposalId, voteWay) + await advanceBlocks(1) + + // Advance time till voting is complete + await advanceBlocks(VOTING_PERIOD + 1) + + // Finished voting - Check proposal state + expect(await governor.state(proposalId)).to.equal(ProposalState.Succeeded) + + // Queue propoal + await governor + .connect(addr1) + .queue([governor.address], [0], [encodedFunctionCall], proposalDescHash) + + // Check proposal state + expect(await governor.state(proposalId)).to.equal(ProposalState.Queued) + + // Advance time required by timelock + await advanceTime(MIN_DELAY + 1) + await advanceBlocks(1) + + // Execute + await expect( + governor + .connect(addr1) + .execute([governor.address], [0], [encodedFunctionCall], proposalDescHash) + ).to.be.revertedWith('TimelockController: underlying transaction reverted') + + // Check proposal state, still queued + expect(await governor.state(proposalId)).to.equal(ProposalState.Queued) + + // Check value was not updated + expect(await governor.votingDelay()).to.equal(VOTING_DELAY) + }) }) }) From ca53604f7a60eb118203d8ec1a19edd65dd5b4b0 Mon Sep 17 00:00:00 2001 From: Julian R <56316686+julianmrodri@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:46:35 -0300 Subject: [PATCH 462/462] add buffer for decimals in shares calculations --- .../plugins/assets/erc20/RewardableERC20.sol | 5 +- test/plugins/RewardableERC20.test.ts | 161 +++++++++++++++--- 2 files changed, 139 insertions(+), 27 deletions(-) diff --git a/contracts/plugins/assets/erc20/RewardableERC20.sol b/contracts/plugins/assets/erc20/RewardableERC20.sol index 58fd23855c..6f0a606b67 100644 --- a/contracts/plugins/assets/erc20/RewardableERC20.sol +++ b/contracts/plugins/assets/erc20/RewardableERC20.sol @@ -7,6 +7,9 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../../../interfaces/IRewardable.sol"; +uint256 constant SHARES_BUFFER_DECIMALS = 9; // to prevent reward rounding issues + +/* /** * @title RewardableERC20 * @notice An abstract class that can be extended to create rewardable wrapper. @@ -35,7 +38,7 @@ abstract contract RewardableERC20 is IRewardable, ERC20, ReentrancyGuard { /// @dev Extending class must ensure ERC20 constructor is called constructor(IERC20 _rewardToken, uint8 _decimals) { rewardToken = _rewardToken; - one = 10**_decimals; // set via pass-in to prevent inheritance issues + one = 10**(_decimals + SHARES_BUFFER_DECIMALS); // set via pass-in to prevent inheritance issues } function claimRewards() external nonReentrant { diff --git a/test/plugins/RewardableERC20.test.ts b/test/plugins/RewardableERC20.test.ts index 55d71e5698..0ab22f22c8 100644 --- a/test/plugins/RewardableERC20.test.ts +++ b/test/plugins/RewardableERC20.test.ts @@ -18,6 +18,9 @@ import snapshotGasCost from '../utils/snapshotGasCost' import { formatUnits, parseUnits } from 'ethers/lib/utils' import { MAX_UINT256 } from '#/common/constants' +const SHARE_DECIMALS = 9 // decimals buffer for shares and rewards per share +const BN_SHARE_FACTOR = bn(10).pow(SHARE_DECIMALS) + type Fixture = () => Promise interface RewardableERC20Fixture { @@ -120,7 +123,7 @@ for (const wrapperName of wrapperNames) { describe(wrapperName, () => { // Decimals let shareDecimals: number - + let rewardShareDecimals: number // Assets let rewardableVault: RewardableERC20WrapperTest | RewardableERC4626VaultTest let rewardableAsset: ERC20MockRewarding @@ -152,7 +155,8 @@ for (const wrapperName of wrapperNames) { await rewardableAsset.mint(bob.address, initBalance) await rewardableAsset.connect(bob).approve(rewardableVault.address, initBalance) - shareDecimals = await rewardableVault.decimals() + shareDecimals = (await rewardableVault.decimals()) + SHARE_DECIMALS + rewardShareDecimals = rewardDecimals + SHARE_DECIMALS initShares = toShares(initBalance, assetDecimals, shareDecimals) oneShare = bn('1').mul(bn(10).pow(shareDecimals)) }) @@ -185,7 +189,9 @@ for (const wrapperName of wrapperNames) { expect(await rewardableVault.lastRewardsPerShare(alice.address)).to.equal(bn(0)) await rewardToken.mint(rewardableVault.address, parseUnits('10', rewardDecimals)) await rewardableVault.sync() - expect(await rewardableVault.rewardsPerShare()).to.equal(parseUnits('1', rewardDecimals)) + expect(await rewardableVault.rewardsPerShare()).to.equal( + parseUnits('1', rewardShareDecimals) + ) }) it('correctly handles reward tracking if supply is burned', async () => { @@ -196,7 +202,9 @@ for (const wrapperName of wrapperNames) { expect(await rewardableVault.lastRewardsPerShare(alice.address)).to.equal(bn(0)) await rewardToken.mint(rewardableVault.address, parseUnits('10', rewardDecimals)) await rewardableVault.sync() - expect(await rewardableVault.rewardsPerShare()).to.equal(parseUnits('1', rewardDecimals)) + expect(await rewardableVault.rewardsPerShare()).to.equal( + parseUnits('1', rewardShareDecimals) + ) // Setting supply to 0 await withdrawAll(rewardableVault.connect(alice)) @@ -215,7 +223,9 @@ for (const wrapperName of wrapperNames) { // Nothing updates.. as totalSupply as totalSupply is 0 await rewardableVault.sync() - expect(await rewardableVault.rewardsPerShare()).to.equal(parseUnits('1', rewardDecimals)) + expect(await rewardableVault.rewardsPerShare()).to.equal( + parseUnits('1', rewardShareDecimals) + ) await rewardableVault .connect(alice) .deposit(parseUnits('10', assetDecimals), alice.address) @@ -280,7 +290,9 @@ for (const wrapperName of wrapperNames) { }) it('alice shows correct balance', async () => { - expect(initShares.mul(3).div(8)).equal(await rewardableVault.balanceOf(alice.address)) + expect(initShares.mul(3).div(8).div(BN_SHARE_FACTOR)).equal( + await rewardableVault.balanceOf(alice.address) + ) }) it('alice shows correct lastRewardsPerShare', async () => { @@ -288,7 +300,9 @@ for (const wrapperName of wrapperNames) { }) it('bob shows correct balance', async () => { - expect(initShares.div(8)).equal(await rewardableVault.balanceOf(bob.address)) + expect(initShares.div(8).div(BN_SHARE_FACTOR)).equal( + await rewardableVault.balanceOf(bob.address) + ) }) it('bob shows correct lastRewardsPerShare', async () => { @@ -297,7 +311,9 @@ for (const wrapperName of wrapperNames) { it('rewardsPerShare is correct', async () => { // rewards / alice's deposit - expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + expect(rewardsPerShare).equal( + rewardAmount.mul(oneShare).div(initShares.div(4)).mul(BN_SHARE_FACTOR) + ) }) }) @@ -324,7 +340,9 @@ for (const wrapperName of wrapperNames) { it('alice shows correct lastRewardsPerShare', async () => { // rewards / alice's deposit - expect(initRewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + expect(initRewardsPerShare).equal( + rewardAmount.mul(oneShare).div(initShares.div(4)).mul(BN_SHARE_FACTOR) + ) expect(initRewardsPerShare).equal( await rewardableVault.lastRewardsPerShare(alice.address) ) @@ -335,6 +353,7 @@ for (const wrapperName of wrapperNames) { .mul(oneShare) .div(initShares.div(4)) .add(rewardAmount.mul(oneShare).div(initShares.div(2))) + .mul(BN_SHARE_FACTOR) expect(rewardsPerShare).equal(expectedRewardsPerShare) expect(rewardsPerShare).equal(await rewardableVault.lastRewardsPerShare(bob.address)) }) @@ -358,7 +377,9 @@ for (const wrapperName of wrapperNames) { it('rewardsPerShare is correct', async () => { // rewards / alice's deposit - expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + expect(rewardsPerShare).equal( + rewardAmount.mul(oneShare).div(initShares.div(4)).mul(BN_SHARE_FACTOR) + ) }) }) @@ -399,7 +420,9 @@ for (const wrapperName of wrapperNames) { it('rewardsPerShare is correct', async () => { // rewards / alice's deposit - expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + expect(rewardsPerShare).equal( + rewardAmount.mul(oneShare).div(initShares.div(4)).mul(BN_SHARE_FACTOR) + ) }) }) @@ -425,7 +448,9 @@ for (const wrapperName of wrapperNames) { }) it('bob shows correct balance', async () => { - expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) + expect(initShares.div(4).div(BN_SHARE_FACTOR)).equal( + await rewardableVault.balanceOf(bob.address) + ) }) it('bob shows correct lastRewardsPerShare', async () => { @@ -434,7 +459,9 @@ for (const wrapperName of wrapperNames) { it('rewardsPerShare is correct', async () => { // rewards / alice's deposit - expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + expect(rewardsPerShare).equal( + rewardAmount.mul(oneShare).div(initShares.div(4)).mul(BN_SHARE_FACTOR) + ) }) }) @@ -454,7 +481,9 @@ for (const wrapperName of wrapperNames) { }) it('alice shows correct balance', async () => { - expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) + expect(initShares.div(4).div(BN_SHARE_FACTOR)).equal( + await rewardableVault.balanceOf(alice.address) + ) }) it('alice has claimed rewards', async () => { @@ -466,7 +495,9 @@ for (const wrapperName of wrapperNames) { }) it('bob shows correct balance', async () => { - expect(initShares.div(8)).equal(await rewardableVault.balanceOf(bob.address)) + expect(initShares.div(8).div(BN_SHARE_FACTOR)).equal( + await rewardableVault.balanceOf(bob.address) + ) }) it('bob shows correct lastRewardsPerShare', async () => { @@ -475,7 +506,9 @@ for (const wrapperName of wrapperNames) { it('rewardsPerShare is correct', async () => { // rewards / alice's deposit - expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4))) + expect(rewardsPerShare).equal( + rewardAmount.mul(oneShare).div(initShares.div(4)).mul(BN_SHARE_FACTOR) + ) }) }) @@ -501,7 +534,9 @@ for (const wrapperName of wrapperNames) { }) it('alice shows correct balance', async () => { - expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) + expect(initShares.div(4).div(BN_SHARE_FACTOR)).equal( + await rewardableVault.balanceOf(alice.address) + ) }) it('alice has claimed rewards', async () => { @@ -515,7 +550,9 @@ for (const wrapperName of wrapperNames) { }) it('bob shows correct balance', async () => { - expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) + expect(initShares.div(4).div(BN_SHARE_FACTOR)).equal( + await rewardableVault.balanceOf(bob.address) + ) }) it('bob shows correct lastRewardsPerShare', async () => { @@ -532,6 +569,7 @@ for (const wrapperName of wrapperNames) { .mul(oneShare) .div(initShares.div(4)) .add(rewardAmount.mul(oneShare).div(initShares.div(2))) + .mul(BN_SHARE_FACTOR) expect(rewardsPerShare).equal(expectedRewardsPerShare) }) }) @@ -561,7 +599,9 @@ for (const wrapperName of wrapperNames) { }) it('alice shows correct balance', async () => { - expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) + expect(initShares.div(4).div(BN_SHARE_FACTOR)).equal( + await rewardableVault.balanceOf(alice.address) + ) }) it('alice shows correct lastRewardsPerShare', async () => { @@ -573,7 +613,9 @@ for (const wrapperName of wrapperNames) { }) it('bob shows correct balance', async () => { - expect(initShares.div(4)).equal(await rewardableVault.balanceOf(bob.address)) + expect(initShares.div(4).div(BN_SHARE_FACTOR)).equal( + await rewardableVault.balanceOf(bob.address) + ) }) it('bob shows correct lastRewardsPerShare', async () => { @@ -586,7 +628,9 @@ for (const wrapperName of wrapperNames) { it('rewardsPerShare is correct', async () => { // (rewards / alice's deposit) + (rewards / bob's deposit) - expect(rewardsPerShare).equal(rewardAmount.mul(oneShare).div(initShares.div(4)).mul(2)) + expect(rewardsPerShare).equal( + rewardAmount.mul(oneShare).div(initShares.div(4)).mul(2).mul(BN_SHARE_FACTOR) + ) }) }) @@ -597,7 +641,9 @@ for (const wrapperName of wrapperNames) { await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) await rewardableVault.connect(bob).deposit(initBalance.div(4), bob.address) - await rewardableVault.connect(alice).transfer(bob.address, initShares.div(4)) + await rewardableVault + .connect(alice) + .transfer(bob.address, initShares.div(4).div(BN_SHARE_FACTOR)) await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) await rewardableVault.connect(alice).deposit(initBalance.div(4), alice.address) await rewardableVault.connect(bob).claimRewards() @@ -607,7 +653,9 @@ for (const wrapperName of wrapperNames) { }) it('alice shows correct balance', async () => { - expect(initShares.div(4)).equal(await rewardableVault.balanceOf(alice.address)) + expect(initShares.div(4).div(BN_SHARE_FACTOR)).equal( + await rewardableVault.balanceOf(alice.address) + ) }) it('alice shows correct lastRewardsPerShare', async () => { @@ -619,7 +667,9 @@ for (const wrapperName of wrapperNames) { }) it('bob shows correct balance', async () => { - expect(initShares.div(2)).equal(await rewardableVault.balanceOf(bob.address)) + expect(initShares.div(2).div(BN_SHARE_FACTOR)).equal( + await rewardableVault.balanceOf(bob.address) + ) }) it('bob shows correct lastRewardsPerShare', async () => { @@ -637,6 +687,7 @@ for (const wrapperName of wrapperNames) { .mul(oneShare) .div(initShares.div(4)) .add(rewardAmount.mul(oneShare).div(initShares.div(2))) + .mul(BN_SHARE_FACTOR) ) }) }) @@ -688,12 +739,70 @@ for (const wrapperName of wrapperNames) { for (let i = 0; i < 10; i++) { await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) await rewardableVault.claimRewards() - - expect(await rewardableVault.rewardsPerShare()).to.equal(Math.floor(1.9 * (i + 1))) + expect(await rewardableVault.rewardsPerShare()).to.equal( + bn(`1.9e${SHARE_DECIMALS}`).mul(i + 1) + ) } }) }) + describe(`${wrapperName.replace('Test', '')} Special Case: Rounding - Regression test`, () => { + // Assets + let rewardableVault: RewardableERC20WrapperTest | RewardableERC4626VaultTest + let rewardableAsset: ERC20MockRewarding + let rewardToken: ERC20MockDecimals + // Main + let alice: Wallet + let bob: Wallet + + const initBalance = parseUnits('1000000', 18) + const rewardAmount = parseUnits('1.7', 6) + + const fixture = getFixture(18, 6) + + before('load wallets', async () => { + ;[alice, bob] = (await ethers.getSigners()) as unknown as Wallet[] + }) + + beforeEach(async () => { + // Deploy fixture + ;({ rewardableVault, rewardableAsset, rewardToken } = await loadFixture(fixture)) + + await rewardableAsset.mint(alice.address, initBalance) + await rewardableAsset.connect(alice).approve(rewardableVault.address, MAX_UINT256) + await rewardableAsset.mint(bob.address, initBalance) + await rewardableAsset.connect(bob).approve(rewardableVault.address, MAX_UINT256) + }) + + it('Avoids wrong distribution of rewards when rounding', async () => { + expect(await rewardToken.balanceOf(alice.address)).to.equal(bn(0)) + expect(await rewardToken.balanceOf(bob.address)).to.equal(bn(0)) + expect(await rewardableVault.rewardsPerShare()).to.equal(0) + + // alice deposit and accrue rewards + await rewardableVault.connect(alice).deposit(initBalance, alice.address) + await rewardableAsset.accrueRewards(rewardAmount, rewardableVault.address) + + // bob deposit + await rewardableVault.connect(bob).deposit(initBalance, bob.address) + + // accrue additional rewards (twice the amount) + await rewardableAsset.accrueRewards(rewardAmount.mul(2), rewardableVault.address) + + // claim all rewards + await rewardableVault.connect(bob).claimRewards() + await rewardableVault.connect(alice).claimRewards() + + // Alice got all first rewards plus half of the second + expect(await rewardToken.balanceOf(alice.address)).to.equal(bn(3.4e6)) + + // Bob only got half of the second rewards + expect(await rewardToken.balanceOf(bob.address)).to.equal(bn(1.7e6)) + + expect(await rewardableVault.rewardsPerShare()).to.equal(bn(`3.4e${SHARE_DECIMALS}`)) + }) + }) + const IMPLEMENTATION: Implementation = useEnv('PROTO_IMPL') == Implementation.P1.toString() ? Implementation.P1 : Implementation.P0